Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


added xml parser for configuration
[palacios.git] / palacios / src / palacios / vmm_xml.c
1 /* ezxml.c
2  *
3  * Copyright 2004-2006 Aaron Voisine <aaron@voisine.org>
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining
6  * a copy of this software and associated documentation files (the
7  * "Software"), to deal in the Software without restriction, including
8  * without limitation the rights to use, copy, modify, merge, publish,
9  * distribute, sublicense, and/or sell copies of the Software, and to
10  * permit persons to whom the Software is furnished to do so, subject to
11  * the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included
14  * in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  */
24
25 /* 
26  * Modified for Palacios by Jack Lange <jarusl@cs.northwestern.edu> 
27  */
28
29
30
31 #include <palacios/vmm_xml.h>
32 #include <palacios/vmm_sprintf.h>
33 #include <stdarg.h>
34 #include <palacios/vmm.h>
35
36 #define V3_XML_BUFSIZE 1024 // size of internal memory buffers
37
38 // Flags for struct v3_xml 
39 #define V3_XML_NAMEM   0x80 // name is malloced
40 #define V3_XML_TXTM    0x40 // txt is malloced
41 #define V3_XML_DUP     0x20 // attribute name and value are strduped
42 //
43
44
45 #define V3_XML_WS   "\t\r\n "  // whitespace
46 #define V3_XML_ERRL 128        // maximum error string length
47
48 struct v3_xml_root {       // additional data for the root tag
49     struct v3_xml xml;     // is a super-struct built on top of v3_xml struct
50     struct v3_xml * cur;          // current xml tree insertion point
51     char *str_ptr;         // original xml string
52     char *tmp_start;              // start of work area
53     char *tmp_end;              // end of work area
54     char **ent;           // general entities (ampersand sequences)
55     char ***attr;         // default attributes
56     short standalone;     // non-zero if <?xml standalone="yes"?>
57     char err[V3_XML_ERRL]; // error string
58 };
59
60 static char * empty_attrib_list[] = { NULL }; // empty, null terminated array of strings
61
62
63 static void * tmp_realloc(void * old_ptr, uint_t old_size, uint_t new_size) {
64     void * new_buf = V3_Malloc(new_size);
65
66     if (new_buf == NULL) {
67         return NULL;
68     }
69
70     memcpy(new_buf, old_ptr, old_size);
71     V3_Free(old_ptr);
72
73     return new_buf;
74 }
75
76
77
78
79
80 // set an error string and return root
81 static void v3_xml_err(struct v3_xml_root * root, char * xml_str, const char * err, ...) {
82     va_list ap;
83     int line = 1;
84     char * tmp;
85     char fmt[V3_XML_ERRL];
86     
87     for (tmp = root->tmp_start; tmp < xml_str; tmp++) {
88         if (*tmp == '\n') {
89             line++;
90         }
91     }
92
93     snprintf(fmt, V3_XML_ERRL, "[error near line %d]: %s", line, err);
94
95     va_start(ap, err);
96     vsnprintf(root->err, V3_XML_ERRL, fmt, ap);
97     va_end(ap);
98
99     PrintError("XML Error: %s\n", root->err);
100
101     // free memory
102     v3_xml_free(&(root->xml));
103
104     return;
105 }
106
107
108
109 // returns the first child tag with the given name or NULL if not found
110 struct v3_xml * v3_xml_child(struct v3_xml * xml, const char * name) {
111     struct v3_xml * child = NULL;
112
113     if (xml != NULL) {
114         child = xml->child;
115     } 
116
117     if (child != NULL) {
118         while (strcmp(name, child->name) != 0) {
119             child = child->sibling;
120         }
121     }
122
123     return child;
124 }
125
126 // returns the Nth tag with the same name in the same subsection or NULL if not
127 // found
128 struct v3_xml * v3_xml_idx(struct v3_xml * xml, int idx) {
129     for (; xml && idx; idx--) {
130         xml = xml->next;
131     }
132
133     return xml;
134 }
135
136 // returns the value of the requested tag attribute or NULL if not found
137 const char * v3_xml_attr(struct v3_xml * xml, const char * attr) {
138     int i = 0;
139     int j = 1;
140     struct v3_xml_root * root = (struct v3_xml_root *)xml;
141
142     if ((!xml) || (!xml->attr)) {
143         return NULL;
144     }
145
146     while ((xml->attr[i]) && (strcmp(attr, xml->attr[i]) != 0)) {
147         i += 2;
148     }
149
150     if (xml->attr[i] != NULL) {
151         return xml->attr[i + 1]; // found attribute
152     }
153
154     while (root->xml.parent != NULL) {
155         root = (struct v3_xml_root *)root->xml.parent; // root tag
156     }
157
158     for (i = 0; 
159          ( (root->attr[i] != NULL) && 
160            (strcmp(xml->name, root->attr[i][0]) != 0) ); 
161          i++);
162
163     if (! root->attr[i]) {
164         return NULL; // no matching default attributes
165     }
166
167     while ((root->attr[i][j] != NULL) && (strcmp(attr, root->attr[i][j]) != 0)) {
168         j += 3;
169     }
170
171     return (root->attr[i][j] != NULL) ? root->attr[i][j + 1] : NULL; // found default
172 }
173
174 // same as v3_xml_get but takes an already initialized va_list
175 static struct v3_xml * v3_xml_vget(struct v3_xml * xml, va_list ap) {
176     char * name = va_arg(ap, char *);
177     int idx = -1;
178
179     if ((name != NULL) && (*name != 0)) {
180         idx = va_arg(ap, int);    
181         xml = v3_xml_child(xml, name);
182     }
183     return (idx < 0) ? xml : v3_xml_vget(v3_xml_idx(xml, idx), ap);
184 }
185
186 // Traverses the xml tree to retrieve a specific subtag. Takes a variable
187 // length list of tag names and indexes. The argument list must be terminated
188 // by either an index of -1 or an empty string tag name. Example: 
189 // title = v3_xml_get(library, "shelf", 0, "book", 2, "title", -1);
190 // This retrieves the title of the 3rd book on the 1st shelf of library.
191 // Returns NULL if not found.
192 struct v3_xml * v3_xml_get(struct v3_xml * xml, ...) {
193     va_list ap;
194     struct v3_xml * r;
195
196     va_start(ap, xml);
197     r = v3_xml_vget(xml, ap);
198     va_end(ap);
199     return r;
200 }
201
202
203 // sets a flag for the given tag and returns the tag
204 static struct v3_xml * v3_xml_set_flag(struct v3_xml * xml, short flag)
205 {
206     if (xml) xml->flags |= flag;
207     return xml;
208 }
209
210
211
212
213
214 // Recursively decodes entity and character references and normalizes new lines
215 // ent is a null terminated array of alternating entity names and values. set t
216 // to '&' for general entity decoding, '%' for parameter entity decoding, 'c'
217 // for cdata sections, ' ' for attribute normalization, or '*' for non-cdata
218 // attribute normalization. Returns s, or if the decoded string is longer than
219 // s, returns a malloced string that must be freed.
220 static char * v3_xml_decode(char * s, char ** ent, char t) {
221     char * e;
222     char * r = s;
223     char * m = s;
224     long b, c, d, l;
225
226     // normalize line endings
227     for (; *s; s++) { 
228         while (*s == '\r') {
229             *(s++) = '\n';
230
231             if (*s == '\n') {
232                 memmove(s, (s + 1), strlen(s));
233             }
234         }
235     }
236     
237     for (s = r; ; ) {
238
239         while ( (*s) && 
240                 (*s != '&') && 
241                 ( (*s != '%') || 
242                   (t != '%')) && 
243                 (!isspace(*s))) {
244             s++;
245         }
246
247         if (*s == '\0') {
248             break;
249         }
250
251         if ((t != 'c') && (strncmp(s, "&#", 2) == 0)) { // character reference
252             if (s[2] == 'x') {
253                 c = strtox(s + 3, &e); // base 16
254             } else {
255                 c = strtoi(s + 2, &e); // base 10
256             }
257
258             if ((!c) || (*e != ';')) { 
259                 // not a character ref
260                 s++; 
261                 continue;
262             }
263
264             *(s++) = c; 
265
266             memmove(s, strchr(s, ';') + 1, strlen(strchr(s, ';')));
267         } else if ( ( (*s == '&') && 
268                       ((t == '&') || (t == ' ') || (t == '*'))) ||
269                     ( (*s == '%') && (t == '%'))) { 
270             // entity reference`
271
272             for ( (b = 0); 
273                   (ent[b]) && (strncmp(s + 1, ent[b], strlen(ent[b])) != 0);
274                   (b += 2)); // find entity in entity list
275
276             if (ent[b++]) { // found a match
277                 if (((c = strlen(ent[b])) - 1) > ((e = strchr(s, ';')) - s)) {
278                     l = (d = (s - r)) + c + strlen(e); // new length
279                     r = ((r == m) ? strcpy(V3_Malloc(l), r) : tmp_realloc(r, strlen(r), l));
280                     e = strchr((s = r + d), ';'); // fix up pointers
281                 }
282
283                 memmove(s + c, e + 1, strlen(e)); // shift rest of string
284                 strncpy(s, ent[b], c); // copy in replacement text
285             } else {
286                 // not a known entity
287                 s++;
288             }
289         } else if ( ( (t == ' ') || (t == '*')) && 
290                     (isspace(*s))) {
291             *(s++) = ' ';
292         } else {
293             // no decoding needed
294             s++;
295         }
296     }
297
298     if (t == '*') { 
299         // normalize spaces for non-cdata attributes
300         for (s = r; *s; s++) {
301             if ((l = strspn(s, " "))) {
302                 memmove(s, s + l, strlen(s + l) + 1);
303             }
304
305             while ((*s) && (*s != ' ')) {
306                 s++;
307             }
308         }
309
310         if ((--s >= r) && (*s == ' ')) {
311             // trim any trailing space
312             *s = '\0'; 
313         }
314     }
315     return r;
316 }
317
318
319
320 // called when parser finds character content between open and closing tag
321 static void v3_xml_char_content(struct v3_xml_root * root, char * s, size_t len, char t) {
322     struct v3_xml * xml = root->cur;
323     char * m = s;
324     size_t l = 0;
325
326     if ((xml == NULL) || (xml->name == NULL) || (len == 0)) {
327         // sanity check
328         return;
329     }
330
331     s[len] = '\0'; // null terminate text (calling functions anticipate this)
332     len = strlen(s = v3_xml_decode(s, root->ent, t)) + 1;
333
334     if (! *(xml->txt)) {
335         // initial character content
336         xml->txt = s;
337     } else { 
338         // allocate our own memory and make a copy
339         xml->txt = (xml->flags & V3_XML_TXTM) ? 
340             (tmp_realloc(xml->txt, strlen(xml->txt), (l = strlen(xml->txt)) + len)) : 
341             (strcpy(V3_Malloc((l = strlen(xml->txt)) + len), xml->txt));
342
343         strcpy(xml->txt + l, s); // add new char content
344         
345         if (s != m) {
346             V3_Free(s); // free s if it was malloced by v3_xml_decode()
347         }
348     }
349
350     if (xml->txt != m) {
351         v3_xml_set_flag(xml, V3_XML_TXTM);
352     }
353 }
354
355 // called when parser finds closing tag
356 static int v3_xml_close_tag(struct v3_xml_root * root, char * name, char * s) {
357     if ( (root->cur == NULL) || 
358          (root->cur->name == NULL) || 
359          (strcmp(name, root->cur->name))) {
360         v3_xml_err(root, s, "unexpected closing tag </%s>", name);
361         return -1;
362     }
363
364     root->cur = root->cur->parent;
365     return 0;
366 }
367
368 // checks for circular entity references, returns non-zero if no circular
369 // references are found, zero otherwise
370 static int v3_xml_ent_ok(char * name, char * s, char ** ent) {
371     int i;
372
373     for (; ; s++) {
374         while ((*s != '\0') && (*s != '&')) {
375             // find next entity reference
376             s++; 
377         }
378
379         if (*s == '\0') {
380             return 1;
381         }
382
383         if (strncmp(s + 1, name, strlen(name)) == 0) {
384             // circular ref.
385             return 0;
386         }
387
388         for (i = 0; (ent[i]) && (strncmp(ent[i], s + 1, strlen(ent[i]))); i += 2);
389
390         if ((ent[i] != NULL) && (v3_xml_ent_ok(name, ent[i + 1], ent) == 0)) {
391             return 0;
392         }
393     }
394 }
395
396
397
398 // frees a tag attribute list
399 static void v3_xml_free_attr(char **attr) {
400     int i = 0;
401     char * m;
402     
403     if ((attr == NULL) || (attr == empty_attrib_list)) {
404         // nothing to free
405         return; 
406     }
407
408     while (attr[i]) {
409         // find end of attribute list
410         i += 2;
411     }
412
413     m = attr[i + 1]; // list of which names and values are malloced
414
415     for (i = 0; m[i]; i++) {
416         if (m[i] & V3_XML_NAMEM) {
417             V3_Free(attr[i * 2]);
418         }
419
420         if (m[i] & V3_XML_TXTM) {
421             V3_Free(attr[(i * 2) + 1]);
422         }
423     }
424
425     V3_Free(m);
426     V3_Free(attr);
427 }
428
429
430
431
432
433
434 // returns a new empty v3_xml structure with the given root tag name
435 static struct v3_xml * v3_xml_new(const char * name) {
436     static char * ent[] = { "lt;", "&#60;", "gt;", "&#62;", "quot;", "&#34;",
437                            "apos;", "&#39;", "amp;", "&#38;", NULL };
438
439     struct v3_xml_root * root = (struct v3_xml_root *)V3_Malloc(sizeof(struct v3_xml_root));
440     memset(root, 0, sizeof(struct v3_xml_root));
441
442     root->xml.name = (char *)name;
443     root->cur = &root->xml;
444     root->xml.txt = "";
445     memset(root->err, 0, V3_XML_ERRL);
446
447     root->ent = V3_Malloc(sizeof(ent));
448     memcpy(root->ent, ent, sizeof(ent));
449
450     root->xml.attr = empty_attrib_list;
451     root->attr = (char ***)(empty_attrib_list);
452
453     return &root->xml;
454 }
455
456 // inserts an existing tag into an v3_xml structure
457 static struct v3_xml * v3_xml_insert(struct v3_xml * xml, struct v3_xml * dest, size_t off) {
458     struct v3_xml * cur, * prev, * head;
459
460     xml->next = NULL;
461     xml->sibling = NULL;
462     xml->ordered = NULL;
463     
464     xml->off = off;
465     xml->parent = dest;
466
467     if ((head = dest->child)) { 
468         // already have sub tags
469
470         if (head->off <= off) {
471             // not first subtag
472
473             for (cur = head; 
474                  ((cur->ordered) && (cur->ordered->off <= off));
475                  cur = cur->ordered);
476
477             xml->ordered = cur->ordered;
478             cur->ordered = xml;
479         } else { 
480             // first subtag
481             xml->ordered = head;
482             dest->child = xml;
483         }
484
485         // find tag type
486         for (cur = head, prev = NULL; 
487              ((cur) && (strcmp(cur->name, xml->name) != 0));
488              prev = cur, cur = cur->sibling); 
489
490
491         if (cur && cur->off <= off) { 
492             // not first of type
493             
494             while (cur->next && cur->next->off <= off) {
495                 cur = cur->next;
496             }
497
498             xml->next = cur->next;
499             cur->next = xml;
500         } else { 
501             // first tag of this type
502             
503             if (prev && cur) {
504                 // remove old first
505                 prev->sibling = cur->sibling;
506             }
507
508             xml->next = cur; // old first tag is now next
509
510             // new sibling insert point
511             for (cur = head, prev = NULL; 
512                  ((cur) && (cur->off <= off));
513                  prev = cur, cur = cur->sibling);
514             
515             xml->sibling = cur;
516
517             if (prev) {
518                 prev->sibling = xml;
519             }
520         }
521     } else {
522         // only sub tag
523         dest->child = xml;
524     }
525
526     return xml;
527 }
528
529
530 // Adds a child tag. off is the offset of the child tag relative to the start
531 // of the parent tag's character content. Returns the child tag.
532 static struct v3_xml * v3_xml_add_child(struct v3_xml * xml, const char * name, size_t off) {
533     struct v3_xml * child;
534
535     if (xml == NULL) {
536         return NULL;
537     }
538
539     child = (struct v3_xml *)V3_Malloc(sizeof(struct v3_xml));
540     memset(child, 0, sizeof(struct v3_xml));
541
542     child->name = (char *)name;
543     child->attr = empty_attrib_list;
544     child->txt = "";
545
546     return v3_xml_insert(child, xml, off);
547 }
548
549
550 // called when parser finds start of new tag
551 static void v3_xml_open_tag(struct v3_xml_root * root, char * name, char ** attr) {
552     struct v3_xml * xml = root->cur;
553     
554     if (xml->name) {
555         xml = v3_xml_add_child(xml, name, strlen(xml->txt));
556     } else {
557         // first open tag
558         xml->name = name; 
559     }
560
561     xml->attr = attr;
562     root->cur = xml; // update tag insertion point
563 }
564
565
566
567
568
569
570
571 // parse the given xml string and return an v3_xml structure
572 struct v3_xml * v3_xml_parse_str(char * buf, size_t len) {
573     struct v3_xml_root * root = (struct v3_xml_root *)v3_xml_new(NULL);
574     char quote_char;
575     char last_char; 
576     char * tag_ptr;
577     char ** attr; 
578     char ** tmp_attr = NULL; // initialize a to avoid compile warning
579     int attr_idx;
580     int i, j;
581
582     root->str_ptr = buf;
583
584     if (len == 0) {
585         v3_xml_err(root, NULL, "Empty XML String\n");
586         return NULL;
587     }
588
589     root->tmp_start = buf;
590     root->tmp_end = buf + len; // record start and end of work area
591     
592     last_char = buf[len - 1]; // save end char
593     buf[len - 1] = '\0'; // turn end char into null terminator
594
595     while ((*buf) && (*buf != '<')) {
596         // find first tag
597         buf++; 
598     }
599
600     if (*buf == '\0') {
601         v3_xml_err(root, buf, "root tag missing");
602         return NULL;
603     }
604
605     for (; ; ) {
606         attr = (char **)empty_attrib_list;
607         tag_ptr = ++buf; // skip first '<'
608         
609         if (isalpha(*buf) || (*buf == '_') || (*buf == ':') || (*buf < '\0')) {
610             // new tag
611
612             if (root->cur == NULL) {
613                 v3_xml_err(root, tag_ptr, "markup outside of root element");
614                 return NULL;
615             }
616
617             buf += strcspn(buf, V3_XML_WS "/>");
618
619             while (isspace(*buf)) {
620                 // null terminate tag name, 
621                 // this writes '\0' to spaces after first tag 
622                 *(buf++) = '\0';
623             }
624
625             // check if attribute follows tag
626             if ((*buf) && (*buf != '/') && (*buf != '>')) {
627                 // there is an attribute
628                 // find attributes for correct tag
629                 for ((i = 0); 
630                      ((tmp_attr = root->attr[i]) && 
631                       (strcmp(tmp_attr[0], tag_ptr) != 0)); 
632                      (i++)) ;
633                 
634                 // 'tmp_attr' now points to the attribute list associated with 'tag_ptr'
635             }
636
637             // attributes are name value pairs, 
638             //     2nd to last entry is null  (end of list)
639             //     last entry points to a string map marking whether values have been malloced...
640             // loop through attributes until hitting the closing bracket
641             for (attr_idx = 0; 
642                  (*buf) && (*buf != '/') && (*buf != '>'); 
643                  attr_idx += 2) {
644                 // buf is incremented later on
645                 // new attrib
646                 int attr_cnt = (attr_idx / 2) + 1;
647                 int val_idx = attr_idx + 1;
648                 int term_idx = attr_idx + 2;
649                 int last_idx = attr_idx + 3;
650
651                 // attr = allocated space
652                 // attr[val_idx] = mem for list of maloced vals
653                 if (attr_cnt > 1) {
654                     attr = tmp_realloc(attr,
655                                        (((attr_cnt - 1) * (2 * sizeof(char *))) + 
656                                         (2 * sizeof(char *))), 
657                                        ((attr_cnt * (2 * sizeof(char *))) + 
658                                         (2 * sizeof(char *))));
659                 
660                     attr[last_idx] = tmp_realloc(attr[last_idx - 2], 
661                                                  attr_cnt,
662                                                  (attr_cnt + 1)); 
663                 } else {
664                     attr = V3_Malloc(4 * sizeof(char *)); 
665                     attr[last_idx] = V3_Malloc(2);
666                 }
667                 
668
669                 attr[attr_idx] = buf; // set attribute name
670                 attr[val_idx] = ""; // temporary attribute value
671                 attr[term_idx] = NULL; // null terminate list
672                 strcpy(attr[last_idx] + attr_cnt, " "); // value is not malloc'd, offset into the stringmap
673
674                 buf += strcspn(buf, V3_XML_WS "=/>");
675
676                 if ((*buf == '=') || isspace(*buf)) {
677                     
678                     *(buf++) = '\0'; // null terminate tag attribute name
679                     
680                     // eat whitespace (and more multiple '=' ?)
681                     buf += strspn(buf, V3_XML_WS "=");
682
683                     quote_char = *buf;
684
685                     if ((quote_char == '"') || (quote_char == '\'')) { // attribute value
686                         attr[val_idx] = ++buf;
687
688                         while ((*buf) && (*buf != quote_char)) {
689                             buf++;
690                         }
691
692                         if (*buf) {
693                             // null terminate attribute val
694                             *(buf++) = '\0';
695                         } else {
696                             v3_xml_free_attr(attr);
697                             v3_xml_err(root, tag_ptr, "missing %c", quote_char);
698                             return NULL;
699                         }
700
701                         for (j = 1; 
702                              ( (tmp_attr) && (tmp_attr[j]) && 
703                                (strcmp(tmp_attr[j], attr[attr_idx]) != 0)); 
704                              j += 3);
705
706                         attr[val_idx] = v3_xml_decode(attr[val_idx], root->ent, 
707                                                       ((tmp_attr && tmp_attr[j]) ? 
708                                                        *tmp_attr[j + 2] : 
709                                                        ' '));
710                         
711                         if ( (attr[val_idx] < tag_ptr) || 
712                              (attr[val_idx] > buf) ) {
713                             attr[last_idx][attr_cnt - 1] = V3_XML_TXTM; // value malloced
714                         }
715                     }
716                 }
717
718                 while (isspace(*buf)) {
719                     buf++;
720                 }
721             }
722
723             if (*buf == '/') { 
724                 // self closing tag
725                 *(buf++) = '\0';
726                 
727                 if ( ((*buf) && (*buf != '>')) || 
728                      ((!*buf) && (last_char != '>'))) {
729
730                     if (attr_idx > 0) {
731                         v3_xml_free_attr(attr);
732                     }
733                     v3_xml_err(root, tag_ptr, "missing >");
734                     return NULL;
735                 }
736                 v3_xml_open_tag(root, tag_ptr, attr);
737                 v3_xml_close_tag(root, tag_ptr, buf);
738             } else if (((quote_char = *buf) == '>') || 
739                        ((!*buf) && (last_char == '>'))) {
740                 // open tag
741                 *buf = '\0'; // temporarily null terminate tag name
742                 v3_xml_open_tag(root, tag_ptr, attr);
743                 *buf = quote_char;
744             } else {
745                 if (attr_idx > 0) {
746                     v3_xml_free_attr(attr);
747                 }
748                 v3_xml_err(root, tag_ptr, "missing >"); 
749                 return NULL;
750             }
751         } else if (*buf == '/') { 
752             // close tag
753             
754             buf += strcspn(tag_ptr = buf + 1, V3_XML_WS ">") + 1;
755             
756             quote_char = *buf;
757             if ((*buf == '\0') && (last_char != '>')) {
758                 v3_xml_err(root, tag_ptr, "missing >");
759                 return NULL;
760             }
761
762             *buf = '\0'; // temporarily null terminate tag name
763             
764             if (v3_xml_close_tag(root, tag_ptr, buf) == -1) {
765                 return NULL;
766             }
767
768             *buf = quote_char;
769             if (isspace(*buf)) {
770                 buf += strspn(buf, V3_XML_WS);
771             }
772         } else if (strncmp(buf, "!--", 3) == 0) {
773             // xml comment
774             if ( ((buf = strstr(buf + 3, "--")) == 0) || 
775                  ((*(buf += 2) != '>') && (*buf)) ||
776                  ((!*buf) && (last_char != '>'))) {
777                 v3_xml_err(root, tag_ptr, "unclosed <!--");
778                 return NULL;
779             }
780         } else if (! strncmp(buf, "![CDATA[", 8)) { 
781             // cdata
782             if ((buf = strstr(buf, "]]>"))) {
783                 v3_xml_char_content(root, tag_ptr + 8, (buf += 2) - tag_ptr - 10, 'c');
784             } else {
785                 v3_xml_err(root, tag_ptr, "unclosed <![CDATA[");
786                 return NULL;
787             }
788         } else {
789             v3_xml_err(root, tag_ptr, "unexpected <");
790             return NULL;
791         }
792
793         if (! buf || ! *buf) {
794             break;
795         }
796
797         *buf = '\0';
798         tag_ptr = ++buf;
799
800         if (*buf && (*buf != '<')) { 
801             // tag character content
802             while (*buf && (*buf != '<')) {
803                 buf++;
804             }
805
806             if (*buf) { 
807                 v3_xml_char_content(root, tag_ptr, buf - tag_ptr, '&');
808             } else {
809                 break;
810             }
811         } else if (*buf == '\0') {
812             break;
813         }
814     }
815
816     if (root->cur == NULL) {
817         return &root->xml;
818     } else if (root->cur->name == NULL) {
819         v3_xml_err(root, tag_ptr, "root tag missing");
820         return NULL;
821     } else {
822         v3_xml_err(root, tag_ptr, "unclosed tag <%s>", root->cur->name);
823         return NULL;
824     }
825 }
826
827
828
829
830
831
832 // free the memory allocated for the v3_xml structure
833 void v3_xml_free(struct v3_xml * xml) {
834     struct v3_xml_root * root = (struct v3_xml_root *)xml;
835     int i, j;
836     char **a, *s;
837
838     if (xml == NULL) {
839         return;
840     }
841
842     v3_xml_free(xml->child);
843     v3_xml_free(xml->ordered);
844
845     if (xml->parent == NULL) { 
846         // free root tag allocations
847         
848         for (i = 10; root->ent[i]; i += 2) {
849             // 0 - 9 are default entites (<>&"')
850             if ((s = root->ent[i + 1]) < root->tmp_start || s > root->tmp_end) {
851                 V3_Free(s);
852             }
853         }
854
855         V3_Free(root->ent); // free list of general entities
856
857         for (i = 0; (a = root->attr[i]); i++) {
858             for (j = 1; a[j++]; j += 2) {
859                 // free malloced attribute values
860                 if (a[j] && (a[j] < root->tmp_start || a[j] > root->tmp_end)) {
861                     V3_Free(a[j]);
862                 }
863             }
864             V3_Free(a);
865         }
866
867         if (root->attr[0]) {
868             // free default attribute list
869             V3_Free(root->attr);
870         }
871
872         V3_Free(root->str_ptr); // malloced xml data
873
874     }
875
876     v3_xml_free_attr(xml->attr); // tag attributes
877
878     if ((xml->flags & V3_XML_TXTM)) {
879         // character content
880         V3_Free(xml->txt); 
881     }
882
883     if ((xml->flags & V3_XML_NAMEM)) {
884         // tag name
885         V3_Free(xml->name);
886     }
887
888     V3_Free(xml);
889 }
890