1 /*******************************************************************************
2 3 Copyright:
4 Copyright (C) 2007 Aaron Craelius and Kris Bell
5 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH.
6 All rights reserved.
7 8 License:
9 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
10 See LICENSE_TANGO.txt for details.
11 12 Version: Initial release: February 2008
13 14 Authors: Aaron, Kris
15 16 *******************************************************************************/17 18 moduleocean.text.xml.Document;
19 20 importArray = ocean.core.Array;
21 importocean.meta.types.Qualifiers;
22 importocean.core.Verify;
23 24 packageimportocean.text.xml.PullParser;
25 26 /*******************************************************************************
27 28 Implements a DOM atop the XML parser, supporting document
29 parsing, tree traversal and ad-hoc tree manipulation.
30 31 The DOM API is non-conformant, yet simple and functional in
32 style - locate a tree node of interest and operate upon or
33 around it. In all cases you will need a document instance to
34 begin, whereupon it may be populated either by parsing an
35 existing document or via API manipulation.
36 37 This particular DOM employs a simple free-list to allocate
38 each of the tree nodes, making it quite efficient at parsing
39 XML documents. The tradeoff with such a scheme is that copying
40 nodes from one document to another requires a little more care
41 than otherwise. We felt this was a reasonable tradeoff, given
42 the throughput gains vs the relative infrequency of grafting
43 operations. For grafting within or across documents, please
44 use the move() and copy() methods.
45 46 Another simplification is related to entity transcoding. This
47 is not performed internally, and becomes the responsibility
48 of the client. That is, the client should perform appropriate
49 entity transcoding as necessary. Paying the (high) transcoding
50 cost for all documents doesn't seem appropriate.
51 52 Parse example
53 ---
54 auto doc = new Document!(char);
55 doc.parse (content);
56 57 auto print = new DocPrinter!(char);
58 Stdout(print(doc)).newline;
59 ---
60 61 API example
62 ---
63 auto doc = new Document!(char);
64 65 // attach an xml header
66 doc.header;
67 68 // attach an element with some attributes, plus
69 // a child element with an attached data value
70 doc.tree.element (null, "element")
71 .attribute (null, "attrib1", "value")
72 .attribute (null, "attrib2")
73 .element (null, "child", "value");
74 75 auto print = new DocPrinter!(char);
76 Stdout(print(doc)).newline;
77 ---
78 79 Note that the document tree() includes all nodes in the tree,
80 and not just elements. Use doc.elements to address the topmost
81 element instead. For example, adding an interior sibling to
82 the prior illustration
83 ---
84 doc.elements.element (null, "sibling");
85 ---
86 87 Printing the name of the topmost (root) element:
88 ---
89 Stdout.formatln ("first element is '{}'", doc.elements.name);
90 ---
91 92 XPath examples:
93 ---
94 auto doc = new Document!(char);
95 96 // attach an element with some attributes, plus
97 // a child element with an attached data value
98 doc.tree.element (null, "element")
99 .attribute (null, "attrib1", "value")
100 .attribute (null, "attrib2")
101 .element (null, "child", "value");
102 103 // select named-elements
104 auto set = doc.query["element"]["child"];
105 106 // select all attributes named "attrib1"
107 set = doc.query.descendant.attribute("attrib1");
108 109 // select elements with one parent and a matching text value
110 set = doc.query[].filter((doc.Node n) {return n.children.hasData("value");});
111 ---
112 113 Note that path queries are temporal - they do not retain content
114 across mulitple queries. That is, the lifetime of a query result
115 is limited unless you explicitly copy it. For example, this will
116 fail
117 ---
118 auto elements = doc.query["element"];
119 auto children = elements["child"];
120 ---
121 122 The above will lose elements because the associated document reuses
123 node space for subsequent queries. In order to retain results, do this
124 ---
125 auto elements = doc.query["element"].dup;
126 auto children = elements["child"];
127 ---
128 129 The above .dup is generally very small (a set of pointers only). On
130 the other hand, recursive queries are fully supported
131 ---
132 set = doc.query[].filter((doc.Node n) {return n.query[].count > 1;});
133 ---
134 135 Typical usage tends to follow the following pattern, Where each query
136 result is processed before another is initiated
137 ---
138 foreach (node; doc.query.child("element"))
139 {
140 // do something with each node
141 }
142 ---
143 144 Note that the parser is templated for char, wchar or dchar.
145 146 *******************************************************************************/147 148 classDocument(T_) : PullParser!(T_)
149 {
150 publicaliasNodeImpl* Node;
151 152 privatealiasconst(T_) T;
153 privatealiasT_MutT;
154 privatealiasXmlPath!(MutT) XmlPathT;
155 156 privateNoderoot;
157 privateNodeImpl[] list;
158 privateNodeImpl[][] lists;
159 privateintindex,
160 chunks,
161 freelists;
162 privateXmlPathTxpath;
163 164 /***********************************************************************
165 166 Construct a DOM instance. The optional parameter indicates
167 the initial number of nodes assigned to the freelist
168 169 ***********************************************************************/170 171 this (uintnodes = 1000)
172 {
173 verify (nodes > 50);
174 super (null);
175 xpath = newXmlPathT;
176 177 chunks = nodes;
178 newlist;
179 root = allocate;
180 root.id = XmlNodeType.Document;
181 }
182 183 /***********************************************************************
184 185 Return an xpath handle to query this document. This starts
186 at the document root.
187 188 See also Node.query
189 190 ***********************************************************************/191 192 finalXmlPathT.NodeSetquery ()
193 {
194 returnxpath.start (root);
195 }
196 197 /***********************************************************************
198 199 Return the root document node, from which all other nodes
200 are descended.
201 202 Returns null where there are no nodes in the document
203 204 ***********************************************************************/205 206 finalNodetree ()
207 {
208 returnroot;
209 }
210 211 /***********************************************************************
212 213 Return the topmost element node, which is generally the
214 root of the element tree.
215 216 Returns null where there are no top-level element nodes
217 218 ***********************************************************************/219 220 finalNodeelements ()
221 {
222 if (root)
223 {
224 autonode = root.lastChild;
225 while (node)
226 if (node.idisXmlNodeType.Element)
227 returnnode;
228 else229 node = node.prev;
230 }
231 returnnull;
232 }
233 234 /***********************************************************************
235 236 Reset the freelist. Subsequent allocation of document nodes
237 will overwrite prior instances.
238 239 ***********************************************************************/240 241 finalDocumentreset ()
242 {
243 root.lastChild = root.firstChild = null;
244 freelists = 0;
245 newlist;
246 index = 1;
247 returnthis;
248 }
249 250 /***********************************************************************
251 252 Prepend an XML header to the document tree
253 254 ***********************************************************************/255 256 finalDocumentheader ()
257 {
258 staticimmutableheader = `xml version="1.0" encoding="UTF-8"`;
259 root.prepend (root.create(XmlNodeType.PI, header));
260 returnthis;
261 }
262 263 /***********************************************************************
264 265 Prepend an XML header to the document tree. Note that this
266 method currently allocates.
267 268 Params:
269 encoding = the encoding of the xml document
270 271 ***********************************************************************/272 273 finalDocumentheader (immutable(T)[] encoding)
274 {
275 if (encoding.length)
276 encoding = `xml version="1.0" encoding="`~encoding~`"`;
277 else278 encoding = `xml version="1.0" encoding="UTF-8"`;
279 280 root.prepend (root.create(XmlNodeType.PI, encoding.dup));
281 returnthis;
282 }
283 284 /***********************************************************************
285 286 Parse the given xml content, which will reuse any existing
287 node within this document. The resultant tree is retrieved
288 via the document 'tree' attribute
289 290 ***********************************************************************/291 292 finalvoidparse(T[] xml)
293 {
294 verify (xml.ptr ! isnull);
295 reset;
296 super.reset (xml);
297 autocur = root;
298 uintdefNamespace;
299 300 while (true)
301 {
302 autop = text.point;
303 switch (super.next)
304 {
305 caseXmlTokenType.EndElement:
306 caseXmlTokenType.EndEmptyElement:
307 verify (cur !isnull);
308 cur.end = text.point;
309 cur = cur.host;
310 break;
311 312 caseXmlTokenType.Data:
313 autonode = allocate;
314 Array.copy(node.rawValue, super.rawValue);
315 node.id = XmlNodeType.Data;
316 cur.append (node);
317 break;
318 319 caseXmlTokenType.StartElement:
320 autonode = allocate;
321 node.host = cur;
322 Array.copy(node.prefixed, super.prefix);
323 node.id = XmlNodeType.Element;
324 Array.copy(node.localName, super.localName);
325 node.start = p;
326 327 // inline append328 if (cur.lastChild)
329 {
330 cur.lastChild.nextSibling = node;
331 node.prevSibling = cur.lastChild;
332 cur.lastChild = node;
333 }
334 else335 {
336 cur.firstChild = node;
337 cur.lastChild = node;
338 }
339 cur = node;
340 break;
341 342 caseXmlTokenType.Attribute:
343 autoattr = allocate;
344 Array.copy(attr.prefixed, super.prefix);
345 Array.copy(attr.rawValue, super.rawValue);
346 Array.copy(attr.localName, super.localName);
347 attr.id = XmlNodeType.Attribute;
348 cur.attrib (attr);
349 break;
350 351 caseXmlTokenType.PI:
352 cur.pi_ (super.rawValue, p[0..text.point-p]);
353 break;
354 355 caseXmlTokenType.Comment:
356 cur.comment_ (super.rawValue);
357 break;
358 359 caseXmlTokenType.CData:
360 cur.cdata_ (super.rawValue);
361 break;
362 363 caseXmlTokenType.Doctype:
364 cur.doctype_ (super.rawValue);
365 break;
366 367 caseXmlTokenType.Done:
368 return;
369 370 default:
371 break;
372 }
373 }
374 }
375 376 /***********************************************************************
377 378 allocate a node from the freelist
379 380 ***********************************************************************/381 382 privatefinalNodeallocate ()
383 {
384 if (index >= list.length)
385 newlist;
386 387 autop = &list[index++];
388 p.doc = this;
389 p.start = p.end = null;
390 p.host =
391 p.prevSibling =
392 p.nextSibling =
393 p.firstChild =
394 p.lastChild =
395 p.firstAttr =
396 p.lastAttr = null;
397 p.rawValue.length = 0;
398 assumeSafeAppend(p.rawValue);
399 p.localName.length = 0;
400 assumeSafeAppend(p.localName);
401 p.prefixed.length = 0;
402 assumeSafeAppend(p.prefixed);
403 returnp;
404 }
405 406 /***********************************************************************
407 408 allocate a node from the freelist
409 410 ***********************************************************************/411 412 privatefinalvoidnewlist ()
413 {
414 index = 0;
415 if (freelists >= lists.length)
416 {
417 lists.length = lists.length + 1;
418 lists[$-1] = newNodeImpl [chunks];
419 }
420 list = lists[freelists++];
421 }
422 423 /***********************************************************************
424 425 foreach support for visiting and selecting nodes.
426 427 A fruct is a low-overhead mechanism for capturing context
428 relating to an opApply, and we use it here to sweep nodes
429 when testing for various relationships.
430 431 See Node.attributes and Node.children
432 433 ***********************************************************************/434 435 privatestructVisitor436 {
437 privateNodenode;
438 439 publicaliasvaluedata;
440 publicaliashasValuehasData;
441 442 /***************************************************************
443 444 Is there anything to visit here?
445 446 Time complexity: O(1)
447 448 ***************************************************************/449 450 boolexist ()
451 {
452 returnnode != null;
453 }
454 455 /***************************************************************
456 457 traverse sibling nodes
458 459 ***************************************************************/460 461 intopApply (scopeintdelegate(refNode) dg)
462 {
463 intret;
464 465 for (auton=node; n; n = n.nextSibling)
466 if ((ret = dg(n)) != 0)
467 break;
468 returnret;
469 }
470 471 /***************************************************************
472 473 Locate a node with a matching name and/or prefix,
474 and which passes an optional filter. Each of the
475 arguments will be ignored where they are null.
476 477 Time complexity: O(n)
478 479 ***************************************************************/480 481 Nodename (T[] prefix, T[] local, scopebooldelegate(Node) dg=null)
482 {
483 for (auton=node; n; n = n.nextSibling)
484 {
485 if (local.ptr && local != n.localName)
486 continue;
487 488 if (prefix.ptr && prefix != n.prefixed)
489 continue;
490 491 if (dg.ptr && dg(n) isfalse)
492 continue;
493 494 returnn;
495 }
496 returnnull;
497 }
498 499 /***************************************************************
500 501 Scan nodes for a matching name and/or prefix. Each
502 of the arguments will be ignored where they are null.
503 504 Time complexity: O(n)
505 506 ***************************************************************/507 508 boolhasName (T[] prefix, T[] local)
509 {
510 returnname (prefix, local) != null;
511 }
512 513 /***************************************************************
514 515 Locate a node with a matching name and/or prefix,
516 and which matches a specified value. Each of the
517 arguments will be ignored where they are null.
518 519 Time complexity: O(n)
520 521 ***************************************************************/522 version (Filter)
523 {
524 Nodevalue (T[] prefix, T[] local, T[] value)
525 {
526 if (value.ptr)
527 returnname (prefix, local, (Noden){returnvalue == n.rawValue;});
528 returnname (prefix, local);
529 }
530 }
531 /***************************************************************
532 533 Sweep nodes looking for a match, and returns either
534 a node or null. See value(x,y,z) or name(x,y,z) for
535 additional filtering.
536 537 Time complexity: O(n)
538 539 ***************************************************************/540 541 Nodevalue (T[] match)
542 {
543 if (match.ptr)
544 for (auton=node; n; n = n.nextSibling)
545 if (match == n.rawValue)
546 returnn;
547 returnnull;
548 }
549 550 /***************************************************************
551 552 Sweep the nodes looking for a value match. Returns
553 true if found. See value(x,y,z) or name(x,y,z) for
554 additional filtering.
555 556 Time complexity: O(n)
557 558 ***************************************************************/559 560 boolhasValue (T[] match)
561 {
562 returnvalue(match) != null;
563 }
564 }
565 566 567 /***********************************************************************
568 569 The node implementation
570 571 ***********************************************************************/572 573 privatestructNodeImpl574 {
575 publicvoid* user; /// open for usage576 packageDocumentdoc; // owning document577 packageXmlNodeTypeid; // node type578 packageMutT[] prefixed; // namespace579 packageMutT[] localName; // name580 packageMutT[] rawValue; // data value581 582 packageNodehost, // parent node583 prevSibling, // prior584 nextSibling, // next585 firstChild, // head586 lastChild, // tail587 firstAttr, // head588 lastAttr; // tail589 590 packageT* end, // slice of the ...591 start; // original xml text592 593 /***************************************************************
594 595 Return the hosting document
596 597 ***************************************************************/598 599 Documentdocument ()
600 {
601 returndoc;
602 }
603 604 /***************************************************************
605 606 Return the node type-id
607 608 ***************************************************************/609 610 XmlNodeTypetype ()
611 {
612 returnid;
613 }
614 615 /***************************************************************
616 617 Return the parent, which may be null
618 619 ***************************************************************/620 621 Nodeparent ()
622 {
623 returnhost;
624 }
625 626 /***************************************************************
627 628 Return the first child, which may be null
629 630 ***************************************************************/631 632 Nodechild ()
633 {
634 returnfirstChild;
635 }
636 637 /***************************************************************
638 639 Return the prior sibling, which may be null
640 641 ***************************************************************/642 643 Nodeprev ()
644 {
645 returnprevSibling;
646 }
647 648 /***************************************************************
649 650 Return the next sibling, which may be null
651 652 ***************************************************************/653 654 Nodenext ()
655 {
656 returnnextSibling;
657 }
658 659 /***************************************************************
660 661 Return the namespace prefix of this node (may be null)
662 663 ***************************************************************/664 665 T[] prefix ()
666 {
667 returnprefixed;
668 }
669 670 /***************************************************************
671 672 Set the namespace prefix of this node (may be null)
673 674 ***************************************************************/675 676 Nodeprefix (T[] replace)
677 {
678 Array.copy(this.prefixed, replace);
679 return &this;
680 }
681 682 /***************************************************************
683 684 Return the vanilla node name (sans prefix)
685 686 ***************************************************************/687 688 T[] name ()
689 {
690 returnlocalName;
691 }
692 693 /***************************************************************
694 695 Set the vanilla node name (sans prefix)
696 697 ***************************************************************/698 699 Nodename (T[] replace)
700 {
701 Array.copy(this.localName, replace);
702 return &this;
703 }
704 705 /***************************************************************
706 707 Return the data content, which may be null
708 709 ***************************************************************/710 711 T[] value ()
712 {
713 if (typeisXmlNodeType.Element)
714 foreach (child; children)
715 if (child.idisXmlNodeType.Data ||
716 child.idisXmlNodeType.CData)
717 returnchild.rawValue;
718 returnrawValue;
719 }
720 721 /***************************************************************
722 723 Set the raw data content, which may be null
724 725 ***************************************************************/726 727 voidvalue (T[] val)
728 {
729 if (typeisXmlNodeType.Element)
730 foreach (child; children)
731 if (child.idisXmlNodeType.Data)
732 returnchild.value (val);
733 Array.copy(this.rawValue, val);
734 mutate;
735 }
736 737 /***************************************************************
738 739 Return the full node name, which is a combination
740 of the prefix & local names. Nodes without a prefix
741 will return local-name only
742 743 ***************************************************************/744 745 T[] toString (MutT[] output = null)
746 {
747 if (prefixed.length)
748 {
749 autolen = prefixed.length + localName.length + 1;
750 751 // is the prefix already attached to the name?752 if (prefixed.ptr + prefixed.length + 1islocalName.ptr &&
753 ':'is *(localName.ptr-1))
754 returnprefixed.ptr [0 .. len];
755 756 // nope, copy the discrete segments into output757 if (output.length < len)
758 output.length = len;
759 output[0..prefixed.length] = prefixed;
760 output[prefixed.length] = ':';
761 output[prefixed.length+1 .. len] = localName;
762 returnoutput[0..len];
763 }
764 765 returnlocalName;
766 }
767 768 /***************************************************************
769 770 Return the index of this node, or how many
771 prior siblings it has.
772 773 Time complexity: O(n)
774 775 ***************************************************************/776 777 uintposition ()
778 {
779 autocount = 0;
780 autoprior = prevSibling;
781 while (prior)
782 ++count, prior = prior.prevSibling;
783 returncount;
784 }
785 786 /***************************************************************
787 788 Detach this node from its parent and siblings
789 790 ***************************************************************/791 792 Nodedetach ()
793 {
794 returnremove;
795 }
796 797 /***************************************************************
798 799 Return an xpath handle to query this node
800 801 See also Document.query
802 803 ***************************************************************/804 805 finalXmlPathT.NodeSetquery ()
806 {
807 returndoc.xpath.start(&this);
808 }
809 810 /***************************************************************
811 812 Return a foreach iterator for node children
813 814 ***************************************************************/815 816 Visitorchildren ()
817 {
818 Visitorv = {firstChild};
819 returnv;
820 }
821 822 /***************************************************************
823 824 Return a foreach iterator for node attributes
825 826 ***************************************************************/827 828 Visitorattributes ()
829 {
830 Visitorv = {firstAttr};
831 returnv;
832 }
833 834 /***************************************************************
835 836 Duplicate the given sub-tree into place as a child
837 of this node.
838 839 Returns a reference to the subtree
840 841 ***************************************************************/842 843 Nodecopy (Nodetree)
844 {
845 verify (tree !isnull);
846 tree = tree.clone;
847 tree.migrate (document);
848 849 if (tree.idisXmlNodeType.Attribute)
850 attrib (tree);
851 else852 append (tree);
853 returntree;
854 }
855 856 /***************************************************************
857 858 Relocate the given sub-tree into place as a child
859 of this node.
860 861 Returns a reference to the subtree
862 863 ***************************************************************/864 865 Nodemove (Nodetree)
866 {
867 tree.detach;
868 if (tree.docisdoc)
869 {
870 if (tree.idisXmlNodeType.Attribute)
871 attrib (tree);
872 else873 append (tree);
874 }
875 else876 tree = copy (tree);
877 returntree;
878 }
879 880 /***************************************************************
881 882 Appends a new (child) Element and returns a reference
883 to it.
884 885 ***************************************************************/886 887 Nodeelement (T[] prefix, T[] local, T[] value = null)
888 {
889 returnelement_ (prefix, local, value).mutate;
890 }
891 892 /***************************************************************
893 894 Attaches an Attribute and returns this, the host
895 896 ***************************************************************/897 898 Nodeattribute (T[] prefix, T[] local, T[] value = null)
899 {
900 returnattribute_ (prefix, local, value).mutate;
901 }
902 903 /***************************************************************
904 905 Attaches a Data node and returns this, the host
906 907 ***************************************************************/908 909 Nodedata (T[] data)
910 {
911 returndata_ (data).mutate;
912 }
913 914 /***************************************************************
915 916 Attaches a CData node and returns this, the host
917 918 ***************************************************************/919 920 Nodecdata (T[] cdata)
921 {
922 returncdata_ (cdata).mutate;
923 }
924 925 /***************************************************************
926 927 Attaches a Comment node and returns this, the host
928 929 ***************************************************************/930 931 Nodecomment (T[] comment)
932 {
933 returncomment_ (comment).mutate;
934 }
935 936 /***************************************************************
937 938 Attaches a Doctype node and returns this, the host
939 940 ***************************************************************/941 942 Nodedoctype (T[] doctype)
943 {
944 returndoctype_ (doctype).mutate;
945 }
946 947 /***************************************************************
948 949 Attaches a PI node and returns this, the host
950 951 ***************************************************************/952 953 Nodepi (T[] pi)
954 {
955 returnpi_ (pi, null).mutate;
956 }
957 958 /***************************************************************
959 960 Attaches a child Element, and returns a reference
961 to the child
962 963 ***************************************************************/964 965 privateNodeelement_ (T[] prefix, T[] local, T[] value = null)
966 {
967 autonode = create (XmlNodeType.Element, null);
968 append (node.set (prefix, local));
969 if (value.length)
970 node.data_ (value);
971 returnnode;
972 }
973 974 /***************************************************************
975 976 Attaches an Attribute, and returns the host
977 978 ***************************************************************/979 980 privateNodeattribute_ (T[] prefix, T[] local, T[] value = null)
981 {
982 autonode = create (XmlNodeType.Attribute, value);
983 attrib (node.set (prefix, local));
984 return &this;
985 }
986 987 /***************************************************************
988 989 Attaches a Data node, and returns the host
990 991 ***************************************************************/992 993 privateNodedata_ (T[] data)
994 {
995 append (create (XmlNodeType.Data, data));
996 return &this;
997 }
998 999 /***************************************************************
1000 1001 Attaches a CData node, and returns the host
1002 1003 ***************************************************************/1004 1005 privateNodecdata_ (T[] cdata)
1006 {
1007 append (create (XmlNodeType.CData, cdata));
1008 return &this;
1009 }
1010 1011 /***************************************************************
1012 1013 Attaches a Comment node, and returns the host
1014 1015 ***************************************************************/1016 1017 privateNodecomment_ (T[] comment)
1018 {
1019 append (create (XmlNodeType.Comment, comment));
1020 return &this;
1021 }
1022 1023 /***************************************************************
1024 1025 Attaches a PI node, and returns the host
1026 1027 ***************************************************************/1028 1029 privateNodepi_ (T[] pi, T[] patch)
1030 {
1031 append (create(XmlNodeType.PI, pi).patch(patch));
1032 return &this;
1033 }
1034 1035 /***************************************************************
1036 1037 Attaches a Doctype node, and returns the host
1038 1039 ***************************************************************/1040 1041 privateNodedoctype_ (T[] doctype)
1042 {
1043 append (create (XmlNodeType.Doctype, doctype));
1044 return &this;
1045 }
1046 1047 /***************************************************************
1048 1049 Append an attribute to this node, The given attribute
1050 cannot have an existing parent.
1051 1052 ***************************************************************/1053 1054 privatevoidattrib (Nodenode)
1055 {
1056 verify (node.parentisnull);
1057 node.host = &this;
1058 if (lastAttr)
1059 {
1060 lastAttr.nextSibling = node;
1061 node.prevSibling = lastAttr;
1062 lastAttr = node;
1063 }
1064 else1065 firstAttr = lastAttr = node;
1066 }
1067 1068 /***************************************************************
1069 1070 Append a node to this one. The given node cannot
1071 have an existing parent.
1072 1073 ***************************************************************/1074 1075 privatevoidappend (Nodenode)
1076 {
1077 verify (node.parentisnull);
1078 node.host = &this;
1079 if (lastChild)
1080 {
1081 lastChild.nextSibling = node;
1082 node.prevSibling = lastChild;
1083 lastChild = node;
1084 }
1085 else1086 firstChild = lastChild = node;
1087 }
1088 1089 /***************************************************************
1090 1091 Prepend a node to this one. The given node cannot
1092 have an existing parent.
1093 1094 ***************************************************************/1095 1096 privatevoidprepend (Nodenode)
1097 {
1098 verify (node.parentisnull);
1099 node.host = &this;
1100 if (firstChild)
1101 {
1102 firstChild.prevSibling = node;
1103 node.nextSibling = firstChild;
1104 firstChild = node;
1105 }
1106 else1107 firstChild = lastChild = node;
1108 }
1109 1110 /***************************************************************
1111 1112 Configure node values
1113 1114 ***************************************************************/1115 1116 privateNodeset (T[] prefix, T[] local)
1117 {
1118 Array.copy(this.localName, local);
1119 Array.copy(this.prefixed, prefix);
1120 return &this;
1121 }
1122 1123 /***************************************************************
1124 1125 Creates and returns a child Element node
1126 1127 ***************************************************************/1128 1129 privateNodecreate (XmlNodeTypetype, T[] value)
1130 {
1131 autonode = document.allocate;
1132 Array.copy(node.rawValue, value);
1133 node.id = type;
1134 returnnode;
1135 }
1136 1137 /***************************************************************
1138 1139 Detach this node from its parent and siblings
1140 1141 ***************************************************************/1142 1143 privateNoderemove()
1144 {
1145 if (! host)
1146 return &this;
1147 1148 mutate;
1149 if (prevSibling && nextSibling)
1150 {
1151 prevSibling.nextSibling = nextSibling;
1152 nextSibling.prevSibling = prevSibling;
1153 prevSibling = null;
1154 nextSibling = null;
1155 host = null;
1156 }
1157 else1158 if (nextSibling)
1159 {
1160 verify(host.firstChild == &this);
1161 parent.firstChild = nextSibling;
1162 nextSibling.prevSibling = null;
1163 nextSibling = null;
1164 host = null;
1165 }
1166 else1167 if (type != XmlNodeType.Attribute)
1168 {
1169 if (prevSibling)
1170 {
1171 verify(host.lastChild == &this);
1172 host.lastChild = prevSibling;
1173 prevSibling.nextSibling = null;
1174 prevSibling = null;
1175 host = null;
1176 }
1177 else1178 {
1179 verify(host.firstChild == &this);
1180 verify(host.lastChild == &this);
1181 host.firstChild = null;
1182 host.lastChild = null;
1183 host = null;
1184 }
1185 }
1186 else1187 {
1188 if (prevSibling)
1189 {
1190 verify(host.lastAttr == &this);
1191 host.lastAttr = prevSibling;
1192 prevSibling.nextSibling = null;
1193 prevSibling = null;
1194 host = null;
1195 }
1196 else1197 {
1198 verify(host.firstAttr == &this);
1199 verify(host.lastAttr == &this);
1200 host.firstAttr = null;
1201 host.lastAttr = null;
1202 host = null;
1203 }
1204 }
1205 1206 return &this;
1207 }
1208 1209 /***************************************************************
1210 1211 Patch the serialization text, causing DocPrinter
1212 to ignore the subtree of this node, and instead
1213 emit the provided text as raw XML output.
1214 1215 Warning: this function does *not* copy the provided
1216 text, and may be removed from future revisions
1217 1218 ***************************************************************/1219 1220 privateNodepatch (T[] text)
1221 {
1222 end = text.ptr + text.length;
1223 start = text.ptr;
1224 return &this;
1225 }
1226 1227 /***************************************************************
1228 1229 purge serialization cache for this node and its
1230 ancestors
1231 1232 ***************************************************************/1233 1234 privateNodemutate ()
1235 {
1236 autonode = &this;
1237 do {
1238 node.end = null;
1239 } while ((node = node.host) !isnull);
1240 1241 return &this;
1242 }
1243 1244 /***************************************************************
1245 1246 Duplicate a single node
1247 1248 ***************************************************************/1249 1250 privateNodedup ()
1251 {
1252 returncreate(type, rawValue.dup).set(prefixed.dup, localName.dup);
1253 }
1254 1255 /***************************************************************
1256 1257 Duplicate a subtree
1258 1259 ***************************************************************/1260 1261 privateNodeclone ()
1262 {
1263 autop = dup;
1264 1265 foreach (attr; attributes)
1266 p.attrib (attr.dup);
1267 foreach (child; children)
1268 p.append (child.clone);
1269 returnp;
1270 }
1271 1272 /***************************************************************
1273 1274 Reset the document host for this subtree
1275 1276 ***************************************************************/1277 1278 privatevoidmigrate (Documenthost)
1279 {
1280 this.doc = host;
1281 foreach (attr; attributes)
1282 attr.migrate (host);
1283 foreach (child; children)
1284 child.migrate (host);
1285 }
1286 }
1287 }
1288 1289 1290 /*******************************************************************************
1291 1292 XPath support
1293 1294 Provides support for common XPath axis and filtering functions,
1295 via a native-D interface instead of typical interpreted notation.
1296 1297 The general idea here is to generate a NodeSet consisting of those
1298 tree-nodes which satisfy a filtering function. The direction, or
1299 axis, of tree traversal is governed by one of several predefined
1300 operations. All methods facilitiate call-chaining, where each step
1301 returns a new NodeSet instance to be operated upon.
1302 1303 The set of nodes themselves are collected in a freelist, avoiding
1304 heap-activity and making good use of D array-slicing facilities.
1305 1306 XPath examples
1307 ---
1308 auto doc = new Document!(char);
1309 1310 // attach an element with some attributes, plus
1311 // a child element with an attached data value
1312 doc.tree.element (null, "element")
1313 .attribute (null, "attrib1", "value")
1314 .attribute (null, "attrib2")
1315 .element (null, "child", "value");
1316 1317 // select named-elements
1318 auto set = doc.query["element"]["child"];
1319 1320 // select all attributes named "attrib1"
1321 set = doc.query.descendant.attribute("attrib1");
1322 1323 // select elements with one parent and a matching text value
1324 set = doc.query[].filter((doc.Node n) {return n.children.hasData("value");});
1325 ---
1326 1327 Note that path queries are temporal - they do not retain content
1328 across mulitple queries. That is, the lifetime of a query result
1329 is limited unless you explicitly copy it. For example, this will
1330 fail to operate as one might expect
1331 ---
1332 auto elements = doc.query["element"];
1333 auto children = elements["child"];
1334 ---
1335 1336 The above will lose elements, because the associated document reuses
1337 node space for subsequent queries. In order to retain results, do this
1338 ---
1339 auto elements = doc.query["element"].dup;
1340 auto children = elements["child"];
1341 ---
1342 1343 The above .dup is generally very small (a set of pointers only). On
1344 the other hand, recursive queries are fully supported
1345 ---
1346 set = doc.query[].filter((doc.Node n) {return n.query[].count > 1;});
1347 ---
1348 1349 Typical usage tends to exhibit the following pattern, Where each query
1350 result is processed before another is initiated
1351 ---
1352 foreach (node; doc.query.child("element"))
1353 {
1354 // do something with each node
1355 }
1356 ---
1357 1358 Supported axis include:
1359 ---
1360 .child immediate children
1361 .parent immediate parent
1362 .next following siblings
1363 .prev prior siblings
1364 .ancestor all parents
1365 .descendant all descendants
1366 .data text children
1367 .cdata cdata children
1368 .attribute attribute children
1369 ---
1370 1371 Each of the above accept an optional string, which is used in an
1372 axis-specific way to filter nodes. For instance, a .child("food")
1373 will filter <food> child elements. These variants are shortcuts
1374 to using a filter to post-process a result. Each of the above also
1375 have variants which accept a delegate instead.
1376 1377 In general, you traverse an axis and operate upon the results. The
1378 operation applied may be another axis traversal, or a filtering
1379 step. All steps can be, and generally should be chained together.
1380 Filters are implemented via a delegate mechanism
1381 ---
1382 .filter (bool delegate(Node))
1383 ---
1384 1385 Where the delegate returns true if the node passes the filter. An
1386 example might be selecting all nodes with a specific attribute
1387 ---
1388 auto set = doc.query.descendant.filter (
1389 (doc.Node n){return n.attributes.hasName (null, "test");}
1390 );
1391 ---
1392 1393 Obviously this is not as clean and tidy as true XPath notation, but
1394 that can be wrapped atop this API instead. The benefit here is one
1395 of raw throughput - important for some applications.
1396 1397 Note that every operation returns a discrete result. Methods first()
1398 and last() also return a set of one or zero elements. Some language
1399 specific extensions are provided for too
1400 ---
1401 * .child() can be substituted with [] notation instead
1402 1403 * [] notation can be used to index a specific element, like .nth()
1404 1405 * the .nodes attribute exposes an underlying Node[], which may be
1406 sliced or traversed in the usual D manner
1407 ---
1408 1409 Other (query result) utility methods include
1410 ---
1411 .dup
1412 .first
1413 .last
1414 .opIndex
1415 .nth
1416 .count
1417 .opApply
1418 ---
1419 1420 XmlPath itself needs to be a class in order to avoid forward-ref issues.
1421 1422 *******************************************************************************/1423 1424 publicclassXmlPath(T)
1425 {
1426 publicaliasDocument!(T) Doc; /// the typed document1427 publicaliasDoc.NodeNode; /// generic document node1428 1429 privateNode[] freelist;
1430 privateuintfreeIndex,
1431 markIndex;
1432 privateuintrecursion;
1433 1434 /***********************************************************************
1435 1436 Prime a query
1437 1438 Returns a NodeSet containing just the given node, which
1439 can then be used to cascade results into subsequent NodeSet
1440 instances.
1441 1442 ***********************************************************************/1443 1444 finalNodeSetstart (Noderoot)
1445 {
1446 // we have to support recursion which may occur within1447 // a filter callback1448 if (recursionis0)
1449 {
1450 if (freelist.lengthis0)
1451 freelist.length = 256;
1452 freeIndex = 0;
1453 }
1454 1455 NodeSetset = {this};
1456 automark = freeIndex;
1457 allocate(root);
1458 returnset.assign (mark);
1459 }
1460 1461 /***********************************************************************
1462 1463 This is the meat of XPath support. All of the NodeSet
1464 operators exist here, in order to enable call-chaining.
1465 1466 Note that some of the axis do double-duty as a filter
1467 also. This is just a convenience factor, and doesn't
1468 change the underlying mechanisms.
1469 1470 ***********************************************************************/1471 1472 structNodeSet1473 {
1474 privateXmlPathhost;
1475 publicNode[] nodes; /// array of selected nodes1476 1477 /***************************************************************
1478 1479 Return a duplicate NodeSet
1480 1481 ***************************************************************/1482 1483 NodeSetdup ()
1484 {
1485 NodeSetcopy = {host};
1486 copy.nodes = nodes.dup;
1487 returncopy;
1488 }
1489 1490 /***************************************************************
1491 1492 Return the number of selected nodes in the set
1493 1494 ***************************************************************/1495 1496 size_tcount ()
1497 {
1498 returnnodes.length;
1499 }
1500 1501 /***************************************************************
1502 1503 Return a set containing just the first node of
1504 the current set
1505 1506 ***************************************************************/1507 1508 NodeSetfirst ()
1509 {
1510 returnnth (0);
1511 }
1512 1513 /***************************************************************
1514 1515 Return a set containing just the last node of
1516 the current set
1517 1518 ***************************************************************/1519 1520 NodeSetlast ()
1521 {
1522 autoi = nodes.length;
1523 if (i > 0)
1524 --i;
1525 returnnth (i);
1526 }
1527 1528 /***************************************************************
1529 1530 Return a set containing just the nth node of
1531 the current set
1532 1533 ***************************************************************/1534 1535 NodeSetopIndex (uinti)
1536 {
1537 returnnth (i);
1538 }
1539 1540 /***************************************************************
1541 1542 Return a set containing just the nth node of
1543 the current set
1544 1545 ***************************************************************/1546 1547 NodeSetnth (size_tindex)
1548 {
1549 NodeSetset = {host};
1550 automark = host.mark;
1551 if (index < nodes.length)
1552 host.allocate (nodes [index]);
1553 returnset.assign (mark);
1554 }
1555 1556 /***************************************************************
1557 1558 Return a set containing all child elements of the
1559 nodes within this set
1560 1561 ***************************************************************/1562 1563 NodeSetopSlice ()
1564 {
1565 returnchild();
1566 }
1567 1568 /***************************************************************
1569 1570 Return a set containing all child elements of the
1571 nodes within this set, which match the given name
1572 1573 ***************************************************************/1574 1575 NodeSetopIndex (inT[] name)
1576 {
1577 returnchild (name);
1578 }
1579 1580 /***************************************************************
1581 1582 Return a set containing all parent elements of the
1583 nodes within this set, which match the optional name
1584 1585 ***************************************************************/1586 1587 NodeSetparent (inT[] name = null)
1588 {
1589 if (name.ptr)
1590 returnparent ((Nodenode){returnnode.name == name;});
1591 returnparent (&always);
1592 }
1593 1594 /***************************************************************
1595 1596 Return a set containing all data nodes of the
1597 nodes within this set, which match the optional
1598 value
1599 1600 ***************************************************************/1601 1602 NodeSetdata (inT[] value = null)
1603 {
1604 if (value.ptr)
1605 returnchild ((Nodenode){returnnode.value == value;},
1606 XmlNodeType.Data);
1607 returnchild (&always, XmlNodeType.Data);
1608 }
1609 1610 /***************************************************************
1611 1612 Return a set containing all cdata nodes of the
1613 nodes within this set, which match the optional
1614 value
1615 1616 ***************************************************************/1617 1618 NodeSetcdata (inT[] value = null)
1619 {
1620 if (value.ptr)
1621 returnchild ((Nodenode){returnnode.value == value;},
1622 XmlNodeType.CData);
1623 returnchild (&always, XmlNodeType.CData);
1624 }
1625 1626 /***************************************************************
1627 1628 Return a set containing all attributes of the
1629 nodes within this set, which match the optional
1630 name
1631 1632 ***************************************************************/1633 1634 NodeSetattribute (inT[] name = null)
1635 {
1636 if (name.ptr)
1637 returnattribute ((Nodenode){returnnode.name == name;});
1638 returnattribute (&always);
1639 }
1640 1641 /***************************************************************
1642 1643 Return a set containing all descendant elements of
1644 the nodes within this set, which match the given name
1645 1646 ***************************************************************/1647 1648 NodeSetdescendant (inT[] name = null)
1649 {
1650 if (name.ptr)
1651 returndescendant ((Nodenode){returnnode.name == name;});
1652 returndescendant (&always);
1653 }
1654 1655 /***************************************************************
1656 1657 Return a set containing all child elements of the
1658 nodes within this set, which match the optional name
1659 1660 ***************************************************************/1661 1662 NodeSetchild (inT[] name = null)
1663 {
1664 if (name.ptr)
1665 returnchild ((Nodenode){returnnode.name == name;});
1666 returnchild (&always);
1667 }
1668 1669 /***************************************************************
1670 1671 Return a set containing all ancestor elements of
1672 the nodes within this set, which match the optional
1673 name
1674 1675 ***************************************************************/1676 1677 NodeSetancestor (inT[] name = null)
1678 {
1679 if (name.ptr)
1680 returnancestor ((Nodenode){returnnode.name == name;});
1681 returnancestor (&always);
1682 }
1683 1684 /***************************************************************
1685 1686 Return a set containing all prior sibling elements of
1687 the nodes within this set, which match the optional
1688 name
1689 1690 ***************************************************************/1691 1692 NodeSetprev (inT[] name = null)
1693 {
1694 if (name.ptr)
1695 returnprev ((Nodenode){returnnode.name == name;});
1696 returnprev (&always);
1697 }
1698 1699 /***************************************************************
1700 1701 Return a set containing all subsequent sibling
1702 elements of the nodes within this set, which
1703 match the optional name
1704 1705 ***************************************************************/1706 1707 NodeSetnext (inT[] name = null)
1708 {
1709 if (name.ptr)
1710 returnnext ((Nodenode){returnnode.name == name;});
1711 returnnext (&always);
1712 }
1713 1714 /***************************************************************
1715 1716 Return a set containing all nodes within this set
1717 which pass the filtering test
1718 1719 ***************************************************************/1720 1721 NodeSetfilter (scopebooldelegate(Node) filter)
1722 {
1723 NodeSetset = {host};
1724 automark = host.mark;
1725 1726 foreach (node; nodes)
1727 test (filter, node);
1728 returnset.assign (mark);
1729 }
1730 1731 /***************************************************************
1732 1733 Return a set containing all child nodes of
1734 the nodes within this set which pass the
1735 filtering test
1736 1737 ***************************************************************/1738 1739 NodeSetchild (scopebooldelegate(Node) filter,
1740 XmlNodeTypetype = XmlNodeType.Element)
1741 {
1742 NodeSetset = {host};
1743 automark = host.mark;
1744 1745 foreach (parent; nodes)
1746 foreach (child; parent.children)
1747 if (child.idistype)
1748 test (filter, child);
1749 returnset.assign (mark);
1750 }
1751 1752 /***************************************************************
1753 1754 Return a set containing all attribute nodes of
1755 the nodes within this set which pass the given
1756 filtering test
1757 1758 ***************************************************************/1759 1760 NodeSetattribute (scopebooldelegate(Node) filter)
1761 {
1762 NodeSetset = {host};
1763 automark = host.mark;
1764 1765 foreach (node; nodes)
1766 foreach (attr; node.attributes)
1767 test (filter, attr);
1768 returnset.assign (mark);
1769 }
1770 1771 /***************************************************************
1772 1773 Return a set containing all descendant nodes of
1774 the nodes within this set, which pass the given
1775 filtering test
1776 1777 ***************************************************************/1778 1779 NodeSetdescendant (scopebooldelegate(Node) filter,
1780 XmlNodeTypetype = XmlNodeType.Element)
1781 {
1782 voidtraverse (Nodeparent)
1783 {
1784 foreach (child; parent.children)
1785 {
1786 if (child.idistype)
1787 test (filter, child);
1788 if (child.firstChild)
1789 traverse (child);
1790 }
1791 }
1792 1793 NodeSetset = {host};
1794 automark = host.mark;
1795 1796 foreach (node; nodes)
1797 traverse (node);
1798 returnset.assign (mark);
1799 }
1800 1801 /***************************************************************
1802 1803 Return a set containing all parent nodes of
1804 the nodes within this set which pass the given
1805 filtering test
1806 1807 ***************************************************************/1808 1809 NodeSetparent (scopebooldelegate(Node) filter)
1810 {
1811 NodeSetset = {host};
1812 automark = host.mark;
1813 1814 foreach (node; nodes)
1815 {
1816 autop = node.parent;
1817 if (p && p.id != XmlNodeType.Document && !set.has(p))
1818 {
1819 test (filter, p);
1820 // continually update our set of nodes, so1821 // that set.has() can see a prior entry.1822 // Ideally we'd avoid invoking test() on1823 // prior nodes, but I don't feel the added1824 // complexity is warranted1825 set.nodes = host.slice (mark);
1826 }
1827 }
1828 returnset.assign (mark);
1829 }
1830 1831 /***************************************************************
1832 1833 Return a set containing all ancestor nodes of
1834 the nodes within this set, which pass the given
1835 filtering test
1836 1837 ***************************************************************/1838 1839 NodeSetancestor (scopebooldelegate(Node) filter)
1840 {
1841 NodeSetset = {host};
1842 automark = host.mark;
1843 1844 voidtraverse (Nodechild)
1845 {
1846 autop = child.host;
1847 if (p && p.id != XmlNodeType.Document && !set.has(p))
1848 {
1849 test (filter, p);
1850 // continually update our set of nodes, so1851 // that set.has() can see a prior entry.1852 // Ideally we'd avoid invoking test() on1853 // prior nodes, but I don't feel the added1854 // complexity is warranted1855 set.nodes = host.slice (mark);
1856 traverse (p);
1857 }
1858 }
1859 1860 foreach (node; nodes)
1861 traverse (node);
1862 returnset.assign (mark);
1863 }
1864 1865 /***************************************************************
1866 1867 Return a set containing all following siblings
1868 of the ones within this set, which pass the given
1869 filtering test
1870 1871 ***************************************************************/1872 1873 NodeSetnext (scopebooldelegate(Node) filter,
1874 XmlNodeTypetype = XmlNodeType.Element)
1875 {
1876 NodeSetset = {host};
1877 automark = host.mark;
1878 1879 foreach (node; nodes)
1880 {
1881 autop = node.nextSibling;
1882 while (p)
1883 {
1884 if (p.idistype)
1885 test (filter, p);
1886 p = p.nextSibling;
1887 }
1888 }
1889 returnset.assign (mark);
1890 }
1891 1892 /***************************************************************
1893 1894 Return a set containing all prior sibling nodes
1895 of the ones within this set, which pass the given
1896 filtering test
1897 1898 ***************************************************************/1899 1900 NodeSetprev (scopebooldelegate(Node) filter,
1901 XmlNodeTypetype = XmlNodeType.Element)
1902 {
1903 NodeSetset = {host};
1904 automark = host.mark;
1905 1906 foreach (node; nodes)
1907 {
1908 autop = node.prevSibling;
1909 while (p)
1910 {
1911 if (p.idistype)
1912 test (filter, p);
1913 p = p.prevSibling;
1914 }
1915 }
1916 returnset.assign (mark);
1917 }
1918 1919 /***************************************************************
1920 1921 Traverse the nodes of this set
1922 1923 ***************************************************************/1924 1925 intopApply (scopeintdelegate(refNode) dg)
1926 {
1927 intret;
1928 1929 foreach (node; nodes)
1930 if ((ret = dg (node)) != 0)
1931 break;
1932 returnret;
1933 }
1934 1935 /***************************************************************
1936 1937 Common predicate
1938 1939 ***************************************************************/1940 1941 privateboolalways (Nodenode)
1942 {
1943 returntrue;
1944 }
1945 1946 /***************************************************************
1947 1948 Assign a slice of the freelist to this NodeSet
1949 1950 ***************************************************************/1951 1952 privateNodeSetassign (uintmark)
1953 {
1954 nodes = host.slice (mark);
1955 returnthis;
1956 }
1957 1958 /***************************************************************
1959 1960 Execute a filter on the given node. We have to
1961 deal with potential query recusion, so we set
1962 all kinda crap to recover from that
1963 1964 ***************************************************************/1965 1966 privatevoidtest (scopebooldelegate(Node) filter, Nodenode)
1967 {
1968 autopop = host.push;
1969 autoadd = filter (node);
1970 host.pop (pop);
1971 if (add)
1972 host.allocate (node);
1973 }
1974 1975 /***************************************************************
1976 1977 We typically need to filter ancestors in order
1978 to avoid duplicates, so this is used for those
1979 purposes
1980 1981 ***************************************************************/1982 1983 privateboolhas (Nodep)
1984 {
1985 foreach (node; nodes)
1986 if (nodeisp)
1987 returntrue;
1988 returnfalse;
1989 }
1990 }
1991 1992 /***********************************************************************
1993 1994 Return the current freelist index
1995 1996 ***********************************************************************/1997 1998 privateuintmark ()
1999 {
2000 returnfreeIndex;
2001 }
2002 2003 /***********************************************************************
2004 2005 Recurse and save the current state
2006 2007 ***********************************************************************/2008 2009 privateuintpush ()
2010 {
2011 ++recursion;
2012 returnfreeIndex;
2013 }
2014 2015 /***********************************************************************
2016 2017 Restore prior state
2018 2019 ***********************************************************************/2020 2021 privatevoidpop (uintprior)
2022 {
2023 freeIndex = prior;
2024 --recursion;
2025 }
2026 2027 /***********************************************************************
2028 2029 Return a slice of the freelist
2030 2031 ***********************************************************************/2032 2033 privateNode[] slice (uintmark)
2034 {
2035 verify (mark <= freeIndex);
2036 returnfreelist [mark .. freeIndex];
2037 }
2038 2039 /***********************************************************************
2040 2041 Allocate an entry in the freelist, expanding as necessary
2042 2043 ***********************************************************************/2044 2045 privateuintallocate (Nodenode)
2046 {
2047 if (freeIndex >= freelist.length)
2048 freelist.length = freelist.length + freelist.length / 2;
2049 2050 freelist[freeIndex] = node;
2051 return ++freeIndex;
2052 }
2053 }
2054 2055 version (unittest)
2056 {
2057 importocean.core.Test;
2058 importocean.text.xml.DocPrinter;
2059 }
2060 2061 unittest2062 {
2063 autodoc = newDocument!(char);
2064 autoprinter = newDocPrinter!(char);
2065 mstringbuffer;
2066 2067 autodoc1 = `<?xml version="1.0" encoding="UTF-8"?>
2068 <root>123456789
2069 <second>second</second>
2070 <third>third</third>
2071 </root>`;
2072 2073 autodoc2 = `<?xml version="1.0" encoding="UTF-8"?>
2074 <root>12345
2075 <one>one</one>
2076 <two>two</two>
2077 </root>`;
2078 2079 voidgenerateBasicXML (cstringroot_value, cstringnode1, cstringvalue1,
2080 cstringnode2, cstringvalue2)
2081 {
2082 // A single text buffer is used to hold the elements to test that the2083 // elements do not just slice the input but actually copy it to the2084 // elements.2085 Array.copy(buffer, root_value);
2086 2087 doc.header();
2088 2089 autoroot = doc.tree.element(null, "root", buffer);
2090 2091 Array.copy(buffer, node1);
2092 root.element(null, node1, buffer);
2093 2094 Array.copy(buffer, node2);
2095 root.element(null, node2, buffer);
2096 }
2097 2098 // Test basic XML file generation.2099 generateBasicXML("123456789", "second", "second", "third", "third");
2100 autoresult1 = printer.print(doc);
2101 test!("==")(result1, doc1);
2102 2103 // Now do a second document generation that re-uses the same root doc and2104 // check for no memory allocations.2105 testNoAlloc({
2106 doc.reset();
2107 generateBasicXML("12345", "one", "one", "two", "two");
2108 }());
2109 2110 autoresult2 = printer.print(doc);
2111 test!("==")(result2, doc2);
2112 }
2113 2114 unittest2115 {
2116 autodoc = newDocument!(char);
2117 autoprinter = newDocPrinter!(char);
2118 mstringbuffer;
2119 2120 autocomplex_doc = `
2121 <VAST version="3.0">
2122 <InLine>
2123 <AdTitle>VAST 3.0 Instream Test</AdTitle>
2124 <Creatives>
2125 <Creative id="123456" adId="654321"/>
2126 </Creatives>
2127 </InLine>
2128 </VAST>`;
2129 2130 // Generate a more complex XML document.2131 autoroot = doc.tree.element(null, "VAST").attribute(null, "version", "3.0");
2132 2133 autoinline = root.element(null, "InLine");
2134 2135 Array.copy(buffer, "VAST 3.0 Instream Test");
2136 inline.element(null, "AdTitle", buffer);
2137 2138 autocreatives = inline.element(null, "Creatives");
2139 2140 Array.copy(buffer, "123456");
2141 autocreative = creatives.element(null, `Creative`).
2142 attribute(null, `id`, buffer);
2143 Array.copy(buffer, "654321");
2144 creative.attribute(null, `adId`, buffer);
2145 2146 autocomplex_result = printer.print(doc);
2147 test!("==")(complex_result, complex_doc);
2148 }
2149 2150 2151 version (Old)
2152 {
2153 /*******************************************************************************
2154 2155 Specification for an XML serializer
2156 2157 *******************************************************************************/2158 2159 interfaceIXmlPrinter(T)
2160 {
2161 publicaliasDocument!(T) Doc; /// the typed document2162 publicaliasDoc.NodeNode; /// generic document node2163 publicaliasprintopCall; /// alias for print method2164 2165 /***********************************************************************
2166 2167 Generate a text representation of the document tree
2168 2169 ***********************************************************************/2170 2171 T[] print (Docdoc);
2172 2173 /***********************************************************************
2174 2175 Generate a representation of the given node-subtree
2176 2177 ***********************************************************************/2178 2179 voidprint (Noderoot, scopevoiddelegate(T[][]...) emit);
2180 }
2181 }
2182 2183 2184 2185 /*******************************************************************************
2186 2187 *******************************************************************************/2188 2189 debug (Document)
2190 {
2191 importocean.io.Stdout;
2192 importocean.text.xml.DocPrinter;
2193 2194 voidmain()
2195 {
2196 autodoc = newDocument!(char);
2197 2198 // attach an xml header2199 doc.header;
2200 2201 // attach an element with some attributes, plus2202 // a child element with an attached data value2203 doc.tree.element (null, "root")
2204 .attribute (null, "attrib1", "value")
2205 .attribute (null, "attrib2", "other")
2206 .element (null, "child")
2207 .cdata ("some text");
2208 2209 // attach a sibling to the interior elements2210 doc.elements.element (null, "sibling");
2211 2212 boolfoo (doc.Nodenode)
2213 {
2214 node = node.attributes.name(null, "attrib1");
2215 returnnode && "value" == node.value;
2216 }
2217 2218 foreach (node; doc.query.descendant("root").filter(&foo).child)
2219 Stdout.formatln(">> {}", node.name);
2220 2221 foreach (node; doc.elements.attributes)
2222 Stdout.formatln("<< {}", node.name);
2223 2224 foreach (node; doc.elements.children)
2225 Stdout.formatln("<< {}", node.name);
2226 2227 foreach (node; doc.query.descendant.cdata)
2228 Stdout.formatln ("{}: {}", node.parent.name, node.value);
2229 2230 // emit the result2231 autoprinter = newDocPrinter!(char);
2232 printer.print (doc, stdout);
2233 doc.reset;
2234 }
2235 }