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.


Cleanup and fixes based on Coverity pass
[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 static char * V3_XML_NIL[] = { NULL }; // empty, null terminated array of strings
45
46
47 #define V3_XML_WS   "\t\r\n "  // whitespace
48 #define V3_XML_ERRL 128        // maximum error string length
49
50 struct v3_xml_root {       // additional data for the root tag
51     struct v3_xml xml;     // is a super-struct built on top of v3_xml struct
52     struct v3_xml * cur;          // current xml tree insertion point
53     char *str_ptr;         // original xml string
54     char *tmp_start;              // start of work area
55     char *tmp_end;              // end of work area
56     short standalone;     // non-zero if <?xml standalone="yes"?>
57 };
58
59 static char * empty_attrib_list[] = { NULL }; // empty, null terminated array of strings
60
61
62
63 static void * tmp_realloc(void * old_ptr, size_t old_size, size_t new_size) {
64     void * new_buf = NULL; 
65
66     new_buf = V3_Malloc(new_size);
67     
68     if (new_buf == NULL) {
69         PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in tmp_realloc in xml\n");
70         return NULL;
71     }
72
73     memset(new_buf, 0, new_size);
74
75     memcpy(new_buf, old_ptr, old_size);
76     V3_Free(old_ptr);
77
78     return new_buf;
79 }
80
81 // set an error string and return root
82 static void v3_xml_err(struct v3_xml_root * root, char * xml_str, const char * err, const char *arg) {
83     int line = 1;
84     char * tmp;
85     
86     for (tmp = root->tmp_start; tmp < xml_str; tmp++) {
87         if (*tmp == '\n') {
88             line++;
89         }
90     }
91
92     PrintError(VM_NONE, VCORE_NONE, "XML Error: [error near line %d]: %s (%s)", line, err ,arg ? arg : "");
93     
94     // free memory
95     v3_xml_free(&(root->xml));
96
97     return;
98 }
99
100
101
102 // returns the first child tag with the given name or NULL if not found
103 struct v3_xml * v3_xml_child(struct v3_xml * xml, const char * name) {
104     struct v3_xml * child = NULL;
105
106     if (xml != NULL) {
107         child = xml->child;
108     }
109
110     while ((child) && (strcasecmp(name, child->name) != 0)) {
111         child = child->sibling;
112     }
113
114     return child;
115 }
116
117 // returns the Nth tag with the same name in the same subsection or NULL if not
118 // found
119 struct v3_xml * v3_xml_idx(struct v3_xml * xml, int idx) {
120     for (; xml && idx; idx--) {
121         xml = xml->next;
122     }
123
124     return xml;
125 }
126
127 // returns the value of the requested tag attribute or NULL if not found
128 const char * v3_xml_attr(struct v3_xml * xml, const char * attr) {
129     int i = 0;
130
131     if ((!xml) || (!xml->attr)) {
132         return NULL;
133     }
134
135     while ((xml->attr[i]) && (strcasecmp(attr, xml->attr[i]) != 0)) {
136         i += 2;
137     }
138
139     if (xml->attr[i] != NULL) {
140         return xml->attr[i + 1]; // found attribute
141     }
142
143     return NULL; // found default
144 }
145
146
147
148
149 // sets a flag for the given tag and returns the tag
150 static struct v3_xml * v3_xml_set_flag(struct v3_xml * xml, short flag)
151 {
152     if (xml) {
153         xml->flags |= flag;
154     }
155     return xml;
156 }
157
158
159
160
161
162 // Recursively decodes entity and character references and normalizes new lines
163 // ent is a null terminated array of alternating entity names and values. set t
164 // to '&' for general entity decoding, '%' for parameter entity decoding, 'c'
165 // for cdata sections, ' ' for attribute normalization, or '*' for non-cdata
166 // attribute normalization. Returns s, or if the decoded string is longer than
167 // s, returns a malloced string that must be freed.
168 static char * v3_xml_decode(char * s, char t) {
169     char * e;
170     char * r = s;
171     long c, l;
172
173     // normalize line endings
174     for (; *s; s++) { 
175         while (*s == '\r') {
176             *(s++) = '\n';
177
178             if (*s == '\n') {
179                 memmove(s, (s + 1), strlen(s));
180             }
181         }
182     }
183     
184     for (s = r; ; ) {
185
186         while ( (*s) && 
187                 (*s != '&') && 
188                 ( (*s != '%') || 
189                   (t != '%')) && 
190                 (!isspace(*s))) {
191             s++;
192         }
193
194         if (*s == '\0') {
195             break;
196         }
197
198         if ((t != 'c') && (strncmp(s, "&#", 2) == 0)) { // character reference
199             if (s[2] == 'x') {
200                 c = strtox(s + 3, &e); // base 16
201             } else {
202                 c = strtoi(s + 2, &e); // base 10
203             }
204
205             if ((!c) || (*e != ';')) { 
206                 // not a character ref
207                 s++; 
208                 continue;
209             }
210
211             *(s++) = c; 
212
213             memmove(s, strchr(s, ';') + 1, strlen(strchr(s, ';')));
214         } else if ( ( (t == ' ') || (t == '*')) && 
215                     (isspace(*s))) {
216             *(s++) = ' ';
217         } else {
218             // no decoding needed
219             s++;
220         }
221     }
222
223     if (t == '*') { 
224         // normalize spaces for non-cdata attributes
225         for (s = r; *s; s++) {
226             if ((l = strspn(s, " "))) {
227                 memmove(s, s + l, strlen(s + l) + 1);
228             }
229
230             while ((*s) && (*s != ' ')) {
231                 s++;
232             }
233         }
234
235         if ((--s >= r) && (*s == ' ')) {
236             // trim any trailing space
237             *s = '\0'; 
238         }
239     }
240     return r;
241 }
242
243
244
245 // called when parser finds character content between open and closing tag
246 static void v3_xml_char_content(struct v3_xml_root * root, char * s, size_t len, char t) {
247     struct v3_xml * xml = root->cur;
248     char * m = s;
249     size_t l = 0;
250
251     if ((xml == NULL) || (xml->name == NULL) || (len == 0)) {
252         // sanity check
253         return;
254     }
255
256     s[len] = '\0'; // null terminate text (calling functions anticipate this)
257     len = strlen(s = v3_xml_decode(s, t)) + 1;
258
259     if (xml->txt[0] == '\0') { // empty string
260         // initial character content
261         xml->txt = s;
262     } else { 
263
264         // allocate our own memory and make a copy
265         if (xml->flags & V3_XML_TXTM) {
266             xml->txt = (tmp_realloc(xml->txt, strlen(xml->txt), (l = strlen(xml->txt)) + len));
267         } else {
268             char * tmp = NULL;
269
270             tmp = V3_Malloc((l = strlen(xml->txt)) + len);
271
272             if (!tmp) {
273                 PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml char content\n");
274                 return ;
275             }
276
277             strcpy(tmp, xml->txt);
278             xml->txt = tmp;
279         }
280
281         strcpy(xml->txt + l, s); // add new char content
282         
283         if (s != m) {
284             V3_Free(s); // free s if it was malloced by v3_xml_decode()
285         }
286     }
287
288     if (xml->txt != m) {
289         v3_xml_set_flag(xml, V3_XML_TXTM);
290     }
291 }
292
293 // called when parser finds closing tag
294 static int v3_xml_close_tag(struct v3_xml_root * root, char * name, char * s) {
295     if ( (root->cur == NULL) || 
296          (root->cur->name == NULL) || 
297          (strcasecmp(name, root->cur->name))) {
298         v3_xml_err(root, s, "unexpected closing tag", name);
299         return -1;
300     }
301
302     root->cur = root->cur->parent;
303     return 0;
304 }
305
306
307 // frees a tag attribute list
308 static void v3_xml_free_attr(char **attr) {
309     int i = 0;
310     char * m;
311     
312     if ((attr == NULL) || (attr == empty_attrib_list)) {
313         // nothing to free
314         return; 
315     }
316
317     while (attr[i]) {
318         // find end of attribute list
319         i += 2;
320     }
321
322     m = attr[i + 1]; // list of which names and values are malloced
323
324     V3_Free(m);
325     V3_Free(attr);
326 }
327
328
329
330
331
332
333 // returns a new empty v3_xml structure with the given root tag name
334 static struct v3_xml * v3_xml_new(const char * name) {
335
336     struct v3_xml_root * root = (struct v3_xml_root *)V3_Malloc(sizeof(struct v3_xml_root));
337
338     if (!root) {
339         PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml_new\n");
340         return NULL;
341     }
342
343     memset(root, 0, sizeof(struct v3_xml_root));
344
345     root->xml.name = (char *)name;
346     root->cur = &root->xml;
347     root->xml.txt = "";
348
349
350     return &root->xml;
351 }
352
353 // inserts an existing tag into an v3_xml structure
354 struct v3_xml * v3_xml_insert(struct v3_xml * xml, struct v3_xml * dest, size_t off) {
355     struct v3_xml * cur, * prev, * head;
356
357     xml->next = NULL;
358     xml->sibling = NULL;
359     xml->ordered = NULL;
360     
361     xml->off = off;
362     xml->parent = dest;
363
364     if ((head = dest->child)) { 
365         // already have sub tags
366
367         if (head->off <= off) {
368             // not first subtag
369
370             for (cur = head; 
371                  ((cur->ordered) && (cur->ordered->off <= off));
372                  cur = cur->ordered);
373
374             xml->ordered = cur->ordered;
375             cur->ordered = xml;
376         } else { 
377             // first subtag
378             xml->ordered = head;
379             dest->child = xml;
380         }
381
382         // find tag type
383         for (cur = head, prev = NULL; 
384              ((cur) && (strcasecmp(cur->name, xml->name) != 0));
385              prev = cur, cur = cur->sibling); 
386
387
388         if (cur && cur->off <= off) { 
389             // not first of type
390             
391             while (cur->next && cur->next->off <= off) {
392                 cur = cur->next;
393             }
394
395             xml->next = cur->next;
396             cur->next = xml;
397         } else { 
398             // first tag of this type
399             
400             if (prev && cur) {
401                 // remove old first
402                 prev->sibling = cur->sibling;
403             }
404
405             xml->next = cur; // old first tag is now next
406
407             // new sibling insert point
408             for (cur = head, prev = NULL; 
409                  ((cur) && (cur->off <= off));
410                  prev = cur, cur = cur->sibling);
411             
412             xml->sibling = cur;
413
414             if (prev) {
415                 prev->sibling = xml;
416             }
417         }
418     } else {
419         // only sub tag
420         dest->child = xml;
421     }
422
423     return xml;
424 }
425
426
427 // Adds a child tag. off is the offset of the child tag relative to the start
428 // of the parent tag's character content. Returns the child tag.
429 static struct v3_xml * v3_xml_add_child(struct v3_xml * xml, const char * name, size_t off) {
430     struct v3_xml * child;
431
432     if (xml == NULL) {
433         return NULL;
434     }
435
436     child = (struct v3_xml *)V3_Malloc(sizeof(struct v3_xml));
437
438     if (!child) {
439         PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml_add_child\n");
440         return NULL;
441     }
442
443     memset(child, 0, sizeof(struct v3_xml));
444
445     child->name = (char *)name;
446     child->attr = empty_attrib_list;
447     child->txt = "";
448
449     return v3_xml_insert(child, xml, off);
450 }
451
452
453 // called when parser finds start of new tag
454 static void v3_xml_open_tag(struct v3_xml_root * root, char * name, char ** attr) {
455     struct v3_xml * xml = root->cur;
456     
457     if (xml->name) {
458         xml = v3_xml_add_child(xml, name, strlen(xml->txt));
459     } else {
460         // first open tag
461         xml->name = name; 
462     }
463
464     xml->attr = attr;
465     root->cur = xml; // update tag insertion point
466 }
467
468
469
470
471
472
473
474 // parse the given xml string and return an v3_xml structure
475 static struct v3_xml * parse_str(char * buf, size_t len) {
476     struct v3_xml_root * root = (struct v3_xml_root *)v3_xml_new(NULL);
477     char quote_char;
478     char last_char; 
479     char * tag_ptr;
480     char ** attr; 
481     int attr_idx;
482
483     if (buf==NULL) {
484       return NULL;
485     }
486
487     if (root==NULL) {
488       return NULL;
489     }
490
491     root->str_ptr = buf;
492
493     if (len == 0) {
494         v3_xml_err(root, NULL, "Empty XML String", 0);
495         return NULL;
496     }
497
498     root->tmp_start = buf;
499     root->tmp_end = buf + len; // record start and end of work area
500     
501     last_char = buf[len - 1]; // save end char
502     buf[len - 1] = '\0'; // turn end char into null terminator
503
504     while ((*buf) && (*buf != '<')) {
505         // find first tag
506         buf++; 
507     }
508
509     if (*buf == '\0') {
510         v3_xml_err(root, buf, "root tag missing", 0);
511         return NULL;
512     }
513
514     for (; ; ) {
515         attr = (char **)empty_attrib_list;
516         tag_ptr = ++buf; // skip first '<'
517         
518         if (isalpha(*buf) || (*buf == '_') || (*buf == ':') || (*buf < '\0')) {
519             // new tag
520
521             if (root->cur == NULL) {
522                 v3_xml_err(root, tag_ptr, "markup outside of root element", 0);
523                 return NULL;
524             }
525
526             buf += strcspn(buf, V3_XML_WS "/>");
527
528             while (isspace(*buf)) {
529                 // null terminate tag name, 
530                 // this writes '\0' to spaces after first tag 
531                 *(buf++) = '\0';
532             }
533
534         
535
536             // attributes are name value pairs, 
537             //     2nd to last entry is null  (end of list)
538             //     last entry points to a string map marking whether values have been malloced...
539             // loop through attributes until hitting the closing bracket
540             for (attr_idx = 0; 
541                  (*buf) && (*buf != '/') && (*buf != '>'); 
542                  attr_idx += 2) {
543                 // buf is incremented later on
544                 // new attrib
545                 int attr_cnt = (attr_idx / 2) + 1;
546                 int val_idx = attr_idx + 1;
547                 int term_idx = attr_idx + 2;
548                 int last_idx = attr_idx + 3;
549
550                 // attr = allocated space
551                 // attr[val_idx] = mem for list of maloced vals
552                 if (attr_cnt > 1) {
553                     attr = tmp_realloc(attr,
554                                        (((attr_cnt - 1) * (2 * sizeof(char *))) + 
555                                         (2 * sizeof(char *))), 
556                                        ((attr_cnt * (2 * sizeof(char *))) + 
557                                         (2 * sizeof(char *))));
558
559                     if (!attr) {
560                         PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml parse string\n");
561                         return NULL;
562                     }
563
564                     attr[last_idx] = tmp_realloc(attr[last_idx - 2], 
565                                                  attr_cnt,
566                                                  (attr_cnt + 1)); 
567
568                     if (!attr[last_idx]) {
569                         PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml parse string\n");
570                         return NULL;
571                     }
572
573                 } else {
574                     attr = V3_Malloc(4 * sizeof(char *)); 
575                     if (!attr) {
576                         PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml parse string\n");
577                         return NULL;
578                     }
579                     attr[last_idx] = V3_Malloc(2);
580                     if (!attr[last_idx]) {
581                         PrintError(VM_NONE, VCORE_NONE, "Cannot alloocate in xml parse string\n");
582                         return NULL;
583                     }
584                 }
585
586                 attr[attr_idx] = buf; // set attribute name
587                 attr[val_idx] = ""; // temporary attribute value
588                 attr[term_idx] = NULL; // null terminate list
589                 strcpy(attr[last_idx] + attr_cnt, " "); // value is not malloc'd, offset into the stringmap
590
591                 buf += strcspn(buf, V3_XML_WS "=/>");
592
593                 if ((*buf == '=') || isspace(*buf)) {
594
595                     *(buf++) = '\0'; // null terminate tag attribute name
596
597                     // eat whitespace (and more multiple '=' ?)
598                     buf += strspn(buf, V3_XML_WS "=");
599
600                     quote_char = *buf;
601
602                     if ((quote_char == '"') || (quote_char == '\'')) { // attribute value
603                         attr[val_idx] = ++buf;
604
605                         while ((*buf) && (*buf != quote_char)) {
606                             buf++;
607                         }
608
609                         if (*buf) {
610                             // null terminate attribute val
611                             *(buf++) = '\0';
612                         } else {
613                             char err_buf[2] = {quote_char,0};
614
615                             v3_xml_free_attr(attr);
616                             v3_xml_err(root, tag_ptr, "missing quote char", err_buf);
617                             return NULL;
618                         }
619
620                         attr[val_idx] = v3_xml_decode(attr[val_idx], ' ');
621                     }
622                 }
623
624                 while (isspace(*buf)) {
625                     buf++;
626                 }
627             }
628
629             if (*buf == '/') { 
630                 // self closing tag
631                 *(buf++) = '\0';
632                 
633                 if ( ((*buf) && (*buf != '>')) || 
634                      ((!*buf) && (last_char != '>'))) {
635
636                     if (attr_idx > 0) {
637                         v3_xml_free_attr(attr);
638                     }
639                     v3_xml_err(root, tag_ptr, "missing >", 0);
640                     return NULL;
641                 }
642                 v3_xml_open_tag(root, tag_ptr, attr);
643                 v3_xml_close_tag(root, tag_ptr, buf);
644             } else if (((quote_char = *buf) == '>') || 
645                        ((!*buf) && (last_char == '>'))) {
646                 // open tag
647                 *buf = '\0'; // temporarily null terminate tag name
648                 v3_xml_open_tag(root, tag_ptr, attr);
649                 *buf = quote_char;
650             } else {
651                 if (attr_idx > 0) {
652                     v3_xml_free_attr(attr);
653                 }
654                 v3_xml_err(root, tag_ptr, "missing >", 0); 
655                 return NULL;
656             }
657         } else if (*buf == '/') { 
658             // close tag
659             
660             buf += strcspn(tag_ptr = buf + 1, V3_XML_WS ">") + 1;
661             
662             quote_char = *buf;
663             if ((*buf == '\0') && (last_char != '>')) {
664                 v3_xml_err(root, tag_ptr, "missing >", 0);
665                 return NULL;
666             }
667
668             *buf = '\0'; // temporarily null terminate tag name
669             
670             if (v3_xml_close_tag(root, tag_ptr, buf) == -1) {
671                 return NULL;
672             }
673
674             *buf = quote_char;
675             if (isspace(*buf)) {
676                 buf += strspn(buf, V3_XML_WS);
677             }
678         } else if (strncmp(buf, "!--", 3) == 0) {
679             // xml comment
680             if ( ((buf = strstr(buf + 3, "--")) == 0) || 
681                  ((*(buf += 2) != '>') && (*buf)) ||
682                  ((!*buf) && (last_char != '>'))) {
683                 v3_xml_err(root, tag_ptr, "unclosed <!--", 0);
684                 return NULL;
685             }
686         } else if (! strncmp(buf, "![CDATA[", 8)) { 
687             // cdata
688             if ((buf = strstr(buf, "]]>"))) {
689                 v3_xml_char_content(root, tag_ptr + 8, (buf += 2) - tag_ptr - 10, 'c');
690             } else {
691                 v3_xml_err(root, tag_ptr, "unclosed <![CDATA[", 0);
692                 return NULL;
693             }
694         } else {
695             v3_xml_err(root, tag_ptr, "unexpected <",0);
696             return NULL;
697         }
698
699         // at this point, buf cannot be NULL
700         if (!*buf) {
701             break;
702         }
703
704         *buf = '\0';
705         tag_ptr = ++buf;
706
707         /* Eat leading whitespace */
708         while (*buf && isspace(*buf)) {
709             buf++;
710         }
711
712         if (*buf && (*buf != '<')) { 
713             // tag character content
714             while (*buf && (*buf != '<')) {
715                 buf++;
716             }
717
718             if (*buf) { 
719                 v3_xml_char_content(root, tag_ptr, buf - tag_ptr, '&');
720             } else {
721                 break;
722             }
723         } else if (*buf == '\0') {
724             break;
725         }
726     }
727
728     if (root->cur == NULL) {
729         return &root->xml;
730     } else if (root->cur->name == NULL) {
731         v3_xml_err(root, tag_ptr, "root tag missing",0);
732         return NULL;
733     } else {
734         v3_xml_err(root, tag_ptr, "unclosed tag", root->cur->name);
735         return NULL;
736     }
737 }
738
739
740 struct v3_xml * v3_xml_parse(char * buf) {
741     int str_len = 0;
742     char * xml_buf = NULL;
743
744     if (!buf) {
745         return NULL;
746     }
747
748     str_len = strlen(buf);
749     xml_buf = (char *)V3_Malloc(str_len + 1);
750
751     if (!xml_buf) {
752         PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml parse\n");
753         return NULL;
754     }
755
756     strcpy(xml_buf, buf);
757
758     return parse_str(xml_buf, str_len);
759 }
760
761
762
763 // free the memory allocated for the v3_xml structure
764 void v3_xml_free(struct v3_xml * xml) {
765     struct v3_xml_root * root = (struct v3_xml_root *)xml;
766
767     if (xml == NULL) {
768         return;
769     }
770
771     v3_xml_free(xml->child);
772     v3_xml_free(xml->ordered);
773
774     if (xml->parent == NULL) { 
775         // free root tag allocations
776         V3_Free(root->str_ptr); // malloced xml data
777     }
778
779     v3_xml_free_attr(xml->attr); // tag attributes
780
781
782
783     if ((xml->flags & V3_XML_TXTM)) {
784         // character content
785         V3_Free(xml->txt); 
786     }
787
788     if ((xml->flags & V3_XML_NAMEM)) {
789         // tag name
790         V3_Free(xml->name);
791     }
792
793     V3_Free(xml);
794 }
795
796
797
798
799
800 /* Adding XML data */
801
802
803
804
805 // sets the character content for the given tag and returns the tag
806 struct v3_xml *  v3_xml_set_txt(struct v3_xml * xml, const char *txt) {
807     if (! xml) {
808         return NULL;
809     }
810
811     if (xml->flags & V3_XML_TXTM) {
812         // existing txt was malloced
813         V3_Free(xml->txt); 
814     }
815
816     xml->flags &= ~V3_XML_TXTM;
817     xml->txt = (char *)txt;
818     return xml;
819 }
820
821 // Sets the given tag attribute or adds a new attribute if not found. A value
822 // of NULL will remove the specified attribute. Returns the tag given.
823 struct v3_xml * v3_xml_set_attr(struct v3_xml * xml, const char * name, const char * value) {
824     int l = 0;
825     int c;
826
827     if (! xml) {
828         return NULL;
829     }
830
831     while (xml->attr[l] && strcmp(xml->attr[l], name)) {
832         l += 2;
833     }
834
835     if (! xml->attr[l]) { 
836         // not found, add as new attribute
837         
838         if (! value) {
839             // nothing to do
840             return xml;
841         }
842        
843         if (xml->attr == V3_XML_NIL) { 
844             // first attribute
845             xml->attr = V3_Malloc(4 * sizeof(char *));
846
847             if (!xml->attr) {
848                 PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml set attr\n");
849                 return NULL;
850             }
851
852             // empty list of malloced names/vals
853             xml->attr[1] = strdup(""); 
854
855             if (!xml->attr[1]) {
856                 PrintError(VM_NONE, VCORE_NONE, "Cannot strdup in xml set attr\n");
857                 return NULL;
858             }
859
860         } else {
861             xml->attr = tmp_realloc(xml->attr, l * sizeof(char *), (l + 4) * sizeof(char *));
862
863             if (!xml->attr) {
864                 PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml set attr\n");
865                 return NULL;
866             }
867         }
868
869         // set attribute name
870         xml->attr[l] = (char *)name; 
871
872         // null terminate attribute list
873         xml->attr[l + 2] = NULL; 
874
875         xml->attr[l + 3] = tmp_realloc(xml->attr[l + 1],
876                                        strlen(xml->attr[l + 1]),
877                                        (c = strlen(xml->attr[l + 1])) + 2);
878
879
880         if (!xml->attr[l + 3]) {
881             PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml set attr\n");
882             return NULL;
883         }
884
885         // set name/value as not malloced
886         strcpy(xml->attr[l + 3] + c, " "); 
887
888         if (xml->flags & V3_XML_DUP) {
889             xml->attr[l + 3][c] = V3_XML_NAMEM;
890         }
891     } else if (xml->flags & V3_XML_DUP) {
892         // name was strduped
893         V3_Free((char *)name); 
894     }
895
896
897     // find end of attribute list
898     for (c = l; xml->attr[c]; c += 2); 
899
900     if (xml->attr[c + 1][l / 2] & V3_XML_TXTM) {
901         //old val
902         V3_Free(xml->attr[l + 1]); 
903     }
904
905     if (xml->flags & V3_XML_DUP) {
906         xml->attr[c + 1][l / 2] |= V3_XML_TXTM;
907     } else {
908         xml->attr[c + 1][l / 2] &= ~V3_XML_TXTM;
909     }
910
911
912     if (value) {
913         // set attribute value
914         xml->attr[l + 1] = (char *)value; 
915     } else { 
916         // remove attribute
917         
918         if (xml->attr[c + 1][l / 2] & V3_XML_NAMEM) {
919             V3_Free(xml->attr[l]);
920         }
921
922         memmove(xml->attr + l, xml->attr + l + 2, (c - l + 2) * sizeof(char*));
923
924         xml->attr = tmp_realloc(xml->attr, c * sizeof(char *), (c + 2) * sizeof(char *));
925
926         // fix list of which name/vals are malloced
927         memmove(xml->attr[c + 1] + (l / 2), xml->attr[c + 1] + (l / 2) + 1,
928                 (c / 2) - (l / 2)); 
929     }
930
931     // clear strdup() flag
932     xml->flags &= ~V3_XML_DUP; 
933
934     return xml;
935 }
936
937 // removes a tag along with its subtags without freeing its memory
938 struct v3_xml * v3_xml_cut(struct v3_xml * xml) {
939     struct v3_xml * cur;
940
941     if (! xml) {
942         // nothing to do
943         return NULL; 
944     }
945
946     if (xml->next) {
947         // patch sibling list
948         xml->next->sibling = xml->sibling; 
949     }
950
951
952     if (xml->parent) { 
953         // not root tag
954
955         // find head of subtag list
956         cur = xml->parent->child; 
957
958         if (cur == xml) {
959             // first subtag
960             xml->parent->child = xml->ordered; 
961         } else { 
962         // not first subtag
963
964             while (cur->ordered != xml) {
965                 cur = cur->ordered;
966             }
967
968             // patch ordered list
969             cur->ordered = cur->ordered->ordered; 
970
971             // go back to head of subtag list
972             cur = xml->parent->child; 
973
974             if (strcmp(cur->name, xml->name)) {
975                 // not in first sibling list
976
977                 while (strcmp(cur->sibling->name, xml->name)) {
978                     cur = cur->sibling;
979                 }
980
981                 if (cur->sibling == xml) { 
982                     // first of a sibling list
983                     cur->sibling = (xml->next) ? xml->next
984                                                : cur->sibling->sibling;
985                 } else {
986                     // not first of a sibling list
987                     cur = cur->sibling;
988                 }
989             }
990
991             while (cur->next && cur->next != xml) {
992                 cur = cur->next;
993             }
994
995             if (cur->next) {
996                 // patch next list
997                 cur->next = cur->next->next; 
998             }
999         } 
1000    }
1001     xml->ordered = xml->sibling = xml->next = NULL;
1002     return xml;
1003 }
1004
1005
1006
1007
1008 /* ************************** */
1009 /* *** XML ENCODING       *** */
1010 /* ************************** */
1011
1012 // Encodes ampersand sequences appending the results to *dst, reallocating *dst
1013 // if length excedes max. a is non-zero for attribute encoding. Returns *dst
1014 static char *ampencode(const char *s, size_t len, char **dst, size_t *dlen,
1015                       size_t * max, short a)
1016 {
1017     const char * e;
1018     
1019     for (e = s + len; s != e; s++) {
1020         while (*dlen + 10 > *max) {
1021             *dst = tmp_realloc(*dst, *max, *max + V3_XML_BUFSIZE);
1022             *max += V3_XML_BUFSIZE;
1023         }
1024
1025         switch (*s) {
1026         case '\0': return *dst;
1027         case '&': *dlen += sprintf(*dst + *dlen, "&amp;"); break;
1028         case '<': *dlen += sprintf(*dst + *dlen, "&lt;"); break;
1029         case '>': *dlen += sprintf(*dst + *dlen, "&gt;"); break;
1030         case '"': *dlen += sprintf(*dst + *dlen, (a) ? "&quot;" : "\""); break;
1031         case '\n': *dlen += sprintf(*dst + *dlen, (a) ? "&#xA;" : "\n"); break;
1032         case '\t': *dlen += sprintf(*dst + *dlen, (a) ? "&#x9;" : "\t"); break;
1033         case '\r': *dlen += sprintf(*dst + *dlen, "&#xD;"); break;
1034         default: (*dst)[(*dlen)++] = *s;
1035         }
1036     }
1037     return *dst;
1038 }
1039
1040
1041
1042 // Recursively converts each tag to xml appending it to *s. Reallocates *s if
1043 // its length excedes max. start is the location of the previous tag in the
1044 // parent tag's character content. Returns *s.
1045 static char *toxml_r(struct v3_xml * xml, char **s, size_t *len, size_t *max,
1046                     size_t start) {
1047     int i;
1048     char *txt = (xml->parent) ? xml->parent->txt : "";
1049     size_t off = 0;
1050
1051     // parent character content up to this tag
1052     *s = ampencode(txt + start, xml->off - start, s, len, max, 0);
1053
1054     while (*len + strlen(xml->name) + 4 > *max) {
1055         // reallocate s
1056         *s = tmp_realloc(*s, *max, *max + V3_XML_BUFSIZE);
1057         *max += V3_XML_BUFSIZE;
1058     }
1059
1060
1061     *len += sprintf(*s + *len, "<%s", xml->name); // open tag
1062     for (i = 0; xml->attr[i]; i += 2) { // tag attributes
1063         if (v3_xml_attr(xml, xml->attr[i]) != xml->attr[i + 1]) continue;
1064         while (*len + strlen(xml->attr[i]) + 7 > *max) {
1065             // reallocate s
1066             *s = tmp_realloc(*s, *max, *max + V3_XML_BUFSIZE);
1067             *max += V3_XML_BUFSIZE;
1068         }
1069
1070         *len += sprintf(*s + *len, " %s=\"", xml->attr[i]);
1071         ampencode(xml->attr[i + 1], -1, s, len, max, 1);
1072         *len += sprintf(*s + *len, "\"");
1073     }
1074
1075   
1076     *len += sprintf(*s + *len, ">");
1077
1078     *s = (xml->child) ? toxml_r(xml->child, s, len, max, 0) //child
1079                       : ampencode(xml->txt, -1, s, len, max, 0);  //data
1080     
1081     while (*len + strlen(xml->name) + 4 > *max) {
1082         // reallocate s
1083         *s = tmp_realloc(*s, *max, *max + V3_XML_BUFSIZE);
1084         *max += V3_XML_BUFSIZE;
1085     }
1086
1087     *len += sprintf(*s + *len, "</%s>", xml->name); // close tag
1088
1089     while (off < xml->off && txt[off]) off++; // make sure off is within bounds
1090     return (xml->ordered) ? toxml_r(xml->ordered, s, len, max, off)
1091                           : ampencode(txt + off, -1, s, len, max, 0);
1092 }
1093
1094 // Converts an xml structure back to xml. Returns a string of xml data that
1095 // must be freed.
1096 char * v3_xml_tostr(struct v3_xml * xml) {
1097     struct v3_xml * p = (xml) ? xml->parent : NULL;
1098     struct v3_xml * o = (xml) ? xml->ordered : NULL;
1099     struct v3_xml_root * root = (struct v3_xml_root *)xml;
1100     size_t len = 0, max = V3_XML_BUFSIZE;
1101     char *s = V3_Malloc(max);
1102
1103     if (!s) {
1104         PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml tostrr\n");
1105         return NULL;
1106     }
1107
1108     strcpy(s, "");
1109
1110     if (! xml || ! xml->name) return tmp_realloc(s, max, len + 1);
1111     while (root->xml.parent) root = (struct v3_xml_root *)root->xml.parent; // root tag
1112
1113
1114     xml->parent = xml->ordered = NULL;
1115     s = toxml_r(xml, &s, &len, &max, 0);
1116     xml->parent = p;
1117     xml->ordered = o;
1118
1119
1120     return tmp_realloc(s, max, len + 1);
1121 }