1 /**
2  * Ultra-simple XML grammar
3  */
4 module pegged.examples.xml;
5 
6 import pegged.grammar;
7 
8 import std.array;
9 
10 string[] nameStack;
11 
12 /// Semantic action to push a tag name on a stack
13 O opening(O)(O o)
14 {
15     if (o.successful)
16         nameStack ~= o.matches;
17     return o;
18 }
19 
20 /**
21  * Semantic action to pop the name stack and compare a tag name
22  * The parsing fails if the tags do not nest correctly (See docs, the Semantic Actions wiki page)
23  */
24 O closing(O)(O o)
25 {
26     if (o.matches.empty || nameStack.back != o.matches[0])
27         o.successful = false;
28     else
29         nameStack.popBack();
30     return o;
31 }
32 
33 /**
34  * Semantic action to flush the name stack
35  */
36 O flush(O)(O o)
37 {
38     nameStack = null;
39     return o;
40 }
41 
42 mixin(grammar(`
43 XML:
44     Node        <- OpeningTag{opening} (Node / Text)* ClosingTag{closing}
45     OpeningTag  <- :"<" identifier :">"
46     ClosingTag  <- :"</" identifier :">"
47     Text        <~ (!OpeningTag !ClosingTag .)+
48 `));
49 
50 unittest
51 {
52     auto p1 = XML("<a></a>");
53     assert(p1.successful);
54     assert(p1.matches == ["a", "a"]);
55     assert(p1.children[0].children.length == 2);
56 
57     assert(p1.children[0].name == "XML.Node");
58     assert(p1.children[0].children[0].name == "XML.OpeningTag");
59     assert(p1.children[0].children[1].name == "XML.ClosingTag");
60 
61     assert(!XML("<b></>").successful); // incomplete closing tag
62     assert(!XML("<c></d>").successful); // unmatched tag
63     assert( XML("<e><f></f></e>").successful); // OK
64     assert(!XML("<g><h></g></h>").successful); // badly enclosed tags
65 
66     auto p2 = XML("<text>Hello World! This is an <emph>important</emph> announcement!</text>");
67     assert(p2.successful);
68 
69     assert(p2.matches == ["text", "Hello World! This is an ", "emph", "important", "emph", " announcement!", "text"]);
70     assert(p2.children[0].children[0].matches == ["text"]);
71     assert(p2.children[0].children[1].matches == ["Hello World! This is an "]);
72     assert(p2.children[0].children[$-1].matches == ["text"]);
73 }