root/trunk/bevutils/TinyXML.d

Revision 20, 38.0 kB (checked in by teales, 5 years ago)

Bevutils files initial check-in.

Line 
1 /***************************************************************
2 Copyright (c) Steve Teale 2007
3 This program is free software; you can use it for any purpose
4 subject to the following conditions.
5
6 This program is distributed in the hope that it will be useful,
7 but WITHOUT ANY WARRANTY; without even the implied warranty of
8 MERCHANTABILITY or FITNESS FOR ANY PARTICULAR PURPOSE.
9 ****************************************************************/
10
11 module bevutils.tinyxml;
12
13 import std.regexp;
14 import std.string;
15 import std.stdio;
16 import std.stream;
17
18 alias std.string.find indexOf;
19 alias std.string.split split;
20 alias std.string.toString toString;
21
22 /**
23  * A compact XML parser and object model.
24  *
25  * TinyXML is aimed mainly at configuration files and the like.
26  *
27  * The TinyXML object model is navigated using a path syntax of two forms.  If tx is
28  * a TinyXML object, you can say:
29  *
30  * XMLElement e = tx("path_string");
31  * XMLElement e = tx << "path_string"
32  *
33  * These both perform the same navigation operation, but while the first simply hands
34  * you a reference to the target element, the second can do the same, but also sets the
35  * tx object context.
36  *
37  */
38 class TinyXML
39 {
40    protected static const char[510] _spaces = ' ';
41    protected static const char[] _WS = "(\\s+)";
42    protected static const char[] _TWS = "\\s+$";
43    protected static const char[] _COMMENT = "(<!--(.*)-->)";
44    protected static const char[] _XJOINT = r"(\.|(([A-Za-z_][0-9A-Za-z_]*)|\^|#|!|')(\[([0-9]+|\$)\])?)";
45    protected static const char[] _AJOINT = r"(@(([A-Za-z_][0-9A-Za-z_]*)|(\[[0-9]+\])))";
46    protected static RegExp _wsrex;
47    protected static RegExp _twsrex;
48    protected static RegExp _crex;
49    protected static RegExp _jrex;
50
51    static this()
52    {
53       //_spaces[0 .. 510] = ' ';
54       _wsrex = RegExp(_WS);
55       _twsrex = RegExp(_TWS);
56       _crex = RegExp(_COMMENT);
57       _jrex = RegExp(_XJOINT);
58    }
59
60    protected char[] prune(char[] s)
61    {
62       return _wsrex.find(s)? s: _wsrex.post;
63    }
64
65    protected char[] rprune(char[] s)
66    {
67       int pos = _twsrex.find(s);
68       return (pos >= 0)? s[0 .. pos]: s;
69    }
70
71    /**
72     * The XML element types supported by TinyXML.
73     *
74     * These are Tags - <tag> ... </tag>, text, as in <tag>This text</tag>, and comments
75     * <!-- A comment -->
76     */
77    enum : int
78    {
79       ALL,
80       TAG,
81       TEXT,
82       COMMENT
83    }
84
85    struct NVPair
86    {
87       char[] name;
88       char[] value;
89    }
90
91    /**
92     * The representation of a set of attributes on an XML tag.
93     */
94    class AttrList
95    {
96    private:
97       NVPair[] list;
98       int[char[]] map;
99    public:
100
101       /**
102        * Add a new attribute to the list.
103        *
104        * Params:
105        *   name = The attribute name.
106        *   value = the attribute value.
107        */
108       void addAttr(char[] name, char[] value)
109       {
110          if (hasKey(name))
111             throw new Exception("Attribute already exists");
112          int n = list.length;
113          list.length = n+1;
114          list[n].name = name;
115          list[n].value = value;
116          map[name] = n;
117       }
118
119       /**
120        * Create a new attribute list identical to the one for which the method was called.
121        */
122       AttrList clone()
123       {
124          AttrList a = new AttrList();
125          int n = length();
126          for (int i = 0; i < n; i++)
127             a.addAttr(name(i), value(i));
128          return a;
129       }
130
131       /**
132        * Delete an attribute from the list by name
133        *
134        * Params:
135        *   name = The name of the attribute to be deleted.
136        */
137       void delAttr(char[] name)
138       {
139          if (!hasKey(name))
140             throw new Exception("No such attribute");
141          int i = map[name];
142          list = list[0 .. i] ~ list[i+1 .. $];
143       }
144
145       /**
146        * A property-style method to get the length of the list
147        */
148       int length() { return list.length; }
149
150       /**
151        * Get the name of the i'th attribute in the list.
152        *
153        * Params:
154        *   i = The desired index.
155        */
156       char[] name(int i) { return list[i].name; }
157      
158       /**
159        * Get the name of the i'th value in the list.
160        *
161        * Params:
162        *   i = The desired index.
163        */
164       char[] value(int i) { return list[i].value; }
165      
166       /**
167        * Get the value of an attribute by name.
168        *
169        * Params:
170        *   name = The desired name.
171        */
172       char[] value(char[] name) { return hasKey(name)? list[map[name]].value: null; }
173      
174       /**
175        * Set the value of an attribute by name.
176        *
177        * Params:
178        *   name = The name of the attribute.
179        */
180       char[] value(char[] name, char[] value)
181          { if (hasKey(name)) { list[map[name]].value = value; return value; } else return null; }
182
183       /**
184        * Clear the list
185        */
186       void clear()
187       {
188          char[][] keys = map.keys;
189          foreach (s; keys)
190             map.remove(s);
191          list.length = 0;
192       }
193
194       /**
195        * Formats the list into a string suitable for XML.
196        */
197       char[] format()
198       {
199          char[] s = "";
200          for (int i = 0; i < list.length; i++)
201          {
202             if (i > 0) s ~= " ";
203             s ~= list[i].name ~ "=\"" ~ Tag.encode_entities(list[i].value) ~ "\"";
204          }
205          return s;
206       }
207
208    private:
209       private bool hasKey(char[] key) { return ((key in map) != null); }
210    }
211
212    /**
213     * A base class for Tags, Comments and body text - EText
214     */
215    class XMLElement
216    {
217       Tag _parent;
218
219       /**
220        * Abstract method to recover the element type.
221        */
222       abstract int EType();
223
224       /**
225        * Abstract method to format elements for XML.
226        */
227       abstract char[] format(char[] s, int indent);
228
229       /**
230        * Abstract method to recover a textual representation of the element.
231        */
232       abstract char[] PText();
233
234       /**
235        * Abstract method to recover an 'attribute' from the element
236        */
237       abstract char[] getValue(char[] name);
238
239       /**
240        * Abstract method to set an 'attribute' of the element.
241        */
242       abstract char[] setValue(char[] name, char[] value) ;
243
244       /**
245        * Abstract method to recover the parent of the element, which will
246        * always be a Tag.
247        */
248       abstract Tag Parent();
249    }
250
251    /**
252     * Class representing XML Tag body text.
253     */
254    class EText : XMLElement
255    {
256       char[] _etext;
257
258       /**
259        * Create an EText object and link it to a Tag.
260        */
261       this(char[] s, Tag parent) { _etext = s; _parent = parent; }
262
263       /**
264        * Get the element type - in this case TEXT.
265        */
266       int EType() { return TEXT; }
267
268       /**
269        * Format the text for inclusion in XML text.
270        *
271        * Params:
272        *   s = An existing string to which the text representing this element is appended.
273        *   indent = Degree to which the formatted text should be indented.
274        *
275        * Returns: The original string with the element text appended.
276        */
277       char[] format(char[] s, int indent)
278       {
279          char[] sp = _spaces[0 .. 3*indent];
280          return (s ~ sp ~ _etext ~ "\n");
281       }
282
283       /**
284        * Get a plain text version - just the text.
285        */
286       char[] PText() { return _etext; }
287
288       /**
289        * Get an 'attribute' by name - in this class the name is ignored.
290        *
291        * Params:
292        *   name = A string value that is ignored.
293        *
294        * Returns: The plain text.
295        */
296       char[] getValue(char[] name) { return _etext; }
297
298       /**
299        * Set an 'attribute' by name - in this class the name is ignored.
300        *
301        * Params:
302        *   name = A string value that is ignored.
303        *   value = the value to be set for the text.
304        *
305        * Returns: The set plain text.
306        */
307       char[] setValue(char[] name, char[] value) { _etext = value; return _etext; }
308
309       /**
310        * Get the element's parent.
311        */
312       Tag Parent() { return _parent; }
313    }
314
315    /**
316     * Class representing an XML comment.
317     */
318    class Comment : XMLElement
319    {
320       char[] _cmt;
321
322       /**
323        * Create a Comment object and link it to a Tag.
324        */
325       this(char[] s, Tag parent) { _cmt = s; _parent = parent; }
326
327       /**
328        * Get the element type - in this case COMMENT.
329        */
330       int EType() { return COMMENT; }
331
332       /**
333        * Format the text for inclusion in XML text.
334        *
335        * Params:
336        *   s = An existing string to which the text representing this element is appended.
337        *   indent = Degree to which the formatted text should be indented.
338        *
339        * Returns: The original string with the element text appended.
340        */
341       char[] format(char[] s, int indent)
342       {
343          char[] sp = _spaces[0 .. 3*indent];
344          return (s ~ sp ~ "<!--" ~ _cmt ~ "-->\n");
345       }
346
347       /**
348        * Get a plain text version - the bare comment.
349        */
350       char[] PText() { return "<!--" ~ _cmt ~ "-->"; }
351
352       /**
353        * Get an 'attribute' by name - in this class the name is ignored.
354        *
355        * Params:
356        *   name = A string value that is ignored.
357        *
358        * Returns: The plain text.
359        */
360       char[] getValue(char[] name) { return _cmt; }
361
362       /**
363        * Set an 'attribute' by name - in this class the name is ignored.
364        *
365        * Params:
366        *   name = A string value that is ignored.
367        *   value = the value to be set for the text.
368        *
369        * Returns: The set plain text.
370        */
371       char[] setValue(char[] name, char[] value) { _cmt = value; return _cmt; }
372
373       /**
374        * Get the element's parent.
375        */
376       Tag Parent() { return _parent; }
377    }
378
379
380    /**
381     * Class representing an XML Tag - &lt;tagname> ... &lt;/tagname>.
382     *
383     * Tags are the structural building blocks of the TinyXML object model.
384     */
385    class Tag : XMLElement
386    {
387       const char[] _TAG = "(<[A-Za-z_][A-Za-z0-9_]*[^>]*>)";
388       const char[] _END = "(<\\/[A-Za-z_][A-Za-z0-9_]*\\s*>)";
389       const char[] _AS = "(=[\"'])";
390
391       static RegExp _xmlrex;
392       static RegExp _tagrex;
393       static RegExp _endrex;
394       static RegExp _asrex;
395
396       static this()
397       {
398          _xmlrex = RegExp(_XML);
399          _tagrex = RegExp(_TAG);
400          _endrex = RegExp(_END);
401          _asrex = RegExp(_AS);
402       }
403
404       bool _valid;
405       char[] _name;
406       XMLElement[] _elist;
407       AttrList _attributes;
408
409
410       /**
411        * Create a Tag object from XML text and possibly link it to a Tag.
412        *
413        * This constructor is called recursively to nibble Tag elements from a string
414        * originally representing the entire XML, and is in essence the XML parser.
415        *
416        * Params:
417        *   so = The source TinyXML object - provides the context and the text to be parsed.
418        *        If so is null this constructor just returns a bare Tag element.
419        *   parentnode = The tag that will be the parent of the new Tag.
420        */
421       this(TinyXML so, Tag parentnode)
422       {
423          _valid = false;
424          _parent = parentnode;
425          _attributes = new AttrList();
426          if (so is null)
427          {
428             // Nothing to parse - just making a bare Tag object
429             _valid = true;
430             return;
431          }
432
433          so._src = prune(so._src);
434          int pt = _tagrex.find(so._src);
435          if (pt == -1)
436          {
437             so.setErr("No opening tag found");
438             return;
439          }
440          char[] tag = _tagrex.match(1);
441          int taglen = tag.length;
442          int tagend = taglen-1;
443
444          bool mt = (so._src[tagend-1] == '/');
445          so._atTop = false;
446
447          int ps = _wsrex.find(so._src);
448          char[] as;
449          if (ps == -1 || ps > tagend)
450             _name = so._src[1 .. (mt? tagend-1: tagend)];
451          else
452          {
453             char[] n = so._src[1 .. ps];
454             _name = prune(n);
455             as = prune(so._src[ps+1 .. (mt? tagend-1: tagend)]);
456             as = rprune(as);
457          }
458
459          if (as.length)
460          {
461             if ((!parse_attribs(so, as)))
462             {
463                return;
464             }
465          }
466          if (so._atTop && mt)
467          {
468             _valid = true;
469             return;
470          }
471
472          so._src = prune(so._src[tagend+1 .. so._src.length]);
473          int n = 0;
474          if (!mt)
475          {
476             // We have stripped of the opening tag construct and any whitespace, so now may have
477             // Some element text
478             // <!-- some comment -->
479             // <A ...> ... </A>
480             // Some more element text
481             // <B ...> ... </B>
482             // </CURRENTTAG  >
483
484             for (;;)
485             {
486                if (so._src == "")
487                {
488                   setErr("Missing end tag after tag: " ~ _name);
489                   return;
490                }
491                if (_endrex.find(so._src) == 0)
492                {
493                   // Hopefully we found the closing tag of the element we are parsing
494                   char[] et = _endrex.match(1);
495                   char[] tn = rprune(et[2 .. et.length-1]);    // gets us "CURRENTTAG  " --> "CURRENTTAG"
496                   if (tn == _name)
497                   {
498                      so._src = prune(_endrex.post);
499                      break;
500                   }
501                   so.setErr("Unexpected closing tag: " ~ et ~ " after " ~ _name);
502                   return;
503                }
504                else if (_tagrex.find(so._src) == 0)
505                {
506                   Tag nn = new Tag(so, this);
507                   if (!nn._valid) {
508                      // error already reported
509                      return;
510                   }
511                   _elist.length = _elist.length+1;
512                   _elist[n] = nn;
513                   n++;
514                }
515                else if (_crex.find(so._src) == 0)
516                {
517                   _elist.length = _elist.length+1;
518                   _elist[n++] = new Comment(_crex.match(2), this);
519                   so._src = prune(_crex.post);
520                }
521                else
522                {
523                   // It is element body text of some sort
524                   int limit = indexOf(so._src, '<');
525                   if (limit == -1)
526                   {
527                      so.setErr("Tag or closing tag expected after element text");
528                      return;
529                   }
530                   char[] t = rprune(so._src[0 .. limit]);
531                   _elist.length = _elist.length+1;
532                   _elist[n++] = new EText(t, this);
533                   so._src = so._src[limit .. $];   // no need to prune
534                }
535             }
536          }
537          this._valid = true;
538       }
539
540       /**
541        * Get the Tag name - same as PText().
542        */
543       char[] Name() { return _name; }
544
545       /**
546        * Get the element type - in this case TAG.
547        */
548       int EType() { return TAG; }
549
550       /**
551        * Get a plain text version - in this case simply the Tag name.
552        */
553       char[] PText() { return _name; }
554
555       /**
556        * Get the element's parent.
557        */
558       Tag Parent() { return _parent; }
559
560       private bool parse_attribs(TinyXML so, char[] as)
561       {
562          int pos = _asrex.find(as);
563          if (pos == -1)
564          {
565             so.setErr("Bad attribute list - no name=\" construct found: " ~ as);
566             return false;
567          }
568          char[] tail;
569          for (int i = 0; pos != -1; i++) {
570             char[] at = _asrex.match(1);
571             tail = _asrex.post;
572             char[] n = _asrex.pre;
573             char quot = at[1];
574             int q2 = std.string.find(tail, quot);
575             if (q2 == -1)
576             {
577                so.setErr("Bad attribute list - missing closing quote: " ~ as);
578                return false;
579             }
580             if (_wsrex.find(n) != -1)
581             {
582                so.setErr("Bad attribute list - space in attribute name: " ~ as);
583                return false;
584             }
585             char[] t = tail[0 .. q2];
586             char[] v = decode_entities(t);
587             _attributes.addAttr(n, v);
588             tail = tail[q2+1 .. tail.length];
589             as = prune(tail);
590             pos = _asrex.find(as);
591          }
592          if (tail.length) {
593             pos = _wsrex.find(tail);
594             if (pos != -1)
595             {
596                so.setErr("Bad attribute list - garbage after attributes: " ~ as);
597                return false;
598             }
599             if (tail.length != _wsrex.match(1).length)
600             {
601                so.setErr("Bad attribute list - garbage after attributes: " ~ as);
602                return false;
603             }
604          }
605
606          return true;
607       }
608
609       /**
610        * Format the text for inclusion in XML text.
611        *
612        * Params:
613        *   s = An existing string to which the text representing this element is appended.
614        *   indent = Degree to which the formatted text should be indented.
615        *
616        * Returns: The original string with the element text appended.
617        */
618       char[] format(char[] s, int indent)
619       {
620          char[] sp = _spaces[0 .. 3*indent];
621          s ~= sp ~ "<" ~ _name;
622          if (_attributes.length())
623          {
624                s ~= " " ~ _attributes.format();
625          }
626          bool mt = (_elist.length == 0);
627          if (mt)
628             s ~= "/>\n";
629          else
630          {
631             s ~= ">";
632             if (_elist.length == 1 && _elist[0].EType() != TAG)
633             {
634                s ~= _elist[0].PText() ~ "</" ~ _name ~ ">\n";
635             }
636             else
637             {
638                s ~= "\n";
639                for (int i = 0; i < _elist.length; i++)
640                {
641                   s = _elist[i].format(s, indent+1);
642                }
643                s ~= sp ~ "</" ~ _name ~ ">\n";
644             }
645          }
646          return s;
647       }
648
649       /**
650        * Decode &amp;lt; and &amp;amp; to < and &.
651        *
652        * Params:
653        *   s = The string to be decoded.
654        *
655        * Returns: The decoded string.
656        */
657       public static char[] decode_entities(char[] s)
658       {
659          s = std.string.replace(s, "&lt;", "<");
660          return std.string.replace(s, "&amp;", "&");
661       }
662
663       /**
664        * Encode < and & to &amp;lt; and &amp;amp;.
665        *
666        * Params:
667        *   s = The string to be encoded.
668        *
669        * Returns: The encoded string.
670        */
671       public static char[] encode_entities(char[] s)
672       {
673          s = std.string.replace(s, "&", "&amp;");
674          return std.string.replace(s, "<", "&lt;");
675       }
676
677       /**
678        * Get an 'attribute' by name - in this class the name is used.
679        *
680        * Params:
681        *   name = The name of the attribute.
682        *
683        * Returns: The attribute value.
684        */
685       char[] getValue(char[] name)
686       {
687          char[] rv = _attributes.value(name);
688          if (rv is null)
689             throw new Exception("No such attribute: " ~ name);
690          return rv;
691       }
692
693       /**
694        * Set an 'attribute' by name - in this class the name is used.
695        *
696        * Params:
697        *   name = The name of the attribute.
698        *   value = the value to be set for the attribute.
699        *
700        * Returns: The set attribute value.
701        */
702       char[] setValue(char[] name, char[] value)
703       {
704          char[] rv = _attributes.value(name, value);
705          if (rv is null)
706             throw new Exception("No such attribute: " ~ name);
707          return rv;
708       }
709
710       /**
711        * Add a new attribute to the Tag element.
712        *
713        * Params:
714        *   name = The attribute name.
715        *   value = the attribute value.
716        */
717       void addAttribute(char[] name, char[] value)
718       {
719          _attributes.addAttr(name, value);
720       }
721
722       /**
723        * Delete an attribute from the Tag element by name
724        *
725        * Params:
726        *   name = The name of the attribute to be deleted.
727        */
728       void deleteAttribute(char[] name)
729       {
730          _attributes.delAttr(name);
731       }
732
733       /**
734        * Append an element to the child list of this Tag.
735        *
736        * Params:
737        *   e = and XMLElement object.
738        */
739       void appendElement(XMLElement e)
740       {
741          _elist = _elist ~ e;
742       }
743
744       /**
745        * Duplicate this Tag object, attaching the duplicate immediately after
746        * this object in the parent object's child list.
747        *
748        * Returns: The duplicated object.
749        */
750       Tag duplicate()
751       {
752          Tag parent = _parent;
753          int i;
754          for (i = 0; i < parent._elist.length; i++)
755          {
756             if (cast(Object) (parent._elist[i]) == cast(Object) this)
757                break;
758          }
759          parent._elist = parent._elist[0 .. i+1] ~ cast(XMLElement) this ~ parent._elist [i+1 .. $];
760          return cast(Tag) parent._elist[i+1];
761       }
762
763       /**
764        * Get an array of text items in the body of this Tag.
765        *
766        * These are fragments that are separated by child Tags or comments.*
767        * Returns: An array of strings.
768        */
769       char[][] getTextFragments()
770       {
771          char[][] rv;
772          rv.length = _elist.length;
773          int n = 0;
774          for (int i = 0; i < _elist.length; i++)
775          {
776             if (_elist[i].EType() == TEXT)
777             {
778                rv[i] = _elist[i].PText();
779                n++;
780             }
781          }
782          rv.length = n;
783          return rv;
784       }
785
786
787       /**
788        * Get an array of comment text items in the body of this Tag.
789        *
790        * Returns: An array of strings.
791        */
792       char[][] getComments()
793       {
794          char[][] rv;
795          rv.length = _elist.length;
796          int n = 0;
797          for (int i = 0; i < _elist.length; i++)
798          {
799             if (_elist[i].EType() == COMMENT)
800             {
801                rv[i] = _elist[i].PText();
802                n++;
803             }
804          }
805          rv.length = n;
806          return rv;
807       }
808
809       private XMLElement addAdjacent(XMLElement e, int rel)
810       {
811          if (_parent is null)
812             throw new Exception("Can't add an element after this tag - no parent");
813          int i;
814          for (i = 0; i < _parent._elist.length; i++)
815          {
816             if (cast(Object) (_parent._elist[i]) == cast(Object) this)
817                break;
818          }
819          e._parent = _parent;
820          _parent._elist = _parent._elist[0 .. i+rel] ~ e ~ _parent._elist [i+rel .. $];
821          return e;
822       }
823
824       /**
825        * Add an XMLElement after this Tag in its parent's child list.
826        *
827        * Params:
828        *   e = An XMLElement object to be added.
829        *
830        * Returns: The added element.
831        */
832       XMLElement addAfter(XMLElement e) { return addAdjacent(e, 1); }
833
834       /**
835        * Add an XMLElement before this Tag in its parent's child list.
836        *
837        * Params:
838        *   e = An XMLElement object to be added.
839        *
840        * Returns: The added element.
841        */
842       XMLElement addBefore(XMLElement e) { return addAdjacent(e, 0); }
843
844       private int countElements(int type)
845       {
846          int n = 0;
847          foreach (x; _elist)
848          {
849             if (x.EType() == type)
850                n++;
851          }
852          return n;
853       }
854
855       /**
856        * Get the number of Tag objects in the child list of this Tag.
857        *
858        * Returns: Number of Tags.
859        */
860       int Tags() { return countElements(TAG); }
861
862       /**
863        * Get the number of EText objects in the child list of this Tag.
864        *
865        * Returns: Number of EText objects.
866        */
867       int ETexts() { return countElements(TEXT); }
868
869       /**
870        * Get the number of Comment objects in the child list of this Tag.
871        *
872        * Returns: Number of Comment objects.
873        */
874       int Comments() { return countElements(COMMENT); }
875
876       /**
877        * Get the number of elements in the child list of this Tag.
878        *
879        * Returns: Total number of child elements.
880        */
881       int Elements() { return _elist.length; }
882
883       /**
884        * Returns a child element of a specified type at a specified index.
885        *
886        * The index is relative to the number of child elements of that type.
887        *
888        * Returns: The child element.
889        *
890        * To use the returned object, you must cast it to the type you specified, as in
891        * Tag t = cast(Tag) ptag[1, TAG];
892        */
893       XMLElement opIndex(int index, int type)
894       {
895          if (type == 0)
896             return _elist[index];
897          if (index < 0 || index >= _elist.length)
898             return null;
899          int found = 0;
900          for (int i = 0; i < _elist.length; i++)
901          {
902             if (_elist[i].EType() == type)
903             {
904                found++;
905                if (found == index+1)
906                   return _elist[i];
907             }
908          }
909          return null;
910       }
911
912       /**
913        * Returns the first EText child element of this Tag.
914        *
915        * This is intended for use in the common case where the tag is like
916        * &lt;tag>Body text&lt;/tag>
917        *
918        * Returns: The child element.
919        */
920       char[] FirstText()
921       {
922          if (ETexts() < 1)
923             throw new Exception("Tag has no text elements");
924          return opIndex(0, TEXT).PText();
925       }
926    }
927
928    const char[] _XML = "(<\\?xml +version *= *\"1.\\d\"( +encoding *= *\"[A-Za-z0-9\\-_]*\")? *\\?>\\s*)";
929
930    protected char[] _src;
931    char[] _xmlDecl;
932    char[][] _outerComments;
933    bool _atTop;
934    Tag _top;
935    RegExp _xmlrex;
936    int findex;
937    char[] _err;
938    Tag _context;
939
940    protected void setErr(char[] msg)
941    {
942       _err = msg;
943    }
944
945    private char[] choppreamble(char[] s)
946    {
947       int i = _xmlrex.find(s);
948       if (i == -1)
949          return null;
950       _xmlDecl = rprune(_xmlrex.match(1));
951       return _xmlrex.post;
952    }
953
954    private char[] getPreComments(char[] s)
955    {
956       int n = 0;
957       while (_crex.find(s) == 0)
958       {
959          _outerComments.length = _outerComments.length+1;
960          _outerComments[n++] = _crex.match(1);
961          s = prune(_crex.post);
962       }
963       return s;
964    }
965
966    private XMLElement findElement(char[] txpath, bool setcontext)
967    {
968       Tag te;
969       int tindex;
970       char[] tname;
971       bool hasattrib = false;
972       if (txpath[0] == '/')
973          txpath = txpath[1 .. $];
974       char[][] a = std.string.split(txpath, "/");
975       for (int i = 0; i < a.length; i++)
976       {
977          if (_jrex.find(a[i]) != 0 || _jrex.post.length > 0)
978             throw new Exception("Bad joint at " ~ a[i]);
979          if (a[i][0] == '^')
980          {
981             if (i > 0)
982                throw new Exception("Only the first joint can begin with '^'");
983             a[i] = _top._name ~ a[i][1 .. $];
984          }
985          if (a[i][0] == '.')
986          {
987             if (i > 0)
988                throw new Exception("Only the first joint can begin with '.'");
989          }
990          if (a[i][0] == '!' || a[i][0] == '\'')
991          {
992             if (i < a.length-1)
993                throw new Exception("No further nodes allowed after Text or Comment joints");
994          }
995       }
996       if (a[0][0] != '.' && indexOf(a[0], _top._name) != 0)
997          throw new Exception("Top joint does not match XML outer tag: " ~ a[0] ~ _top._name);
998       if (indexOf(a[0], '[') >= 0)
999          throw new Exception("Top joint can't be indexed: " ~ a[0]);
1000
1001       void extractIndex(char[] s)
1002       {
1003          int ob = indexOf(s, '[');
1004          if (ob == -1)
1005          {
1006             tname = s;
1007             tindex = 0;
1008             return;
1009          }
1010          tname = s[0 .. ob];
1011          int cb = indexOf(s, ']');
1012          char[] inner = s[ob+1 .. cb];
1013          if (inner == "$")
1014             tindex = -1;
1015          else
1016             tindex = cast(int) atoi(inner);
1017       }
1018       if (a[0][0] != '.')
1019       {
1020          te = _top;  // Otherwise navigate from existing context
1021          _context = _top;
1022       }
1023       else
1024          te = _context;
1025       int index = 0;
1026    mainloop:
1027       for (int i = 1; i < a.length; i++)
1028       {
1029          extractIndex(a[i]);
1030          int found = 0;
1031          if (indexOf("#!'", a[i][0]) >= 0)
1032          {
1033             // Search purely by position
1034             int EType = (a[i][0] == '#')? TAG:((a[i][0] == '!')? COMMENT: TEXT);
1035             if (tindex >= 0)
1036             {
1037                for (int j = 0; j < te._elist.length; j++)
1038                {
1039                   if (te._elist[j].EType() == EType)
1040                   {
1041                      found++;
1042                      if (found > tindex)
1043                      {
1044                         if (i == a.length-1)
1045                         {
1046                            if (setcontext)
1047                            {
1048                               if (EType == TAG)
1049                                  _context = cast(Tag) te._elist[j];
1050                               else
1051                                  _context = cast(Tag) te;
1052                            }
1053                            return te._elist[j];
1054                         }
1055                         te = cast(Tag) te._elist[j];
1056                         continue mainloop;
1057                      }
1058                   }
1059                }
1060                throw new Exception("Joint index out of range: " ~ a[i]);
1061             }
1062             else
1063             {
1064                for (int j = te._elist.length-1; j >= 0; j--)
1065                {
1066                   if (te._elist[j].EType() == EType)
1067                   {
1068                      if (i == a.length-1)
1069                      {
1070                         if (setcontext)
1071                         {
1072                            if (EType == TAG)
1073                               _context = cast(Tag) te._elist[j];
1074                            else
1075                               _context = cast(Tag) te;
1076                         }
1077                         return te._elist[j];
1078                      }
1079                      te = cast(Tag) te._elist[j];
1080                      continue mainloop;
1081                   }
1082                }
1083                throw new Exception("Joint index out of range: " ~ a[i]);
1084             }
1085          }
1086          else
1087          {
1088             // Search for a tag by name and index
1089             if (tindex >= 0)
1090             {
1091                for (int j = 0; j < te._elist.length; j++)
1092                {
1093                   if (te._elist[j].EType() == TAG && te._elist[j].PText() == tname)
1094                   {
1095                      found++;
1096                      if (found > tindex)
1097                      {
1098                
1099                         if (i == a.length-1)
1100                         {
1101                            if (setcontext)
1102                            {
1103                               _context = cast(Tag) te._elist[j];
1104                              }
1105                            return te._elist[j];
1106                         }
1107                         te = cast(Tag) te._elist[j];
1108                         continue mainloop;
1109                      }
1110                   }
1111                }
1112                throw new Exception("Joint not found: " ~ a[i]);
1113             }
1114             else
1115             {
1116                for (int j = te._elist.length-1; j >= 0; j--)
1117                {
1118                   if (te._elist[j].EType() == TAG && (cast(Tag) te._elist[j])._name == tname)
1119                   {
1120                      if (i == a.length-1)
1121                      {
1122                         if (setcontext)
1123                         {
1124                            _context = cast(Tag) te._elist[j];
1125                         }
1126                         return te._elist[j];
1127                      }
1128                      te = te = cast(Tag) te._elist[j];
1129                      continue mainloop;
1130                   }
1131                }
1132                throw new Exception("Joint not found: " ~ a[i]);
1133             }
1134          }
1135       }
1136       return te;
1137    }
1138
1139 /**
1140  * The public methods of TinyXML
1141  */
1142 public:
1143    /**
1144     * Construct a TinyXML object from a string.
1145     */
1146    this(char[] s)
1147    {
1148       _xmlrex = RegExp(_XML);
1149       _src = choppreamble(s);
1150       _src = getPreComments(_src);
1151       _atTop = true;
1152        _top = new Tag(this, null);
1153       _context = _top;
1154    }
1155
1156    /**
1157     * Construct a TinyXML object from a File.
1158     */
1159    this(File f)
1160    {
1161       this(f.toString());
1162    }
1163
1164    /**
1165     * Generate formated XML text from a TinyXML object.
1166     */
1167    public char[] XML()
1168    {
1169       char[] s = _xmlDecl ~ "\n";
1170       foreach (char[] c; _outerComments)
1171          s ~= c ~ "\n";
1172       return s ~ _top.format("", 0);
1173    }
1174
1175    /**
1176     * Navigate to (access) an element in the TinyXML object model, setting the context to the found object
1177     * if it is a Tag.
1178     *
1179     * Use as in: XMLElement x = txml << "path_expression";
1180     *
1181     * Params:
1182     *   xpath = A string describing the required destination element.<br>
1183     * Usage:
1184     * The xpath parameter may take the following forms:<br>
1185     * /outer_tagname/tagname1/tagname2 ... - find a Tag element by element names<br>
1186     * ^/tagname1/tagname2 ...    - equivalent to the above
1187     *
1188     * /outer_tagname/tagname1/#[0] - find a Tag by its offset under its parent<br>
1189     * ^/tagname1/#[0] - equivalent
1190     *
1191     * /outer_tagname/tagname1/![0] - find a Comment by its offset under its parent<br>
1192     * ^/tagname1/![0] - equivalent
1193     *
1194     * /outer_tagname/tagname1/'[0] - find a text element by its offset under its parent<br>
1195     * ^/tagname1/'[0] - equivalent
1196     *
1197     * ./... - find an element down a path relative to the current context
1198     */
1199    XMLElement opShl(char[] xpath) { return findElement(xpath, true); }
1200
1201    /**
1202     * Navigate to (access) an element in the TinyXML object model, without setting the context.
1203     *
1204     * Use as in: XMLElement x = txml["path_expression"];
1205     *
1206     * Params:
1207     *   xpath = A string describing the required destination element.<br>
1208     *
1209     * As described for opShl
1210     */
1211    XMLElement opIndex(char[] xpath) { return findElement(xpath, false); }
1212
1213    /**
1214     * Return the Tag that is the current context.
1215     */
1216    Tag Context() { return _context; }
1217
1218    /**
1219     * Create a Tag element.
1220     *
1221     * Params:
1222     *   name = Name of the Tag.
1223     * Returns: The new Tag.
1224     */
1225    Tag makeTag(char[] name)
1226    {
1227       Tag t = new Tag(null, _context);
1228       t._name = name;
1229       return t;
1230    }
1231
1232    /**
1233     * Create a Comment element.
1234     *
1235     * Params:
1236     *   text = Text of the Comment.
1237     * Returns: The new Comment.
1238     */
1239    Comment makeComment(char[] text)
1240    {
1241       Comment c = new Comment(text, _context);
1242       return c;
1243    }
1244
1245    /**
1246     * Create an EText element.
1247     *
1248     * Params:
1249     *   text = Text of the element.
1250     * Returns: The new EText object.
1251     */
1252    EText makeEText(char[] text)
1253    {
1254       EText t = new EText(text, _context);
1255       return t;
1256    }
1257
1258    /**
1259     * Return the name of the top-level Tag.
1260     */
1261    char[] OuterName()
1262    {
1263        return _top._name;
1264    }
1265
1266    /**
1267     * Check if test passed to the constructor parsed correctly.
1268     */
1269    bool ok()
1270    {
1271       return _top._valid;
1272    }
1273
1274    /**
1275     * Get the error message if it did not parse correctly.
1276     */
1277    char[] error() { return _err; }
1278 }
1279
1280 alias TinyXML.AttrList AttrList;
1281 alias TinyXML.EText EText;
1282 alias TinyXML.Comment Comment;
1283 alias TinyXML.Tag Tag;
1284 alias TinyXML.XMLElement XMLElement;
1285
1286 /+
1287 // Here is an example of usage.
1288 char[] src =
1289 "<?xml  version = \"1.0\" encoding=\"utf8\" ?>\n" ~
1290 "<!-- Pre comment 1 -->\n" ~
1291 "<!-- Pre comment 2 -->\n" ~
1292 "<individual name=\"John Doe\" age=\"&lt; 30 &amp; handsome\">\n" ~
1293 "      <!-- A comment -->\n" ~
1294 "      Some element text." ~
1295 "      <street a=\"a\" b=\"b\">22 Washington Ave.        <house></house></street>\n" ~
1296 //"      <street>22 Washington Ave.        <house></house>\n" ~
1297 "      <city><!-- city comment --></city>\n" ~
1298 "      <!-- Another comment -->\n" ~
1299 "      <state>                  NJ                            </state >\n" ~
1300 "      <zip><whatever /></zip>\n" ~
1301 "      Some more element text." ~
1302 "      <list length=\"3\">\n" ~
1303 "         <item>A</item>\n" ~
1304 "         <item>B<!--xxx--></item>\n" ~
1305 "         <item>C</item>\n" ~
1306 "      </list>\n" ~
1307 "      Yet more element text." ~
1308 "</individual >";
1309
1310 void main(char[][] args)
1311 {
1312    TinyXML tx = new TinyXML("<?xml  version = \"1.0\"?><foo bar=\"bar\" />");
1313    writefln(tx.XML());
1314    tx = new TinyXML(src);
1315    if (!tx.ok)
1316    {
1317       writefln(tx.error);
1318       return;
1319    }
1320    writefln(tx.XML);
1321
1322    writefln("1)");
1323    writefln(tx.OuterName);
1324
1325    writefln("2)");
1326    XMLElement e = tx << "/individual/city/!";
1327    writefln(e.PText);
1328    writefln(tx.Context().Name);
1329    e = tx["^/list/#[1]/!"];
1330    writefln(e.PText);
1331
1332    writefln("3)");
1333    e = tx << "^/list";
1334    writefln(e.PText());
1335    writefln(tx.Context().PText);
1336    e = tx["./#[1]/!"];
1337    writefln(e.PText);
1338
1339    writefln("4)");
1340    e = tx << "^/#";
1341    writefln(e.PText);
1342    tx.Context().addBefore(tx.makeComment("Comment added"));
1343    writefln(tx.XML);
1344
1345    writefln("5)");
1346    e = tx["^/#[$]"];
1347    writefln(e.PText);
1348
1349    writefln("6)");
1350    e = tx["^/list/#[1]/![$]"];
1351    writefln(e.PText);
1352
1353    writefln("7)");
1354    e = tx << "^";
1355    writefln(e.PText);
1356    int n = tx.Context.Elements;
1357    for (int i=0; i < n; i++)
1358    {
1359       writefln(tx.Context[i, TinyXML.ALL].PText);
1360    }
1361    writefln("8)");
1362    n = tx.Context.Tags;
1363    for (int i=0; i < n; i++)
1364    {
1365       XMLElement x = tx["./#[" ~ toString(i) ~ "]"];
1366       writefln(x.PText);
1367    }
1368
1369    writefln("9)");
1370    Tag top = tx.Context;
1371    for (int i=0; i < n; i++)
1372    {
1373       Tag y = cast(Tag) top[i, TinyXML.TAG];
1374       writefln(y.PText);
1375    }
1376    writefln(top.FirstText);
1377
1378    writefln("10)");
1379    e = tx["^/street"];
1380    writefln(e.getValue("a"));
1381 }
1382 +/
Note: See TracBrowser for help on using the browser.