1 /*
2 *             Copyright László Szerémi 2022 - .
3 *  Distributed under the Boost Software License, Version 1.0.
4 *      (See accompanying file LICENSE_1_0.txt or copy at
5 *            http://www.boost.org/LICENSE_1_0.txt)
6 */
7 
8 /++
9 +   Provides an implementation of the DOM Level 3 specification.
10 +
11 +   Authors:
12 +   Lodovico Giaretta
13 +   László Szerémi
14 +
15 +   License:
16 +   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
17 +
18 +   Copyright:
19 +   Copyright Lodovico Giaretta 2016 --
20 +/
21 
22 module newxml.domimpl;
23 
24 import newxml.interfaces;
25 import newxml.domstring;
26 import dom = newxml.dom;
27 import std.typecons : rebindable, Flag, BitFlags;
28 //import std.experimental.allocator;//import stdx.allocator;
29 //import std.experimental.allocator.gc_allocator;//import stdx.allocator.gc_allocator;
30 import std.range.primitives;
31 
32 /* // this is needed because compilers up to at least DMD 2.071.1 suffer from issue 16319
33 private auto multiVersionMake(Type, Args...)(auto ref Args args)
34 {
35     static if (Args.length == 0  && __traits(compiles, allocator.make!Type(args)))
36         return allocator.make!Type(args);
37     else static if (Args.length > 0 && __traits(compiles, allocator.make!Type(args[0 .. $])))
38     {
39         auto res = allocator.make!Type(args[0 .. $]);
40         return res;
41     }
42     else
43         static assert(0, "multiVersionMake failed...");
44 } */
45 
46 /++
47 +   An implementation of $(LINK2 ../dom/DOMImplementation, `std.experimental.xml.dom.DOMImplementation`).
48 +
49 +   It allows to specify a custom allocator to be used when creating instances of the DOM classes.
50 +   As keeping track of the lifetime of every node would be very complex, this implementation
51 +   does not try to do so. Instead, no object is ever deallocated; it is the users responsibility
52 +   to directly free the allocator memory when all objects are no longer reachable.
53 +/
54 class DOMImplementation : dom.DOMImplementation
55 {
56 @safe:
57     //mixin UsesAllocator!(Alloc, true);
58     this() @nogc @safe pure nothrow {
59         
60     }
61     override
62     {
63         /++
64         +   Implementation of $(LINK2 ../dom/DOMImplementation.createDocumentType,
65         +   `std.experimental.xml.dom.DOMImplementation.createDocumentType`).
66         +/
67         DocumentType createDocumentType(DOMString qualifiedName, DOMString publicId, DOMString systemId)
68         {
69             DocumentType res = new DocumentType();//allocator.multiVersionMake!DocumentType(this);
70             res._name = qualifiedName;
71             res._publicId = publicId;
72             res._systemId = systemId;
73             return res;
74         }
75         /++
76         +   Implementation of $(LINK2 ../dom/DOMImplementation.createDocument,
77         +   `std.experimental.xml.dom.DOMImplementation.createDocument`).
78         +/
79         Document createDocument(DOMString namespaceURI, DOMString qualifiedName, dom.DocumentType _doctype)
80         {
81             DocumentType doctype = cast(DocumentType)_doctype;
82             if (_doctype && !doctype)
83                 throw new DOMException(dom.ExceptionCode.wrongDocument);//allocator.multiVersionMake!DOMException(this, dom.ExceptionCode.wrongDocument);
84 
85             Document doc = new Document();//allocator.multiVersionMake!Document(this);
86             doc._ownerDocument = doc;
87             doc._doctype = doctype;
88             doc._config = new DOMConfiguration();//allocator.multiVersionMake!DOMConfiguration(this);
89 
90             if (namespaceURI)
91             {
92                 if (!qualifiedName)
93                     throw new DOMException(dom.ExceptionCode.namespace);//allocator.multiVersionMake!DOMException(this, dom.ExceptionCode.namespace);
94                 doc.appendChild(doc.createElementNS(namespaceURI, qualifiedName));
95             }
96             else if (qualifiedName)
97                 doc.appendChild(doc.createElement(qualifiedName));
98 
99             return doc;
100         }
101         /++
102         +   Implementation of $(LINK2 ../dom/DOMImplementation.hasFeature,
103         +   `std.experimental.xml.dom.DOMImplementation.hasFeature`).
104         +
105         +   Only recognizes features `"Core"`and `"XML"` with versions `"1.0"`,
106         +   `"2.0"` or `"3.0"`.
107         +/
108         bool hasFeature(string feature, string version_)
109         {
110             import std.uni: sicmp;
111             return (!sicmp(feature, "Core") || !sicmp(feature, "XML"))
112                 && (version_ == "1.0" || version_ == "2.0" || version_ == "3.0");
113         }
114         /++
115         +   Implementation of $(LINK2 ../dom/DOMImplementation.hasFeature,
116         +   `std.experimental.xml.dom.DOMImplementation.hasFeature`).
117         +
118         +   Only recognizes features `"Core"`and `"XML"` with versions `"1.0"`,
119         +   `"2.0"` or `"3.0"`. Always returns `this`.
120         +/
121         DOMImplementation getFeature(string feature, string version_)
122         {
123             if (hasFeature(feature, version_))
124                 return this;
125             else
126                 return null;
127         }
128     }
129     
130     /++
131     +   The implementation of $(LINK2 ../dom/DOMException, `std.experimental.xml.dom.DOMException`)
132     +   thrown by this DOM implementation.
133     +/
134     class DOMException: dom.DOMException
135     {
136         /// Constructs a `DOMException` with a specific `dom.ExceptionCode`.
137         pure nothrow @nogc @safe this(dom.ExceptionCode code, string file = __FILE__, size_t line = __LINE__, 
138             Throwable nextInChain = null)
139         {
140             _code = code;
141             super("", file, line);
142         }
143         @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
144         {
145         super(msg, file, line, nextInChain);
146         }
147         /// Implementation of $(LINK2 ../dom/DOMException.code, `std.experimental.xml.dom.DOMException.code`).
148         override @property dom.ExceptionCode code()
149         {
150             return _code;
151         }
152         private dom.ExceptionCode _code;
153     }
154     /// Implementation of $(LINK2 ../dom/Node, `std.experimental.xml.dom.Node`)
155     abstract class Node : dom.Node
156     {
157         package this() {
158 
159         }
160         override
161         {
162             /// Implementation of $(LINK2 ../dom/Node.ownerDocument, `std.experimental.xml.dom.Node.ownerDocument`).
163             @property Document ownerDocument() { return _ownerDocument; }
164 
165             /// Implementation of $(LINK2 ../dom/Node.parentNode, `std.experimental.xml.dom.Node.parentNode`).
166             @property Node parentNode() { return _parentNode; }
167             /++
168             +   Implementation of $(LINK2 ../dom/Node.previousSibling,
169             +   `std.experimental.xml.dom.Node.previousSibling`).
170             +/
171             @property Node previousSibling() { return _previousSibling; }
172             /// Implementation of $(LINK2 ../dom/Node.nextSibling, `std.experimental.xml.dom.Node.nextSibling`).
173             @property Node nextSibling() { return _nextSibling; }
174 
175             /++
176             +   Implementation of $(LINK2 ../dom/Node.isSameNode, `std.experimental.xml.dom.Node.isSameNode`).
177             +
178             +   Equivalent to a call to `this is other`.
179             +/
180             bool isSameNode(dom.Node other)
181             {
182                 return this is other;
183             }
184             /// Implementation of $(LINK2 ../dom/Node.isEqualNode, `std.experimental.xml.dom.Node.isEqualNode`).
185             bool isEqualNode(dom.Node other)
186             {
187                 import std.meta: AliasSeq;
188 
189                 if (!other || nodeType != other.nodeType)
190                     return false;
191 
192                 foreach (field; AliasSeq!("nodeName", "localName", "namespaceURI", "prefix", "nodeValue"))
193                 {
194                     mixin("auto a = " ~ field ~ ";\n");
195                     mixin("auto b = other." ~ field ~ ";\n");
196                     if ((a is null && b !is null) || (b is null && a !is null) || (a !is null && b !is null && a != b))
197                         return false;
198                 }
199 
200                 auto thisWithChildren = cast(NodeWithChildren)this;
201                 if (thisWithChildren)
202                 {
203                     auto otherChild = other.firstChild;
204                     foreach (child; thisWithChildren.childNodes)
205                     {
206                         if (!child.isEqualNode(otherChild))
207                             return false;
208                         otherChild = otherChild.nextSibling;
209                     }
210                     if (otherChild !is null)
211                         return false;
212                 }
213 
214                 return true;
215             }
216 
217             /// Implementation of $(LINK2 ../dom/Node.setUserData, `std.experimental.xml.dom.Node.setUserData`).
218             dom.UserData setUserData(string key, dom.UserData data, dom.UserDataHandler handler) @trusted
219             {
220                 userData[key] = data;
221                 if (handler)
222                     userDataHandlers[key] = handler;
223                 return data;
224             }
225             /// Implementation of $(LINK2 ../dom/Node.getUserData, `std.experimental.xml.dom.Node.getUserData`).
226             dom.UserData getUserData(string key) const @trusted
227             {
228                 if (key in userData)
229                     return userData[key];
230                 return dom.UserData(null);
231             }
232 
233             /++
234             +   Implementation of $(LINK2 ../dom/Node.isSupported, `std.experimental.xml.dom.Node.isSupported`).
235             +
236             +   Only recognizes features `"Core"`and `"XML"` with versions `"1.0"`,
237             +   `"2.0"` or `"3.0"`.
238             +/
239             bool isSupported(string feature, string version_)
240             {
241                 return (feature == "Core" || feature == "XML")
242                     && (version_ == "1.0" || version_ == "2.0" || version_ == "3.0");
243             }
244             /++
245             +   Implementation of $(LINK2 ../dom/Node.getFeature, `std.experimental.xml.dom.Node.getFeature`).
246             +
247             +   Only recognizes features `"Core"`and `"XML"` with versions `"1.0"`,
248             +   `"2.0"` or `"3.0"`. Always returns this.
249             +/
250             Node getFeature(string feature, string version_)
251             {
252                 if (isSupported(feature, version_))
253                     return this;
254                 else
255                     return null;
256             }
257 
258             /++
259             +   Implementation of $(LINK2 ../dom/Node.compareDocumentPosition,
260             +   `std.experimental.xml.dom.Node.compareDocumentPosition`).
261             +/
262             BitFlags!(dom.DocumentPosition) compareDocumentPosition(dom.Node _other) @trusted
263             {
264                 enum Ret(dom.DocumentPosition flag) = cast(BitFlags!(dom.DocumentPosition)) flag;
265 
266                 Node other = cast(Node)_other;
267                 if (!other)
268                     return Ret!(dom.DocumentPosition.disconnected);
269 
270                 if (this is other)
271                     return Ret!(dom.DocumentPosition.none);
272 
273                 Node node1 = other;
274                 Node node2 = this;
275                 Attr attr1 = cast(Attr)node1;
276                 Attr attr2 = cast(Attr)node2;
277 
278                 if (attr1 && attr1.ownerElement)
279                     node1 = attr1.ownerElement;
280                 if (attr2 && attr2.ownerElement)
281                 {
282                     node2 = attr2.ownerElement;
283                     if (attr1 && node2 is node1)
284                     {
285                         foreach (attr; (cast(Element)node2).attributes) with (dom.DocumentPosition)
286                         {
287                             if (attr is attr1)
288                                 return Ret!implementationSpecific | Ret!preceding;
289                             else if (attr is attr2) {
290                                 return Ret!implementationSpecific | Ret!following;
291                             }
292                         }
293                     }
294                 }
295                 void rootAndDepth(ref Node node, out int depth)
296                 {
297                     while (node.parentNode)
298                     {
299                         node = node.parentNode;
300                         depth++;
301                     }
302                 }
303                 Node root1 = node1, root2 = node2;
304                 int depth1, depth2;
305                 rootAndDepth(root1, depth1);
306                 rootAndDepth(root2, depth2);
307 
308                 if (root1 !is root2) with (dom.DocumentPosition)
309                 {
310                     if (cast(void*)root1 < cast(void*)root2)
311                         return Ret!disconnected | Ret!implementationSpecific | Ret!preceding;
312                     else
313                         return Ret!disconnected | Ret!implementationSpecific | Ret!following;
314                 }
315 
316                 bool swapped = depth1 < depth2;
317                 if (swapped)
318                 {
319                     import std.algorithm: swap;
320                     swap(depth1, depth2);
321                     swap(node1, node2);
322                     swapped = true;
323                 }
324                 while (depth1-- > depth2)
325                 {
326                     node1 = node1.parentNode;
327                 }
328                 if (node1 is node2) with (dom.DocumentPosition)
329                 {
330                     if (swapped)
331                         return Ret!contains | Ret!preceding;
332                     else
333                         return Ret!containedBy | Ret!following;
334                 }
335                 while(true)
336                 {
337                     if (node1.parentNode is node2.parentNode)
338                     {
339                         while (node1.nextSibling)
340                         {
341                             node1 = node1.nextSibling;
342                             if (node1 is node2)
343                                 return Ret!(dom.DocumentPosition.preceding);
344                         }
345                         return Ret!(dom.DocumentPosition.following);
346                     }
347                     node1 = node1.parentNode;
348                     node2 = node2.parentNode;
349                 }
350                 assert(0, "Control flow should never reach this...\nPlease file an issue");
351             }
352         }
353         private
354         {
355             dom.UserData[string] userData;
356             dom.UserDataHandler[string] userDataHandlers;
357             Node _previousSibling, _nextSibling, _parentNode;
358             Document _ownerDocument;
359             bool _readonly = false;
360 
361             // internal methods
362             Element parentElement()
363             {
364                 auto parent = parentNode;
365                 while (parent && parent.nodeType != dom.NodeType.element)
366                     parent = parent.parentNode;
367                 return cast(Element)parent;
368             }
369             void performClone(Node dest, bool deep) @trusted
370             {
371                 foreach (data; userDataHandlers.byKeyValue)
372                 {
373                     auto value = data.value;
374                     // putting data.value directly in the following line causes an error; should investigate further
375                     value(dom.UserDataOperation.nodeCloned, new DOMString(data.key), userData[data.key], this, dest);
376                 }
377             }
378         }
379         // method that must be overridden
380         // just because otherwise it doesn't work [bugzilla 16318]
381         abstract override DOMString nodeName();
382         // methods specialized in NodeWithChildren
383         override
384         {
385             @property ChildList childNodes()
386             {
387                 static ChildList emptyList;
388                 if (!emptyList)
389                 {
390                     emptyList = new ChildList();//allocator.multiVersionMake!ChildList(this);
391                     emptyList.currentChild = firstChild;
392                 }
393                 return emptyList;
394             }
395             @property Node firstChild() { return null; }
396             @property Node lastChild() { return null; }
397            
398             Node insertBefore(dom.Node _newChild, dom.Node _refChild)
399             {
400                 throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
401             }
402             
403             Node replaceChild(dom.Node newChild, dom.Node oldChild)
404             {
405                 throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
406             }
407             
408             Node removeChild(dom.Node oldChild)
409             {
410                 throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notFound);
411             }
412             
413             Node appendChild(dom.Node newChild)
414             {
415                 throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
416             }
417             
418             bool hasChildNodes() const { return false; }
419         }
420         // methods specialized in Element
421         override
422         {
423             @property Element.Map attributes() { return null; }
424             bool hasAttributes() { return false; }
425         }
426         // methods specialized in various subclasses
427         override
428         {
429             @property DOMString nodeValue() { return null; }
430             @property void nodeValue(DOMString) {}
431             @property DOMString textContent() { return null; }
432             @property void textContent(DOMString) {}
433             @property DOMString baseURI()
434             {
435                 if (parentNode)
436                     return parentNode.baseURI;
437                 return null;
438             }
439 
440             Node cloneNode(bool deep) { return null; }
441         }
442         // methods specialized in Element and Attribute
443         override
444         {
445             @property DOMString localName() { return null; }
446             @property DOMString prefix() { return null; }
447             @property void prefix(DOMString) { }
448             @property DOMString namespaceURI() { return null; }
449         }
450         // methods specialized in Document, Element and Attribute
451         override
452         {
453             DOMString lookupPrefix(DOMString namespaceURI)
454             {
455                 if (!namespaceURI)
456                     return null;
457 
458                 switch (nodeType) with (dom.NodeType)
459                 {
460                     case entity:
461                     case notation:
462                     case documentFragment:
463                     case documentType:
464                         return null;
465                     case attribute:
466                         Attr attr = cast(Attr)this;
467                         if (attr.ownerElement)
468                             return attr.ownerElement.lookupNamespacePrefix(namespaceURI, attr.ownerElement);
469                         return null;
470                     default:
471                         auto parentElement = parentElement();
472                         if (parentElement)
473                             return parentElement.lookupNamespacePrefix(namespaceURI, parentElement);
474                         return null;
475                 }
476             }
477             DOMString lookupNamespaceURI(DOMString prefix)
478             {
479                 switch (nodeType) with (dom.NodeType)
480                 {
481                     case entity:
482                     case notation:
483                     case documentType:
484                     case documentFragment:
485                         return null;
486                     case attribute:
487                         auto attr = cast(Attr)this;
488                         if (attr.ownerElement)
489                             return attr.ownerElement.lookupNamespaceURI(prefix);
490                         return null;
491                     default:
492                         auto parentElement = parentElement();
493                         if (parentElement)
494                             return parentElement.lookupNamespaceURI(prefix);
495 
496                         return null;
497                 }
498             }
499             bool isDefaultNamespace(DOMString namespaceURI)
500             {
501                 switch (nodeType) with (dom.NodeType)
502                 {
503                     case entity:
504                     case notation:
505                     case documentType:
506                     case documentFragment:
507                         return false;
508                     case attribute:
509                         auto attr = cast(Attr)this;
510                         if (attr.ownerElement)
511                             return attr.ownerElement.isDefaultNamespace(namespaceURI);
512                         return false;
513                     default:
514                         auto parentElement = parentElement();
515                         if (parentElement)
516                             return parentElement.isDefaultNamespace(namespaceURI);
517                         return false;
518                 }
519             }
520         }
521         // TODO methods
522         override
523         {
524             void normalize() {}
525         }
526         // inner class for use in NodeWithChildren
527         class ChildList : dom.NodeList
528         {
529             private Node currentChild;
530             package this() {
531 
532             }
533             // methods specific to NodeList
534             override
535             {
536                 Node item(size_t index)
537                 {
538                     auto result = rebindable(this.outer.firstChild);
539                     for (size_t i = 0; i < index && result !is null; i++)
540                     {
541                         result = result.nextSibling;
542                     }
543                     return result;
544                 }
545                 @property size_t length()
546                 {
547                     auto child = rebindable(this.outer.firstChild);
548                     size_t result = 0;
549                     while (child)
550                     {
551                         result++;
552                         child = child.nextSibling;
553                     }
554                     return result;
555                 }
556             }
557             // more idiomatic methods
558             auto opIndex(size_t i)
559             {
560                 return item(i);
561             }
562             // range interface
563             auto front() { return currentChild; }
564             void popFront() { currentChild = currentChild.nextSibling; }
565             bool empty() { return currentChild is null; }
566         }
567         // method not required by the spec, specialized in NodeWithChildren
568         bool isAncestor(Node other) { return false; }
569         /++
570         +   `true` if and only if this node is _readonly.
571         +
572         +   The DOM specification defines a _readonly node as "a node that is immutable.
573         +   This means its list of children, its content, and its attributes, when it is
574         +   an element, cannot be changed in any way. However, a read only node can
575         +   possibly be moved, when it is not itself contained in a read only node."
576         +
577         +   For example, `Notation`s, `EntityReference`s and all of theirs descendants
578         +   are always readonly.
579         +/
580         // method not required by the spec, specialized in varous subclasses
581         @property bool readonly() { return _readonly; }
582     }
583     private abstract class NodeWithChildren : Node
584     {
585         package this() {
586 
587         }
588         override
589         {
590             @property ChildList childNodes()
591             {
592                 ChildList res = new ChildList();//allocator.multiVersionMake!ChildList(this);
593                 res.currentChild = firstChild;
594                 return res;
595             }
596             @property Node firstChild()
597             {
598                 return _firstChild;
599             }
600             @property Node lastChild()
601             {
602                 return _lastChild;
603             }
604 
605             Node insertBefore(dom.Node _newChild, dom.Node _refChild)
606             {
607                 if (readonly)
608                     throw new DOMException(dom.ExceptionCode.noModificationAllowed); //allocator.multiVersionMake!DOMException(this.outer,dom.ExceptionCode.noModificationAllowed);
609                 if (!_refChild)
610                     return appendChild(_newChild);
611 
612                 Node newChild = cast(Node)_newChild;
613                 Node refChild = cast(Node)_refChild;
614                 if (!newChild || !refChild || newChild.ownerDocument !is ownerDocument)
615                     throw new DOMException(dom.ExceptionCode.wrongDocument); //allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.wrongDocument);
616                 if (this is newChild || newChild.isAncestor(this) || newChild is refChild)
617                     throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
618                 if (refChild.parentNode !is this)
619                     throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notFound);
620 
621                 if (newChild.nodeType == dom.NodeType.documentFragment)
622                 {
623                     for (auto child = rebindable(newChild); child !is null; child = child.nextSibling)
624                         insertBefore(child, refChild);
625                     return newChild;
626                 }
627 
628                 if (newChild.parentNode)
629                     newChild.parentNode.removeChild(newChild);
630                 newChild._parentNode = this;
631                 if (refChild.previousSibling)
632                 {
633                     refChild.previousSibling._nextSibling = newChild;
634                     newChild._previousSibling = refChild.previousSibling;
635                 }
636                 refChild._previousSibling = newChild;
637                 newChild._nextSibling = refChild;
638                 if (firstChild is refChild)
639                     _firstChild = newChild;
640                 return newChild;
641             }
642             Node replaceChild(dom.Node newChild, dom.Node oldChild)
643             {
644                 insertBefore(newChild, oldChild);
645                 return removeChild(oldChild);
646             }
647             Node removeChild(dom.Node _oldChild)
648             {
649                 if (readonly)
650                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
651                 Node oldChild = cast(Node)_oldChild;
652                 if (!oldChild || oldChild.parentNode !is this)
653                     throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notFound);
654 
655                 if (oldChild is firstChild)
656                     _firstChild = oldChild.nextSibling;
657                 else
658                     oldChild.previousSibling._nextSibling = oldChild.nextSibling;
659 
660                 if (oldChild is lastChild)
661                     _lastChild = oldChild.previousSibling;
662                 else
663                     oldChild.nextSibling._previousSibling = oldChild.previousSibling;
664 
665                 oldChild._parentNode = null;
666                 oldChild._previousSibling = null;
667                 oldChild._nextSibling = null;
668                 return oldChild;
669             }
670             Node appendChild(dom.Node _newChild)
671             {
672                 if (readonly)
673                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
674                 Node newChild = cast(Node)_newChild;
675                 if (!newChild || newChild.ownerDocument !is ownerDocument)
676                     throw new DOMException(dom.ExceptionCode.wrongDocument);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.wrongDocument);
677                 if (this is newChild || newChild.isAncestor(this))
678                     throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
679                 if (newChild.parentNode !is null)
680                     newChild.parentNode.removeChild(newChild);
681 
682                 if (newChild.nodeType == dom.NodeType.documentFragment)
683                 {
684                     for (auto node = rebindable(newChild.firstChild); node !is null; node = node.nextSibling)
685                         appendChild(node);
686                     return newChild;
687                 }
688 
689                 newChild._parentNode = this;
690                 if (lastChild)
691                 {
692                     newChild._previousSibling = lastChild;
693                     lastChild._nextSibling = newChild;
694                 }
695                 else
696                     _firstChild = newChild;
697                 _lastChild = newChild;
698                 return newChild;
699             }
700             bool hasChildNodes() const
701             {
702                 return _firstChild !is null;
703             }
704             bool isAncestor(Node other)
705             {
706                 for (auto child = rebindable(firstChild); child !is null; child = child.nextSibling)
707                 {
708                     if (child is other)
709                         return true;
710                     if (child.isAncestor(other))
711                         return true;
712                 }
713                 return false;
714             }
715 
716             @property DOMString textContent()
717             {
718                 //import std.experimental.xml.appender;
719                 DOMString result;
720                 //auto result = Appender!(typeof(this.textContent()[0]), typeof(*allocator))(allocator);
721                 for (auto child = rebindable(firstChild); child !is null; child = child.nextSibling)
722                 {
723                     if (child.nodeType != dom.NodeType.comment &&
724                         child.nodeType != dom.NodeType.processingInstruction)
725                     {
726                         result ~= child.textContent();//result.put(child.textContent);
727                     }
728                 }
729                 return result;
730             }
731             @property void textContent(DOMString newVal)
732             {
733                 if (readonly)
734                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);// allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
735 
736                 while (firstChild)
737                     removeChild(firstChild);
738 
739                 _firstChild = _lastChild = ownerDocument.createTextNode(newVal);
740             }
741         }
742         private
743         {
744             Node _firstChild, _lastChild;
745 
746             void performClone(NodeWithChildren dest, bool deep)
747             {
748                 super.performClone(dest, deep);
749                 if (deep)
750                     foreach (child; childNodes)
751                     {
752                         auto childClone = child.cloneNode(true);
753                         dest.appendChild(childClone);
754                     }
755             }
756         }
757     }
758     /// Implementation of $(LINK2 ../dom/DocumentFragment, `std.experimental.xml.dom.DocumentFragment`)
759     class DocumentFragment : NodeWithChildren, dom.DocumentFragment 
760     {
761         package this() {
762 
763         }
764         // inherited from Node
765         override
766         {
767             @property dom.NodeType nodeType() { return dom.NodeType.documentFragment; }
768             @property DOMString nodeName() { return new DOMString("#document-fragment"w); }
769         }
770     }
771     /// Implementation of $(LINK2 ../dom/Document, `std.experimental.xml.dom.Document`)
772     class Document : NodeWithChildren, dom.Document
773     {
774         package this() {
775 
776         }
777         // specific to Document
778         override
779         {
780             @property DocumentType doctype() { return _doctype; }
781             @property DOMImplementation implementation() { return this.outer; }
782             @property Element documentElement() { return _root; }
783 
784             Element createElement(DOMString tagName)
785             {
786                 Element res = new Element(); //allocator.multiVersionMake!Element(this.outer);
787                 res._name = tagName;
788                 res._ownerDocument = this;
789                 res._attrs = res.createMap();//new Element.Map(); //allocator.multiVersionMake!(Element.Map)(res);
790                 return res;
791             }
792             Element createElementNS(DOMString namespaceURI, DOMString qualifiedName)
793             {
794                 Element res = new Element();//allocator.multiVersionMake!Element(this.outer);
795                 res.setQualifiedName(qualifiedName);
796                 res._namespaceURI = namespaceURI;
797                 res._ownerDocument = this;
798                 res._attrs = res.createMap();//res._attrs = new Element.Map();//allocator.multiVersionMake!(Element.Map)(res);
799                 return res;
800             }
801             DocumentFragment createDocumentFragment()
802             {
803                 DocumentFragment res = new DocumentFragment();//allocator.multiVersionMake!DocumentFragment(this.outer);
804                 res._ownerDocument = this;
805                 return res;
806             }
807             Text createTextNode(DOMString data)
808             {
809                 Text res = new Text();//allocator.multiVersionMake!Text(this.outer);
810                 res._data = data;
811                 res._ownerDocument = this;
812                 return res;
813             }
814             Comment createComment(DOMString data)
815             {
816                 Comment res = new Comment();//allocator.multiVersionMake!Comment(this.outer);
817                 res._data = data;
818                 res._ownerDocument = this;
819                 return res;
820             }
821             CDATASection createCDATASection(DOMString data)
822             {
823                 CDATASection res = new CDATASection();//allocator.multiVersionMake!CDATASection(this.outer);
824                 res._data = data;
825                 res._ownerDocument = this;
826                 return res;
827             }
828             ProcessingInstruction createProcessingInstruction(DOMString target, DOMString data)
829             {
830                 ProcessingInstruction res = new ProcessingInstruction();//allocator.multiVersionMake!ProcessingInstruction(this.outer);
831                 res._target = target;
832                 res._data = data;
833                 res._ownerDocument = this;
834                 return res;
835             }
836             Attr createAttribute(DOMString name)
837             {
838                 Attr res = new Attr();//allocator.multiVersionMake!Attr(this.outer);
839                 res._name = name;
840                 res._ownerDocument = this;
841                 return res;
842             }
843             Attr createAttributeNS(DOMString namespaceURI, DOMString qualifiedName)
844             {
845                 Attr res = new Attr();//allocator.multiVersionMake!Attr(this.outer);
846                 res.setQualifiedName(qualifiedName);
847                 res._namespaceURI = namespaceURI;
848                 res._ownerDocument = this;
849                 return res;
850             }
851             EntityReference createEntityReference(DOMString name) { return null; }
852 
853             ElementsByTagName getElementsByTagName(DOMString tagname)
854             {
855                 ElementsByTagName res = new ElementsByTagName();//allocator.multiVersionMake!ElementsByTagName;
856                 res.root = this;
857                 res.tagname = tagname;
858                 res.current = res.item(0);
859                 return res;
860             }
861             ElementsByTagNameNS getElementsByTagNameNS(DOMString namespaceURI, DOMString localName)
862             {
863                 ElementsByTagNameNS res = new ElementsByTagNameNS();//allocator.multiVersionMake!ElementsByTagNameNS;
864                 res.root = this;
865                 res.namespaceURI = namespaceURI;
866                 res.localName = localName;
867                 res.current = res.item(0);
868                 return res;
869             }
870             Element getElementById(DOMString elementId)
871             {
872                 Element find(dom.Node node) @safe
873                 {
874                     if (node.nodeType == dom.NodeType.element && node.hasAttributes)
875                         foreach (attr; node.attributes)
876                         {
877                             if ((cast(Attr)attr).isId && attr.nodeValue == elementId)
878                                 return cast(Element)node;
879                         }
880                     foreach (child; node.childNodes)
881                     {
882                         auto res = find(child);
883                         if (res)
884                             return res;
885                     }
886                     return null;
887                 }
888                 return find(_root);
889             }
890 
891             Node importNode(dom.Node node, bool deep)
892             {
893                 switch (node.nodeType) with (dom.NodeType)
894                 {
895                     case attribute:
896                         Attr result;
897                         if (node.prefix)
898                             result = createAttributeNS(node.namespaceURI, node.nodeName);
899                         else
900                             result = createAttribute(node.nodeName);
901                         auto children = node.childNodes;
902                         foreach (i; 0..children.length)
903                             result.appendChild(importNode(children.item(i), true));
904                         return result;
905                     case documentFragment:
906                         auto result = createDocumentFragment();
907                         if (deep)
908                         {
909                             auto children = node.childNodes;
910                             foreach (i; 0..children.length)
911                                 result.appendChild(importNode(children.item(i), deep));
912                         }
913                         return result;
914                     case element:
915                         Element result;
916                         if (node.prefix)
917                             result = createElementNS(node.namespaceURI, node.nodeName);
918                         else
919                             result = createElement(node.nodeName);
920                         if (node.hasAttributes)
921                         {
922                             auto attributes = node.attributes;
923                             foreach (i; 0..attributes.length)
924                             {
925                                 auto attr = cast(Attr)(importNode(attributes.item(i), deep));
926                                 assert(attr);
927                                 result.setAttributeNode(attr);
928                             }
929                         }
930                         if (deep)
931                         {
932                             auto children = node.childNodes;
933                             foreach (i; 0..children.length)
934                                 result.appendChild(importNode(children.item(i), true));
935                         }
936                         return result;
937                     case processingInstruction:
938                         return createProcessingInstruction(node.nodeName, node.nodeValue);
939                     default:
940                         throw new DOMException(dom.ExceptionCode.notSupported);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notSupported);
941                 }
942             }
943             Node adoptNode(dom.Node source) { return null; }
944 
945             @property DOMString inputEncoding() { return null; }
946             @property DOMString xmlEncoding() { return null; }
947 
948             @property bool xmlStandalone() { return _standalone; }
949             @property void xmlStandalone(bool b) { _standalone = b; }
950 
951             @property DOMString xmlVersion() { return _xmlVersion; }
952             @property void xmlVersion(DOMString ver)
953             {
954                 if (ver == "1.0" || ver == "1.1")
955                     _xmlVersion = ver;
956                 else
957                     throw new DOMException(dom.ExceptionCode.notSupported);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notSupported);
958             }
959 
960             @property bool strictErrorChecking() { return _strictErrorChecking; }
961             @property void strictErrorChecking(bool b) { _strictErrorChecking = b; }
962 
963             @property DOMString documentURI() { return _documentURI; }
964             @property void documentURI(DOMString uri) { _documentURI = uri; }
965 
966             @property DOMConfiguration domConfig() { return _config; }
967             void normalizeDocument() { }
968             Node renameNode(dom.Node n, DOMString namespaceURI, DOMString qualifiedName)
969             {
970                 auto node = cast(Node)n;
971                 if (!node || node.ownerDocument !is this)
972                     throw new DOMException(dom.ExceptionCode.wrongDocument);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.wrongDocument);
973 
974                 auto type = node.nodeType;
975                 if (type != dom.NodeType.element && type != dom.NodeType.attribute)
976                     throw new DOMException(dom.ExceptionCode.notSupported);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notSupported);
977 
978                 auto withNs = (cast(NodeWithNamespace)node);
979                 withNs.setQualifiedName(qualifiedName);
980                 withNs._namespaceURI = namespaceURI;
981                 return node;
982             }
983         }
984         private
985         {
986             DOMString _documentURI, _xmlVersion = new DOMString("1.0"w);
987             DocumentType _doctype;
988             Element _root;
989             DOMConfiguration _config;
990             bool _strictErrorChecking = true, _standalone = false;
991         }
992         // inherited from Node
993         override
994         {
995             @property dom.NodeType nodeType() { return dom.NodeType.document; }
996             @property DOMString nodeName() { return new DOMString("#document"w); }
997 
998             DOMString lookupPrefix(DOMString namespaceURI)
999             {
1000                 return documentElement.lookupPrefix(namespaceURI);
1001             }
1002             DOMString lookupNamespaceURI(DOMString prefix)
1003             {
1004                 return documentElement.lookupNamespaceURI(prefix);
1005             }
1006             bool isDefaultNamespace(DOMString namespaceURI)
1007             {
1008                 return documentElement.isDefaultNamespace(namespaceURI);
1009             }
1010         }
1011         // inherited from NodeWithChildren
1012         override
1013         {
1014             Node insertBefore(dom.Node newChild, dom.Node refChild)
1015             {
1016                 if (newChild.nodeType == dom.NodeType.element)
1017                 {
1018                     if (_root)
1019                         throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
1020 
1021                     auto res = super.insertBefore(newChild, refChild);
1022                     _root = cast(Element)newChild;
1023                     return res;
1024                 }
1025                 else if (newChild.nodeType == dom.NodeType.documentType)
1026                 {
1027                     if (_doctype)
1028                         throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
1029 
1030                     auto res = super.insertBefore(newChild, refChild);
1031                     _doctype = cast(DocumentType)newChild;
1032                     return res;
1033                 }
1034                 else if (newChild.nodeType != dom.NodeType.comment &&
1035                          newChild.nodeType != dom.NodeType.processingInstruction)
1036                     throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
1037                 else
1038                     return super.insertBefore(newChild, refChild);
1039             }
1040             Node replaceChild(dom.Node newChild, dom.Node oldChild)
1041             {
1042                 if (newChild.nodeType == dom.NodeType.element)
1043                 {
1044                     if (oldChild !is _root)
1045                         throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
1046 
1047                     auto res = super.replaceChild(newChild, oldChild);
1048                     _root = cast(Element)newChild;
1049                     return res;
1050                 }
1051                 else if (newChild.nodeType == dom.NodeType.documentType)
1052                 {
1053                     if (oldChild !is _doctype)
1054                         throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
1055 
1056                     auto res = super.replaceChild(newChild, oldChild);
1057                     _doctype = cast(DocumentType)newChild;
1058                     return res;
1059                 }
1060                 else if (newChild.nodeType != dom.NodeType.comment &&
1061                          newChild.nodeType != dom.NodeType.processingInstruction)
1062                     throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
1063                 else
1064                     return super.replaceChild(newChild, oldChild);
1065             }
1066             Node removeChild(dom.Node oldChild)
1067             {
1068                 if (oldChild.nodeType == dom.NodeType.element)
1069                 {
1070                     auto res = super.removeChild(oldChild);
1071                     _root = null;
1072                     return res;
1073                 }
1074                 else if (oldChild.nodeType == dom.NodeType.documentType)
1075                 {
1076                     auto res = super.removeChild(oldChild);
1077                     _doctype = null;
1078                     return res;
1079                 }
1080                 else
1081                     return super.removeChild(oldChild);
1082             }
1083             Node appendChild(dom.Node newChild)
1084             {
1085                 if (newChild.nodeType == dom.NodeType.element)
1086                 {
1087                     if (_root)
1088                         throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
1089 
1090                     auto res = super.appendChild(newChild);
1091                     _root = cast(Element)newChild;
1092                     return res;
1093                 }
1094                 else if (newChild.nodeType == dom.NodeType.documentType)
1095                 {
1096                     if (_doctype)
1097                         throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.hierarchyRequest);
1098 
1099                     auto res = super.appendChild(newChild);
1100                     _doctype = cast(DocumentType)newChild;
1101                     return res;
1102                 }
1103                 else
1104                     return super.appendChild(newChild);
1105             }
1106         }
1107     }
1108     alias ElementsByTagName = ElementsByTagNameImpl!false;
1109     alias ElementsByTagNameNS = ElementsByTagNameImpl!true;
1110     static class ElementsByTagNameImpl(bool ns) : dom.NodeList
1111     {
1112         package this() {
1113 
1114         }
1115         private Node root;
1116         private Element current;
1117         static if (ns)
1118             private DOMString namespaceURI, localName;
1119         else
1120             private DOMString tagname;
1121 
1122         private bool check(Node node)
1123         {
1124             static if (ns)
1125             {
1126                 if (node.nodeType == dom.NodeType.element)
1127                 {
1128                     Element elem = cast(Element)node;
1129                     return elem.namespaceURI == namespaceURI && elem.localName == localName;
1130                 }
1131                 else
1132                     return false;
1133             }
1134             else
1135                 return node.nodeType == dom.NodeType.element && node.nodeName == tagname;
1136         }
1137 
1138         private Element findNext(Node node)
1139         {
1140             if (node.firstChild)
1141             {
1142                 auto item = node.firstChild;
1143                 if (check(item))
1144                     return cast(Element)item;
1145                 else
1146                     return findNext(item);
1147             }
1148             else
1149                 return findNextBack(node);
1150         }
1151         private Element findNextBack(Node node)
1152         {
1153             if (node.nextSibling)
1154             {
1155                 auto item = node.nextSibling;
1156 
1157                 if (check(item))
1158                     return cast(Element)item;
1159                 else
1160                     return findNext(item);
1161             }
1162             else if (node.parentNode && node.parentNode !is node.ownerDocument)
1163                 return findNextBack(node.parentNode);
1164             else
1165                 return null;
1166         }
1167 
1168         // methods specific to NodeList
1169         override
1170         {
1171             @property size_t length()
1172             {
1173                 size_t res = 0;
1174                 auto node = findNext(root);
1175                 while (node !is null)
1176                 {
1177                     //writeln("Found node ", node.nodeName);
1178                     res++;
1179                     node = findNext(node);
1180                 }
1181                 return res;
1182             }
1183             Element item(size_t i)
1184             {
1185                 auto res = findNext(root);
1186                 while (res && i > 0)
1187                 {
1188                     res = findNext(res);
1189                     i--;
1190                 }
1191                 return res;
1192             }
1193         }
1194         // more idiomatic methods
1195         auto opIndex(size_t i) { return item(i); }
1196 
1197         // range interface
1198         bool empty() { return current is null; }
1199         void popFront() { current = findNext(current); }
1200         auto front() { return current; }
1201     }
1202     /// Implementation of $(LINK2 ../dom/CharacterData, `std.experimental.xml.dom.CharacterData`)
1203     abstract class CharacterData : Node, dom.CharacterData
1204     {
1205         // specific to CharacterData
1206         override
1207         {
1208             @property DOMString data() { return _data; }
1209             @property void data(DOMString newVal) { _data = newVal; }
1210             @property size_t length() { return _data.length; }
1211 
1212             DOMString substringData(size_t offset, size_t count)
1213             {
1214                 if (offset > length)
1215                     throw new DOMException(dom.ExceptionCode.indexSize);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.indexSize);
1216 
1217                 import std.algorithm : min;
1218                 return _data[offset..min(offset + count, length)];
1219             }
1220             void appendData(DOMString arg)
1221             {
1222                 import std.traits : Unqual;
1223 
1224                 //auto newData = allocator.makeArray!(Unqual!(typeof(_data[0])))(_data.length + arg.length);
1225 
1226                 _data ~= arg;
1227             }
1228             void insertData(size_t offset, DOMString arg)
1229             {
1230                 import std.traits : Unqual;
1231 
1232                 if (offset > length)
1233                     throw new DOMException(dom.ExceptionCode.indexSize);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.indexSize);
1234 
1235                 //auto newData = allocator.makeArray!(Unqual!(typeof(_data[0])))(_data.length + arg.length);
1236                 /+CharType[] newData;
1237                 newData.reserve = _data.length + arg.length;
1238                 newData = _data[0 .. offset];
1239                 newData[offset .. (offset + arg.length)] = arg;
1240                 newData[(offset + arg.length) .. $] = _data[offset .. $];
1241 
1242                 _data = cast(typeof(_data))newData;+/
1243                 _data.insertData(offset, arg);
1244             }
1245             void deleteData(size_t offset, size_t count)
1246             {
1247                 _data.deleteData(offset, count);
1248             }
1249             void replaceData(size_t offset, size_t count, DOMString arg)
1250             {
1251                 _data.deleteData(offset, count);
1252                 _data.insertData(offset, arg);
1253             }
1254         }
1255         // inherited from Node
1256         override
1257         {
1258             @property DOMString nodeValue() { return data; }
1259             @property void nodeValue(DOMString newVal)
1260             {
1261                 if (readonly)
1262                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
1263                 data = newVal;
1264             }
1265             @property DOMString textContent() { return data; }
1266             @property void textContent(DOMString newVal)
1267             {
1268                 if (readonly)
1269                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
1270                 data = newVal;
1271             }
1272         }
1273         private
1274         {
1275             DOMString _data;
1276 
1277             // internal method
1278             void performClone(CharacterData dest, bool deep)
1279             {
1280                 super.performClone(dest, deep);
1281                 dest._data = _data;
1282             }
1283         }
1284     }
1285     private abstract class NodeWithNamespace : NodeWithChildren
1286     {
1287         private
1288         {
1289             DOMString _name, _namespaceURI;
1290             size_t _colon;
1291 
1292             void setQualifiedName(DOMString name)
1293             {
1294                 import newxml.faststrings : fastIndexOf;
1295 
1296                 _name = name;
1297                 ptrdiff_t i = name.getDString.fastIndexOf(':');
1298                 if (i > 0)
1299                     _colon = i;
1300             }
1301             void performClone(NodeWithNamespace dest, bool deep)
1302             {
1303                 super.performClone(dest, deep);
1304                 dest._name = _name;
1305                 dest._namespaceURI = namespaceURI;
1306                 dest._colon = _colon;
1307             }
1308         }
1309         // inherited from Node
1310         override
1311         {
1312             @property DOMString nodeName() { return _name; }
1313 
1314             @property DOMString localName()
1315             {
1316                 if (!_colon)
1317                     return null;
1318                 return _name[(_colon+1)..$];
1319             }
1320             @property DOMString prefix()
1321             {
1322                 return _name[0.._colon];
1323             }
1324             @property void prefix(DOMString pre)
1325             {
1326                 if (readonly)
1327                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
1328 
1329                 import std.traits : Unqual;
1330 
1331                 /+auto newName = allocator.makeArray!(Unqual!(typeof(_name[0])))(pre.length + localName.length + 1);
1332                 newName[0 .. pre.length] = pre[];
1333                 newName[pre.length] = ':';
1334                 newName[(pre.length + 1) .. $] = localName[];+/
1335 
1336                 //_name = pre ~ ":" ~ localName;
1337                 _name ~= pre;
1338                 _name ~= "w";
1339                 _name ~= localName;
1340                 _colon = pre.length;
1341             }
1342             @property DOMString namespaceURI() { return _namespaceURI; }
1343         }
1344     }
1345     /// Implementation of $(LINK2 ../dom/Attr, `std.experimental.xml.dom.Attr`)
1346     class Attr : NodeWithNamespace, dom.Attr
1347     {
1348         package this() {
1349 
1350         }
1351         // specific to Attr
1352         override
1353         {
1354             /// Implementation of $(LINK2 ../dom/Attr.name, `std.experimental.xml.dom.Attr.name`).
1355             @property DOMString name() { return _name; }
1356             /// Implementation of $(LINK2 ../dom/Attr.specified, `std.experimental.xml.dom.Attr.specified`).
1357             @property bool specified() { return _specified; }
1358             /// Implementation of $(LINK2 ../dom/Attr.value, `std.experimental.xml.dom.Attr.value`).
1359             @property DOMString value()
1360             {
1361 
1362                 //auto result = Appender!(typeof(_name[0]), typeof(*allocator))(allocator);
1363                 DOMString result = new DOMString();
1364                 auto child = rebindable(firstChild);
1365                 while (child)
1366                 {
1367                     result ~= child.textContent;//result.put(child.textContent);
1368                     child = child.nextSibling;
1369                 }
1370                 return result;
1371             }
1372             /// ditto
1373             @property void value(DOMString newVal)
1374             {
1375                 while (firstChild)
1376                     removeChild(firstChild);
1377                 appendChild(ownerDocument.createTextNode(newVal));
1378             }
1379 
1380             /// Implementation of $(LINK2 ../dom/Attr.ownerElement, `std.experimental.xml.dom.Attr.ownerElement`).
1381             @property Element ownerElement() { return _ownerElement; }
1382             /// Implementation of $(LINK2 ../dom/Attr.schemaTypeInfo, `std.experimental.xml.dom.Attr.schemaTypeInfo`).
1383             @property dom.XMLTypeInfo schemaTypeInfo() { return null; }
1384             /// Implementation of $(LINK2 ../dom/Attr.isId, `std.experimental.xml.dom.Attr.isId`).
1385             @property bool isId() { return _isId; }
1386         }
1387         private
1388         {
1389             Element _ownerElement;
1390             bool _specified = true, _isId = false;
1391             @property Attr _nextAttr() { return cast(Attr)_nextSibling; }
1392             @property Attr _previousAttr() { return cast(Attr)_previousSibling; }
1393         }
1394         // inherited from Node
1395         override
1396         {
1397             @property dom.NodeType nodeType() { return dom.NodeType.attribute; }
1398 
1399             @property DOMString nodeValue() { return value; }
1400             @property void nodeValue(DOMString newVal)
1401             {
1402                 if (readonly)
1403                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
1404                 value = newVal;
1405             }
1406 
1407             // overridden because we reuse _nextSibling and _previousSibling with another meaning
1408             @property Attr nextSibling() { return null; }
1409             @property Attr previousSibling() { return null; }
1410 
1411             Attr cloneNode(bool deep)
1412             {
1413                 Attr cloned = new Attr();//allocator.multiVersionMake!Attr(this.outer);
1414                 cloned._ownerDocument = _ownerDocument;
1415                 super.performClone(cloned, true);
1416                 cloned._specified = true;
1417                 return cloned;
1418             }
1419 
1420             DOMString lookupPrefix(DOMString namespaceURI)
1421             {
1422                 if (ownerElement)
1423                     return ownerElement.lookupPrefix(namespaceURI);
1424                 return null;
1425             }
1426             DOMString lookupNamespaceURI(DOMString prefix)
1427             {
1428                 if (ownerElement)
1429                     return ownerElement.lookupNamespaceURI(prefix);
1430                 return null;
1431             }
1432             bool isDefaultNamespace(DOMString namespaceURI)
1433             {
1434                 if (ownerElement)
1435                     return ownerElement.isDefaultNamespace(namespaceURI);
1436                 return false;
1437             }
1438         }
1439     }
1440     /// Implementation of $(LINK2 ../dom/Element, `std.experimental.xml.dom.Element`)
1441     class Element : NodeWithNamespace, dom.Element
1442     {
1443         package this() {
1444 
1445         }
1446         ///Created as a workaround to a common D compiler bug/artifact.
1447         package Map createMap() {
1448             return new Map();
1449         }
1450         // specific to Element
1451         override
1452         {
1453             /// Implementation of $(LINK2 ../dom/Element.tagName, `std.experimental.xml.dom.Element.tagName`).
1454             @property DOMString tagName() { return _name; }
1455 
1456             /++
1457             +   Implementation of $(LINK2 ../dom/Element.getAttribute,
1458             +   `std.experimental.xml.dom.Element.getAttribute`).
1459             +/
1460             DOMString getAttribute(DOMString name)
1461             {
1462                 return _attrs.getNamedItem(name).value;
1463             }
1464             /++
1465             +   Implementation of $(LINK2 ../dom/Element.setAttribute,
1466             +   `std.experimental.xml.dom.Element.setAttribute`).
1467             +/
1468             void setAttribute(DOMString name, DOMString value)
1469             {
1470                 auto attr = ownerDocument.createAttribute(name);
1471                 attr.nodeValue = value;
1472                 attr._ownerElement = this;
1473                 _attrs.setNamedItem(attr);
1474             }
1475             /++
1476             +   Implementation of $(LINK2 ../dom/Element.removeAttribute,
1477             +   `std.experimental.xml.dom.Element.removeAttribute`).
1478             +/
1479             void removeAttribute(DOMString name)
1480             {
1481                 _attrs.removeNamedItem(name);
1482             }
1483 
1484             /++
1485             +   Implementation of $(LINK2 ../dom/Element.getAttributeNode,
1486             +   `std.experimental.xml.dom.Element.getAttributeNode`).
1487             +/
1488             Attr getAttributeNode(DOMString name)
1489             {
1490                 return _attrs.getNamedItem(name);
1491             }
1492             /++
1493             +   Implementation of $(LINK2 ../dom/Element.setAttributeNode,
1494             +   `std.experimental.xml.dom.Element.setAttributeNode`).
1495             +/
1496             Attr setAttributeNode(dom.Attr newAttr)
1497             {
1498                 return _attrs.setNamedItem(newAttr);
1499             }
1500             /++
1501             +   Implementation of $(LINK2 ../dom/Element.removeAttributeNode,
1502             +   `std.experimental.xml.dom.Element.removeAttributeNode`).
1503             +/
1504             Attr removeAttributeNode(dom.Attr oldAttr)
1505             {
1506                 if (_attrs.getNamedItemNS(oldAttr.namespaceURI, oldAttr.name) is oldAttr)
1507                     return _attrs.removeNamedItemNS(oldAttr.namespaceURI, oldAttr.name);
1508                 else if (_attrs.getNamedItem(oldAttr.name) is oldAttr)
1509                     return _attrs.removeNamedItem(oldAttr.name);
1510 
1511                 throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notFound);
1512             }
1513 
1514             /++
1515             +   Implementation of $(LINK2 ../dom/Element.getAttributeNS,
1516             +   `std.experimental.xml.dom.Element.getAttributeNS`).
1517             +/
1518             DOMString getAttributeNS(DOMString namespaceURI, DOMString localName)
1519             {
1520                 return _attrs.getNamedItemNS(namespaceURI, localName).value;
1521             }
1522             /++
1523             +   Implementation of $(LINK2 ../dom/Element.setAttributeNS,
1524             +   `std.experimental.xml.dom.Element.setAttributeNS`).
1525             +/
1526             void setAttributeNS(DOMString namespaceURI, DOMString qualifiedName, DOMString value)
1527             {
1528                 auto attr = ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
1529                 attr.nodeValue = value;
1530                 attr._ownerElement = this;
1531                 _attrs.setNamedItem(attr);
1532             }
1533             /++
1534             +   Implementation of $(LINK2 ../dom/Element.removeAttributeNS,
1535             +   `std.experimental.xml.dom.Element.removeAttributeNS`).
1536             +/
1537             void removeAttributeNS(DOMString namespaceURI, DOMString localName)
1538             {
1539                 _attrs.removeNamedItemNS(namespaceURI, localName);
1540             }
1541 
1542             /++
1543             +   Implementation of $(LINK2 ../dom/Element.getAttributeNodeNS,
1544             +   `std.experimental.xml.dom.Element.getAttributeNodeNS`).
1545             +/
1546             Attr getAttributeNodeNS(DOMString namespaceURI, DOMString localName)
1547             {
1548                 return _attrs.getNamedItemNS(namespaceURI, localName);
1549             }
1550             /++
1551             +   Implementation of $(LINK2 ../dom/Element.setAttributeNodeNS,
1552             +   `std.experimental.xml.dom.Element.setAttributeNodeNS`).
1553             +/
1554             Attr setAttributeNodeNS(dom.Attr newAttr)
1555             {
1556                 return _attrs.setNamedItemNS(newAttr);
1557             }
1558 
1559             /++
1560             +   Implementation of $(LINK2 ../dom/Element.hasAttribute,
1561             +   `std.experimental.xml.dom.Element.hasAttribute`).
1562             +/
1563             bool hasAttribute(DOMString name)
1564             {
1565                 return _attrs.getNamedItem(name) !is null;
1566             }
1567             /++
1568             +   Implementation of $(LINK2 ../dom/Element.hasAttributeNS,
1569             +   `std.experimental.xml.dom.Element.hasAttributeNS`).
1570             +/
1571             bool hasAttributeNS(DOMString namespaceURI, DOMString localName)
1572             {
1573                 return _attrs.getNamedItemNS(namespaceURI, localName) !is null;
1574             }
1575 
1576             /++
1577             +   Implementation of $(LINK2 ../dom/Element.setIdAttribute,
1578             +   `std.experimental.xml.dom.Element.setIdAttribute`).
1579             +/
1580             void setIdAttribute(DOMString name, bool isId)
1581             {
1582                 auto attr = _attrs.getNamedItem(name);
1583                 if (attr)
1584                     attr._isId = isId;
1585                 else
1586                     throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notFound);
1587             }
1588             /++
1589             +   Implementation of $(LINK2 ../dom/Element.setIdAttributeNS,
1590             +   `std.experimental.xml.dom.Element.setIdAttributeNS`).
1591             +/
1592             void setIdAttributeNS(DOMString namespaceURI, DOMString localName, bool isId)
1593             {
1594                 auto attr = _attrs.getNamedItemNS(namespaceURI, localName);
1595                 if (attr)
1596                     attr._isId = isId;
1597                 else
1598                     throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notFound);
1599             }
1600             /++
1601             +   Implementation of $(LINK2 ../dom/Element.getAttribute,
1602             +   `std.experimental.xml.dom.Element.getAttribute`).
1603             +/
1604             void setIdAttributeNode(dom.Attr idAttr, bool isId)
1605             {
1606                 if (_attrs.getNamedItemNS(idAttr.namespaceURI, idAttr.name) is idAttr)
1607                     (cast(Attr)idAttr)._isId = isId;
1608                 else if (_attrs.getNamedItem(idAttr.name) is idAttr)
1609                     (cast(Attr)idAttr)._isId = isId;
1610                 else
1611                     throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notFound);
1612             }
1613 
1614             /++
1615             +   Implementation of $(LINK2 ../dom/Element.getElementsByTagName,
1616             +   `std.experimental.xml.dom.Element.getElementsByTagName`).
1617             +/
1618             ElementsByTagName getElementsByTagName(DOMString tagname)
1619             {
1620                 ElementsByTagName res = new ElementsByTagName();//allocator.multiVersionMake!ElementsByTagName;
1621                 res.root = this;
1622                 res.tagname = tagname;
1623                 res.current = res.item(0);
1624                 return res;
1625             }
1626             /++
1627             +   Implementation of $(LINK2 ../dom/Element.getElementsByTagNameNS,
1628             +   `std.experimental.xml.dom.Element.getElementsByTagNameNS`).
1629             +/
1630             ElementsByTagNameNS getElementsByTagNameNS(DOMString namespaceURI, DOMString localName)
1631             {
1632                 ElementsByTagNameNS res = new ElementsByTagNameNS();//allocator.multiVersionMake!ElementsByTagNameNS;
1633                 res.root = this;
1634                 res.namespaceURI = namespaceURI;
1635                 res.localName = localName;
1636                 res.current = res.item(0);
1637                 return res;
1638             }
1639 
1640             /++
1641             +   Implementation of $(LINK2 ../dom/Element.schemaTypeInfo,
1642             +   `std.experimental.xml.dom.Element.schemaTypeInfo`).
1643             +/
1644             @property dom.XMLTypeInfo schemaTypeInfo() { return null; }
1645         }
1646         private
1647         {
1648             Map _attrs;
1649 
1650             // internal methods
1651             DOMString lookupNamespacePrefix(DOMString namespaceURI, Element originalElement)
1652             {
1653                 if (this.namespaceURI && this.namespaceURI == namespaceURI
1654                     && this.prefix && originalElement.lookupNamespaceURI(this.prefix) == namespaceURI)
1655                 {
1656                     return this.prefix;
1657                 }
1658                 if (hasAttributes)
1659                     foreach (attr; attributes)
1660                         if (attr.prefix == "xmlns" && attr.nodeValue == namespaceURI &&
1661                             originalElement.lookupNamespaceURI(attr.localName) == namespaceURI)
1662                         {
1663                             return attr.localName;
1664                         }
1665                 auto parentElement = parentElement();
1666                 if (parentElement)
1667                     return parentElement.lookupNamespacePrefix(namespaceURI, originalElement);
1668                 return null;
1669             }
1670         }
1671         // inherited from Node
1672         override
1673         {
1674             @property dom.NodeType nodeType() { return dom.NodeType.element; }
1675 
1676             @property Map attributes() { return _attrs.length > 0 ? _attrs : null; }
1677             bool hasAttributes() { return _attrs.length > 0; }
1678 
1679             Element cloneNode(bool deep)
1680             {
1681                 Element cloned = new Element();//allocator.multiVersionMake!Element(this.outer);
1682                 cloned._ownerDocument = ownerDocument;
1683                 cloned._attrs = new Map();//allocator.multiVersionMake!Map(this);
1684                 super.performClone(cloned, deep);
1685                 return cloned;
1686             }
1687 
1688             DOMString lookupPrefix(DOMString namespaceURI)
1689             {
1690                 return lookupNamespacePrefix(namespaceURI, this);
1691             }
1692             DOMString lookupNamespaceURI(DOMString prefix)
1693             {
1694                 if (namespaceURI && prefix == prefix)
1695                     return namespaceURI;
1696 
1697                 if (hasAttributes)
1698                 {
1699                     foreach (attr; attributes)
1700                         if (attr.prefix == "xmlns" && attr.localName == prefix)
1701                             return attr.nodeValue;
1702                         else if (attr.nodeName == "xmlns" && !prefix)
1703                             return attr.nodeValue;
1704                 }
1705                 auto parentElement = parentElement();
1706                 if (parentElement)
1707                     return parentElement.lookupNamespaceURI(prefix);
1708                 return null;
1709             }
1710             bool isDefaultNamespace(DOMString namespaceURI)
1711             {
1712                 if (!prefix)
1713                     return this.namespaceURI == namespaceURI;
1714                 if (hasAttributes)
1715                 {
1716                     foreach (attr; attributes)
1717                         if (attr.nodeName == "xmlns")
1718                             return attr.nodeValue == namespaceURI;
1719                 }
1720                 auto parentElement = parentElement();
1721                 if (parentElement)
1722                     return parentElement.isDefaultNamespace(namespaceURI);
1723                 return false;
1724             }
1725         }
1726 
1727         class Map : dom.NamedNodeMap
1728         {
1729             package this() {
1730 
1731             }
1732             // specific to NamedNodeMap
1733             public override
1734             {
1735                 size_t length()
1736                 {
1737                     size_t res = 0;
1738                     auto attr = firstAttr;
1739                     while (attr)
1740                     {
1741                         res++;
1742                         attr = attr._nextAttr;
1743                     }
1744                     return res;
1745                 }
1746                 Attr item(size_t index)
1747                 {
1748                     size_t count = 0;
1749                     auto res = firstAttr;
1750                     while (res && count < index)
1751                     {
1752                         count++;
1753                         res = res._nextAttr;
1754                     }
1755                     return res;
1756                 }
1757 
1758                 Attr getNamedItem(DOMString name)
1759                 {
1760                     auto res = firstAttr;
1761                     while (res && res.nodeName != name)
1762                         res = res._nextAttr;
1763                     return res;
1764                 }
1765                 Attr setNamedItem(dom.Node arg)
1766                 {
1767                     if (arg.ownerDocument !is this.outer.ownerDocument)
1768                         throw new DOMException(dom.ExceptionCode.wrongDocument);//allocator.multiVersionMake!DOMException(this.outer.outer, dom.ExceptionCode.wrongDocument);
1769 
1770                     Attr attr = cast(Attr)arg;
1771                     if (!attr)
1772                         throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer.outer, dom.ExceptionCode.hierarchyRequest);
1773 
1774                     if (attr._previousAttr)
1775                         attr._previousAttr._nextSibling = attr._nextAttr;
1776                     if (attr._nextAttr)
1777                         attr._nextAttr._previousSibling = attr._previousAttr;
1778 
1779                     auto res = firstAttr;
1780                     while (res && res.nodeName != attr.nodeName)
1781                         res = res._nextAttr;
1782 
1783                     if (res)
1784                     {
1785                         attr._previousSibling = res._previousAttr;
1786                         attr._nextSibling = res._nextAttr;
1787                         if (res is firstAttr) firstAttr = attr;
1788                     }
1789                     else
1790                     {
1791                         attr._nextSibling = firstAttr;
1792                         firstAttr = attr;
1793                         attr._previousSibling = null;
1794                         currentAttr = firstAttr;
1795                     }
1796 
1797                     return res;
1798                 }
1799                 Attr removeNamedItem(DOMString name)
1800                 {
1801                     auto res = firstAttr;
1802                     while (res && res.nodeName != name)
1803                         res = res._nextAttr;
1804 
1805                     if (res)
1806                     {
1807                         if (res._previousAttr)
1808                             res._previousAttr._nextSibling = res._nextAttr;
1809                         if (res._nextAttr)
1810                             res._nextAttr._previousSibling = res._previousAttr;
1811                         return res;
1812                     }
1813                     else
1814                         throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer.outer, dom.ExceptionCode.notFound);
1815                 }
1816 
1817                 Attr getNamedItemNS(DOMString namespaceURI, DOMString localName)
1818                 {
1819                     auto res = firstAttr;
1820                     while (res && (res.localName != localName || res.namespaceURI != namespaceURI))
1821                         res = res._nextAttr;
1822                     return res;
1823                 }
1824                 Attr setNamedItemNS(dom.Node arg)
1825                 {
1826                     if (arg.ownerDocument !is this.outer.ownerDocument)
1827                         throw new DOMException(dom.ExceptionCode.wrongDocument);//allocator.multiVersionMake!DOMException(this.outer.outer, dom.ExceptionCode.wrongDocument);
1828 
1829                     Attr attr = cast(Attr)arg;
1830                     if (!attr)
1831                         throw new DOMException(dom.ExceptionCode.hierarchyRequest);//allocator.multiVersionMake!DOMException(this.outer.outer, dom.ExceptionCode.hierarchyRequest);
1832 
1833                     if (attr._previousAttr)
1834                         attr._previousAttr._nextSibling = attr._nextAttr;
1835                     if (attr._nextAttr)
1836                         attr._nextAttr._previousSibling = attr._previousAttr;
1837 
1838                     auto res = firstAttr;
1839                     while (res && (res.localName != attr.localName || res.namespaceURI != attr.namespaceURI))
1840                         res = res._nextAttr;
1841 
1842                     if (res)
1843                     {
1844                         attr._previousSibling = res._previousAttr;
1845                         attr._nextSibling = res._nextAttr;
1846                         if (res is firstAttr) firstAttr = attr;
1847                     }
1848                     else
1849                     {
1850                         attr._nextSibling = firstAttr;
1851                         firstAttr = attr;
1852                         attr._previousSibling = null;
1853                         currentAttr = firstAttr;
1854                     }
1855 
1856                     return res;
1857                 }
1858                 Attr removeNamedItemNS(DOMString namespaceURI, DOMString localName)
1859                 {
1860                     auto res = firstAttr;
1861                     while (res && (res.localName != localName || res.namespaceURI != namespaceURI))
1862                         res = res._nextAttr;
1863 
1864                     if (res)
1865                     {
1866                         if (res._previousAttr)
1867                             res._previousAttr._nextSibling = res._nextAttr;
1868                         if (res._nextAttr)
1869                             res._nextAttr._previousSibling = res._previousAttr;
1870                         return res;
1871                     }
1872                     else
1873                         throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer.outer, dom.ExceptionCode.notFound);
1874                 }
1875             }
1876             private
1877             {
1878                 Attr firstAttr;
1879                 Attr currentAttr;
1880             }
1881             // better methods
1882             auto opIndex(size_t i) { return item(i); }
1883 
1884             // range interface
1885             auto opSlice()
1886             {
1887                 struct Range
1888                 {
1889                     Attr currentAttr;
1890 
1891                     auto front() { return currentAttr; }
1892                     void popFront() { currentAttr = currentAttr._nextAttr; }
1893                     bool empty() { return currentAttr is null; }
1894                 }
1895                 return Range(firstAttr);
1896             }
1897         }
1898     }
1899     /// Implementation of $(LINK2 ../dom/Text, `std.experimental.xml.dom.Text`)
1900     class Text: CharacterData, dom.Text
1901     {
1902         package this() {
1903 
1904         }
1905         // specific to Text
1906         override
1907         {
1908             /// Implementation of $(LINK2 ../dom/Text.splitText, `std.experimental.xml.dom.Text.splitText`).
1909             Text splitText(size_t offset)
1910             {
1911                 if (offset > data.length)
1912                     throw new DOMException(dom.ExceptionCode.indexSize);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.indexSize);
1913                 auto second = ownerDocument.createTextNode(data[offset..$]);
1914                 data = data[0..offset];
1915                 if (parentNode)
1916                 {
1917                     if (nextSibling)
1918                         parentNode.insertBefore(second, nextSibling);
1919                     else
1920                         parentNode.appendChild(second);
1921                 }
1922                 return second;
1923             }
1924             /++
1925             +   Implementation of $(LINK2 ../dom/Text.isElementContentWhitespace,
1926             +   `std.experimental.xml.dom.Text.isElementContentWhitespace`).
1927             +/
1928             @property bool isElementContentWhitespace()
1929             {
1930                 import newxml.faststrings: fastIndexOfNeither;
1931 
1932                 return _data.getDString.fastIndexOfNeither(" \r\n\t") == -1;
1933             }
1934             /// Implementation of $(LINK2 ../dom/Text.wholeText, `std.experimental.xml.dom.Text.wholeText`).
1935             @property DOMString wholeText()
1936             {
1937                 Text findPreviousText(Text text)
1938                 {
1939                     Node node = text;
1940                     do
1941                     {
1942                         if (node.previousSibling)
1943                             switch (node.previousSibling.nodeType) with (dom.NodeType)
1944                             {
1945                                 case text:
1946                                 case cdataSection:
1947                                     return cast(Text) node.previousSibling;
1948                                 case entityReference:
1949                                     if (cast(Text)(node.previousSibling.lastChild))
1950                                         return cast(Text) node.previousSibling.lastChild;
1951                                     else
1952                                         return null;
1953                                 default:
1954                                     return null;
1955                             }
1956                         node = node.parentNode;
1957                     }
1958                     while (node && node.nodeType == dom.NodeType.entityReference);
1959                     return null;
1960                 }
1961                 Text findNextText(Text text)
1962                 {
1963                     Node node = text;
1964                     do
1965                     {
1966                         if (node.nextSibling)
1967                             switch (node.nextSibling.nodeType) with (dom.NodeType)
1968                             {
1969                                 case text:
1970                                 case cdataSection:
1971                                     return cast(Text) node.nextSibling;
1972                                 case entityReference:
1973                                     if (cast(Text)(node.nextSibling.firstChild))
1974                                         return cast(Text) node.nextSibling.firstChild;
1975                                     else
1976                                         return null;
1977                                 default:
1978                                     return null;
1979                             }
1980                         node = node.parentNode;
1981                     }
1982                     while (node && node.nodeType == dom.NodeType.entityReference);
1983                     return null;
1984                 }
1985 
1986                 //import std.experimental.xml.appender;
1987                 DOMString result;//auto result = Appender!(typeof(_data[0]), typeof(*allocator))(allocator);
1988 
1989                 Text node, prev = this;
1990                 do
1991                 {
1992                     node = prev;
1993                     prev = findPreviousText(node);
1994                 }
1995                 while (prev);
1996                 while (node)
1997                 {
1998                     result ~= node.data;
1999                     node = findNextText(node);
2000                 }
2001                 return result;
2002             }
2003             /++
2004             +   Implementation of $(LINK2 ../dom/Text.replaceWholeText,
2005             +   `std.experimental.xml.dom.Text.replaceWholeText`).
2006             +/
2007             // the W3C DOM spec explains the details of this
2008             @property Text replaceWholeText(DOMString newText)
2009             {
2010                 bool hasOnlyText(Node reference) @safe
2011                 {
2012                     foreach (child; reference.childNodes)
2013                         switch (child.nodeType) with (dom.NodeType)
2014                         {
2015                             case text:
2016                             case cdataSection:
2017                                 break;
2018                             case entityReference:
2019                                 if (!hasOnlyText(reference))
2020                                     return false;
2021                                 break;
2022                             default:
2023                                 return false;
2024                         }
2025                     return false;
2026                 }
2027                 bool startsWithText(Node reference)
2028                 {
2029                     if (!reference.firstChild) return false;
2030                     switch (reference.firstChild.nodeType) with (dom.NodeType)
2031                     {
2032                         case text:
2033                         case cdataSection:
2034                             return true;
2035                         case entityReference:
2036                             return startsWithText(reference.firstChild);
2037                         default:
2038                             return false;
2039                     }
2040                 }
2041                 bool endsWithText(Node reference)
2042                 {
2043                     if (!reference.lastChild) return false;
2044                     switch (reference.lastChild.nodeType) with (dom.NodeType)
2045                     {
2046                         case text:
2047                         case cdataSection:
2048                             return true;
2049                         case entityReference:
2050                             return endsWithText(reference.lastChild);
2051                         default:
2052                             return false;
2053                     }
2054                 }
2055 
2056                 Node current;
2057                 if (parentNode && parentNode.nodeType == dom.NodeType.entityReference)
2058                 {
2059                     current = parentNode;
2060                     while (current.parentNode && current.parentNode.nodeType == dom.NodeType.entityReference)
2061                         current = current.parentNode;
2062 
2063                     if (!hasOnlyText(current))
2064                         throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
2065                 }
2066                 else if (readonly)
2067                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
2068                 else
2069                     current = this;
2070 
2071                 size_t previousToKill, nextToKill;
2072                 auto node = current.previousSibling;
2073                 while (node)
2074                 {
2075                     if (node.nodeType == dom.NodeType.entityReference)
2076                     {
2077                         if (endsWithText(node))
2078                         {
2079                             if (!hasOnlyText(node))
2080                                 throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
2081                         }
2082                         else break;
2083                     }
2084                     else if (!cast(Text)node)
2085                         break;
2086 
2087                     previousToKill++;
2088                     node = node.previousSibling;
2089                 }
2090                 node = current.nextSibling;
2091                 while (node)
2092                 {
2093                     if (node.nodeType == dom.NodeType.entityReference)
2094                     {
2095                         if (startsWithText(node))
2096                         {
2097                             if (!hasOnlyText(node))
2098                                 throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
2099                         }
2100                         else break;
2101                     }
2102                     else if (!cast(Text)node)
2103                         break;
2104 
2105                     nextToKill++;
2106                     node = node.nextSibling;
2107                 }
2108 
2109                 foreach (i; 0..previousToKill)
2110                     current.parentNode.removeChild(current.previousSibling);
2111                 foreach (i; 0..nextToKill)
2112                     current.parentNode.removeChild(current.nextSibling);
2113 
2114                 if (current.nodeType == dom.NodeType.entityReference)
2115                 {
2116                     if (current.parentNode && newText)
2117                     {
2118                         auto result = ownerDocument.createTextNode(newText);
2119                         current.parentNode.replaceChild(current, result);
2120                         return result;
2121                     }
2122 
2123                     if (current.parentNode)
2124                         current.removeChild(current);
2125 
2126                     if (!newText) return null;
2127 
2128                     return ownerDocument.createTextNode(newText);
2129                 }
2130                 _data = newText;
2131                 return this;
2132             }
2133         }
2134         // inherited from Node
2135         override
2136         {
2137             @property dom.NodeType nodeType() { return dom.NodeType.text; }
2138             @property DOMString nodeName() { return new DOMString("#text"); }
2139 
2140             Text cloneNode(bool deep)
2141             {
2142                 Text cloned = new Text();//allocator.multiVersionMake!Text(this.outer);
2143                 cloned._ownerDocument = _ownerDocument;
2144                 super.performClone(cloned, deep);
2145                 return cloned;
2146             }
2147         }
2148     }
2149     /// Implementation of $(LINK2 ../dom/Comment, `std.experimental.xml.dom.Comment`)
2150     class Comment : CharacterData, dom.Comment
2151     {
2152         package this() {
2153 
2154         }
2155         // inherited from Node
2156         override
2157         {
2158             @property dom.NodeType nodeType() { return dom.NodeType.comment; }
2159             @property DOMString nodeName() { return new DOMString("#comment"); }
2160 
2161             Comment cloneNode(bool deep)
2162             {
2163                 Comment cloned = new Comment();//allocator.multiVersionMake!Comment(this.outer);
2164                 cloned._ownerDocument = _ownerDocument;
2165                 super.performClone(cloned, deep);
2166                 return cloned;
2167             }
2168         }
2169     }
2170     /// Implementation of $(LINK2 ../dom/DocumentType, `std.experimental.xml.dom.DocumentType`)
2171     class DocumentType : Node, dom.DocumentType
2172     {
2173         package this() {
2174 
2175         }
2176         // specific to DocumentType
2177         override
2178         {
2179             /// Implementation of $(LINK2 ../dom/DocumentType.name, `std.experimental.xml.dom.DocumentType.name`).
2180             @property DOMString name() { return _name; }
2181             /++
2182             +   Implementation of $(LINK2 ../dom/DocumentType.entities,
2183             +   `std.experimental.xml.dom.DocumentType.entities`).
2184             +/
2185             @property dom.NamedNodeMap entities() { return null; }
2186             /++
2187             +   Implementation of $(LINK2 ../dom/DocumentType.notations,
2188             +   `std.experimental.xml.dom.DocumentType.notations`).
2189             +/
2190             @property dom.NamedNodeMap notations() { return null; }
2191             /++
2192             +   Implementation of $(LINK2 ../dom/DocumentType.publicId,
2193             +   `std.experimental.xml.dom.DocumentType.publicId`).
2194             +/
2195             @property DOMString publicId() { return _publicId; }
2196             /++
2197             +   Implementation of $(LINK2 ../dom/DocumentType.systemId,
2198             +   `std.experimental.xml.dom.DocumentType.systemId`).
2199             +/
2200             @property DOMString systemId() { return _systemId; }
2201             /++
2202             +   Implementation of $(LINK2 ../dom/DocumentType.internalSubset,
2203             +   `std.experimental.xml.dom.DocumentType.internalSubset`).
2204             +/
2205             @property DOMString internalSubset() { return _internalSubset; }
2206         }
2207         private DOMString _name, _publicId, _systemId, _internalSubset;
2208         // inherited from Node
2209         override
2210         {
2211             @property dom.NodeType nodeType() { return dom.NodeType.documentType; }
2212             @property DOMString nodeName() { return _name; }
2213         }
2214     }
2215     /// Implementation of $(LINK2 ../dom/CDATASection, `std.experimental.xml.dom.CDATASection`)
2216     class CDATASection : Text, dom.CDATASection
2217     {
2218         package this() {
2219 
2220         }
2221         // inherited from Node
2222         override
2223         {
2224             @property dom.NodeType nodeType() { return dom.NodeType.cdataSection; }
2225             @property DOMString nodeName() { return new DOMString("#cdata-section"); }
2226 
2227             CDATASection cloneNode(bool deep)
2228             {
2229                 CDATASection cloned = new CDATASection();//allocator.multiVersionMake!CDATASection(this.outer);
2230                 cloned._ownerDocument = _ownerDocument;
2231                 super.performClone(cloned, deep);
2232                 return cloned;
2233             }
2234         }
2235     }
2236     /// Implementation of $(LINK2 ../dom/ProcessingInstruction, `std.experimental.xml.dom.ProcessingInstruction`)
2237     class ProcessingInstruction : Node, dom.ProcessingInstruction
2238     {
2239         package this() {
2240 
2241         }
2242         // specific to ProcessingInstruction
2243         override
2244         {
2245             /++
2246             +   Implementation of $(LINK2 ../dom/ProcessingInstruction.target,
2247             +   `std.experimental.xml.dom.ProcessingInstruction.target`).
2248             +/
2249             @property DOMString target() { return _target; }
2250             /++
2251             +   Implementation of $(LINK2 ../dom/ProcessingInstruction.data,
2252             +   `std.experimental.xml.dom.ProcessingInstruction.data`).
2253             +/
2254             @property DOMString data() { return _data; }
2255             /// ditto
2256             @property void data(DOMString newVal) { _data = newVal; }
2257         }
2258         private DOMString _target, _data;
2259         // inherited from Node
2260         override
2261         {
2262             @property dom.NodeType nodeType() { return dom.NodeType.processingInstruction; }
2263             @property DOMString nodeName() { return target; }
2264             @property DOMString nodeValue() { return _data; }
2265             @property void nodeValue(DOMString newVal)
2266             {
2267                 if (readonly)
2268                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
2269                 _data = newVal;
2270             }
2271 
2272             @property DOMString textContent() { return _data; }
2273             @property void textContent(DOMString newVal)
2274             {
2275                 if (readonly)
2276                     throw new DOMException(dom.ExceptionCode.noModificationAllowed);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.noModificationAllowed);
2277                 _data = newVal;
2278             }
2279 
2280             ProcessingInstruction cloneNode(bool deep)
2281             {
2282                 auto cloned = new ProcessingInstruction();//allocator.multiVersionMake!ProcessingInstruction(this.outer);
2283                 cloned._ownerDocument = _ownerDocument;
2284                 super.performClone(cloned, deep);
2285                 cloned._target = _target;
2286                 cloned._data = _data;
2287                 return cloned;
2288             }
2289         }
2290     }
2291     /// Implementation of $(LINK2 ../dom/EntityReference, `std.experimental.xml.dom.EntityReference`)
2292     class EntityReference : NodeWithChildren, dom.EntityReference
2293     {
2294         package this() {
2295 
2296         }
2297         // inherited from Node
2298         override
2299         {
2300             @property dom.NodeType nodeType() { return dom.NodeType.entityReference; }
2301             @property DOMString nodeName() { return _ent_name; }
2302             @property bool readonly() { return true; }
2303         }
2304         private DOMString _ent_name;
2305     }
2306     /// Implementation of $(LINK2 ../dom/DOMConfiguration, `std.experimental.xml.dom.DOMConfiguration`)
2307     class DOMConfiguration : dom.DOMConfiguration
2308     {
2309         import std.meta;
2310         import std.traits;
2311         package this() {
2312 
2313         }
2314         private
2315         {
2316             enum string always = "((x) => true)";
2317 
2318             static struct Config
2319             {
2320                 string name;
2321                 string type;
2322                 string settable;
2323             }
2324 
2325             struct Params
2326             {
2327                 @Config("cdata-sections", "bool", always) bool cdata_sections;
2328                 @Config("comments", "bool", always) bool comments;
2329                 @Config("entities", "bool", always) bool entities;
2330                 //@Config("error-handler", "ErrorHandler", always) ErrorHandler error_handler;
2331                 @Config("namespace-declarations", "bool", always) bool namespace_declarations;
2332                 @Config("split-cdata-sections", "bool", always) bool split_cdata_sections;
2333             }
2334             Params params;
2335 
2336             void assign(string field, string type)(dom.UserData val) @trusted
2337             {
2338                 mixin("if (val.convertsTo!(" ~ type ~ ")) params." ~ field ~ " = val.get!(" ~ type ~ "); \n");
2339             }
2340             bool canSet(string type, string settable)(dom.UserData val) @trusted
2341             {
2342                 mixin("if (val.convertsTo!(" ~ type ~ ")) return " ~ settable ~ "(val.get!(" ~ type ~ ")); \n");
2343                 return false;
2344             }
2345         }
2346         // specific to DOMConfiguration
2347         override
2348         {
2349             /++
2350             +   Implementation of $(LINK2 ../dom/DOMConfiguration.setParameter,
2351             +   `std.experimental.xml.dom.DOMConfiguration.setParameter`).
2352             +/
2353             void setParameter(string name, dom.UserData value) @trusted
2354             {
2355                 switch (name)
2356                 {
2357                     foreach (field; AliasSeq!(__traits(allMembers, Params)))
2358                     {
2359                         mixin("enum type = getUDAs!(Params." ~ field ~ ", Config)[0].type; \n");
2360                         mixin("case getUDAs!(Params." ~ field ~ ", Config)[0].name: assign!(field, type)(value); \n");
2361                     }
2362                     default:
2363                         throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notFound);
2364                 }
2365             }
2366             /++
2367             +   Implementation of $(LINK2 ../dom/DOMConfiguration.getParameter,
2368             +   `std.experimental.xml.dom.DOMConfiguration.getParameter`).
2369             +/
2370             dom.UserData getParameter(string name) @trusted
2371             {
2372                 switch (name)
2373                 {
2374                     foreach (field; AliasSeq!(__traits(allMembers, Params)))
2375                     {
2376                         mixin("case getUDAs!(Params." ~ field ~ ", Config)[0].name: \n" ~
2377                                     "return dom.UserData(params." ~ field ~ "); \n");
2378                     }
2379                     default:
2380                         throw new DOMException(dom.ExceptionCode.notFound);//allocator.multiVersionMake!DOMException(this.outer, dom.ExceptionCode.notFound);
2381                 }
2382             }
2383             /++
2384             +   Implementation of $(LINK2 ../dom/DOMConfiguration.canSetParameter,
2385             +   `std.experimental.xml.dom.DOMConfiguration.canSetParameter`).
2386             +/
2387             bool canSetParameter(string name, dom.UserData value) @trusted
2388             {
2389                 switch (name)
2390                 {
2391                     foreach (field; AliasSeq!(__traits(allMembers, Params)))
2392                     {
2393                         mixin("enum type = getUDAs!(Params." ~ field ~ ", Config)[0].type; \n");
2394                         mixin("enum settable = getUDAs!(Params." ~ field ~ ", Config)[0].settable; \n");
2395                         mixin("case getUDAs!(Params." ~ field ~ ", Config)[0].name: \n" ~
2396                                     "return canSet!(type, settable)(value); \n");
2397                     }
2398                     default:
2399                         return false;
2400                 }
2401             }
2402             /++
2403             +   Implementation of $(LINK2 ../dom/DOMConfiguration.parameterNames,
2404             +   `std.experimental.xml.dom.DOMConfiguration.parameterNames`).
2405             +/
2406             @property dom.DOMStringList parameterNames()
2407             {
2408                 return new StringList();
2409             }
2410         }
2411 
2412         class StringList : dom.DOMStringList
2413         {
2414             package this() {
2415 
2416             }
2417             private template MapToConfigName(Members...)
2418             {
2419                 static if (Members.length > 0)
2420                     mixin("alias MapToConfigName = AliasSeq!(getUDAs!(Params." ~ Members[0] ~
2421                             ", Config)[0].name, MapToConfigName!(Members[1..$])); \n");
2422                 else
2423                     alias MapToConfigName = AliasSeq!();
2424             }
2425             static immutable string[] arr = [MapToConfigName!(__traits(allMembers, Params))];
2426 
2427             // specific to DOMStringList
2428             override
2429             {
2430                 DOMString item(size_t index) { return new DOMString(arr[index]); }
2431                 size_t length() { return arr.length; }
2432 
2433                 bool contains(DOMString str)
2434                 {
2435                     import std.algorithm: canFind;
2436                     return arr.canFind(str);
2437                 }
2438             }
2439             //alias arr this;
2440         }
2441     }
2442 }
2443 
2444 /++
2445 +   Instantiates a `DOMBuilder` specialized for the `DOMImplementation` implemented
2446 +   in this module.
2447 +/
2448 /+auto domBuilder(CursorType)(auto ref CursorType cursor)
2449 {
2450     //import std.experimental.allocator.gc_allocator;//import stdx.allocator.gc_allocator;
2451     import dompar = std.experimental.xml.domparser;
2452     return dompar.domBuilder(cursor, new DOMImplementation());//return dompar.domBuilder(cursor, new DOMImplementation!(CursorType.StringType, shared(GCAllocator))());
2453 }+/
2454 
2455 unittest
2456 {
2457     
2458     DOMImplementation impl = new DOMImplementation();
2459 
2460     auto doc = impl.createDocument(new DOMString("myNamespaceURI"), new DOMString("myPrefix:myRootElement"), null);
2461     auto root = doc.documentElement;
2462     assert(root.prefix == "myPrefix");
2463 
2464     auto attr = doc.createAttributeNS(new DOMString("myAttrNamespace"), new DOMString("myAttrPrefix:myAttrName"));
2465     root.setAttributeNode(attr);
2466     assert(root.attributes.length == 1);
2467     assert(root.getAttributeNodeNS(new DOMString("myAttrNamespace"), new DOMString("myAttrName")) is attr);
2468 
2469     attr.nodeValue = new DOMString("myAttrValue");
2470     assert(attr.childNodes.length == 1);
2471     assert(attr.firstChild.nodeType == dom.NodeType.text);
2472     assert(attr.firstChild.nodeValue == attr.nodeValue);
2473 
2474     auto elem = doc.createElementNS(new DOMString("myOtherNamespace"), new DOMString("myOtherPrefix:myOtherElement"));
2475     assert(root.ownerDocument is doc);
2476     assert(elem.ownerDocument is doc);
2477     root.appendChild(elem);
2478     assert(root.firstChild is elem);
2479     assert(root.firstChild.namespaceURI == "myOtherNamespace");
2480 
2481     auto comm = doc.createComment(new DOMString("myWonderfulComment"));
2482     doc.insertBefore(comm, root);
2483     assert(doc.childNodes.length == 2);
2484     assert(doc.firstChild is comm);
2485 
2486     assert(comm.substringData(1, 4) == "yWon");
2487     comm.replaceData(0, 2, new DOMString("your"));
2488     comm.deleteData(4, 9);
2489     comm.insertData(4, new DOMString("Questionable"));
2490     assert(comm.data == "yourQuestionableComment");
2491 
2492     auto pi = doc.createProcessingInstruction(new DOMString("myPITarget"), new DOMString("myPIData"));
2493     elem.appendChild(pi);
2494     assert(elem.lastChild is pi);
2495     auto cdata = doc.createCDATASection(new DOMString("mycdataContent"));
2496     elem.replaceChild(cdata, pi);
2497     assert(elem.lastChild is cdata);
2498     elem.removeChild(cdata);
2499     assert(elem.childNodes.length == 0);
2500 
2501     assert(doc.getElementsByTagNameNS(new DOMString("myOtherNamespace"), new DOMString("myOtherElement")).item(0) is elem);
2502 
2503     doc.setUserData("userDataKey1", dom.UserData(3.14), null);
2504     doc.setUserData("userDataKey2", dom.UserData(new Object()), null);
2505     doc.setUserData("userDataKey3", dom.UserData(null), null);
2506     assert(doc.getUserData("userDataKey1") == 3.14);
2507     assert(doc.getUserData("userDataKey2").type == typeid(Object));
2508     assert(doc.getUserData("userDataKey3").peek!long is null);
2509 
2510     assert(elem.lookupNamespaceURI(new DOMString("myOtherPrefix")) == "myOtherNamespace");
2511     assert(doc.lookupPrefix(new DOMString("myNamespaceURI")) == "myPrefix");
2512 
2513     assert(elem.isEqualNode(elem.cloneNode(false)));
2514     assert(root.isEqualNode(root.cloneNode(true)));
2515     assert(comm.isEqualNode(comm.cloneNode(false)));
2516     assert(pi.isEqualNode(pi.cloneNode(false)));
2517 }
2518 
2519 unittest
2520 {
2521     import newxml.lexers;
2522     import newxml.parser;
2523     import newxml.cursor;
2524     //import newxml.domparser;
2525     import std.stdio;
2526 
2527     string xml = q"{
2528     <?xml version = '1.0' standalone = 'yes'?>
2529     <books>
2530         <book ISBN = '078-5342635362'>
2531             <title>The D Programming Language</title>
2532             <author>A. Alexandrescu</author>
2533         </book>
2534         <book ISBN = '978-1515074601'>
2535             <title>Programming in D</title>
2536             <author>Ali Çehreli</author>
2537         </book>
2538         <book ISBN = '978-0201704310' about-d = 'no'>
2539             <title>Modern C++ Design</title>
2540             <author>A. Alexandrescu</author>
2541         </book>
2542     </books>
2543     }";
2544 
2545     /+auto builder =
2546          xml
2547         .lexer
2548         .parser
2549         .cursor
2550         .domBuilder;
2551 
2552     builder.setSource(xml);
2553     builder.buildRecursive;
2554 
2555     auto doc = builder.getDocument;
2556     auto books = doc.getElementsByTagName("book");
2557     auto authors = doc.getElementsByTagName("author");
2558     auto titles = doc.getElementsByTagName("title");
2559 
2560     assert(doc.xmlVersion == "1.0");
2561     assert(doc.xmlStandalone);
2562 
2563     enum Pos(dom.DocumentPosition pos) = cast(BitFlags!(dom.DocumentPosition))pos;
2564     with (dom.DocumentPosition)
2565     {
2566         assert(books[1].compareDocumentPosition(authors[2]) == Pos!following);
2567         assert(authors[2].compareDocumentPosition(titles[0]) == Pos!preceding);
2568         assert(books[1].compareDocumentPosition(titles[1]) == (Pos!containedBy | Pos!following));
2569         assert(authors[0].compareDocumentPosition(books[0]) == (Pos!contains | Pos!preceding));
2570         assert(titles[2].compareDocumentPosition(titles[2]) == Pos!none);
2571         assert(books[2].attributes[0].compareDocumentPosition(books[2].attributes[1])
2572                 == (Pos!implementationSpecific | Pos!following));
2573         assert(books[2].attributes[1].compareDocumentPosition(books[2].attributes[0])
2574                 == (Pos!implementationSpecific | Pos!preceding));
2575     }
2576 
2577     assert(books[1].cloneNode(true).childNodes[1].isEqualNode(authors[1]));
2578 
2579     books[2].setIdAttributeNode(books[2].attributes[1], true);
2580     assert(books[2].attributes[1].isId);
2581     assert(doc.getElementById("978-0201704310") is books[2]);
2582 
2583     alias Text = typeof(doc.implementation).Text;
2584     titles[1].appendChild(doc.createTextNode(" for Dummies"));
2585     assert((cast(Text)(titles[1].firstChild)).wholeText == "Programming in D for Dummies");
2586     (cast(Text)(titles[1].lastChild)).replaceWholeText(titles[1].firstChild.textContent);
2587     assert(titles[1].textContent == "Programming in D");
2588     assert(titles[1].childNodes.length == 1);+/
2589 }
2590