%%%------------------------------------------------------------------- %%% @copyright (C) 2011-2021, Erlang Solutions Ltd. %%% @doc XML stream parser %%% @end %%% Created : 21 Jul 2011 by Michal Ptaszek %%%------------------------------------------------------------------- -module(exml_stream). -include("exml_stream.hrl"). -export([new_parser/0, new_parser/1, parse/2, reset_parser/1, free_parser/1]). -export_type([element/0, start/0, stop/0, parser/0, parser_opt/0]). -record(parser, { event_parser :: exml_nif:parser(), buffer :: [binary()] }). -type start() :: #xmlstreamstart{}. -type stop() :: #xmlstreamend{}. -type element() :: exml_nif:stream_element(). -type parser() :: #parser{}. %% infinite_stream - no distinct "stream start" or "stream end", only #xmlel{} will be returned %% max_child_size - specifies maximum byte size of any child of the root element. The byte size is %% counted from the start tag until the opening character of its end tag. Disabled %% if set to 0 (default). -type parser_opt() :: {infinite_stream, boolean()} | {max_child_size, non_neg_integer()}. %%%=================================================================== %%% Public API %%%=================================================================== -spec new_parser() -> {ok, parser()} | {error, any()}. new_parser() -> new_parser([]). -spec new_parser([parser_opt()]) -> {ok, parser()} | {error, any()}. new_parser(Opts)-> MaxChildSize = proplists:get_value(max_child_size, Opts, 0), InfiniteStream = proplists:get_value(infinite_stream, Opts, false), case exml_nif:create(MaxChildSize, InfiniteStream) of {ok, EventParser} -> {ok, #parser{event_parser = EventParser, buffer = []}}; Error -> Error end. -spec parse(parser(), binary()) -> {ok, parser(), [exml_stream:element()]} | {error, Reason :: any()}. parse(Parser, Input) when is_binary(Input) -> #parser{event_parser = EventParser, buffer = OldBuf} = Parser, Buffer = OldBuf ++ [Input], case parse_all(EventParser, Buffer, []) of {ok, Elems, NewBuffer} -> {ok, Parser#parser{buffer = NewBuffer}, Elems}; Other -> Other end. -spec reset_parser(parser()) -> {ok, parser()}. reset_parser(#parser{event_parser = NifParser} = Parser) -> exml_nif:reset_parser(NifParser), {ok, Parser#parser{buffer = []}}. -spec free_parser(parser()) -> ok. free_parser(#parser{}) -> ok. %%%=================================================================== %%% Helpers %%%=================================================================== parse_all(_Parser, [], Acc) -> {ok, lists:reverse(Acc), []}; parse_all(Parser, Buffer, Acc) -> Val = exml_nif:parse_next(Parser, Buffer), case Val of {ok, undefined, Offset} -> {ok, lists:reverse(Acc), drop_offset(Buffer, Offset)}; {ok, Element, Offset} -> parse_all(Parser, drop_offset(Buffer, Offset), [Element | Acc]); {error, _} = Error -> Error end. drop_offset(Buffer, 0) -> Buffer; drop_offset([Front | Rest], Offset) when byte_size(Front) =< Offset -> drop_offset(Rest, Offset - byte_size(Front)); drop_offset([Front | Rest], Offset) -> <<_:Offset/binary, Part/binary>> = Front, [Part | Rest].