Changeset 553

Show
Ignore:
Timestamp:
02/09/08 08:54:42 (10 months ago)
Author:
Janice Caron
Message:

Removed functions addComment(), addText(), etc.
Added classes Comment, Text, etc.
Added pretty-printing
Added example of XML construction to ddoc comments.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • candidate/phobos/std/xml.d

    r551 r553  
    1717import std.xml; 
    1818import std.stdio; 
     19import std.string; 
    1920 
    2021// books.xml is used in various samples throughout the Microsoft XML Core Services (MSXML) SDK. 
    2122// See http://msdn2.microsoft.com/en-us/library/ms762271(VS.85).aspx 
    2223 
     24struct 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 
    2335void main() 
    2436{ 
    2537    string s = import("books.xml"); 
     38 
     39    // Take it apart 
     40    Book[] books; 
     41 
    2642    auto xml = new DocumentParser(s); 
    27  
    2843    xml.onStartTag["book"] = delegate void(ElementParser xml) 
    2944    { 
    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; }; 
    4354 
    4455        xml.parse(); 
    4556 
    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; 
    5358    }; 
    54  
    5559    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")); 
    5680} 
    5781-------------------------------------------------------------------------------------------------- 
     
    6690 
    6791    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 
    7293*/ 
    7394 
     
    130151 * Returns: The decoded string 
    131152 * 
    132  * Throws: XMLDecodeException on unrecognized or malformed entity 
     153 * Throws: DecodeException on unrecognized or malformed entity 
    133154 * 
    134155 * Examples: 
     
    158179                dchar parseHex(string hex) 
    159180                { 
    160                     if (hex.length == 0) throw new XMLDecodeException(s); 
     181                    if (hex.length == 0) throw new DecodeException(s); 
    161182                    dchar d = 0; 
    162183                    foreach(dchar c;hex) 
     
    181202                            case '1': ++d; 
    182203                            case '0': break; 
    183                             default: throw new XMLDecodeException(s); 
     204                            default: throw new DecodeException(s); 
    184205                        } 
    185206                    } 
     
    190211                { 
    191212                    dchar d = parse!(uint)(dec); 
    192                     if (dec.length != 0) throw new XMLDecodeException(s); 
     213                    if (dec.length != 0) throw new DecodeException(s); 
    193214                    return d; 
    194215                } 
    195216 
    196217                int j = s[i..$].find(';'); 
    197                 if (j == -1) throw new XMLDecodeException(s); 
     218                if (j == -1) throw new DecodeException(s); 
    198219                j += i; 
    199220                string codepoint = s[i+2..j]; 
    200                 if (codepoint.length == 0) throw new XMLDecodeException(s); 
     221                if (codepoint.length == 0) throw new DecodeException(s); 
    201222                if (codepoint[0] == 'x' || codepoint[0] == 'X') buffer ~= parseHex(codepoint[1..$]); 
    202223                else buffer ~= parseDecimal(codepoint); 
     
    208229            else if (startsWith(s[i..$],"&lt;"  )) { buffer ~= '<';  i += 3; } 
    209230            else if (startsWith(s[i..$],"&gt;"  )) { buffer ~= '>';  i += 3; } 
    210             else { throw new XMLDecodeException(s); } 
     231            else { throw new DecodeException(s); } 
    211232        } 
    212233    } 
     
    220241        bool b = false; 
    221242        try { decode(s); } 
    222         catch (XMLDecodeException e) { b = true; } 
     243        catch (DecodeException e) { b = true; } 
    223244        assert(b,s); 
    224245    } 
     
    258279 
    259280    /** 
    260      * Constructs a Document given the root name and the string interior
     281     * Constructs a Document given the root name
    261282     * 
    262283     * Params: 
    263284     *      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); 
    269289    } 
    270290 
     
    276296     *      interior = (optional) the string interior of the root element. Must be well-formed XML. 
    277297     */ 
    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); 
    293301    } 
    294302 
     
    340348         * can be used as associative array keys. 
    341349         */ 
    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); } 
    348351 
    349352        /** 
     
    352355        override string toString() 
    353356        { 
    354             return prolog ~ "\r\n" ~ super.toString(); 
     357            return prolog ~ super.toString(); 
    355358        } 
    356359    } 
     
    366369 * Bugs: class invariant not yet written 
    367370 */ 
    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 "&lt;b&gt;&lt;i&gt;hello&lt;/i&gt;&lt;/b&gt;" would be stored as a 
    380      * start tag representing &lt;b&gt;, and the content string would be "&lt;i&gt;hello&lt;/i&gt;". 
    381      * 
    382      */ 
    383     string content; 
    384  
    385     /** 
    386      * Constructs an Element given a name and the string interior. 
     371class 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. 
    387378     * 
    388379     * Params: 
    389380     *      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    */ 
    392383    this(string name, string interior=null) 
    393384    { 
    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. 
    399391     * 
    400392     * 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) 
    411396    { 
    412397        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; 
    426399    } 
    427400 
     
    433406 
    434407    /** 
    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 &lt; 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 
    541409     * 
    542410     * Params: 
     
    546414     * -------------- 
    547415     * 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; 
    557483    } 
    558484 
     
    565491         * XML such as "&lt;title&gt;Good &amp;amp; Bad&lt;/title&gt;", will return "Good &amp; Bad". 
    566492         * 
    567          * Throws: XMLDecodeException if decode fails 
     493         * Throws: DecodeException if decode fails 
    568494         */ 
    569495        string text() 
    570496        { 
    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; 
    572505        } 
    573506 
    574507        /** 
    575          * Compares two Elements for equality 
     508         * Returns an indented string representation of this item 
    576509         * 
    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. 
    582512         */ 
    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; 
    624538        } 
    625539 
     
    635549        override string toString() 
    636550        { 
    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 
    641560    } 
    642561} 
     
    699618        this.name = name; 
    700619        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; } 
    713620    } 
    714621 
     
    745652            if (opt(s,'/')) 
    746653            { 
    747                 if (type == TagType.END) throw new XMLTagException(""); 
     654                if (type == TagType.END) throw new TagException(""); 
    748655                type = TagType.EMPTY; 
    749656            } 
     
    751658            tagString.length = (s.ptr - tagString.ptr); 
    752659        } 
    753         catch(XMLException e) 
     660        catch(Exception e) 
    754661        { 
    755662            tagString.length = (s.ptr - tagString.ptr); 
    756             throw new XMLTagException(tagString); 
     663            throw new TagException(tagString); 
    757664        } 
    758665    } 
     
    835742                string s = "<" ~ name; 
    836743                foreach(key,val;attr) s ~= format(" %s=\"%s\"",key,decode(val)); 
    837                 return s ~ ">"
     744                return s
    838745            } 
    839746 
     
    875782        bool isEmpty() { return type == TagType.EMPTY; } 
    876783    } 
     784} 
     785 
     786/** 
     787 * Class representing a comment 
     788 */ 
     789class 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 */ 
     867class 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 */ 
     945class 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 &lt; 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 */ 
     1021class 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 */ 
     1099class 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 */ 
     1177abstract 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 
    8771193} 
    8781194 
     
    10411357     * -------------- 
    10421358     */ 
    1043     void onText            (Handler handler) { textHandler         = handler; } 
     1359    void onText(Handler handler) { textHandler = handler; } 
    10441360 
    10451361    /** 
     
    10601376     * -------------- 
    10611377     */ 
    1062     void onCData       (Handler handler) { cdataHandler        = handler; } 
     1378    void onCData(Handler handler) { cdataHandler = handler; } 
    10631379 
    10641380