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 ? &currentSibling.nextSibling : &currentParent.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 ? &currentSibling.nextSibling : &currentParent.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 }