Current section
Files
Jump to
Current section
Files
lib/saxy.ex
defmodule Saxy do
@moduledoc ~S"""
Saxy is a XML SAX parser which provides functions to parse XML file in both binary and streaming way.
Comply with [Extensible Markup Language (XML) 1.0 (Fifth Edition)](https://www.w3.org/TR/xml/).
## SAX Events
There are currently 5 types of events emitted by the parser.
* `:start_document`.
* `:start_element`.
* `:characters`.
* `:end_element`.
* `:end_document`.
See `Saxy.Handler` for more information.
## Encoding
Saxy supports ASCII and UTF-8 encodings and respects the encoding set in XML document prolog. That
means that if the prolog declares an encoding that is not supported, it simply stops parsing and returns.
Though encoding declaration is optional in XML, so when encoding is missing in the document, UTF-8 will be
the default encoding.
## Reference
Saxy converts character references by default, for example `A` is converted to `"A"` and `&` is
converted to `"&"`.
The parser **DOES NOT** convert any entity reference, the handler that uses `Saxy.Handler` behaviour needs to convert
all entity references during parsing by implementing `handle_entity_reference/1` callback.
See `Saxy.Handler` for more details.
## Creation of atoms
Saxy does not automatically create new atoms during the parsing process.
## XSD Schema
Saxy does not support XSD schemas.
"""
alias Saxy.{Parser, ParsingError, State}
@doc ~S"""
Parses XML binary data.
This function takes XML binary, SAX event handler (see more at `Saxy.Handler`) and an initial state as the input, it returns
`{:ok, state}` if parsing is successful, otherwise `{:error, exception}`, where `exception` is a
`Saxy.ParsingError` struct which can be converted into readable message with `Exception.message/1`.
The third argument `state` can be used to keep track of data and parsing progress when parsing is happening, which will be
returned when parsing finishes.
## Examples
defmodule MyEventHandler do
@behaviour Saxy.Handler
def handle_event(:start_document, prolog, state) do
IO.inspect "Start parsing document"
[{:start_document, prolog} | state]
end
def handle_event(:end_document, _data, state) do
IO.inspect "Finish parsing document"
[{:end_document} | state]
end
def handle_event(:start_element, {name, attributes}, state) do
IO.inspect "Start parsing element #{name} with attributes #{inspect(attributes)}"
[{:start_element, name, attributes} | state]
end
def handle_event(:end_element, {name}, state) do
IO.inspect "Finish parsing element #{name}"
[{:end_element, name} | state]
end
def handle_event(:characters, chars, state) do
IO.inspect "Receive characters #{chars}"
[{:chacters, chars} | state]
end
def handle_entity_reference(reference_name) do
MyEntitiesConverter.convert(reference_name)
end
end
iex> xml = "<?xml version='1.0' ?><foo bar='value'></foo>"
iex> Saxy.parse_string(xml, MyEventHandler, [])
{:ok,
[
{:end_document},
{:end_element, "foo"},
{:start_element, "foo", [{"bar", "value"}]},
{:start_document, [version: "1.0", encoding: "UTF-8", standalone: false]}
]}
"""
@spec parse_string(
data :: binary,
handler :: module | function,
state :: term
) :: {:ok, state :: term} | {:error, exception :: ParsingError.t()}
def parse_string(data, handler, state) when is_binary(data) and is_atom(handler) do
initial_state = %State{
prolog: nil,
handler: handler,
user_state: state
}
Parser.parse_document(data, :done, initial_state)
end
@doc ~S"""
Parses XML stream data.
This function takes a stream, SAX event handler (see more at `Saxy.Handler`) and an initial state as the input, it returns
`{:ok, state}` if parsing is successful, otherwise `{:error, exception}`, where `exception` is a
`Saxy.ParsingError` struct which can be converted into readable message with `Exception.message/1`.
## Examples
defmodule MyEventHandler do
@behaviour Saxy.Handler
def handle_event(:start_document, prolog, state) do
IO.inspect "Start parsing document"
[{:start_document, prolog} | state]
end
def handle_event(:end_document, _data, state) do
IO.inspect "Finish parsing document"
[{:end_document} | state]
end
def handle_event(:start_element, {name, attributes}, state) do
IO.inspect "Start parsing element #{name} with attributes #{inspect(attributes)}"
[{:start_element, name, attributes} | state]
end
def handle_event(:end_element, {name}, state) do
IO.inspect "Finish parsing element #{name}"
[{:end_element, name} | state]
end
def handle_event(:characters, chars, state) do
IO.inspect "Receive characters #{chars}"
[{:chacters, chars} | state]
end
def handle_entity_reference(reference_name) do
MyEntitiesConverter.convert(reference_name)
end
end
iex> stream = File.stream!("/path/to/file.xml")
iex> Saxy.parse_stream(stream, MyEventHandler, [])
{:ok,
[
{:end_document},
{:end_element, "foo"},
{:start_element, "foo", [{"bar", "value"}]},
{:start_document, [version: "1.0", encoding: "UTF-8", standalone: false]}
]}
## Memory usage
`Saxy.parse_stream/3` takes a `File.Stream` or `Stream` as the input, so you are in control of how many bytes
in each chunk in the file you want to buffer. Anyway, Saxy will try trimming off the parsed parts of buffer
when it exceeds 4096 bytes (this number is not configurable yet) to keep the memory usage in a reasonable limit.
"""
@spec parse_stream(
stream :: File.Stream.t() | Stream.t(),
handler :: module | function,
state :: term
) :: {:ok, state :: term} | {:error, exception :: ParsingError.t()}
def parse_stream(%module{} = stream, handler, state) when module in [File.Stream, Stream] do
initial_state = %State{
prolog: nil,
handler: handler,
user_state: state
}
Parser.parse_document(<<>>, stream, initial_state)
end
end