1 /**
2 * XmlParser client which builds a DOM efficiently.
3 * WORK IN PROGRESS.
4 *
5 * License:
6 * This Source Code Form is subject to the terms of
7 * the Mozilla Public License, v. 2.0. If a copy of
8 * the MPL was not distributed with this file, You
9 * can obtain one at http://mozilla.org/MPL/2.0/.
10 *
11 * Authors:
12 * Vladimir Panteleev <ae@cy.md>
13 */
14
15 deprecated module ae.utils.xmldom;
16 deprecated:
17
18 import std.exception;
19
20 import ae.utils.xmlparser;
21 import ae.utils.alloc;
22
23 enum XmlNodeType
24 {
25 root,
26 tag,
27 attribute,
28 directive,
29 processingInstruction,
30 text
31 }
32
33
34 struct XmlDom(STRING=string, XML_STRING=XmlString!STRING)
35 {
36 struct Node
37 {
38 XmlNodeType type;
39 union
40 {
41 STRING tag;
42 alias tag attributeName;
43 XML_STRING text;
44 }
45 union
46 {
47 XML_STRING attributeValue; /// Attribute tags only
48 Node* firstChild; /// Non-attribute tags only
49 }
50 Node* nextSibling;
51 Node* parent;
52 }
53
54 Node* root;
55
56 struct Cursor
57 {
58 Node* currentParent, currentSibling;
59
60 void descend()
61 {
62 assert(currentSibling, "Nowhere to descend to");
63 currentParent = currentSibling;
64 //currentSibling = null;
65 currentSibling = currentSibling.firstChild;
66 }
67
68 void ascend()
69 {
70 assert(currentParent, "Nowhere to ascend to");
71 currentSibling = currentParent;
72 currentParent = currentSibling.parent;
73 }
74
75 void insertNode(Node* n)
76 {
77 n.parent = currentParent;
78 auto pNext = currentSibling ? ¤tSibling.nextSibling : ¤tParent.firstChild;
79 n.nextSibling = *pNext;
80 }
81
82 /// Assumes we are at the last sibling
83 void appendNode(Node* n)
84 {
85 n.parent = currentParent;
86 n.nextSibling = null;
87 auto pNext = currentSibling ? ¤tSibling.nextSibling : ¤tParent.firstChild;
88 assert(*pNext, "Cannot append when not at the last sibling");
89 currentSibling = *pNext = n;
90 }
91
92 @property bool empty() { return currentSibling !is null; }
93 alias currentSibling front;
94 void popFront() { currentSibling = currentSibling.nextSibling; }
95 }
96
97 Cursor getCursor()
98 {
99 assert(root, "No root node");
100 Cursor cursor;
101 cursor.currentParent = root;
102 cursor.currentSibling = null;
103 return cursor;
104 }
105 }
106
107 static template XmlDomWriter(alias dom_, alias allocator=heapAllocator)
108 {
109 alias dom = dom_;
110 alias DOM = typeof(dom);
111 alias DOM.Node Node;
112
113 private Node* newNode()
114 {
115 auto n = allocator.allocate!Node();
116 n.nextSibling = null;
117 return n;
118 }
119
120 DOM.Cursor newDocument()
121 {
122 with (*(dom.root = newNode()))
123 type = XmlNodeType.root,
124 parent = null;
125 return dom.getCursor();
126 }
127 }
128
129 /// STRING_FILTER is a policy type which determines how strings are
130 /// transformed for permanent storage inside the DOM.
131 /// By default, we store slices of the original XML document.
132 /// However, if the parsing is done using temporary buffers,
133 /// STRING_FILTER will want to copy (.idup) the strings before
134 /// letting us store them.
135 /// STRING_FILTER can also be used to implement a string pool, to
136 /// make a trade-off between memory consumption and speed
137 /// (an XML document is likely to contain many repeating strings,
138 /// such as tag and attribute names). Merging identical strings
139 /// to obtain unique string pointers or IDs would also allow very
140 /// quick tag/attribute name lookup, and avoid repeated entity
141 /// decoding.
142 struct XmlDomParser(alias WRITER, alias STRING_FILTER=NoopStringFilter, bool CHECKED=true)
143 {
144 WRITER.DOM.Cursor cursor;
145
146 STRING_FILTER stringFilter;
147
148 alias WRITER.Node Node;
149
150 private Node* addNode(XmlNodeType type)
151 {
152 assert(cursor.currentParent.type != XmlNodeType.attribute);
153 Node* n = WRITER.newNode();
154 n.type = type;
155 cursor.appendNode(n);
156 return n;
157 }
158
159 void startDocument()
160 {
161 cursor = WRITER.newDocument();
162 }
163
164 void text(XML_STRING)(XML_STRING s)
165 {
166 with (*addNode(XmlNodeType.text))
167 text = stringFilter.handleXmlString(s);
168 }
169
170 void directive(XML_STRING)(XML_STRING s)
171 {
172 with (*addNode(XmlNodeType.directive))
173 text = stringFilter.handleXmlString(s);
174 }
175
176 void startProcessingInstruction(STRING)(STRING s)
177 {
178 with (*addNode(XmlNodeType.processingInstruction))
179 tag = stringFilter.handleString(s);
180 cursor.descend();
181 }
182
183 void endProcessingInstruction()
184 {
185 cursor.ascend();
186 }
187
188 void startTag(STRING)(STRING s)
189 {
190 with (*addNode(XmlNodeType.tag))
191 tag = stringFilter.handleString(s);
192 cursor.descend();
193 }
194
195 void attribute(STRING, XML_STRING)(STRING name, XML_STRING value)
196 {
197 with (*addNode(XmlNodeType.attribute))
198 {
199 attributeName = stringFilter.handleString (name);
200 attributeValue = stringFilter.handleXmlString(value);
201 }
202 }
203
204 void endAttributes() {}
205
206 void endAttributesAndTag() {}
207
208 void endTag(STRING)(STRING s)
209 {
210 cursor.ascend();
211 static if (CHECKED)
212 enforce(stringFilter.handleString(s) == cursor.currentSibling.tag);
213 }
214
215 void endDocument()
216 {
217 cursor.ascend();
218 enforce(cursor.currentSibling is WRITER.dom.root, "Unexpected end of document");
219 }
220 }
221
222 struct NoopStringFilter
223 {
224 auto handleString (S)(S s) { return s; }
225 auto handleXmlString(S)(S s) { return s; }
226 }
227
228 unittest
229 {
230 // Test instantiation
231 XmlDom!string dom;
232 alias XmlDomWriter!dom WRITER;
233 alias XmlDomParser!WRITER OUTPUT;
234 alias XmlParser!(string, OUTPUT) PARSER;
235 PARSER p;
236 }