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 }