-
Notifications
You must be signed in to change notification settings - Fork 51
/
simple_xml.exs
52 lines (41 loc) · 1.55 KB
/
simple_xml.exs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# Run it from root with `mix run examples/simple_xml.exs`
defmodule SimpleXML do
import NimbleParsec
@doc """
Parses a simple XML.
It is meant to show NimbleParsec recursive features.
It doesn't support attributes. The content of a tag is
either another tag or a text node.
"""
defparsec :parse, parsec(:node) |> eos()
tag = ascii_string([?a..?z, ?A..?Z], min: 1)
text = ascii_string([not: ?<], min: 1)
opening_tag = ignore(string("<")) |> concat(tag) |> ignore(string(">"))
closing_tag = ignore(string("</")) |> concat(tag) |> ignore(string(">"))
defcombinatorp :node,
opening_tag
|> repeat(lookahead_not(string("</")) |> choice([parsec(:node), text]))
|> wrap()
|> concat(closing_tag)
|> post_traverse(:match_and_emit_tag)
defp match_and_emit_tag(rest, [tag, [tag, text]], context, _line, _offset),
do: {rest, [{String.to_atom(tag), [], text}], context}
defp match_and_emit_tag(rest, [tag, [tag | nodes]], context, _line, _offset),
do: {rest, [{String.to_atom(tag), [], nodes}], context}
defp match_and_emit_tag(_rest, [opening, [closing | _]], _context, _line, _offset),
do: {:error, "closing tag #{inspect(closing)} did not match opening tag #{inspect(opening)}"}
end
inputs = [
"<foo>bar</foo>",
"<foo><bar>baz</bar></foo>",
"<foo><bar>one</bar><bar>two</bar></foo>",
"<>bar</>",
"<foo>bar</baz>",
"<foo>bar</foo>oops",
"<foo>bar"
]
for input <- inputs do
IO.puts(input)
IO.inspect(SimpleXML.parse(input))
IO.puts("")
end