Changeset 553
- Timestamp:
- 02/09/08 08:54:42 (10 months ago)
- Files:
-
- candidate/phobos/std/xml.d (modified) (35 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
candidate/phobos/std/xml.d
r551 r553 17 17 import std.xml; 18 18 import std.stdio; 19 import std.string; 19 20 20 21 // books.xml is used in various samples throughout the Microsoft XML Core Services (MSXML) SDK. 21 22 // See http://msdn2.microsoft.com/en-us/library/ms762271(VS.85).aspx 22 23 24 struct Book 25 { 26 string id; 27 string author; 28 string title; 29 string genre; 30 string price; 31 string pubDate; 32 string description; 33 } 34 23 35 void main() 24 36 { 25 37 string s = import("books.xml"); 38 39 // Take it apart 40 Book[] books; 41 26 42 auto xml = new DocumentParser(s); 27 28 43 xml.onStartTag["book"] = delegate void(ElementParser xml) 29 44 { 30 string author; 31 string title; 32 string genre; 33 string price; 34 string pubDate; 35 string description; 36 37 xml.onEndTag["author"] = delegate void(in Element e) { author = e.text; }; 38 xml.onEndTag["title"] = delegate void(in Element e) { title = e.text; }; 39 xml.onEndTag["genre"] = delegate void(in Element e) { genre = e.text; }; 40 xml.onEndTag["price"] = delegate void(in Element e) { price = e.text; }; 41 xml.onEndTag["publish-date"] = delegate void(in Element e) { pubDate = e.text; }; 42 xml.onEndTag["description"] = delegate void(in Element e) { description = e.text; }; 45 Book book; 46 book.id = xml.tag.attr["id"]; 47 48 xml.onEndTag["author"] = delegate void(in Element e) { book.author = e.text; }; 49 xml.onEndTag["title"] = delegate void(in Element e) { book.title = e.text; }; 50 xml.onEndTag["genre"] = delegate void(in Element e) { book.genre = e.text; }; 51 xml.onEndTag["price"] = delegate void(in Element e) { book.price = e.text; }; 52 xml.onEndTag["publish-date"] = delegate void(in Element e) { book.pubDate = e.text; }; 53 xml.onEndTag["description"] = delegate void(in Element e) { book.description = e.text; }; 43 54 44 55 xml.parse(); 45 56 46 writefln("author = \"%s\"",author); 47 writefln("title = \"%s\"",title); 48 writefln("genre = \"%s\"",genre); 49 writefln("price = \"%s\"",price); 50 writefln("publication date = \"%s\"",pubDate); 51 writefln("description = \"%s\"",description); 52 writefln(); 57 books ~= book; 53 58 }; 54 55 59 xml.parse(); 60 61 // Put it back together again; 62 auto doc = new Document("catalog"); 63 foreach(book;books) 64 { 65 auto element = new Element("book"); 66 element.tag.attr["id"] = book.id; 67 68 element ~= new Element("author", book.author); 69 element ~= new Element("title", book.title); 70 element ~= new Element("genre", book.genre); 71 element ~= new Element("price", book.price); 72 element ~= new Element("publish-date",book.pubDate); 73 element ~= new Element("description", book.description); 74 75 doc ~= element; 76 } 77 78 // Now let's see pretty-print it to see what it looks like 79 writefln(join(doc.pretty(3),"\n")); 56 80 } 57 81 -------------------------------------------------------------------------------------------------- … … 66 90 67 91 void check(string xml); 68 // throws XMLException if string is not well formed XML 69 70 string tidy(string xml,int indent=4) 71 // pretty formats xml strings for readability 92 // throws Exception if string is not well formed XML 72 93 */ 73 94 … … 130 151 * Returns: The decoded string 131 152 * 132 * Throws: XMLDecodeException on unrecognized or malformed entity153 * Throws: DecodeException on unrecognized or malformed entity 133 154 * 134 155 * Examples: … … 158 179 dchar parseHex(string hex) 159 180 { 160 if (hex.length == 0) throw new XMLDecodeException(s);181 if (hex.length == 0) throw new DecodeException(s); 161 182 dchar d = 0; 162 183 foreach(dchar c;hex) … … 181 202 case '1': ++d; 182 203 case '0': break; 183 default: throw new XMLDecodeException(s);204 default: throw new DecodeException(s); 184 205 } 185 206 } … … 190 211 { 191 212 dchar d = parse!(uint)(dec); 192 if (dec.length != 0) throw new XMLDecodeException(s);213 if (dec.length != 0) throw new DecodeException(s); 193 214 return d; 194 215 } 195 216 196 217 int j = s[i..$].find(';'); 197 if (j == -1) throw new XMLDecodeException(s);218 if (j == -1) throw new DecodeException(s); 198 219 j += i; 199 220 string codepoint = s[i+2..j]; 200 if (codepoint.length == 0) throw new XMLDecodeException(s);221 if (codepoint.length == 0) throw new DecodeException(s); 201 222 if (codepoint[0] == 'x' || codepoint[0] == 'X') buffer ~= parseHex(codepoint[1..$]); 202 223 else buffer ~= parseDecimal(codepoint); … … 208 229 else if (startsWith(s[i..$],"<" )) { buffer ~= '<'; i += 3; } 209 230 else if (startsWith(s[i..$],">" )) { buffer ~= '>'; i += 3; } 210 else { throw new XMLDecodeException(s); }231 else { throw new DecodeException(s); } 211 232 } 212 233 } … … 220 241 bool b = false; 221 242 try { decode(s); } 222 catch ( XMLDecodeException e) { b = true; }243 catch (DecodeException e) { b = true; } 223 244 assert(b,s); 224 245 } … … 258 279 259 280 /** 260 * Constructs a Document given the root name and the string interior.281 * Constructs a Document given the root name. 261 282 * 262 283 * Params: 263 284 * name = the name of the root element of the document. 264 * interior = (optional) the string interior of the root element. Must be well-formed XML. 265 */ 266 this(string name, string interior=null) 267 { 268 super(name,interior); 285 */ 286 this(string name) 287 { 288 super(name); 269 289 } 270 290 … … 276 296 * interior = (optional) the string interior of the root element. Must be well-formed XML. 277 297 */ 278 this(Tag tag, string interior=null) 279 { 280 super(tag,interior); 281 } 282 283 /** 284 * Copy constructor. Makes a deep copy. 285 * 286 * Params: 287 * doc = another Document. 288 */ 289 this(Document doc) 290 { 291 this(new Tag(tag),content); 292 prolog = doc.prolog; 298 this(Tag tag) 299 { 300 super(tag); 293 301 } 294 302 … … 340 348 * can be used as associative array keys. 341 349 */ 342 override hash_t toHash() 343 { 344 hash_t hash = super.toHash; 345 foreach(dchar c;prolog) hash = hash * 11 + c; 346 return hash; 347 } 350 override hash_t toHash() { return super.toHash + stringToHash(prolog); } 348 351 349 352 /** … … 352 355 override string toString() 353 356 { 354 return prolog ~ "\r\n" ~super.toString();357 return prolog ~ super.toString(); 355 358 } 356 359 } … … 366 369 * Bugs: class invariant not yet written 367 370 */ 368 class Element 369 { 370 /** 371 * The start tag of the element. 372 */ 373 Tag tag; 374 375 /** 376 * The content of the element, as text. 377 * 378 * This is the literal content of the element as plain text. It is <i>not</i> a heirarchy 379 * of elements. So, for example, the element "<b><i>hello</i></b>" would be stored as a 380 * start tag representing <b>, and the content string would be "<i>hello</i>". 381 * 382 */ 383 string content; 384 385 /** 386 * Constructs an Element given a name and the string interior. 371 class Element : Item 372 { 373 Tag tag; /// The start tag of the element 374 Item[] items; /// The element's items 375 376 /** 377 * Constructs an Element given a name and a string to be used as a Text interior. 387 378 * 388 379 * Params: 389 380 * name = the name of the element. 390 * interior = (optional) the string interior of the root element. Must be well-formed XML.391 */381 * interior = (optional) the string interior. 382 */ 392 383 this(string name, string interior=null) 393 384 { 394 this(new Tag(name),interior); 395 } 396 397 /** 398 * Constructs an Element from a Tag and the string interior. 385 this(new Tag(name)); 386 if (interior.length != 0) opCatAssign(new Text(interior)); 387 } 388 389 /** 390 * Constructs an Element from a Tag. 399 391 * 400 392 * Params: 401 * tag = the start tag of the element. 402 * interior = (optional) the string interior of the tag. Must be well-formed XML. 403 */ 404 this(Tag tag, string interior=null) 405 in 406 { 407 // assert content_ is well-formed XML content 408 // TO DO -- Finish this 409 } 410 body 393 * tag = the start or empty tag of the element. 394 */ 395 this(Tag tag) 411 396 { 412 397 this.tag = tag; 413 this.content = interior; 414 tag.type = content.length == 0 ? TagType.EMPTY : TagType.START; 415 } 416 417 /** 418 * Copy constructor. Makes a deep copy. 419 * 420 * Params: 421 * element = another Element. 422 */ 423 this(Element element) 424 { 425 this(new Tag(tag),content); 398 tag.type = TagType.EMPTY; 426 399 } 427 400 … … 433 406 434 407 /** 435 * Append a comment to the element interior 436 * 437 * Params: 438 * comment = the text you wish to append as a comment 439 * 440 * Throws: XMLCommentException if the comment body is illegal (contains "--" or exactly equals "-") 441 * 442 * Examples: 443 * -------------- 444 * element.addComment("This is a comment"); 445 * // inserts <!--This is a comment--> 446 * -------------- 447 */ 448 void addComment(string comment) 449 { 450 if (comment == "-" || comment.find("--") != -1) throw new XMLCommentException(comment); 451 content ~= "<!--"; 452 content ~= comment; 453 content ~= "-->"; 454 tag.type = TagType.START; 455 } 456 457 /** 458 * Append a CData section to the element interior 459 * 460 * Params: 461 * cdata = the text you wish to append as CData 462 * 463 * Throws: XMLCDataException if the comment body is illegal (contains "]]>") 464 * 465 * Examples: 466 * -------------- 467 * element.addCData("a < b"); 468 * // inserts <![CDATA[a < b]]> 469 * -------------- 470 */ 471 void addCData(string cdata) 472 { 473 if (cdata.find("]]>") != -1) throw new XMLCDataException(cdata); 474 content ~= "<![CDATA["; 475 content ~= cdata; 476 content ~= "]]>"; 477 tag.type = TagType.START; 478 } 479 480 /** 481 * Append text to the element interior 482 * 483 * This function encodes the text before insertion, so it is safe to insert any text 484 * 485 * Params: 486 * s = the text you wish to append 487 * 488 * Examples: 489 * -------------- 490 * element.addText("a < b"); 491 * // inserts a < b 492 * -------------- 493 */ 494 void addText(string s) 495 { 496 content ~= encode(s); 497 if (content.length != 0) tag.type = TagType.START; 498 499 } 500 501 /+ 502 // Commented out because XML Instructions are only allowed in prolog 503 504 void addXI(string xi) 505 { 506 if (xi.find('>') != -1) throw new XMLXIException(xi); 507 content ~= "<!"; 508 content ~= xi; 509 content ~= ">"; 510 } 511 +/ 512 513 /** 514 * Append a Processing Instruction section to the element interior 515 * 516 * Params: 517 * pi = the text you wish to append as a Processing Instruction 518 * 519 * Throws: XMLPIException if the instruction body is illegal (contains ">") 520 * 521 * Examples: 522 * -------------- 523 * element.addPI("php"); 524 * // inserts <?php> 525 * -------------- 526 */ 527 void addPI(string pi) 528 { 529 if (pi.find('>') != -1) throw new XMLPIException(pi); 530 content ~= "<!"; 531 content ~= pi; 532 content ~= ">"; 533 tag.type = TagType.START; 534 } 535 536 /** 537 * Append a complete element to the interior of this element 538 * 539 * This function converts the supplied element to string form 540 * and then appends it to the element interior 408 * Append a complete item to the interior of this element 541 409 * 542 410 * Params: … … 546 414 * -------------- 547 415 * Element element; 548 * Element other = new Element(new Tag("br")); 549 * element.addElement(other); 550 * // inserts <br /> 551 * -------------- 552 */ 553 void addElement(Element e) 554 { 555 content ~= e.toString; 556 tag.type = TagType.START; 416 * Element other = new Element("br"); 417 * element ~= other; 418 * // appends element representing <br /> 419 * -------------- 420 */ 421 void opCatAssign(Item item) 422 { 423 items ~= item; 424 if (tag.type == TagType.EMPTY && !item.isEmptyXML) tag.type = TagType.START; 425 } 426 427 /** 428 * Compares two Elements for equality 429 * 430 * Examples: 431 * -------------- 432 * Element e1,e2; 433 * if (e1 == e2) { } 434 * -------------- 435 */ 436 override int opEquals(Object o) 437 { 438 const element = toType!(const Element)(o); 439 uint len = items.length; 440 if (len != element.items.length) return false; 441 for (uint i=0; i<len; ++i) 442 { 443 if (!items[i].opEquals(element.items[i])) return false; 444 } 445 return true; 446 } 447 448 /** 449 * Compares two Elements 450 * 451 * You should rarely need to call this function. It exists so that Elements 452 * can be used as associative array keys. 453 * 454 * Examples: 455 * -------------- 456 * Element e1,e2; 457 * if (e1 < e2) { } 458 * -------------- 459 */ 460 override int opCmp(Object o) 461 { 462 const element = toType!(const Element)(o); 463 for (uint i=0; ; ++i) 464 { 465 if (i == items.length && i == element.items.length) return 0; 466 if (i == items.length) return -1; 467 if (i == element.items.length) return 1; 468 if (items[i] != element.items[i]) return items[i].opCmp(element.items[i]); 469 } 470 } 471 472 /** 473 * Returns the hash of an Element 474 * 475 * You should rarely need to call this function. It exists so that Elements 476 * can be used as associative array keys. 477 */ 478 override hash_t toHash() 479 { 480 hash_t hash = tag.toHash; 481 foreach(item;items) hash += item.toHash(); 482 return hash; 557 483 } 558 484 … … 565 491 * XML such as "<title>Good &amp; Bad</title>", will return "Good & Bad". 566 492 * 567 * Throws: XMLDecodeException if decode fails493 * Throws: DecodeException if decode fails 568 494 */ 569 495 string text() 570 496 { 571 return decode(content); 497 string buffer; 498 foreach(item;items) 499 { 500 Text t = cast(Text)item; 501 if (t is null) throw new DecodeException(item.toString); 502 buffer ~= decode(t.toString); 503 } 504 return buffer; 572 505 } 573 506 574 507 /** 575 * Compares two Elements for equality508 * Returns an indented string representation of this item 576 509 * 577 * Examples: 578 * -------------- 579 * Element e1,e2; 580 * if (e1 == e2) { } 581 * -------------- 510 * Params: 511 * indent = (optional) number of spaces by which to indent this element. Defaults to 2. 582 512 */ 583 override int opEquals(Object o) 584 { 585 const element = toType!(const Element)(o); 586 return 587 (tag != element.tag ) ? false : ( 588 (content != element.content) ? false : ( 589 true )); 590 } 591 592 /** 593 * Compares two Elements 594 * 595 * You should rarely need to call this function. It exists so that Elements 596 * can be used as associative array keys. 597 * 598 * Examples: 599 * -------------- 600 * Element e1,e2; 601 * if (e1 < e2) { } 602 * -------------- 603 */ 604 override int opCmp(Object o) 605 { 606 const element = toType!(const Element)(o); 607 return 608 ((tag != element.tag ) ? ( tag < element.tag ? -1 : 1 ) : 609 ((content != element.content) ? ( content < element.content ? -1 : 1 ) : 610 0 )); 611 } 612 613 /** 614 * Returns the hash of an Element 615 * 616 * You should rarely need to call this function. It exists so that Elements 617 * can be used as associative array keys. 618 */ 619 override hash_t toHash() 620 { 621 hash_t hash = tag.toHash; 622 foreach(dchar c;content) hash = hash * 11 + c; 623 return hash; 513 string[] pretty(uint indent=2) 514 { 515 516 if (isEmptyXML) return [ tag.toEmptyString ]; 517 518 if (items.length == 1) 519 { 520 Text t = cast(Text)(items[0]); 521 if (t !is null) 522 { 523 return [ tag.toStartString ~ t.toString ~ tag.toEndString ]; 524 } 525 } 526 527 string[] a = [ tag.toStartString ]; 528 foreach(item;items) 529 { 530 string[] b = item.pretty(indent); 531 foreach(s;b) 532 { 533 a ~= rjustify(s,s.length + indent); 534 } 535 } 536 a ~= tag.toEndString; 537 return a; 624 538 } 625 539 … … 635 549 override string toString() 636 550 { 637 return (content.length == 0) 638 ? tag.toEmptyString 639 : tag.toStartString ~ content ~ tag.toEndString; 640 } 551 if (isEmptyXML) return tag.toEmptyString; 552 553 string buffer = tag.toStartString; 554 foreach(item;items) { buffer ~= item.toString; } 555 buffer ~= tag.toEndString; 556 return buffer; 557 } 558 559 override bool isEmptyXML() { return false; } /// Returns false always 641 560 } 642 561 } … … 699 618 this.name = name; 700 619 this.type = type; 701 }702 703 /**704 * Copy constructor. Makes a deep copy.705 *706 * Params:707 * tag = another Tag.708 */709 this(Tag tag)710 {711 this(name, tag.type);712 foreach(k,v;tag.attr) { attr[k] = v; }713 620 } 714 621 … … 745 652 if (opt(s,'/')) 746 653 { 747 if (type == TagType.END) throw new XMLTagException("");654 if (type == TagType.END) throw new TagException(""); 748 655 type = TagType.EMPTY; 749 656 } … … 751 658 tagString.length = (s.ptr - tagString.ptr); 752 659 } 753 catch( XMLException e)660 catch(Exception e) 754 661 { 755 662 tagString.length = (s.ptr - tagString.ptr); 756 throw new XMLTagException(tagString);663 throw new TagException(tagString); 757 664 } 758 665 } … … 835 742 string s = "<" ~ name; 836 743 foreach(key,val;attr) s ~= format(" %s=\"%s\"",key,decode(val)); 837 return s ~ ">";744 return s; 838 745 } 839 746 … … 875 782 bool isEmpty() { return type == TagType.EMPTY; } 876 783 } 784 } 785 786 /** 787 * Class representing a comment 788 */ 789 class Comment : Item 790 { 791 private string content; 792 793 /** 794 * Construct a comment 795 * 796 * Params: 797 * content = the body of the comment 798 * 799 * Throws: CommentException if the comment body is illegal (contains "--" or exactly equals "-") 800 * 801 * Examples: 802 * -------------- 803 * auto item = new Comment("This is a comment"); 804 * // constructs <!--This is a comment--> 805 * -------------- 806 */ 807 this(string content) 808 { 809 if (content == "-" || content.find("==") != -1) throw new CommentException(content); 810 this.content = content; 811 } 812 813 /** 814 * Compares two comments for equality 815 * 816 * Examples: 817 * -------------- 818 * Comment item1,item2; 819 * if (item1 == item2) { } 820 * -------------- 821 */ 822 override int opEquals(Object o) 823 { 824 const item = toType!(const Item)(o); 825 const t = cast(Comment)item; 826 return t !is null && content == t.content; 827 } 828 829 /** 830 * Compares two comments 831 * 832 * You should rarely need to call this function. It exists so that Comments 833 * can be used as associative array keys. 834 * 835 * Examples: 836 * -------------- 837 * Comment item1,item2; 838 * if (item1 < item2) { } 839 * -------------- 840 */ 841 override int opCmp(Object o) 842 { 843 const item = toType!(const Item)(o); 844 const t = cast(Comment)item; 845 return t !is null && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 ); 846 } 847 848 /** 849 * Returns the hash of a Comment 850 * 851 * You should rarely need to call this function. It exists so that Comments 852 * can be used as associative array keys. 853 */ 854 override hash_t toHash() { return stringToHash(content); } 855 856 /** 857 * Returns a string representation of this comment 858 */ 859 override const string toString() { return "<!--" ~ content ~ "-->"; } 860 861 override const bool isEmptyXML() { return false; } /// Returns false always 862 } 863 864 /** 865 * Class representing a Character Data section 866 */ 867 class CData : Item 868 { 869 private string content; 870 871 /** 872 * Construct a chraracter data section 873 * 874 * Params: 875 * content = the body of the character data segment 876 * 877 * Throws: CDataException if the segment body is illegal (contains "]]>") 878 * 879 * Examples: 880 * -------------- 881 * auto item = new CData("<b>hello</b>"); 882 * // constructs <![CDATA[<b>hello</b>]]> 883 * -------------- 884 */ 885 this(string content) 886 { 887 if (content.find("]]>") != -1) throw new CDataException(content); 888 this.content = content; 889 } 890 891 /** 892 * Compares two CDatas for equality 893 * 894 * Examples: 895 * -------------- 896 * CData item1,item2; 897 * if (item1 == item2) { } 898 * -------------- 899 */ 900 override int opEquals(Object o) 901 { 902 const item = toType!(const Item)(o); 903 const t = cast(CData)item; 904 return t !is null && content == t.content; 905 } 906 907 /** 908 * Compares two CDatas 909 * 910 * You should rarely need to call this function. It exists so that CDatas 911 * can be used as associative array keys. 912 * 913 * Examples: 914 * -------------- 915 * CData item1,item2; 916 * if (item1 < item2) { } 917 * -------------- 918 */ 919 override int opCmp(Object o) 920 { 921 const item = toType!(const Item)(o); 922 const t = cast(CData)item; 923 return t !is null && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 ); 924 } 925 926 /** 927 * Returns the hash of a CData 928 * 929 * You should rarely need to call this function. It exists so that Documents 930 * can be used as associative array keys. 931 */ 932 override hash_t toHash() { return stringToHash(content); } 933 934 /** 935 * Returns a string representation of this CData section 936 */ 937 override const string toString() { return "<[CDATA[" ~ content ~ "]]>"; } 938 939 override const bool isEmptyXML() { return false; } /// Returns false always 940 } 941 942 /** 943 * Class representing a text (aka Parsed Character Data) section 944 */ 945 class Text : Item 946 { 947 private string content; 948 949 /** 950 * Construct a text (aka PCData) section 951 * 952 * Params: 953 * content = the text. This function encodes the text before insertion, 954 * so it is safe to insert any text 955 * 956 * Examples: 957 * -------------- 958 * auto Text = new CData("a < b"); 959 * // constructs a < b 960 * -------------- 961 */ 962 this(string content) 963 { 964 this.content = encode(content); 965 } 966 967 /** 968 * Compares two text sections for equality 969 * 970 * Examples: 971 * -------------- 972 * Text item1,item2; 973 * if (item1 == item2) { } 974 * -------------- 975 */ 976 override int opEquals(Object o) 977 { 978 const item = toType!(const Item)(o); 979 const t = cast(Text)item; 980 return t !is null && content == t.content; 981 } 982 983 /** 984 * Compares two text sections 985 * 986 * You should rarely need to call this function. It exists so that Texts 987 * can be used as associative array keys. 988 * 989 * Examples: 990 * -------------- 991 * Text item1,item2; 992 * if (item1 < item2) { } 993 * -------------- 994 */ 995 override int opCmp(Object o) 996 { 997 const item = toType!(const Item)(o); 998 const t = cast(Text)item; 999 return t !is null && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 ); 1000 } 1001 1002 /** 1003 * Returns the hash of a text section 1004 * 1005 * You should rarely need to call this function. It exists so that Texts 1006 * can be used as associative array keys. 1007 */ 1008 override hash_t toHash() { return stringToHash(content); } 1009 1010 /** 1011 * Returns a string representation of this Text section 1012 */ 1013 override const string toString() { return content; } 1014 1015 override const bool isEmptyXML() { return content.length == 0; } /// Returns true if the content is the empty string 1016 } 1017 1018 /** 1019 * Class representing an XML Instruction section 1020 */ 1021 class XMLInstruction : Item 1022 { 1023 private string content; 1024 1025 /** 1026 * Construct an XML Instruction section 1027 * 1028 * Params: 1029 * content = the body of the instruction segment 1030 * 1031 * Throws: XIException if the segment body is illegal (contains ">") 1032 * 1033 * Examples: 1034 * -------------- 1035 * auto item = new XMLInstruction("ATTLIST"); 1036 * // constructs <!ATTLIST> 1037 * -------------- 1038 */ 1039 this(string content) 1040 { 1041 if (content.find(">") != -1) throw new XIException(content); 1042 this.content = content; 1043 } 1044 1045 /** 1046 * Compares two XML instructions for equality 1047 * 1048 * Examples: 1049 * -------------- 1050 * XMLInstruction item1,item2; 1051 * if (item1 == item2) { } 1052 * -------------- 1053 */ 1054 override int opEquals(Object o) 1055 { 1056 const item = toType!(const Item)(o); 1057 const t = cast(XMLInstruction)item; 1058 return t !is null && content == t.content; 1059 } 1060 1061 /** 1062 * Compares two XML instructions 1063 * 1064 * You should rarely need to call this function. It exists so that XmlInstructions 1065 * can be used as associative array keys. 1066 * 1067 * Examples: 1068 * -------------- 1069 * XMLInstruction item1,item2; 1070 * if (item1 < item2) { } 1071 * -------------- 1072 */ 1073 override int opCmp(Object o) 1074 { 1075 const item = toType!(const Item)(o); 1076 const t = cast(XMLInstruction)item; 1077 return t !is null && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 ); 1078 } 1079 1080 /** 1081 * Returns the hash of an XMLInstruction 1082 * 1083 * You should rarely need to call this function. It exists so that XmlInstructions 1084 * can be used as associative array keys. 1085 */ 1086 override hash_t toHash() { return stringToHash(content); } 1087 1088 /** 1089 * Returns a string representation of this XmlInstruction 1090 */ 1091 override const string toString() { return "<!" ~ content ~ ">"; } 1092 1093 override const bool isEmptyXML() { return false; } /// Returns false always 1094 } 1095 1096 /** 1097 * Class representing a Processing Instruction section 1098 */ 1099 class ProcessingInstruction : Item 1100 { 1101 private string content; 1102 1103 /** 1104 * Construct a Processing Instruction section 1105 * 1106 * Params: 1107 * content = the body of the instruction segment 1108 * 1109 * Throws: PIException if the segment body is illegal (contains ">") 1110 * 1111 * Examples: 1112 * -------------- 1113 * auto item = new ProcessingInstruction("php"); 1114 * // constructs <?php> 1115 * -------------- 1116 */ 1117 this(string content) 1118 { 1119 if (content.find(">") != -1) throw new XIException(content); 1120 this.content = content; 1121 } 1122 1123 /** 1124 * Compares two processing instructions for equality 1125 * 1126 * Examples: 1127 * -------------- 1128 * ProcessingInstruction item1,item2; 1129 * if (item1 == item2) { } 1130 * -------------- 1131 */ 1132 override int opEquals(Object o) 1133 { 1134 const item = toType!(const Item)(o); 1135 const t = cast(ProcessingInstruction)item; 1136 return t !is null && content == t.content; 1137 } 1138 1139 /** 1140 * Compares two processing instructions 1141 * 1142 * You should rarely need to call this function. It exists so that ProcessingInstructions 1143 * can be used as associative array keys. 1144 * 1145 * Examples: 1146 * -------------- 1147 * ProcessingInstruction item1,item2; 1148 * if (item1 < item2) { } 1149 * -------------- 1150 */ 1151 override int opCmp(Object o) 1152 { 1153 const item = toType!(const Item)(o); 1154 const t = cast(ProcessingInstruction)item; 1155 return t !is null && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 ); 1156 } 1157 1158 /** 1159 * Returns the hash of a ProcessingInstruction 1160 * 1161 * You should rarely need to call this function. It exists so that ProcessingInstructions 1162 * can be used as associative array keys. 1163 */ 1164 override hash_t toHash() { return stringToHash(content); } 1165 1166 /** 1167 * Returns a string representation of this ProcessingInstruction 1168 */ 1169 override const string toString() { return "<?" ~ content ~ ">"; } 1170 1171 override const bool isEmptyXML() { return false; } /// Returns false always 1172 } 1173 1174 /** 1175 * Abstract base class for XML items 1176 */ 1177 abstract class Item 1178 { 1179 abstract override int opEquals(Object o); /// Compares with another Item of same type for equality 1180 abstract override int opCmp(Object o); /// Compares with another Item of same type 1181 abstract override hash_t toHash(); /// Returns the hash of this item 1182 abstract override const string toString(); /// Returns a string representation of this item 1183 1184 /** 1185 * Returns an indented string representation of this item 1186 * 1187 * Params: 1188 * indent = number of spaces by which to indent child elements 1189 */ 1190 const string[] pretty(uint indent) { return [ toString() ]; } 1191 1192 abstract const bool isEmptyXML(); /// Returns true if the item represents empty XML text 877 1193 } 878 1194 … … 1041 1357 * -------------- 1042 1358 */ 1043 void onText (Handler handler) { textHandler= handler; }1359 void onText(Handler handler) { textHandler = handler; } 1044 1360 1045 1361 /** … … 1060 1376 * -------------- 1061 1377 */ 1062 void onCData (Handler handler) { cdataHandler= handler; }1378 void onCData(Handler handler) { cdataHandler = handler; } 1063 1379 1064 1380  
