• Main Page
  • Namespaces
  • Classes
  • Files
  • File List

xmlstorage.d

Go to the documentation of this file.
00001 module xmlstorage;
00002 
00003  //
00004  // XML storage D classes version 1.2
00005  //
00006  // Copyright (c) 2007 Martin Fuchs <martin-fuchs@gmx.net>
00007  //
00008 
00009  /// \file xmlstorage.d
00010  /// XMLStorage implementation file
00011 
00012 
00013 /*
00014 
00015  All rights reserved.
00016 
00017  Redistribution and use in source and binary forms, with or without
00018  modification, are permitted provided that the following conditions are met:
00019 
00020  * Redistributions of source code must retain the above copyright
00021     notice, this list of conditions and the following disclaimer.
00022  * Redistributions in binary form must reproduce the above copyright
00023     notice, this list of conditions and the following disclaimer in
00024     the documentation and/or other materials provided with the
00025     distribution.
00026 
00027  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00028  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00029  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00030  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00031  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00032  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00033  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00034  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00035  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00036  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00037  POSSIBILITY OF SUCH DAMAGE.
00038 
00039 */
00040 
00041 import std.stdio;
00042 import std.ctype;
00043 import std.string;
00044 import std.stream;
00045 import std.cstream; // dout
00046 import std.conv;    // toDouble()
00047 
00048 
00049 const BUFFER_LEN = 2048;
00050 
00051 const char[] XS_TRUE = "true";
00052 const char[] XS_FALSE = "false";
00053 
00054 const char[] XS_INTFMT = "%d";
00055 const char[] XS_FLOATFMT = "%d";
00056 
00057 const char[] XS_EMPTY = "";
00058 
00059 
00060 int isxmlsym(byte c)
00061 {
00062     return isalnum(c) || c=='_' || c=='-';
00063 }
00064 
00065 
00066  /// utility input file stream
00067 class tifstream : public BufferedFile
00068 {
00069 public:
00070     this(in char[] path)
00071     {
00072         super(path, FileMode.In);
00073     }
00074 };
00075 
00076  /// utility output file stream
00077 class tofstream : public BufferedFile
00078 {
00079 public:
00080     this(in char[] path)
00081     {
00082         super(path, FileMode.Out);
00083     }
00084 
00085     ~this()
00086     {
00087         close();
00088     }
00089 };
00090 
00091 
00092 class list(T)
00093 {
00094 public:
00095     void push_back(T entry)
00096     {
00097         _entries.length = _entries.length + 1;
00098         _entries[_entries.length-1] = entry;
00099     }
00100 
00101     T front()
00102     {
00103         assert(_entries.length>0);
00104         return _entries[0];
00105     }
00106 
00107     T back()
00108     {
00109         assert(_entries.length>0);
00110         return _entries[_entries.length-1];
00111     }
00112 
00113     bool empty()
00114     {
00115         return !_entries.length;
00116     }
00117 
00118     int length()
00119     {
00120         return _entries.length;
00121     }
00122 
00123     void clear()
00124     {
00125         foreach(entry; _entries)
00126             delete entry;
00127 
00128         _entries = null;
00129     }
00130 
00131     int opApply(int delegate(inout T) dg)
00132     {
00133         int result = 0;
00134 
00135         foreach(e; _entries) {
00136             result = dg(e);
00137 
00138             if (result)
00139                 break;
00140         }
00141 
00142         return result;
00143     }
00144 
00145 protected:
00146     T[] _entries;
00147 };
00148 
00149 
00150 class stack(T)
00151 {
00152 public:
00153     void push(T entry)
00154     {
00155         _entries.length = _entries.length + 1;
00156         _entries[_entries.length-1] = entry;
00157     }
00158 
00159     T pop()
00160     {
00161         assert(_entries.length>0);
00162         T ret = _entries[_entries.length-1];
00163 
00164         _entries.length = _entries.length - 1;
00165 
00166         return ret;
00167     }
00168 
00169     T top()
00170     {
00171         assert(_entries.length>0);
00172         return _entries[_entries.length-1];
00173     }
00174 
00175     bool empty()
00176     {
00177         return !_entries.length;
00178     }
00179 
00180     int length()
00181     {
00182         return _entries.length;
00183     }
00184 
00185     void clear()
00186     {
00187         foreach(entry; _entries)
00188             delete entry;
00189 
00190         _entries = null;
00191     }
00192 
00193 protected:
00194     T[] _entries;
00195 };
00196 
00197 
00198  // write XML files with 2 spaces indenting
00199 const char[] XML_INDENT_SPACE = "  ";
00200 
00201 
00202  /// XML Error with message and location
00203 class XMLError
00204 {
00205 public:
00206     this()
00207     {
00208         _line = 0;
00209         _column = 0;
00210         _error_code = 0;
00211     }
00212 
00213      /// return formated error message
00214     char[] str()
00215     {
00216         return format("%s(%d) [column %d] : %s", _systemId, _line, _column, _message);
00217     }
00218 
00219     Stream output(Stream o)
00220     {
00221         o.writeString(str());
00222 
00223         return o;
00224     }
00225 
00226     char[] _message;
00227     char[] _systemId;
00228     int _line;
00229     int _column;
00230     int _error_code;
00231 };
00232 
00233  /// list of XMLError entries
00234 class XMLErrorList : public list!(XMLError)
00235 {
00236 public:
00237      /// return merged error strings
00238     char[] str()
00239     {
00240         Stream o = new MemoryStream;
00241 
00242         foreach(e; _entries)
00243             o.writeString(e.str() ~ "\n");
00244 
00245         return o.toString();
00246     }
00247 };
00248 
00249 
00250 version(XMLNODE_LOCATION) {
00251 
00252  /// location of XML Node including XML file name
00253 class XMLLocation
00254 {
00255     this()
00256     {
00257         _display_path = null;
00258         _line = 0;
00259         _column = 0;
00260     }
00261 
00262     this(in char[] display_path, int line, int column)
00263     {
00264         _display_path = display_path;
00265         _line = line;
00266         _column = column;
00267     }
00268 
00269     char[] str()
00270     {
00271         return "";  //TODO
00272     }
00273 
00274 protected:
00275     char[]  _display_path;  // character pointer for fast reference
00276     int _line;
00277     int _column;
00278 };
00279 
00280 } // XMLNODE_LOCATION
00281 
00282 
00283 enum PRETTY_FLAGS {
00284     PRETTY_PLAIN    = 0,
00285     PRETTY_LINEFEED = 1,
00286     PRETTY_INDENT   = 2
00287 };
00288 
00289 
00290  /// XML Stylesheet entry
00291 class StyleSheet
00292 {
00293 public:
00294 
00295     char[]  _href;      // CDATA #REQUIRED
00296     char[]  _type;      // CDATA #REQUIRED
00297     char[]  _title;     // CDATA #IMPLIED
00298     char[]  _media;     // CDATA #IMPLIED
00299     char[]  _charset;   // CDATA #IMPLIED
00300     bool    _alternate; // (yes|no) "no"
00301 
00302     this()
00303     {
00304         _alternate = false;
00305     }
00306 
00307     this(in char[] href, in char[] type="text/xsl", bool alternate=false)
00308     {
00309         _href = href;
00310         _type = type;
00311         _alternate = alternate;
00312     }
00313 
00314     bool empty()
00315     {
00316         return !_href.length;
00317     }
00318 
00319     void print(Stream o)
00320     {
00321         o.writeString("<?xml-stylesheet" ~
00322                 " href=\"" ~ _href ~ "\""
00323                 " type=\"" ~ _type ~ "\"");
00324 
00325         if (_title.length)
00326             o.writeString(" title=\"" ~ _title ~ "\"");
00327 
00328         if (_media.length)
00329             o.writeString(" media=\"" ~ _media ~ "\"");
00330 
00331         if (_charset.length)
00332             o.writeString(" charset=\"" ~ _charset ~ "\"");
00333 
00334         if (_alternate)
00335             o.writeString(" alternate=\"yes\"");
00336 
00337         o.writeString("?>");
00338     }
00339 };
00340 
00341  /// list of StyleSheet entries
00342 class StyleSheetList : public list!(StyleSheet)
00343 {
00344 public:
00345 
00346     void set(StyleSheet stylesheet)
00347     {
00348         clear();
00349         push_back(stylesheet);
00350     }
00351 };
00352 
00353 
00354  /// XML document type description
00355 class DocType
00356 {
00357     char[]  _name;
00358 
00359      // External Document Types are noted, but not parsed.
00360     char[]  _public;
00361     char[]  _system;
00362 
00363      // Internal DTDs are not supported.
00364 
00365     void parse(in char[] str)
00366     {
00367         int i = 0;
00368 
00369         while(i<str.length && isspace(cast(dchar)str[i])) ++i;
00370 
00371         int start = i;
00372         while(i<str.length && isxmlsym(str[i])) ++i;
00373         _name = str[start..i];
00374 
00375         while(i<str.length && isspace(cast(dchar)str[i])) ++i;
00376 
00377         start = i;
00378         while(i<str.length && isxmlsym(str[i])) ++i;
00379 
00380         char[] keyword = str[start..i]; // "PUBLIC" or "SYSTEM"
00381 
00382         while(i<str.length && isspace(cast(dchar)str[i])) ++i;
00383 
00384         if (i<str.length && str[i]=='"' || str[i]=='\'') {
00385             char delim = str[i];
00386 
00387             start = ++i;
00388             while(i<str.length && str[i]!=delim) ++i;
00389 
00390             if (str[i] == delim)
00391                 _public = str[start..i++];
00392         } else
00393             _public.length = 0;
00394 
00395         while(i<str.length && isspace(cast(dchar)str[i])) ++i;
00396 
00397         if (i<str.length && str[i]=='"' || str[i]=='\'') {
00398             char delim = str[i];
00399 
00400             start = ++i;
00401             while(i<str.length && str[i]!=delim) ++i;
00402 
00403             if (str[i] == delim)
00404                 _system = str[start..i++];
00405         } else
00406             _system.length = 0;
00407     }
00408 
00409     bool empty() {return !_name.length;}
00410 };
00411 
00412  /// Management of XML file headers and formating
00413 class XMLFormat
00414 {
00415 public:
00416     this(PRETTY_FLAGS pretty=PRETTY_FLAGS.PRETTY_INDENT, in char[] xml_version="1.0", in char[] encoding="utf-8", in DocType doctype=new DocType)
00417     {
00418         _pretty = pretty;
00419         _endl = "\n";
00420         _version = xml_version;
00421         _encoding = encoding;
00422         _doctype = doctype;
00423         _standalone = -1;
00424 
00425         _stylesheets = new StyleSheetList;
00426     }
00427 
00428     void print_header(Stream o, bool lf=true)
00429     {
00430         o.writeString("<?xml version=\"" ~ _version ~ "\" encoding=\"" ~ _encoding ~ "\"");
00431 
00432         if (_standalone != -1)
00433             o.writeString(" standalone=\"yes\"");
00434 
00435         o.writeString("?>");
00436 
00437         if (lf)
00438             o.writeString(_endl);
00439 
00440         if (!_doctype.empty()) {
00441             o.writeString("<!DOCTYPE " ~ _doctype._name);
00442 
00443             if (_doctype._public.length) {
00444                 o.writeString(" PUBLIC \"" ~ _doctype._public ~ '"');
00445 
00446                 if (lf)
00447                     o.writeString(_endl);
00448 
00449                 o.writeString(" \"" ~ _doctype._system ~ '"');
00450             } else if (_doctype._system.length)
00451                 o.writeString(" SYSTEM \"" ~ _doctype._system ~ '"');
00452 
00453             o.writeString("?>");
00454 
00455             if (lf)
00456                 o.writeString(_endl);
00457         }
00458 
00459         foreach(it; _stylesheets) {
00460             it.print(o);
00461 
00462             if (lf)
00463                 o.writeString(_endl);
00464         }
00465 
00466     /*  if (!_additional.empty()) {
00467             o.writeString(_additional);
00468 
00469             if (lf)
00470                 o.writeString(_endl;
00471         } */
00472     }
00473 
00474     PRETTY_FLAGS _pretty;
00475     char[]  _endl;      // line ending string: "\n" or "\r\n"
00476 
00477     char[] _version;
00478     char[] _encoding;
00479 
00480     DocType _doctype;
00481 
00482     StyleSheetList _stylesheets;
00483 
00484 //  char[]  _additional;
00485 
00486     int     _standalone;
00487 };
00488 
00489 
00490 enum WRITE_MODE {
00491     FORMAT_PLAIN,       /// write XML without any white space
00492     FORMAT_SMART,       /// preserve original white space and comments if present; pretty print otherwise
00493     FORMAT_ORIGINAL,    /// write XML stream preserving original white space and comments
00494     FORMAT_PRETTY       /// pretty print node to stream without preserving original white space
00495 };
00496 
00497 
00498  /// in memory representation of an XML node
00499 class XMLNode
00500 {
00501 public:
00502 
00503     char[]  _name;   // instead of deriving from string class
00504 
00505     /// map of XML node attributes
00506     public typedef char[][char[]] AttributeMap;
00507 
00508      /// internal children node list
00509     class Children : public list!(XMLNode)
00510     {
00511     public:
00512         void assign(Children other)
00513         {
00514             clear();
00515 
00516             foreach(node; other)
00517                 push_back(new XMLNode(node));
00518         }
00519     };
00520 
00521     this(in char[] name)
00522     {
00523         _name = name;
00524 
00525         _children = new Children;
00526     }
00527 
00528     this(in char[] name, char[] leading)
00529     {
00530         this(name);
00531 
00532         _leading = leading;
00533     }
00534 
00535     this(XMLNode other)
00536     {
00537         this(other._name);
00538 
00539         _attributes = other._attributes;
00540         _leading = other._leading;
00541         _content = other._content;
00542         _end_leading = other._end_leading;
00543         _trailing = other._trailing;
00544 
00545       version(XMLNODE_LOCATION) {
00546         _location = other._location;
00547       }
00548 
00549         foreach(node; other._children)
00550             _children.push_back(new XMLNode(node));
00551     }
00552 
00553 /* leads to access violation
00554     ~this()
00555     {
00556         _children.clear();
00557     }
00558 */
00559     void clear()
00560     {
00561         _leading.length = 0;
00562         _content.length = 0;
00563         _end_leading.length = 0;
00564         _trailing.length = 0;
00565 
00566         _attributes = null;
00567         _children.clear();
00568 
00569         _name.length = 0;
00570     }
00571 
00572     XMLNode assign(XMLNode other)
00573     {
00574         _children.assign(other._children);
00575 
00576         _attributes = other._attributes;
00577 
00578         _leading = other._leading;
00579         _content = other._content;
00580         _end_leading = other._end_leading;
00581         _trailing = other._trailing;
00582 
00583         return this;
00584     }
00585 
00586      /// add a new child node
00587     void add_child(XMLNode child)
00588     {
00589         _children.push_back(child);
00590     }
00591 
00592      /// write access to an attribute
00593     void put(in char[] attr_name, in char[] value)
00594     {
00595         _attributes[attr_name] = value;
00596     }
00597 
00598      /// index operator read access to an attribute
00599     char[] opIndex(in char[] attr_name)
00600     {
00601         return _attributes[attr_name];
00602     }
00603 
00604      /// index operator write access to an attribute
00605     void opIndexAssign(in char[] attr_name, in char[] value)
00606     {
00607         _attributes[attr_name] = value;
00608     }
00609 
00610      /// read only access to an attribute
00611     char[] get(in char[] attr_name, in char[] def="")
00612     {
00613         char[]* found = attr_name in _attributes;
00614 
00615         if (found)
00616             return *found;
00617         else
00618             return def;
00619     }
00620 
00621      /// write access to an attribute
00622     char[]* get_ref(in char[] attr_name, in char[] def="")
00623     {
00624         char[]* found = attr_name in _attributes;
00625 
00626         if (found)
00627             return found;
00628         else {
00629             _attributes[attr_name] = def;
00630             return &_attributes[attr_name];
00631         }
00632     }
00633 
00634      /// convenient value access in children node
00635     char[] subvalue(in char[] name, char[] attr_name, int n=0)
00636     {
00637         XMLNode node = find(name, n);
00638 
00639         if (node)
00640             return node.get(attr_name);
00641         else
00642             return "";
00643     }
00644 
00645      /// convenient value access in children node
00646     char[]* subvalue_ref(in char[] name, char[] attr_name, int n=0)
00647     {
00648         XMLNode node = find(name, n);
00649 
00650         if (node)
00651             return node.get_ref(attr_name);
00652         else {
00653             _attributes[attr_name] = "";
00654             return &_attributes[attr_name];
00655         }
00656     }
00657 
00658      /// convenient storage of distinct values in children node
00659     char[] put_subvalue(in char[] name, char[] attr_name, int n=0)
00660     {
00661         XMLNode node = find(name, n);
00662 
00663         if (!node) {
00664             node = new XMLNode(name);
00665             add_child(node);
00666         }
00667 
00668         return node.get(attr_name);
00669     }
00670 
00671     Children get_children()
00672     {
00673         return _children;
00674     }
00675 
00676     AttributeMap get_attributes()
00677     {
00678         return _attributes;
00679     }
00680 
00681     char[] get_content()
00682     {
00683         return DecodeXMLString(_content);
00684     }
00685 
00686     void set_content(in char[] s, bool cdata=false)
00687     {
00688         _content = EncodeXMLString(s, cdata);
00689     }
00690 
00691   version(XMLNODE_LOCATION) {
00692     XMLLocation get_location() {return _location;}
00693   }
00694 
00695      /// write node with children tree to output stream
00696     void write(Stream o, XMLFormat format, WRITE_MODE mode=WRITE_MODE.FORMAT_SMART, int indent=0)
00697     {
00698         switch(mode) {
00699           case WRITE_MODE.FORMAT_PLAIN:
00700             plain_write_worker(o);
00701             break;
00702 
00703           case WRITE_MODE.FORMAT_PRETTY:
00704             pretty_write_worker(o, format, indent);
00705             break;
00706 
00707           case WRITE_MODE.FORMAT_ORIGINAL:
00708             write_worker(o);
00709             break;
00710 
00711         default: // WRITE_MODE.FORMAT_SMART
00712             smart_write_worker(o, format, indent);
00713         }
00714     }
00715 
00716 protected:
00717     Children _children;
00718     AttributeMap _attributes;
00719 
00720     char[] _leading;        // UTF-8 encoded
00721     char[] _content;        // UTF-8 and entity encoded, may contain CDATA sections; decode with DecodeXMLString()
00722     char[] _end_leading;    // UTF-8 encoded
00723     char[] _trailing;       // UTF-8 encoded
00724 
00725   version(XMLNODE_LOCATION) {
00726     XMLLocation _location;
00727   }
00728 
00729     XMLNode get_first_child()
00730     {
00731         if (!_children.empty())
00732             return _children.front();
00733         else
00734             return null;
00735     }
00736 
00737     XMLNode find(in char[] name, int n=0)
00738     {
00739         foreach(node; _children)
00740             if (node._name == name)
00741                 if (!n--)
00742                     return node;
00743 
00744         return null;
00745     }
00746 
00747     XMLNode find(in char[] name, char[] attr_name, char[] attr_value, int n=0)
00748     {
00749         foreach(node; _children)
00750             if (node._name==name && node.get(attr_name)==attr_value)
00751                 if (!n--)
00752                     return node;
00753 
00754         return null;
00755     }
00756 
00757      /// XPath find function
00758     XMLNode find_relative(in char[] path)
00759     {
00760         XMLNode node = this;
00761 
00762          // parse relative path
00763         while(path.length) {
00764             node = node.get_child_relative(path, false);
00765 
00766             if (!node)
00767                 return null;
00768 
00769             if (*path == '/')
00770                 path = path[1..length];
00771         }
00772 
00773         return node;
00774     }
00775 
00776      /// relative XPath create function
00777     XMLNode create_relative(in char[] path)
00778     {
00779         XMLNode node = this;
00780 
00781          // parse relative path
00782         while(path.length) {
00783             node = node.get_child_relative(path, true);
00784 
00785             if (*path == '/')
00786                 path = path[1..length];
00787         }
00788 
00789         return node;
00790     }
00791 
00792      /// write node with children tree to output stream using original white space
00793     void write_worker(Stream o)
00794     {
00795         o.writeString(_leading ~ '<' ~ EncodeXMLString(_name));
00796 
00797         foreach(key,value; _attributes)
00798             o.writeString(' ' ~ EncodeXMLString(key) ~ "=\"" ~ EncodeXMLString(value) ~ "\"");
00799 
00800         if (!_children.empty() || _content.length) {
00801             o.writeString('>' ~ _content);
00802 
00803             foreach(child; _children)
00804                 child.write_worker(o);
00805 
00806             o.writeString(_end_leading ~ "</" ~ EncodeXMLString(_name) ~ '>');
00807         } else
00808             o.writeString("/>");
00809 
00810         o.writeString(_trailing);
00811     }
00812 
00813      /// print node without any white space
00814     void plain_write_worker(Stream o)
00815     {
00816         o.writeString('<' ~ EncodeXMLString(_name));
00817 
00818         foreach(key,value; _attributes)
00819             o.writeString(' ' ~ EncodeXMLString(key) ~ "=\"" ~ EncodeXMLString(value) ~ "\"");
00820 
00821          // strip leading white space from content
00822         char[] content = _content;
00823         while(content.length && isspace(cast(dchar)*content)) content = content[1 .. length];
00824 
00825         if (!_children.empty() || content.length) {
00826             o.writeString('>' ~ content);
00827 
00828             foreach(child; _children)
00829                 child.plain_write_worker(o);
00830 
00831             o.writeString("</" ~ EncodeXMLString(_name) ~ ">");
00832         } else
00833             o.writeString("/>");
00834     }
00835 
00836      /// pretty print node with children tree to output stream
00837     void pretty_write_worker(Stream o, XMLFormat format, int indent)
00838     {
00839         for(int i=indent; i--; )
00840             o.writeString(XML_INDENT_SPACE);
00841 
00842         o.writeString('<' ~ EncodeXMLString(_name));
00843 
00844         foreach(key,value; _attributes)
00845             o.writeString(' ' ~ EncodeXMLString(key) ~ "=\"" ~ EncodeXMLString(value) ~ "\"");
00846 
00847          // strip leading white space from content
00848         char[] content = _content;
00849         while(content.length && isspace(cast(dchar)*content)) content = content[1 .. length];
00850 
00851         if (!_children.empty() || content.length) {
00852             o.writeString('>' ~ content);
00853 
00854             if (!_children.empty())
00855                 o.writeString(format._endl);
00856 
00857             foreach(child; _children)
00858                 child.pretty_write_worker(o, format, indent+1);
00859 
00860             for(int i=indent; i--; )
00861                 o.writeString(XML_INDENT_SPACE);
00862 
00863             o.writeString("</" ~ EncodeXMLString(_name) ~ '>' ~ format._endl);
00864         } else
00865             o.writeString("/>" ~ format._endl);
00866     }
00867 
00868      /// write node with children tree to output stream using smart formating
00869     void smart_write_worker(Stream o, XMLFormat format, int indent)
00870     {
00871          // strip the first line feed from _leading
00872         char[] leading = _leading;
00873         while(leading.length && *leading=='\n')
00874             leading = leading[1 .. length];
00875 
00876         if (!leading.length)
00877             for(int i=indent; i--; )
00878                 o.writeString(XML_INDENT_SPACE);
00879         else
00880             o.writeString(leading);
00881 
00882         o.writeString('<' ~ EncodeXMLString(_name));
00883 
00884         foreach(key,value; _attributes)
00885             o.writeString(' ' ~ EncodeXMLString(key) ~ "=\"" ~ EncodeXMLString(value) ~ "\"");
00886 
00887          // strip leading white space from content
00888         char[] content = _content;
00889         while(content.length && isspace(cast(dchar)*content)) content = content[1 .. length];
00890 
00891         if (_children.empty() && !content.length)
00892             o.writeString("/>");
00893         else {
00894             o.writeString(">");
00895 
00896             if (!content.length)
00897                 o.writeString(format._endl);
00898             else
00899                 o.writeString(content);
00900 
00901             if (!_children.empty()) {
00902                 foreach(child; _children)
00903                     child.smart_write_worker(o, format, indent+1);
00904 
00905                  // strip the first line feed from _end_leading
00906                 char[] end_leading = _end_leading;
00907                 while(end_leading.length && *end_leading=='\n')
00908                     end_leading = end_leading[1 .. length];
00909 
00910                 if (!end_leading.length)
00911                     for(int i=indent; i--; )
00912                         o.writeString(XML_INDENT_SPACE);
00913                 else
00914                     o.writeString(end_leading);
00915             } else
00916                 o.writeString(_end_leading);
00917 
00918             o.writeString("</" ~ EncodeXMLString(_name) ~ '>');
00919         }
00920 
00921         if (!_trailing.length)
00922             o.writeString(format._endl);
00923         else
00924             o.writeString(_trailing);
00925     }
00926 
00927 protected:
00928     XMLNode get_child_relative(inout char[] path, bool create)
00929     {
00930         int slash_pos = .find(path, '/');
00931         if (slash_pos == 0)
00932             return null;
00933 
00934         size_t l = slash_pos!=-1? slash_pos: path.length;
00935         char[] comp = path[0 .. l];
00936         path = path[l .. length];
00937 
00938          // look for [n] and [@attr_name="attr_value"] expressions in path components
00939         int bracket_pos = .find(comp, '[');
00940         l = bracket_pos!=-1? bracket_pos: comp.length;
00941 
00942         char[] child_name = comp[0 .. l];
00943 
00944         char[] attr_name, attr_value;
00945         int n = 0;
00946 
00947         if (bracket_pos != -1) {
00948             char[] expr = unescape(comp[bracket_pos..length], '[', ']');
00949 
00950             n = atoi(expr); // read index number
00951 
00952             if (n)
00953                 n = n - 1;  // convert into zero based index
00954 
00955             int at_pos = .find(expr, '@');
00956 
00957             if (at_pos != -1) {
00958                 expr = expr[at_pos+1 .. length];
00959                 int equal_pos = .find(expr, '=');
00960 
00961                  // read attribute name and value
00962                 if (equal_pos != -1) {
00963                     attr_name = unescape(expr[0 .. equal_pos]);
00964                     attr_value = unescape(expr[equal_pos+1 .. length]);
00965                 }
00966             }
00967         }
00968 
00969         XMLNode child;
00970 
00971         if (!attr_name.length)
00972              // search n.th child node with specified name
00973             child = find(child_name, n);
00974         else
00975              // search n.th child node with specified name and matching attribute value
00976             child = find(child_name, attr_name, attr_value, n);
00977 
00978         if (!child && create) {
00979             child = new XMLNode(child_name);
00980             add_child(child);
00981 
00982             if (attr_name.length)
00983                 this[attr_name] = attr_value;
00984         }
00985 
00986         return child;
00987     }
00988 };
00989 
00990 
00991  /// iterator access to children nodes with name filtering
00992 class XMLChildrenFilter
00993 {
00994 public:
00995 
00996     this(XMLNode.Children children, char[] name)
00997     {
00998         _children = children;
00999         _filter_name = name;
01000     }
01001 
01002     this(XMLNode node, char[] name)
01003     {
01004         this(node.get_children(), name);
01005     }
01006 
01007     int opApply(int delegate(inout XMLNode) dg)
01008     {
01009         int result = 0;
01010 
01011         foreach(child; _children)
01012             if (child._name == _filter_name) {
01013                 result = dg(child);
01014 
01015                 if (result)
01016                     break;
01017             }
01018 
01019         return result;
01020     }
01021 
01022 protected:
01023     XMLNode.Children _children;
01024     char[]  _filter_name;
01025 };
01026 
01027 
01028  /// iterator for XML trees
01029 class XMLPos
01030 {
01031 public:
01032 
01033     this(XMLNode root)
01034     {
01035         _root = root;
01036         _cur = root;
01037 
01038         _stack = new stack!(XMLNode);
01039     }
01040 
01041     this(XMLPos other)
01042     {
01043         _root = other._root;
01044         _cur = other._cur;
01045 
01046         // don't copy _stack
01047         _stack = new stack!(XMLNode);
01048     }
01049 
01050     this(XMLNode node, char[] name)
01051     {
01052         this(node);
01053 
01054         smart_create(name);
01055     }
01056 
01057     this(XMLNode node, char[] name, char[] attr_name, char[] attr_value)
01058     {
01059         this(node);
01060 
01061         smart_create(name, attr_name, attr_value);
01062     }
01063 
01064     this(XMLPos other, char[] name)
01065     {
01066         this(other);
01067 
01068         smart_create(name);
01069     }
01070 
01071     this(XMLPos other, char[] name, char[] attr_name, char[] attr_value)
01072     {
01073         this(other);
01074 
01075         smart_create(name, attr_name, attr_value);
01076     }
01077 
01078      /// access to current node
01079     XMLNode cur()
01080     {
01081         return _cur;
01082     }
01083 
01084      /// automatic access to current node
01085     XMLNode opCast() {return _cur;}
01086 
01087      /// call operator access to current node (instead of the C++ operator-> and operator*)
01088     XMLNode opCall()
01089     {
01090         return _cur;
01091     }
01092 
01093      /// attribute access
01094     char[] get(in char[] attr_name, in char[] def="")
01095     {
01096         return _cur.get(attr_name, def);
01097     }
01098 
01099      /// attribute setting
01100     void put(in char[] attr_name, in char[] value)
01101     {
01102         _cur.put(attr_name, value);
01103     }
01104 
01105     char[] opIndex(in char[] attr_name)
01106     {
01107         return _cur[attr_name];
01108     }
01109 
01110     void opIndexAssign(in char[] attr_name, in char[] value)
01111     {
01112         _cur[attr_name] = value;
01113     }
01114 
01115      /// insert children when building tree
01116     void add_down(XMLNode child)
01117     {
01118         _cur.add_child(child);
01119         go_to(child);
01120     }
01121 
01122      /// go back to previous position
01123     bool back()
01124     {
01125         if (!_stack.empty()) {
01126             _cur = _stack.pop();
01127             return true;
01128         } else
01129             return false;
01130     }
01131 
01132      /// go down to first child
01133     bool go_down()
01134     {
01135         XMLNode node = _cur.get_first_child();
01136 
01137         if (node) {
01138             go_to(node);
01139             return true;
01140         } else
01141             return false;
01142     }
01143 
01144      /// search for child and go down
01145     bool go_down(in char[] name, int n=0)
01146     {
01147         XMLNode node = _cur.find(name, n);
01148 
01149         if (node) {
01150             go_to(node);
01151             return true;
01152         } else
01153             return false;
01154     }
01155 
01156      /// move XPath like to position in XML tree
01157     bool go(in char[] path)
01158     {
01159         XMLNode node = _cur;
01160 
01161          // Is this an absolute path?
01162         if (path.length>0 && path[0]=='/') {
01163             node = _root;
01164             path = path[1..length];
01165         }
01166 
01167         node = node.find_relative(path);
01168 
01169         if (node) {
01170             go_to(node);
01171             return true;
01172         } else
01173             return false;
01174     }
01175 
01176      /// create child nodes using XPath notation and move to the deepest child
01177     bool create_relative(in char[] path)
01178     {
01179         XMLNode node = _cur.create_relative(path);
01180         if (!node)
01181             return false;   // invalid path specified
01182 
01183         go_to(node);
01184         return true;
01185     }
01186 
01187      /// create node and move to it
01188     void create(in char[] name)
01189     {
01190         add_down(new XMLNode(name));
01191     }
01192 
01193      /// create node if not already existing and move to it
01194     void smart_create(in char[] name)
01195     {
01196         XMLNode node = _cur.find(name);
01197 
01198         if (node)
01199             go_to(node);
01200         else
01201             add_down(new XMLNode(name));
01202     }
01203 
01204      /// search matching child node identified by key name and an attribute value
01205     void smart_create(in char[] name, char[] attr_name, char[] attr_value)
01206     {
01207         XMLNode node = _cur.find(name, attr_name, attr_value);
01208 
01209         if (node)
01210             go_to(node);
01211         else {
01212             node = new XMLNode(name);
01213             add_down(node);
01214             node.put(attr_name, attr_value);
01215         }
01216     }
01217 
01218     char[] str() {return _cur._name;}
01219 
01220 protected:
01221     XMLNode _root;
01222     XMLNode _cur;
01223     stack!(XMLNode) _stack;
01224 
01225      /// go to specified node
01226     void go_to(XMLNode child)
01227     {
01228         _stack.push(_cur);
01229         _cur = child;
01230     }
01231 };
01232 
01233 
01234  /// type converter for boolean data
01235 class XMLBool
01236 {
01237 public:
01238     this(bool value=false)
01239     {
01240         _value = value;
01241     }
01242 
01243     this(in char[] value, bool def=false)
01244     {
01245         if (value && *value)
01246             _value = !icmp(value, XS_TRUE);
01247         else
01248             _value = def;
01249     }
01250 
01251     this(XMLNode node, char[] attr_name, bool def=false)
01252     {
01253         char[] value = node.get(attr_name);
01254 
01255         if (value.length)
01256             _value = !icmp(value, XS_TRUE);
01257         else
01258             _value = def;
01259     }
01260 
01261     bool opCast()
01262     {
01263         return _value;
01264     }
01265 
01266     bool opNot()
01267     {
01268         return !_value;
01269     }
01270 
01271     char[] str()
01272     {
01273         return _value? XS_TRUE: XS_FALSE;
01274     }
01275 
01276 
01277 protected:
01278     bool    _value;
01279 
01280 private:
01281     void opAssign(XMLBool); // disallow assignment operations
01282 };
01283 
01284  /// type converter for boolean data with write access
01285 class XMLBoolRef
01286 {
01287 public:
01288     this(XMLNode node, char[] attr_name, bool def=false)
01289     {
01290         _pref = node.get_ref(attr_name);
01291 
01292         if (!(*_pref).length)
01293             assign(def);
01294     }
01295 
01296     bool opCast()
01297     {
01298         return !icmp(*_pref, XS_TRUE);
01299     }
01300 
01301     bool opNot()
01302     {
01303         return icmp(*_pref, XS_TRUE)? true: false;
01304     }
01305 
01306     void toggle()
01307     {
01308         assign(!opNot());
01309     }
01310 
01311     XMLBoolRef opAssign(bool value)
01312     {
01313         assign(value);
01314 
01315         return this;
01316     }
01317 
01318     void assign(bool value)
01319     {
01320         *_pref = value? XS_TRUE: XS_FALSE;
01321     }
01322 
01323 protected:
01324     char[]* _pref;
01325 };
01326 
01327 
01328  /// type converter for integer data
01329 class XMLInt
01330 {
01331 public:
01332     this(int value)
01333     {
01334         _value = value;
01335     }
01336 
01337     this(in char[] value, int def=0)
01338     {
01339         if (value && *value)
01340             _value = cast(int)atoi(value);
01341         else
01342             _value = def;
01343     }
01344 
01345     this(XMLNode node, char[] attr_name, int def=0)
01346     {
01347         char[] value = node.get(attr_name);
01348 
01349         if (value.length)
01350             _value = cast(int)atoi(value);
01351         else
01352             _value = def;
01353     }
01354 
01355     int opCast()
01356     {
01357         return _value;
01358     }
01359 
01360     char[] str()
01361     {
01362         return format(XS_INTFMT, _value);
01363     }
01364 
01365 protected:
01366     int _value;
01367 
01368 private:
01369     void opAssign(XMLInt); // disallow assignment operations
01370 };
01371 
01372  /// type converter for integer data with write access
01373 class XMLIntRef
01374 {
01375 public:
01376     this(XMLNode node, char[] attr_name, int def=0)
01377     {
01378         _pref = node.get_ref(attr_name);
01379 
01380         if (!(*_pref).length)
01381             assign(def);
01382     }
01383 
01384     XMLIntRef opAssign(int value)
01385     {
01386         assign(value);
01387 
01388         return this;
01389     }
01390 
01391     int opCast()
01392     {
01393         return atoi(*_pref);
01394     }
01395 
01396     void assign(int value)
01397     {
01398         *_pref = format(XS_INTFMT, value);
01399     }
01400 
01401 protected:
01402     char[]* _pref;
01403 };
01404 
01405 
01406  /// type converter for numeric data
01407 class XMLDouble
01408 {
01409 public:
01410     this(double value)
01411     {
01412         _value = value;
01413     }
01414 
01415     this(in char[] value, double def=0.)
01416     {
01417         if (value && *value)
01418             _value = toDouble(value);
01419         else
01420             _value = def;
01421     }
01422 
01423     this(XMLNode node, char[] attr_name, double def=0.)
01424     {
01425         char[] value = node.get(attr_name);
01426 
01427         if (value.length)
01428             _value = toDouble(value);
01429         else
01430             _value = def;
01431     }
01432 
01433     double opCast()
01434     {
01435         return _value;
01436     }
01437 
01438     char[] str()
01439     {
01440         return format(XS_FLOATFMT, _value);
01441     }
01442 
01443 protected:
01444     double _value;
01445 
01446 private:
01447     void opAssign(XMLDouble); // disallow assignment operations
01448 };
01449 
01450  /// type converter for numeric data with write access
01451 class XMLDoubleRef
01452 {
01453 public:
01454     this(XMLNode node, char[] attr_name, double def=0.)
01455     {
01456         _pref = node.get_ref(attr_name);
01457 
01458         if (!(*_pref).length)
01459             assign(def);
01460     }
01461 
01462     XMLDoubleRef opAssign(double value)
01463     {
01464         assign(value);
01465 
01466         return this;
01467     }
01468 
01469     double opCast()
01470     {
01471         return toDouble(*_pref);
01472     }
01473 
01474     void assign(double value)
01475     {
01476         *_pref = format(XS_FLOATFMT, value);
01477     }
01478 
01479 protected:
01480     char[]* _pref;
01481 };
01482 
01483 
01484  /// type converter for string data
01485 class XMLString
01486 {
01487 public:
01488     this(in char[] value)
01489     {
01490         _value = value;
01491     }
01492 
01493     this(in char[] value, in char[] def=XS_EMPTY)
01494     {
01495         if (value && *value)
01496             _value = value;
01497         else
01498             _value = def;
01499     }
01500 
01501     this(XMLNode node, char[] attr_name, char[] def=XS_EMPTY)
01502     {
01503         char[] value = node.get(attr_name);
01504 
01505         if (value.length)
01506             _value = value;
01507         else
01508             _value = def;
01509     }
01510 
01511     char[] str()
01512     {
01513         return _value;
01514     }
01515 
01516 protected:
01517     char[]  _value;
01518 
01519 private:
01520     void opAssign(XMLString); // disallow assignment operations
01521 };
01522 
01523  /// type converter for string data with write access
01524 class XMLStringRef
01525 {
01526 public:
01527     this(XMLNode node, char[] attr_name, char[] def=XS_EMPTY)
01528     {
01529         _pref = node.get_ref(attr_name);
01530 
01531         if (!(*_pref).length)
01532             assign(def);
01533     }
01534 
01535     this(in char[] node_name, XMLNode node, char[] attr_name, char[] def=XS_EMPTY)
01536     {
01537         _pref = node.subvalue_ref(node_name, attr_name);
01538 
01539         if (!(*_pref).length)
01540             assign(def);
01541     }
01542 
01543     XMLStringRef opAssign(in char[] value)
01544     {
01545         assign(value);
01546 
01547         return this;
01548     }
01549 
01550     char[] str()
01551     {
01552         return *_pref;
01553     }
01554 
01555     void assign(in char[] value)
01556     {
01557         *_pref = value;
01558     }
01559 
01560 protected:
01561     char[]* _pref;
01562 };
01563 
01564 
01565 void read_option(T)(out T var, XMLPos cfg, char[] key)
01566 {
01567     char[] val = cfg.get(key);
01568 
01569     if (!val.empty())
01570         var = val;
01571 }
01572 
01573 void read_option(T)(out int var, XMLPos cfg, char[] key)
01574 {
01575     char[] val = cfg.get(key);
01576 
01577     if (!val.empty())
01578         var = atoi(val);
01579 }
01580 
01581 
01582  /// line buffer for XS-native parser
01583 
01584 class Buffer
01585 {
01586     this()
01587     {
01588         _buffer.length = BUFFER_LEN;
01589 
01590         reset();
01591     }
01592 
01593     void reset()
01594     {
01595         _idx_write = 0;
01596         _buffer_str.length = 0;
01597     }
01598 
01599     void append(char c)
01600     {
01601         if (_idx_write >= _buffer.length)
01602             _buffer.length = _buffer.length << 1;
01603 
01604         _buffer[_idx_write++] = c;
01605     }
01606 
01607     char[] str()    // returns UTF-8 encoded buffer content
01608     {
01609         _buffer_str = _buffer[0 .. _idx_write];
01610 
01611         return _buffer_str;
01612     }
01613 
01614     size_t len()
01615     {
01616         return _idx_write;
01617     }
01618 
01619     bool has_CDEnd()
01620     {
01621         return _idx_write>=3 && _buffer[0..2]=="]]>";
01622     }
01623 
01624     char[] get_tag()
01625     {
01626         int i;
01627 
01628          // skip opening tag characters
01629         if (_buffer_str.length>=1 && _buffer_str[0]=='<') {
01630             if (_buffer_str.length>=2 && _buffer_str[1]=='/')
01631                 i = 2;
01632             else
01633                 i = 1;
01634         } else
01635             i = 0;
01636 
01637         int j = i;
01638 
01639         if (j<_buffer_str.length && _buffer_str[j]=='?')
01640             ++j;
01641 
01642          // find end of tag name
01643         while(j<_buffer_str.length && isxmlsym(_buffer_str[j]))
01644             ++j;
01645 
01646         return _buffer_str[i..j];
01647     }
01648 
01649      /// read attributes and values
01650     void get_attributes(out XMLNode.AttributeMap attributes)
01651     {
01652         int i;
01653 
01654          // skip opening tag characters
01655         if (_buffer_str.length>=1 && _buffer_str[0]=='<') {
01656             if (_buffer_str.length>=2 && (_buffer_str[1]=='/' || _buffer_str[i]=='?'))
01657                 i = 2;
01658             else
01659                 i = 1;
01660         } else
01661             i = 0;
01662 
01663          // find end of tag name
01664         while(i<_buffer_str.length && isxmlsym(_buffer_str[i]))
01665             ++i;
01666 
01667          // read attributes from buffer
01668         while(i<_buffer_str.length && _buffer_str[i]!='>' && _buffer_str[i]!='/') {
01669             while(isspace(cast(dchar)_buffer_str[i]))
01670                 ++i;
01671 
01672             int attr_idx = i;
01673 
01674             while(isxmlsym(_buffer_str[i]))
01675                 ++i;
01676 
01677             if (i>=_buffer_str.length || _buffer_str[i] != '=')
01678                 break;  //@TODO error handling
01679 
01680             int attr_end = i;
01681 
01682             if (++i>=_buffer_str.length || _buffer_str[i]!='"' && _buffer_str[i]!='\'')
01683                 break;  //@TODO error handling
01684 
01685             char delim = _buffer_str[i];
01686             int value_idx = ++i;
01687 
01688             while(i<_buffer_str.length && _buffer_str[i]!=delim)
01689                 ++i;
01690 
01691             int value_end = i;
01692 
01693             if (i < _buffer_str.length)
01694                 ++i;    // '"'
01695 
01696             char[] name_str = _buffer_str[attr_idx .. attr_end];
01697             char[] value_str = _buffer_str[value_idx .. value_end];
01698 
01699             attributes[name_str.dup] = DecodeXMLString(value_str).dup;
01700         }
01701     }
01702 
01703 protected:
01704     char[]  _buffer;    // UTF-8 encoded
01705     int     _idx_write;
01706     char[]  _buffer_str;// UTF-8 encoded
01707 };
01708 
01709 
01710  /// XML reader base class
01711 
01712 class XMLReaderBase
01713 {
01714 public:
01715 
01716     this(XMLNode node)
01717     {
01718         _pos = new XMLPos(node);
01719         _endl_defined = false;
01720         _utf8 = false;
01721         _last_tag = TAG.TAG_NONE;
01722 
01723         _format = new XMLFormat;
01724         _errors = new XMLErrorList;
01725         _warnings = new XMLErrorList;
01726     }
01727 
01728     bool parse()
01729     {
01730         Buffer buffer = new Buffer;
01731         int c = get();
01732         bool in_comment = false;
01733 
01734         while(c != EOF) {
01735             if (in_comment || c=='<') {
01736                 buffer.append(c);
01737 
01738                  // read start or end tag
01739                 for(;;) {
01740                     c = get();
01741 
01742                     if (c == EOF)
01743                         break;
01744 
01745                     buffer.append(c);
01746 
01747                     if (c == '>')
01748                         break;
01749                 }
01750 
01751                 char[] b = buffer.str();
01752 
01753                 if (in_comment || (b.length>=4 && b[1..4]=="!--")) {
01754                      // XML comment
01755                     DefaultHandler(b);
01756 
01757                     if (b.length>=3 && b[length-3..length]=="-->")
01758                         in_comment = true;
01759                     else
01760                         in_comment = false;
01761 
01762                     c = get();
01763                 } else if (b.length >= 2) {
01764                     if (b[1] == '/') {
01765                          // end tag
01766 
01767                         /*@TODO error handling
01768                         char[]& tag = buffer.get_tag();
01769 
01770                             if (tag != last_opened_tag) {
01771                                 ERROR
01772                             }
01773                         */
01774 
01775                         EndElementHandler();
01776 
01777                         c = get();
01778                     } else if (b[1] == '?') {
01779                          // XML declaration
01780                         char[] tag = buffer.get_tag();
01781 
01782                         if (tag == "?xml") {
01783                             XMLNode.AttributeMap attributes;
01784                             buffer.get_attributes(attributes);
01785 
01786                             char[]* ver = "version" in attributes;
01787                             char[]* encoding = "encoding" in attributes;
01788 
01789                             int standalone;
01790                             char[]* found = "standalone" in attributes;
01791                             if (found)
01792                                 standalone = !icmp(*found, "yes");
01793                             else
01794                                 standalone = -1;
01795 
01796                             XmlDeclHandler(ver, encoding, standalone);
01797 
01798                             if (encoding && !icmp(*encoding, "utf-8"))
01799                                 _utf8 = true;
01800 
01801                             c = eat_endl();
01802                         } else if (tag == "?xml-stylesheet") {
01803                             XMLNode.AttributeMap attributes;
01804                             buffer.get_attributes(attributes);
01805 
01806                             auto phref = "href" in attributes;
01807                             auto ptype = "type" in attributes;
01808                             auto palternate = "alternate" in attributes;
01809 
01810                             StyleSheet stylesheet = new StyleSheet(phref?*phref:"", ptype?*ptype:"", palternate?!icmp(*palternate,"yes"):false);
01811 
01812                             auto ptitle = "title" in attributes;
01813                             if (ptitle)
01814                                 stylesheet._title = *ptitle;
01815 
01816                             auto pmedia = "media" in attributes;
01817                             if (pmedia)
01818                                 stylesheet._media = *pmedia;
01819 
01820                             auto pcharset = "charset" in attributes;
01821                             if (pcharset)
01822                                 stylesheet._charset = *pcharset;
01823 
01824                             _format._stylesheets.push_back(stylesheet);
01825 
01826                             c = eat_endl();
01827                         } else {
01828                             DefaultHandler(b);
01829                             c = get();
01830                         }
01831                     } else if (b[1] == '!') {
01832                         if (b.length>=10 && b[2..10]=="DOCTYPE ") {
01833                             _format._doctype.parse(b[10..length]);
01834 
01835                             c = eat_endl();
01836                         } else if (b.length>=9 && b[2..9]=="[CDATA[") {
01837                              // parse <![CDATA[ ... ]]> strings
01838                             while(!buffer.has_CDEnd()) {
01839                                 c = get();
01840 
01841                                 if (c == EOF)
01842                                     break;
01843 
01844                                 buffer.append(c);
01845                             }
01846 
01847                             DefaultHandler(buffer.str());
01848 
01849                             c = get();
01850                         }
01851                     } else {
01852                          // start tag
01853                         char[] tag = buffer.get_tag();
01854 
01855                         if (tag.length) {
01856                             XMLNode.AttributeMap attributes;
01857                             buffer.get_attributes(attributes);
01858 
01859                             StartElementHandler(tag.dup, attributes);
01860 
01861                             if (b[length-2] == '/')
01862                                 EndElementHandler();
01863                         }
01864 
01865                         c = get();
01866                     }
01867                 } else // b.length < 2
01868                     c = get();
01869             } else {
01870                 buffer.append(c);
01871 
01872                  // read white space
01873                 for(;;) {
01874                      // check for the encoding of the first line end
01875                     if (!_endl_defined)
01876                         if (c == '\n') {
01877                             _format._endl = "\n";
01878                             _endl_defined = true;
01879                         } else if (c == '\r') {
01880                             _format._endl = "\r\n";
01881                             _endl_defined = true;
01882                         }
01883 
01884                     c = get();
01885 
01886                     if (c == EOF)
01887                         break;
01888 
01889                     if (c == '<')
01890                         break;
01891 
01892                     buffer.append(c);
01893                 }
01894 
01895                 DefaultHandler(buffer.str());
01896             }
01897 
01898             buffer.reset();
01899         }
01900 
01901         return true; //TODO return false on invalid XML
01902     }
01903 
01904 public:
01905 
01906     //read XML stream into XML tree below _pos
01907     void read()
01908     {
01909         if (!parse()) {
01910             XMLError error;
01911 
01912             error._message = "XML parsing error";
01913             //error._line = ;
01914             //error._column = ;
01915 
01916             _errors.push_back(error);
01917         }
01918 
01919         finish_read();
01920     }
01921 
01922     // return current parser position as string
01923     char[] get_position()
01924     {
01925     /*@TODO display parser position in case of errors
01926         return format("%s(%d) [column %d] : %s", _systemId, _line, _column, _message);
01927     */
01928         return "";
01929     }
01930 
01931 
01932     XMLFormat get_format() {return _format;}
01933     char[] get_endl() {return _endl_defined? _format._endl: "\n";}
01934 
01935     XMLErrorList get_errors() {return _errors;}
01936     XMLErrorList get_warnings() {return _warnings;}
01937 
01938     void clear_errors()
01939     {
01940         _errors.clear();
01941         _warnings.clear();
01942     }
01943 
01944   version(XMLNODE_LOCATION) {
01945 
01946     char[] _display_path;   // character pointer for fast reference in XMLLocation
01947 
01948     XMLLocation get_location()
01949     {
01950         return new XMLLocation; //@TODO XMLLocation for XS-native
01951     }
01952 
01953   }
01954 
01955 protected:
01956     XMLPos  _pos;
01957 
01958     char[]  _content;   // UTF-8 encoded
01959 
01960     enum TAG {TAG_NONE, TAG_START, TAG_END};
01961     TAG _last_tag;
01962 
01963     XMLErrorList _errors;
01964     XMLErrorList _warnings;
01965 
01966     XMLFormat   _format;
01967     bool    _endl_defined;
01968 
01969     abstract int get();
01970 
01971     int eat_endl()
01972     {
01973         int c = get();
01974 
01975         if (c == '\r')
01976             c = get();
01977 
01978         if (c == '\n')
01979             c = get();
01980 
01981         return c;
01982     }
01983 
01984     bool    _utf8;
01985 
01986     void finish_read()
01987     {
01988         if (_pos()._children.empty())
01989             _pos()._trailing ~= _content;
01990         else
01991             _pos()._children.back()._trailing ~= _content;
01992 
01993         _content.length = 0;
01994     }
01995 
01996      /// store XML version and encoding into XML reader
01997     void XmlDeclHandler(in char[]* ver, char[]* encoding, int standalone)
01998     {
01999         if (ver)
02000             _format._version = *ver;
02001 
02002         if (encoding)
02003             _format._encoding = *encoding;
02004 
02005         _format._standalone = standalone;
02006     }
02007 
02008      /// notifications about XML start tag
02009     void StartElementHandler(in char[] name, XMLNode.AttributeMap attributes)
02010     {
02011         int i = 0;
02012 
02013          // search for content end leaving only white space for leading
02014         for(i=_content.length; i>0; --i)
02015             if (!isspace(cast(dchar)_content[i-1]))
02016                 break;
02017 
02018         if (i > 0)
02019             if (_pos()._children.empty()) { // no children in last node?
02020                 if (_last_tag == TAG.TAG_START)
02021                     _pos()._content ~= _content[0 .. i];
02022                 else if (_last_tag == TAG.TAG_END)
02023                     _pos()._trailing ~= _content[0 .. i];
02024                 else // TAG_NONE at root node
02025                     i = 0;
02026             } else
02027                 _pos()._children.back()._trailing ~= _content[0 .. i];
02028 
02029         char[] leading;
02030 
02031         if (i < _content.length)
02032             leading = _content[i .. length];
02033 
02034         XMLNode node = new XMLNode(name, leading);
02035 
02036         _pos.add_down(node);
02037 
02038       version(XMLNODE_LOCATION) {
02039         node._location = get_location();
02040       }
02041 
02042         node._attributes = attributes;
02043 
02044         _last_tag = TAG.TAG_START;
02045         _content.length = 0;
02046     }
02047 
02048      /// notifications about XML end tag
02049     void EndElementHandler()
02050     {
02051         int s = 0;
02052         int i = 0;
02053         int e = _content.length;
02054 
02055         if (_content.length>=9+3 &&
02056             _content[0..9]=="<![CDATA[" && _content[length-3..length]=="]]>") {
02057             s += 9;
02058             i = (e-=3);
02059         } else {
02060              // search for content end leaving only white space for _end_leading
02061             for(i=e; i>0; --i)
02062                 if (!isspace(cast(dchar)_content[i-1]))
02063                     break;
02064         }
02065 
02066         if (i > s)
02067             if (_pos()._children.empty())   // no children in current node?
02068                 _pos()._content ~= _content[s .. i];
02069             else if (_last_tag == TAG.TAG_START)
02070                 _pos()._content ~= _content[s .. i];
02071             else
02072                 _pos()._children.back()._trailing ~= _content[s .. i];
02073 
02074         if (i != e)
02075             _pos()._end_leading = _content[i .. e];
02076 
02077         _pos.back();
02078 
02079         _last_tag = TAG.TAG_END;
02080         _content.length = 0;
02081     }
02082 
02083     // store content, white space and comments
02084     void DefaultHandler(in char[] s)
02085     {
02086         _content ~= s;
02087     }
02088 };
02089 
02090 
02091  /// XML reader class
02092 class XMLReader : public XMLReaderBase
02093 {
02094 public:
02095     this(XMLNode node, Stream i)
02096     {
02097         super(node);
02098         _in = i;
02099     }
02100 
02101      /// read one character from XML stream
02102     int get()
02103     {
02104         int c = _in.getc();
02105 
02106         return !_in.eof()? c: EOF;
02107     }
02108 
02109 protected:
02110     Stream  _in;
02111 };
02112 
02113 
02114  /// XML document holder
02115 class XMLDoc : public XMLNode
02116 {
02117 public:
02118     this()
02119     {
02120         super("");
02121 
02122         _format = new XMLFormat;
02123         _errors = new XMLErrorList;
02124     }
02125 
02126     this(in char[] path)
02127     {
02128         this();
02129 
02130         read_file(path);
02131     }
02132 
02133     bool read_file(in char[] path)
02134     {
02135         Stream i = new BufferedFile(path, FileMode.In);
02136         scope(exit) i.close();
02137 
02138         XMLReader reader = new XMLReader(this, i);
02139 
02140         return read(reader, path);
02141     }
02142 
02143     bool read_buffer(in char[] buffer, in char[] system_id="")
02144     {
02145         return read(new MemoryStream(buffer), system_id);
02146     }
02147 
02148     bool read(in Stream i, in char[] system_id="")
02149     {
02150         XMLReader reader = new XMLReader(this, i);
02151 
02152         return read(reader, system_id);
02153     }
02154 
02155     bool read(XMLReaderBase reader, char[] display_path)
02156     {
02157       version(XMLNODE_LOCATION) {
02158          // make a string copy to handle temporary string objects
02159         _display_path = display_path.dup;
02160         reader._display_path = _display_path;
02161       }
02162 
02163         reader.clear_errors();
02164         reader.read();
02165 
02166         _format = reader.get_format();
02167         _format._endl = reader.get_endl();
02168 
02169         if (!reader.get_errors().empty()) {
02170             _errors = reader.get_errors();
02171             return false;
02172         }
02173 
02174         return true;
02175     }
02176 
02177      /// write XML stream preserving previous white space and comments
02178     void write(Stream o, WRITE_MODE mode=WRITE_MODE.FORMAT_SMART)
02179     {
02180         _format.print_header(o, mode!=WRITE_MODE.FORMAT_PLAIN);
02181 
02182         if (_children.length() == 1)
02183             _children.front().write(o, _format, mode);
02184         else if (!_children.empty()) {
02185             //throw Exception("more than one XML root!");
02186             return false;
02187         }
02188     }
02189 
02190      /// write XML stream with formating
02191     void write_formating(Stream o)
02192     {
02193         write(o, WRITE_MODE.FORMAT_PRETTY);
02194     }
02195 
02196     void write_file(in char[] path, WRITE_MODE mode=WRITE_MODE.FORMAT_SMART)
02197     {
02198         Stream o = new BufferedFile(path, FileMode.Out);
02199         scope(exit) o.close();
02200 
02201         write(o, mode);
02202     }
02203 
02204     void write_formating(in char[] path)
02205     {
02206         Stream o = new BufferedFile(path, FileMode.Out);
02207         scope(exit) o.close();
02208 
02209         write_formating(o);
02210     }
02211 
02212     XMLFormat       _format;
02213     XMLErrorList    _errors;
02214 
02215   version(XMLNODE_LOCATION) {
02216     char[]  _display_path;
02217   }
02218 };
02219 
02220 
02221  /// XML message wrapper
02222 class XMLMessage : public XMLDoc
02223 {
02224 public:
02225     this(in char[] name)
02226     {
02227         _pos = new XMLPos(this);
02228         _pos.create(name);
02229     }
02230 
02231     char[] toString()
02232     {
02233         Stream m = new MemoryStream();
02234 
02235         write(m);
02236 
02237         return m.toString();
02238     }
02239 
02240     XMLPos  _pos;
02241 
02242 protected:
02243     this()
02244     {
02245         _pos = new XMLPos(this);
02246     }
02247 };
02248 
02249 
02250  /// helper structure to read XML messages from strings
02251 class XMLMessageFromString : public XMLMessage
02252 {
02253 public:
02254     this(in char[] xml_str, in char[] system_id="")
02255     {
02256         read_buffer(xml_str, system_id);
02257     }
02258 };
02259 
02260 
02261  /// Reader for XML Messages
02262 class XMLMessageReader : public XMLPos
02263 {
02264 public:
02265     this(in char[] xml_str, in char[] system_id="")
02266     {
02267         super(_msg);
02268 
02269         _msg.read_buffer(xml_str, system_id);
02270     }
02271 
02272     XMLDoc get_document()
02273     {
02274         return _msg;
02275     }
02276 
02277 protected:
02278     XMLDoc  _msg;
02279 };
02280 
02281 
02282  /// on the fly XML writer
02283 class XMLWriter
02284 {
02285 private:
02286     this(XMLFormat format, Stream o)
02287     {
02288         _out = o;
02289         _format = format;
02290         _stack = new stack!(StackEntry);
02291 
02292         format.print_header(_out, false);   // _format._endl is printed in write_pre()
02293     }
02294 
02295 public:
02296     this(Stream o, in XMLFormat format=new XMLFormat)
02297     {
02298         _own_stream = null;
02299 
02300         this(format, o);
02301     }
02302 
02303     this(in char[] path, in XMLFormat format=new XMLFormat)
02304     {
02305         _format = format;
02306 
02307         _own_stream = new BufferedFile(new File(path, FileMode.Out));
02308 
02309         this(format, _own_stream);
02310     }
02311 
02312     ~this()
02313     {
02314         close();
02315     }
02316 
02317     void close()
02318     {
02319         if (_out) {
02320             _out.writeString(_format._endl);
02321 
02322             if (_own_stream) {
02323                 _own_stream.close();
02324                 delete _own_stream;
02325             }
02326 
02327             _out = null;
02328         }
02329     }
02330 
02331      /// create node and move to it
02332     void create(in char[] name)
02333     {
02334         if (!_stack.empty()) {
02335             StackEntry last = _stack.top();
02336 
02337             if (last._state < WRITESTATE.PRE_CLOSED) {
02338                 write_attributes(last);
02339                 close_pre(last);
02340             }
02341 
02342             last._children = true;
02343         }
02344 
02345         auto entry = new StackEntry;
02346         entry._node_name = name;
02347         _stack.push(entry);
02348 
02349         write_pre(entry);
02350     }
02351 
02352      /// go back to previous position
02353     bool back()
02354     {
02355         if (!_stack.empty()) {
02356             write_post(_stack.top());
02357 
02358             _stack.pop();
02359             return true;
02360         } else
02361             return false;
02362     }
02363 
02364      /// attribute setting
02365     void put(in char[] attr_name, in char[] value)
02366     {
02367         if (!_stack.empty())
02368             _stack.top()._attributes[attr_name] = value;
02369     }
02370 
02371      /// index operator write access to an attribute
02372     void opIndexAssign(in char[] attr_name, in char[] value)
02373     {
02374         if (!_stack.empty())
02375             _stack.top()._attributes[attr_name] = value;
02376     }
02377 
02378     void set_content(in char[] s, bool cdata=false)
02379     {
02380         if (!_stack.empty())
02381             _stack.top()._content = EncodeXMLString(s, cdata);
02382     }
02383 
02384      // public for access in StackEntry
02385     enum WRITESTATE {
02386         NOTHING, /*PRE,*/ ATTRIBUTES, PRE_CLOSED, /*CONTENT,*/ POST
02387     };
02388 
02389 protected:
02390     Stream      _own_stream;
02391     Stream      _out;
02392     XMLFormat   _format;
02393 
02394     typedef XMLNode.AttributeMap AttrMap;
02395 
02396      /// container for XMLWriter state information
02397     class StackEntry {
02398       public:
02399         char[]      _node_name;
02400         AttrMap     _attributes;
02401         char[]      _content;
02402         WRITESTATE  _state;
02403         bool        _children;
02404 
02405         this()
02406         {
02407             _state = WRITESTATE.NOTHING;
02408             _children = false;
02409         }
02410     };
02411 
02412     stack!(StackEntry) _stack;
02413 
02414     void close_pre(StackEntry entry)
02415     {
02416         _out.writeString(">");
02417 
02418         entry._state = WRITESTATE.PRE_CLOSED;
02419     }
02420 
02421     void write_pre(StackEntry entry)
02422     {
02423         if (_format._pretty >= PRETTY_FLAGS.PRETTY_LINEFEED)
02424             _out.writeString(_format._endl);
02425 
02426         if (_format._pretty == PRETTY_FLAGS.PRETTY_INDENT)
02427             for(size_t i=_stack.length(); --i>0; )
02428                 _out.writeString(XML_INDENT_SPACE);
02429 
02430         _out.writeString('<' ~ EncodeXMLString(entry._node_name));
02431         //entry._state = PRE;
02432     }
02433 
02434     void write_attributes(StackEntry entry)
02435     {
02436         foreach(key,value; entry._attributes)
02437             _out.writeString(' ' ~ EncodeXMLString(key) ~ "=\"" ~ EncodeXMLString(value) ~ "\"");
02438 
02439         entry._state = WRITESTATE.ATTRIBUTES;
02440     }
02441 
02442     void write_post(StackEntry entry)
02443     {
02444         if (entry._state < WRITESTATE.ATTRIBUTES)
02445             write_attributes(entry);
02446 
02447         if (entry._children || entry._content.length) {
02448             if (entry._state < WRITESTATE.PRE_CLOSED)
02449                 close_pre(entry);
02450 
02451             _out.writeString(entry._content);
02452             //entry._state = CONTENT;
02453 
02454             if (_format._pretty>=PRETTY_FLAGS.PRETTY_LINEFEED && !entry._content.length)
02455                 _out.writeString(_format._endl);
02456 
02457             if (_format._pretty==PRETTY_FLAGS.PRETTY_INDENT && !entry._content.length)
02458                 for(size_t i=_stack.length(); --i>0; )
02459                     _out.writeString(XML_INDENT_SPACE);
02460 
02461             _out.writeString("</" ~ EncodeXMLString(entry._node_name) ~ ">");
02462         } else {
02463             _out.writeString("/>");
02464         }
02465 
02466         entry._state = WRITESTATE.POST;
02467     }
02468 };
02469 
02470 
02471  /// remove escape characters from string
02472 char[] unescape(in char[] str, char b, char e)
02473 {
02474     if (str.length>0 && str[0]==b && str[length-1]==e)
02475         return str[1 .. length-1];
02476     else
02477         return str;
02478 }
02479 
02480 char[] unescape(in char[] s)
02481 {
02482     return unescape(s, '"', '"');
02483 }
02484 
02485 
02486  /// encode XML string literals
02487 char[] EncodeXMLString(in char[] str, bool cdata=false)
02488 {
02489     char[] s = str;
02490     size_t l = str.length;
02491 
02492     if (cdata) {
02493          // encode the whole string in a CDATA section
02494         char[] ret = "<![CDATA[";
02495 
02496         ret ~= str;
02497 
02498         ret ~= "]]>";
02499 
02500         return ret;
02501     } else if (l <= BUFFER_LEN) {
02502         char[] buffer;
02503         buffer.length = 6*str.length;   // worst case "&quot;" / "&apos;"
02504         int w = 0;
02505 
02506         foreach(char c; s)
02507             switch(c) {
02508               case '&':
02509                 buffer[w++] = '&';  buffer[w++] = 'a';  buffer[w++] = 'm';  buffer[w++] = 'p';  buffer[w++] = ';';              // "&amp;"
02510                 break;
02511 
02512               case '<':
02513                 buffer[w++] = '&';  buffer[w++] = 'l'; buffer[w++] = 't';   buffer[w++] = ';';                          // "&lt;"
02514                 break;
02515 
02516               case '>':
02517                 buffer[w++] = '&';  buffer[w++] = 'g'; buffer[w++] = 't';   buffer[w++] = ';';                          // "&gt;"
02518                 break;
02519 
02520               case '"':
02521                 buffer[w++] = '&';  buffer[w++] = 'q'; buffer[w++] = 'u'; buffer[w++] = 'o'; buffer[w++] = 't'; buffer[w++] = ';';  // "&quot;"
02522                 break;
02523 
02524               case '\'':
02525                 buffer[w++] = '&';  buffer[w++] = 'a'; buffer[w++] = 'p'; buffer[w++] = 'o'; buffer[w++] = 's'; buffer[w++] = ';';  // "&apos;"
02526                 break;
02527 
02528               default:
02529                 if (cast(dchar)c<20 && c!='\t' && c!='\r' && c!='\n') {
02530                     char[] b = format("&%d;", cast(int)c);
02531                     foreach(cc; b)
02532                         buffer[w++] = cc;
02533                 } else
02534                     buffer[w++] = c;
02535             }
02536 
02537         buffer.length = w;
02538 
02539         return buffer;
02540     }
02541     else { // l > BUFFER_LEN
02542          // alternative code for larger strings using a memory stream
02543         Stream o = new MemoryStream;
02544 
02545         foreach(c; str)
02546             switch(c) {
02547               case '&':
02548                 o.writeString("&amp;");
02549                 break;
02550 
02551               case '<':
02552                 o.writeString("&lt;");
02553                 break;
02554 
02555               case '>':
02556                 o.writeString("&gt;");
02557                 break;
02558 
02559               case '"':
02560                 o.writeString("&quot;");
02561                 break;
02562 
02563               case '\'':
02564                 o.writeString("&apos;");
02565                 break;
02566 
02567               default:
02568                 if (cast(dchar)c<20 && c!='\t' && c!='\r' && c!='\n')
02569                     o.writeString(format("&%d;", cast(int)c));
02570                 else
02571                     o.write(c);
02572             }
02573 
02574         return o.toString();
02575     }
02576 }
02577 
02578 bool icmpLiteral(in char[] str, in char[] lit_str, inout int i)
02579 {
02580     if (str.length>=lit_str.length && !icmp(str[0..lit_str.length], lit_str)) {
02581         i += lit_str.length;
02582         return true;
02583     } else
02584         return false;
02585 }
02586 
02587  /// decode XML string literals
02588 char[] DecodeXMLString(in char[] str)
02589 {
02590     char[] buffer;
02591     buffer.length = str.length;
02592 
02593     size_t o = 0;
02594     bool modified = false;
02595 
02596     for(int i=0; i<str.length; ++i) {
02597         if (str[i] == '&') {
02598             char[] cur = str[i+1..length];
02599 
02600             if (icmpLiteral(cur, "lt;", i))
02601                 {buffer[o++] = '<'; modified = true;}
02602             else if (icmpLiteral(cur, "gt;", i))
02603                 {buffer[o++] = '>'; modified = true;}
02604             else if (icmpLiteral(cur, "amp;", i))
02605                 {buffer[o++] = '&'; modified = true;}
02606             else if (icmpLiteral(cur, "quot;", i))
02607                 {buffer[o++] = '\"'; modified = true;}
02608             else if (icmpLiteral(cur, "apos;", i))
02609                 {buffer[o++] = '\''; modified = true;}
02610             else
02611                 buffer[o++] = str[i];
02612         } else if (i+9<=str.length && !icmp(str[i..i+9], "<![CDATA[")) {
02613              // search for CDEnd "]]>"
02614             for(int e=i+9; ; ++e) {
02615                 if (e+3 > str.length) {
02616                     buffer[o++] = str[i];
02617                     break;
02618                 }
02619 
02620                 if (str[e..e+3] == "]]>") {
02621                     i += 9;
02622                     size_t l = e - i;
02623                     buffer[o..o+l] = str[i..i+l];
02624                     o += l;
02625                     i += l + 2;
02626                     modified = true;
02627                     break;
02628                 }
02629             }
02630         } else
02631             buffer[o++] = str[i];
02632     }
02633 
02634     return modified? buffer[0..o]: str;
02635 }
02636 
02637 
02638 unittest
02639 {
02640     debug(string) printf("xmlstorage unittest\n");
02641 
02642 
02643 //  void print_content(XMLDoc doc, FILE* f=stdout)
02644 //  {
02645 //      Stream o = new CFile(f, FileMode.Out);
02646 //      doc.write(o, WRITE_MODE.FORMAT_PLAIN);
02647 //  }
02648 
02649     void print_content(XMLDoc doc, Stream o=dout)
02650     {
02651         doc.write(o, WRITE_MODE.FORMAT_PLAIN);
02652     }
02653 
02654     void assert_content(XMLDoc doc, char[] str)
02655     {
02656         Stream m = new MemoryStream();
02657         doc.write(m, WRITE_MODE.FORMAT_PLAIN);
02658 
02659     //  writefln(m.toString());
02660 
02661         assert(m.toString()==str);
02662     }
02663 
02664     void testEncodeDecode(in char[] a, in char[] b)
02665     {
02666         assert(DecodeXMLString(b)==a);
02667         assert(EncodeXMLString(a)==b);
02668     }
02669 
02670     void testEncodeDecodeCDATA(in char[] a, in char[] b)
02671     {
02672         assert(DecodeXMLString(b)==a);
02673         assert(EncodeXMLString(a, true)==b);
02674     }
02675 
02676 
02677      // test string encoding/decoding
02678     testEncodeDecode("abc", "abc");
02679     testEncodeDecode("x<>y", "x&lt;&gt;y");
02680     testEncodeDecode("\"&'", "&quot;&amp;&apos;");
02681     testEncodeDecodeCDATA("ABC", "<![CDATA[ABC]]>");
02682 
02683 
02684      // create a XML document and fill with test data
02685 
02686     auto doc = new XMLDoc();
02687     auto pos = new XMLPos(doc);
02688 
02689     pos.create("node1");
02690         pos.put("id", "1");
02691         pos["attr"] = "a";
02692 
02693         pos.create_relative("a/b/c/d");
02694         pos.back();
02695 
02696         pos.smart_create("node2");
02697             (new XMLIntRef(pos(), "id")) = 2;
02698         assert(pos.back()); // </node2>
02699 
02700         pos.create("node2");
02701             pos().set_content("hello");
02702         assert(pos.back()); // </node2>
02703 
02704         *pos().subvalue_ref("node2", "attr2", 1) = "xyz";
02705     assert(pos.back()); // </node1>
02706 
02707     (new XMLStringRef("node1", pos(), "attr1")).assign("abc");
02708 
02709     print_content(doc);
02710 
02711     assert_content(doc,
02712         "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
02713         "<node1 attr=\"a\" attr1=\"abc\" id=\"1\">"
02714             "<a>"
02715                 "<b>"
02716                     "<c>"
02717                         "<d/>"
02718                     "</c>"
02719                 "</b>"
02720             "</a>"
02721             "<node2 id=\"2\"/>"
02722             "<node2 attr2=\"xyz\">hello</node2>"
02723         "</node1>"
02724     );
02725 
02726 
02727      // test read functions and compare content
02728 
02729     pos = new XMLPos(doc);
02730 
02731     bool ok = pos.go_down("node1");
02732     assert(ok); {
02733         assert(pos.get("id")=="1");
02734 
02735         ok = pos.go("node2");
02736         assert(ok); {
02737             assert(pos.get("id")=="2");
02738             assert(pos.back()); // </node2>
02739         }
02740 
02741         ok = pos.go_down("node2", 1);
02742         assert(ok); {
02743             assert(pos["attr2"]=="xyz");
02744             assert(pos.back()); // </node2>
02745         }
02746 
02747         assert(pos.back()); // </node1>
02748     }
02749     assert(!pos.back());
02750 
02751 
02752      // test foreach() iterations
02753 
02754     foreach(XMLNode e; new XMLChildrenFilter(doc, "node1")) {
02755         writefln(e._name);
02756 
02757         foreach(XMLNode e; new XMLChildrenFilter(e, "node2")) {
02758             writefln(e._name);
02759         }
02760     }
02761 
02762 
02763      // test XMLWriter and file I/O
02764 
02765     {
02766     auto w = new XMLWriter("test1.xml");
02767     scope(exit) w.close();
02768     w.create("root");
02769         w.create("level 1");
02770             w.create("level 2");
02771             w.back(); // </level 2>
02772         w.back(); // </level 1>
02773     w.back();
02774     }
02775 
02776     auto d = new XMLDoc();
02777     assert(d.read_file("test1.xml"));
02778 
02779     d.write_file("test2.xml");
02780 }

Generated on Thu Nov 11 2010 22:11:39 for XMLStorage/D by  doxygen 1.7.2