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 module ocean.text.xml.Document; 19 20 import Array = ocean.core.Array; 21 import ocean.meta.types.Qualifiers; 22 import ocean.core.Verify; 23 24 package import ocean.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 class Document(T_) : PullParser!(T_) 149 { 150 public alias NodeImpl* Node; 151 152 private alias const(T_) T; 153 private alias T_ MutT; 154 private alias XmlPath!(MutT) XmlPathT; 155 156 private Node root; 157 private NodeImpl[] list; 158 private NodeImpl[][] lists; 159 private int index, 160 chunks, 161 freelists; 162 private XmlPathT xpath; 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 (uint nodes = 1000) 172 { 173 verify (nodes > 50); 174 super (null); 175 xpath = new XmlPathT; 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 final XmlPathT.NodeSet query () 193 { 194 return xpath.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 final Node tree () 207 { 208 return root; 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 final Node elements () 221 { 222 if (root) 223 { 224 auto node = root.lastChild; 225 while (node) 226 if (node.id is XmlNodeType.Element) 227 return node; 228 else 229 node = node.prev; 230 } 231 return null; 232 } 233 234 /*********************************************************************** 235 236 Reset the freelist. Subsequent allocation of document nodes 237 will overwrite prior instances. 238 239 ***********************************************************************/ 240 241 final Document reset () 242 { 243 root.lastChild = root.firstChild = null; 244 freelists = 0; 245 newlist; 246 index = 1; 247 return this; 248 } 249 250 /*********************************************************************** 251 252 Prepend an XML header to the document tree 253 254 ***********************************************************************/ 255 256 final Document header () 257 { 258 static immutable header = `xml version="1.0" encoding="UTF-8"`; 259 root.prepend (root.create(XmlNodeType.PI, header)); 260 return this; 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 final Document header (immutable(T)[] encoding) 274 { 275 if (encoding.length) 276 encoding = `xml version="1.0" encoding="`~encoding~`"`; 277 else 278 encoding = `xml version="1.0" encoding="UTF-8"`; 279 280 root.prepend (root.create(XmlNodeType.PI, encoding.dup)); 281 return this; 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 final void parse(T[] xml) 293 { 294 verify (xml.ptr ! is null); 295 reset; 296 super.reset (xml); 297 auto cur = root; 298 uint defNamespace; 299 300 while (true) 301 { 302 auto p = text.point; 303 switch (super.next) 304 { 305 case XmlTokenType.EndElement: 306 case XmlTokenType.EndEmptyElement: 307 verify (cur !is null); 308 cur.end = text.point; 309 cur = cur.host; 310 break; 311 312 case XmlTokenType.Data: 313 auto node = allocate; 314 Array.copy(node.rawValue, super.rawValue); 315 node.id = XmlNodeType.Data; 316 cur.append (node); 317 break; 318 319 case XmlTokenType.StartElement: 320 auto node = 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 append 328 if (cur.lastChild) 329 { 330 cur.lastChild.nextSibling = node; 331 node.prevSibling = cur.lastChild; 332 cur.lastChild = node; 333 } 334 else 335 { 336 cur.firstChild = node; 337 cur.lastChild = node; 338 } 339 cur = node; 340 break; 341 342 case XmlTokenType.Attribute: 343 auto attr = 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 case XmlTokenType.PI: 352 cur.pi_ (super.rawValue, p[0..text.point-p]); 353 break; 354 355 case XmlTokenType.Comment: 356 cur.comment_ (super.rawValue); 357 break; 358 359 case XmlTokenType.CData: 360 cur.cdata_ (super.rawValue); 361 break; 362 363 case XmlTokenType.Doctype: 364 cur.doctype_ (super.rawValue); 365 break; 366 367 case XmlTokenType.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 private final Node allocate () 383 { 384 if (index >= list.length) 385 newlist; 386 387 auto p = &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 return p; 404 } 405 406 /*********************************************************************** 407 408 allocate a node from the freelist 409 410 ***********************************************************************/ 411 412 private final void newlist () 413 { 414 index = 0; 415 if (freelists >= lists.length) 416 { 417 lists.length = lists.length + 1; 418 lists[$-1] = new NodeImpl [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 private struct Visitor 436 { 437 private Node node; 438 439 public alias value data; 440 public alias hasValue hasData; 441 442 /*************************************************************** 443 444 Is there anything to visit here? 445 446 Time complexity: O(1) 447 448 ***************************************************************/ 449 450 bool exist () 451 { 452 return node != null; 453 } 454 455 /*************************************************************** 456 457 traverse sibling nodes 458 459 ***************************************************************/ 460 461 int opApply (scope int delegate(ref Node) dg) 462 { 463 int ret; 464 465 for (auto n=node; n; n = n.nextSibling) 466 if ((ret = dg(n)) != 0) 467 break; 468 return ret; 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 Node name (T[] prefix, T[] local, scope bool delegate(Node) dg=null) 482 { 483 for (auto n=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) is false) 492 continue; 493 494 return n; 495 } 496 return null; 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 bool hasName (T[] prefix, T[] local) 509 { 510 return name (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 Node value (T[] prefix, T[] local, T[] value) 525 { 526 if (value.ptr) 527 return name (prefix, local, (Node n){return value == n.rawValue;}); 528 return name (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 Node value (T[] match) 542 { 543 if (match.ptr) 544 for (auto n=node; n; n = n.nextSibling) 545 if (match == n.rawValue) 546 return n; 547 return null; 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 bool hasValue (T[] match) 561 { 562 return value(match) != null; 563 } 564 } 565 566 567 /*********************************************************************** 568 569 The node implementation 570 571 ***********************************************************************/ 572 573 private struct NodeImpl 574 { 575 public void* user; /// open for usage 576 package Document doc; // owning document 577 package XmlNodeType id; // node type 578 package MutT[] prefixed; // namespace 579 package MutT[] localName; // name 580 package MutT[] rawValue; // data value 581 582 package Node host, // parent node 583 prevSibling, // prior 584 nextSibling, // next 585 firstChild, // head 586 lastChild, // tail 587 firstAttr, // head 588 lastAttr; // tail 589 590 package T* end, // slice of the ... 591 start; // original xml text 592 593 /*************************************************************** 594 595 Return the hosting document 596 597 ***************************************************************/ 598 599 Document document () 600 { 601 return doc; 602 } 603 604 /*************************************************************** 605 606 Return the node type-id 607 608 ***************************************************************/ 609 610 XmlNodeType type () 611 { 612 return id; 613 } 614 615 /*************************************************************** 616 617 Return the parent, which may be null 618 619 ***************************************************************/ 620 621 Node parent () 622 { 623 return host; 624 } 625 626 /*************************************************************** 627 628 Return the first child, which may be null 629 630 ***************************************************************/ 631 632 Node child () 633 { 634 return firstChild; 635 } 636 637 /*************************************************************** 638 639 Return the prior sibling, which may be null 640 641 ***************************************************************/ 642 643 Node prev () 644 { 645 return prevSibling; 646 } 647 648 /*************************************************************** 649 650 Return the next sibling, which may be null 651 652 ***************************************************************/ 653 654 Node next () 655 { 656 return nextSibling; 657 } 658 659 /*************************************************************** 660 661 Return the namespace prefix of this node (may be null) 662 663 ***************************************************************/ 664 665 T[] prefix () 666 { 667 return prefixed; 668 } 669 670 /*************************************************************** 671 672 Set the namespace prefix of this node (may be null) 673 674 ***************************************************************/ 675 676 Node prefix (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 return localName; 691 } 692 693 /*************************************************************** 694 695 Set the vanilla node name (sans prefix) 696 697 ***************************************************************/ 698 699 Node name (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 (type is XmlNodeType.Element) 714 foreach (child; children) 715 if (child.id is XmlNodeType.Data || 716 child.id is XmlNodeType.CData) 717 return child.rawValue; 718 return rawValue; 719 } 720 721 /*************************************************************** 722 723 Set the raw data content, which may be null 724 725 ***************************************************************/ 726 727 void value (T[] val) 728 { 729 if (type is XmlNodeType.Element) 730 foreach (child; children) 731 if (child.id is XmlNodeType.Data) 732 return child.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 auto len = prefixed.length + localName.length + 1; 750 751 // is the prefix already attached to the name? 752 if (prefixed.ptr + prefixed.length + 1 is localName.ptr && 753 ':' is *(localName.ptr-1)) 754 return prefixed.ptr [0 .. len]; 755 756 // nope, copy the discrete segments into output 757 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 return output[0..len]; 763 } 764 765 return localName; 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 uint position () 778 { 779 auto count = 0; 780 auto prior = prevSibling; 781 while (prior) 782 ++count, prior = prior.prevSibling; 783 return count; 784 } 785 786 /*************************************************************** 787 788 Detach this node from its parent and siblings 789 790 ***************************************************************/ 791 792 Node detach () 793 { 794 return remove; 795 } 796 797 /*************************************************************** 798 799 Return an xpath handle to query this node 800 801 See also Document.query 802 803 ***************************************************************/ 804 805 final XmlPathT.NodeSet query () 806 { 807 return doc.xpath.start(&this); 808 } 809 810 /*************************************************************** 811 812 Return a foreach iterator for node children 813 814 ***************************************************************/ 815 816 Visitor children () 817 { 818 Visitor v = {firstChild}; 819 return v; 820 } 821 822 /*************************************************************** 823 824 Return a foreach iterator for node attributes 825 826 ***************************************************************/ 827 828 Visitor attributes () 829 { 830 Visitor v = {firstAttr}; 831 return v; 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 Node copy (Node tree) 844 { 845 verify (tree !is null); 846 tree = tree.clone; 847 tree.migrate (document); 848 849 if (tree.id is XmlNodeType.Attribute) 850 attrib (tree); 851 else 852 append (tree); 853 return tree; 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 Node move (Node tree) 866 { 867 tree.detach; 868 if (tree.doc is doc) 869 { 870 if (tree.id is XmlNodeType.Attribute) 871 attrib (tree); 872 else 873 append (tree); 874 } 875 else 876 tree = copy (tree); 877 return tree; 878 } 879 880 /*************************************************************** 881 882 Appends a new (child) Element and returns a reference 883 to it. 884 885 ***************************************************************/ 886 887 Node element (T[] prefix, T[] local, T[] value = null) 888 { 889 return element_ (prefix, local, value).mutate; 890 } 891 892 /*************************************************************** 893 894 Attaches an Attribute and returns this, the host 895 896 ***************************************************************/ 897 898 Node attribute (T[] prefix, T[] local, T[] value = null) 899 { 900 return attribute_ (prefix, local, value).mutate; 901 } 902 903 /*************************************************************** 904 905 Attaches a Data node and returns this, the host 906 907 ***************************************************************/ 908 909 Node data (T[] data) 910 { 911 return data_ (data).mutate; 912 } 913 914 /*************************************************************** 915 916 Attaches a CData node and returns this, the host 917 918 ***************************************************************/ 919 920 Node cdata (T[] cdata) 921 { 922 return cdata_ (cdata).mutate; 923 } 924 925 /*************************************************************** 926 927 Attaches a Comment node and returns this, the host 928 929 ***************************************************************/ 930 931 Node comment (T[] comment) 932 { 933 return comment_ (comment).mutate; 934 } 935 936 /*************************************************************** 937 938 Attaches a Doctype node and returns this, the host 939 940 ***************************************************************/ 941 942 Node doctype (T[] doctype) 943 { 944 return doctype_ (doctype).mutate; 945 } 946 947 /*************************************************************** 948 949 Attaches a PI node and returns this, the host 950 951 ***************************************************************/ 952 953 Node pi (T[] pi) 954 { 955 return pi_ (pi, null).mutate; 956 } 957 958 /*************************************************************** 959 960 Attaches a child Element, and returns a reference 961 to the child 962 963 ***************************************************************/ 964 965 private Node element_ (T[] prefix, T[] local, T[] value = null) 966 { 967 auto node = create (XmlNodeType.Element, null); 968 append (node.set (prefix, local)); 969 if (value.length) 970 node.data_ (value); 971 return node; 972 } 973 974 /*************************************************************** 975 976 Attaches an Attribute, and returns the host 977 978 ***************************************************************/ 979 980 private Node attribute_ (T[] prefix, T[] local, T[] value = null) 981 { 982 auto node = 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 private Node data_ (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 private Node cdata_ (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 private Node comment_ (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 private Node pi_ (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 private Node doctype_ (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 private void attrib (Node node) 1055 { 1056 verify (node.parent is null); 1057 node.host = &this; 1058 if (lastAttr) 1059 { 1060 lastAttr.nextSibling = node; 1061 node.prevSibling = lastAttr; 1062 lastAttr = node; 1063 } 1064 else 1065 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 private void append (Node node) 1076 { 1077 verify (node.parent is null); 1078 node.host = &this; 1079 if (lastChild) 1080 { 1081 lastChild.nextSibling = node; 1082 node.prevSibling = lastChild; 1083 lastChild = node; 1084 } 1085 else 1086 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 private void prepend (Node node) 1097 { 1098 verify (node.parent is null); 1099 node.host = &this; 1100 if (firstChild) 1101 { 1102 firstChild.prevSibling = node; 1103 node.nextSibling = firstChild; 1104 firstChild = node; 1105 } 1106 else 1107 firstChild = lastChild = node; 1108 } 1109 1110 /*************************************************************** 1111 1112 Configure node values 1113 1114 ***************************************************************/ 1115 1116 private Node set (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 private Node create (XmlNodeType type, T[] value) 1130 { 1131 auto node = document.allocate; 1132 Array.copy(node.rawValue, value); 1133 node.id = type; 1134 return node; 1135 } 1136 1137 /*************************************************************** 1138 1139 Detach this node from its parent and siblings 1140 1141 ***************************************************************/ 1142 1143 private Node remove() 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 else 1158 if (nextSibling) 1159 { 1160 verify(host.firstChild == &this); 1161 parent.firstChild = nextSibling; 1162 nextSibling.prevSibling = null; 1163 nextSibling = null; 1164 host = null; 1165 } 1166 else 1167 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 else 1178 { 1179 verify(host.firstChild == &this); 1180 verify(host.lastChild == &this); 1181 host.firstChild = null; 1182 host.lastChild = null; 1183 host = null; 1184 } 1185 } 1186 else 1187 { 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 else 1197 { 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 private Node patch (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 private Node mutate () 1235 { 1236 auto node = &this; 1237 do { 1238 node.end = null; 1239 } while ((node = node.host) !is null); 1240 1241 return &this; 1242 } 1243 1244 /*************************************************************** 1245 1246 Duplicate a single node 1247 1248 ***************************************************************/ 1249 1250 private Node dup () 1251 { 1252 return create(type, rawValue.dup).set(prefixed.dup, localName.dup); 1253 } 1254 1255 /*************************************************************** 1256 1257 Duplicate a subtree 1258 1259 ***************************************************************/ 1260 1261 private Node clone () 1262 { 1263 auto p = dup; 1264 1265 foreach (attr; attributes) 1266 p.attrib (attr.dup); 1267 foreach (child; children) 1268 p.append (child.clone); 1269 return p; 1270 } 1271 1272 /*************************************************************** 1273 1274 Reset the document host for this subtree 1275 1276 ***************************************************************/ 1277 1278 private void migrate (Document host) 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 public class XmlPath(T) 1425 { 1426 public alias Document!(T) Doc; /// the typed document 1427 public alias Doc.Node Node; /// generic document node 1428 1429 private Node[] freelist; 1430 private uint freeIndex, 1431 markIndex; 1432 private uint recursion; 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 final NodeSet start (Node root) 1445 { 1446 // we have to support recursion which may occur within 1447 // a filter callback 1448 if (recursion is 0) 1449 { 1450 if (freelist.length is 0) 1451 freelist.length = 256; 1452 freeIndex = 0; 1453 } 1454 1455 NodeSet set = {this}; 1456 auto mark = freeIndex; 1457 allocate(root); 1458 return set.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 struct NodeSet 1473 { 1474 private XmlPath host; 1475 public Node[] nodes; /// array of selected nodes 1476 1477 /*************************************************************** 1478 1479 Return a duplicate NodeSet 1480 1481 ***************************************************************/ 1482 1483 NodeSet dup () 1484 { 1485 NodeSet copy = {host}; 1486 copy.nodes = nodes.dup; 1487 return copy; 1488 } 1489 1490 /*************************************************************** 1491 1492 Return the number of selected nodes in the set 1493 1494 ***************************************************************/ 1495 1496 size_t count () 1497 { 1498 return nodes.length; 1499 } 1500 1501 /*************************************************************** 1502 1503 Return a set containing just the first node of 1504 the current set 1505 1506 ***************************************************************/ 1507 1508 NodeSet first () 1509 { 1510 return nth (0); 1511 } 1512 1513 /*************************************************************** 1514 1515 Return a set containing just the last node of 1516 the current set 1517 1518 ***************************************************************/ 1519 1520 NodeSet last () 1521 { 1522 auto i = nodes.length; 1523 if (i > 0) 1524 --i; 1525 return nth (i); 1526 } 1527 1528 /*************************************************************** 1529 1530 Return a set containing just the nth node of 1531 the current set 1532 1533 ***************************************************************/ 1534 1535 NodeSet opIndex (uint i) 1536 { 1537 return nth (i); 1538 } 1539 1540 /*************************************************************** 1541 1542 Return a set containing just the nth node of 1543 the current set 1544 1545 ***************************************************************/ 1546 1547 NodeSet nth (size_t index) 1548 { 1549 NodeSet set = {host}; 1550 auto mark = host.mark; 1551 if (index < nodes.length) 1552 host.allocate (nodes [index]); 1553 return set.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 NodeSet opSlice () 1564 { 1565 return child(); 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 NodeSet opIndex (in T[] name) 1576 { 1577 return child (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 NodeSet parent (in T[] name = null) 1588 { 1589 if (name.ptr) 1590 return parent ((Node node){return node.name == name;}); 1591 return parent (&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 NodeSet data (in T[] value = null) 1603 { 1604 if (value.ptr) 1605 return child ((Node node){return node.value == value;}, 1606 XmlNodeType.Data); 1607 return child (&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 NodeSet cdata (in T[] value = null) 1619 { 1620 if (value.ptr) 1621 return child ((Node node){return node.value == value;}, 1622 XmlNodeType.CData); 1623 return child (&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 NodeSet attribute (in T[] name = null) 1635 { 1636 if (name.ptr) 1637 return attribute ((Node node){return node.name == name;}); 1638 return attribute (&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 NodeSet descendant (in T[] name = null) 1649 { 1650 if (name.ptr) 1651 return descendant ((Node node){return node.name == name;}); 1652 return descendant (&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 NodeSet child (in T[] name = null) 1663 { 1664 if (name.ptr) 1665 return child ((Node node){return node.name == name;}); 1666 return child (&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 NodeSet ancestor (in T[] name = null) 1678 { 1679 if (name.ptr) 1680 return ancestor ((Node node){return node.name == name;}); 1681 return ancestor (&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 NodeSet prev (in T[] name = null) 1693 { 1694 if (name.ptr) 1695 return prev ((Node node){return node.name == name;}); 1696 return prev (&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 NodeSet next (in T[] name = null) 1708 { 1709 if (name.ptr) 1710 return next ((Node node){return node.name == name;}); 1711 return next (&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 NodeSet filter (scope bool delegate(Node) filter) 1722 { 1723 NodeSet set = {host}; 1724 auto mark = host.mark; 1725 1726 foreach (node; nodes) 1727 test (filter, node); 1728 return set.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 NodeSet child (scope bool delegate(Node) filter, 1740 XmlNodeType type = XmlNodeType.Element) 1741 { 1742 NodeSet set = {host}; 1743 auto mark = host.mark; 1744 1745 foreach (parent; nodes) 1746 foreach (child; parent.children) 1747 if (child.id is type) 1748 test (filter, child); 1749 return set.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 NodeSet attribute (scope bool delegate(Node) filter) 1761 { 1762 NodeSet set = {host}; 1763 auto mark = host.mark; 1764 1765 foreach (node; nodes) 1766 foreach (attr; node.attributes) 1767 test (filter, attr); 1768 return set.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 NodeSet descendant (scope bool delegate(Node) filter, 1780 XmlNodeType type = XmlNodeType.Element) 1781 { 1782 void traverse (Node parent) 1783 { 1784 foreach (child; parent.children) 1785 { 1786 if (child.id is type) 1787 test (filter, child); 1788 if (child.firstChild) 1789 traverse (child); 1790 } 1791 } 1792 1793 NodeSet set = {host}; 1794 auto mark = host.mark; 1795 1796 foreach (node; nodes) 1797 traverse (node); 1798 return set.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 NodeSet parent (scope bool delegate(Node) filter) 1810 { 1811 NodeSet set = {host}; 1812 auto mark = host.mark; 1813 1814 foreach (node; nodes) 1815 { 1816 auto p = node.parent; 1817 if (p && p.id != XmlNodeType.Document && !set.has(p)) 1818 { 1819 test (filter, p); 1820 // continually update our set of nodes, so 1821 // that set.has() can see a prior entry. 1822 // Ideally we'd avoid invoking test() on 1823 // prior nodes, but I don't feel the added 1824 // complexity is warranted 1825 set.nodes = host.slice (mark); 1826 } 1827 } 1828 return set.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 NodeSet ancestor (scope bool delegate(Node) filter) 1840 { 1841 NodeSet set = {host}; 1842 auto mark = host.mark; 1843 1844 void traverse (Node child) 1845 { 1846 auto p = child.host; 1847 if (p && p.id != XmlNodeType.Document && !set.has(p)) 1848 { 1849 test (filter, p); 1850 // continually update our set of nodes, so 1851 // that set.has() can see a prior entry. 1852 // Ideally we'd avoid invoking test() on 1853 // prior nodes, but I don't feel the added 1854 // complexity is warranted 1855 set.nodes = host.slice (mark); 1856 traverse (p); 1857 } 1858 } 1859 1860 foreach (node; nodes) 1861 traverse (node); 1862 return set.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 NodeSet next (scope bool delegate(Node) filter, 1874 XmlNodeType type = XmlNodeType.Element) 1875 { 1876 NodeSet set = {host}; 1877 auto mark = host.mark; 1878 1879 foreach (node; nodes) 1880 { 1881 auto p = node.nextSibling; 1882 while (p) 1883 { 1884 if (p.id is type) 1885 test (filter, p); 1886 p = p.nextSibling; 1887 } 1888 } 1889 return set.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 NodeSet prev (scope bool delegate(Node) filter, 1901 XmlNodeType type = XmlNodeType.Element) 1902 { 1903 NodeSet set = {host}; 1904 auto mark = host.mark; 1905 1906 foreach (node; nodes) 1907 { 1908 auto p = node.prevSibling; 1909 while (p) 1910 { 1911 if (p.id is type) 1912 test (filter, p); 1913 p = p.prevSibling; 1914 } 1915 } 1916 return set.assign (mark); 1917 } 1918 1919 /*************************************************************** 1920 1921 Traverse the nodes of this set 1922 1923 ***************************************************************/ 1924 1925 int opApply (scope int delegate(ref Node) dg) 1926 { 1927 int ret; 1928 1929 foreach (node; nodes) 1930 if ((ret = dg (node)) != 0) 1931 break; 1932 return ret; 1933 } 1934 1935 /*************************************************************** 1936 1937 Common predicate 1938 1939 ***************************************************************/ 1940 1941 private bool always (Node node) 1942 { 1943 return true; 1944 } 1945 1946 /*************************************************************** 1947 1948 Assign a slice of the freelist to this NodeSet 1949 1950 ***************************************************************/ 1951 1952 private NodeSet assign (uint mark) 1953 { 1954 nodes = host.slice (mark); 1955 return this; 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 private void test (scope bool delegate(Node) filter, Node node) 1967 { 1968 auto pop = host.push; 1969 auto add = 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 private bool has (Node p) 1984 { 1985 foreach (node; nodes) 1986 if (node is p) 1987 return true; 1988 return false; 1989 } 1990 } 1991 1992 /*********************************************************************** 1993 1994 Return the current freelist index 1995 1996 ***********************************************************************/ 1997 1998 private uint mark () 1999 { 2000 return freeIndex; 2001 } 2002 2003 /*********************************************************************** 2004 2005 Recurse and save the current state 2006 2007 ***********************************************************************/ 2008 2009 private uint push () 2010 { 2011 ++recursion; 2012 return freeIndex; 2013 } 2014 2015 /*********************************************************************** 2016 2017 Restore prior state 2018 2019 ***********************************************************************/ 2020 2021 private void pop (uint prior) 2022 { 2023 freeIndex = prior; 2024 --recursion; 2025 } 2026 2027 /*********************************************************************** 2028 2029 Return a slice of the freelist 2030 2031 ***********************************************************************/ 2032 2033 private Node[] slice (uint mark) 2034 { 2035 verify (mark <= freeIndex); 2036 return freelist [mark .. freeIndex]; 2037 } 2038 2039 /*********************************************************************** 2040 2041 Allocate an entry in the freelist, expanding as necessary 2042 2043 ***********************************************************************/ 2044 2045 private uint allocate (Node node) 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 import ocean.core.Test; 2058 import ocean.text.xml.DocPrinter; 2059 } 2060 2061 unittest 2062 { 2063 auto doc = new Document!(char); 2064 auto printer = new DocPrinter!(char); 2065 mstring buffer; 2066 2067 auto doc1 = `<?xml version="1.0" encoding="UTF-8"?> 2068 <root>123456789 2069 <second>second</second> 2070 <third>third</third> 2071 </root>`; 2072 2073 auto doc2 = `<?xml version="1.0" encoding="UTF-8"?> 2074 <root>12345 2075 <one>one</one> 2076 <two>two</two> 2077 </root>`; 2078 2079 void generateBasicXML (cstring root_value, cstring node1, cstring value1, 2080 cstring node2, cstring value2) 2081 { 2082 // A single text buffer is used to hold the elements to test that the 2083 // elements do not just slice the input but actually copy it to the 2084 // elements. 2085 Array.copy(buffer, root_value); 2086 2087 doc.header(); 2088 2089 auto root = 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 auto result1 = printer.print(doc); 2101 test!("==")(result1, doc1); 2102 2103 // Now do a second document generation that re-uses the same root doc and 2104 // check for no memory allocations. 2105 testNoAlloc({ 2106 doc.reset(); 2107 generateBasicXML("12345", "one", "one", "two", "two"); 2108 }()); 2109 2110 auto result2 = printer.print(doc); 2111 test!("==")(result2, doc2); 2112 } 2113 2114 unittest 2115 { 2116 auto doc = new Document!(char); 2117 auto printer = new DocPrinter!(char); 2118 mstring buffer; 2119 2120 auto complex_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 auto root = doc.tree.element(null, "VAST").attribute(null, "version", "3.0"); 2132 2133 auto inline = root.element(null, "InLine"); 2134 2135 Array.copy(buffer, "VAST 3.0 Instream Test"); 2136 inline.element(null, "AdTitle", buffer); 2137 2138 auto creatives = inline.element(null, "Creatives"); 2139 2140 Array.copy(buffer, "123456"); 2141 auto creative = creatives.element(null, `Creative`). 2142 attribute(null, `id`, buffer); 2143 Array.copy(buffer, "654321"); 2144 creative.attribute(null, `adId`, buffer); 2145 2146 auto complex_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 interface IXmlPrinter(T) 2160 { 2161 public alias Document!(T) Doc; /// the typed document 2162 public alias Doc.Node Node; /// generic document node 2163 public alias print opCall; /// alias for print method 2164 2165 /*********************************************************************** 2166 2167 Generate a text representation of the document tree 2168 2169 ***********************************************************************/ 2170 2171 T[] print (Doc doc); 2172 2173 /*********************************************************************** 2174 2175 Generate a representation of the given node-subtree 2176 2177 ***********************************************************************/ 2178 2179 void print (Node root, scope void delegate(T[][]...) emit); 2180 } 2181 } 2182 2183 2184 2185 /******************************************************************************* 2186 2187 *******************************************************************************/ 2188 2189 debug (Document) 2190 { 2191 import ocean.io.Stdout; 2192 import ocean.text.xml.DocPrinter; 2193 2194 void main() 2195 { 2196 auto doc = new Document!(char); 2197 2198 // attach an xml header 2199 doc.header; 2200 2201 // attach an element with some attributes, plus 2202 // a child element with an attached data value 2203 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 elements 2210 doc.elements.element (null, "sibling"); 2211 2212 bool foo (doc.Node node) 2213 { 2214 node = node.attributes.name(null, "attrib1"); 2215 return node && "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 result 2231 auto printer = new DocPrinter!(char); 2232 printer.print (doc, stdout); 2233 doc.reset; 2234 } 2235 }