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.


f3cf7e3237015a3b5274d2b209efb2f4bc79e091
[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) {
484         return NULL;
485     }
486
487     root->str_ptr = buf;
488
489     if (len == 0) {
490         v3_xml_err(root, NULL, "Empty XML String", 0);
491         return NULL;
492     }
493
494     root->tmp_start = buf;
495     root->tmp_end = buf + len; // record start and end of work area
496     
497     last_char = buf[len - 1]; // save end char
498     buf[len - 1] = '\0'; // turn end char into null terminator
499
500     while ((*buf) && (*buf != '<')) {
501         // find first tag
502         buf++; 
503     }
504
505     if (*buf == '\0') {
506         v3_xml_err(root, buf, "root tag missing", 0);
507         return NULL;
508     }
509
510     for (; ; ) {
511         attr = (char **)empty_attrib_list;
512         tag_ptr = ++buf; // skip first '<'
513         
514         if (isalpha(*buf) || (*buf == '_') || (*buf == ':') || (*buf < '\0')) {
515             // new tag
516
517             if (root->cur == NULL) {
518                 v3_xml_err(root, tag_ptr, "markup outside of root element", 0);
519                 return NULL;
520             }
521
522             buf += strcspn(buf, V3_XML_WS "/>");
523
524             while (isspace(*buf)) {
525                 // null terminate tag name, 
526                 // this writes '\0' to spaces after first tag 
527                 *(buf++) = '\0';
528             }
529
530         
531
532             // attributes are name value pairs, 
533             //     2nd to last entry is null  (end of list)
534             //     last entry points to a string map marking whether values have been malloced...
535             // loop through attributes until hitting the closing bracket
536             for (attr_idx = 0; 
537                  (*buf) && (*buf != '/') && (*buf != '>'); 
538                  attr_idx += 2) {
539                 // buf is incremented later on
540                 // new attrib
541                 int attr_cnt = (attr_idx / 2) + 1;
542                 int val_idx = attr_idx + 1;
543                 int term_idx = attr_idx + 2;
544                 int last_idx = attr_idx + 3;
545
546                 // attr = allocated space
547                 // attr[val_idx] = mem for list of maloced vals
548                 if (attr_cnt > 1) {
549                     attr = tmp_realloc(attr,
550                                        (((attr_cnt - 1) * (2 * sizeof(char *))) + 
551                                         (2 * sizeof(char *))), 
552                                        ((attr_cnt * (2 * sizeof(char *))) + 
553                                         (2 * sizeof(char *))));
554
555                     if (!attr) {
556                         PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml parse string\n");
557                         return NULL;
558                     }
559
560                     attr[last_idx] = tmp_realloc(attr[last_idx - 2], 
561                                                  attr_cnt,
562                                                  (attr_cnt + 1)); 
563
564                     if (!attr[last_idx]) {
565                         PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml parse string\n");
566                         return NULL;
567                     }
568
569                 } else {
570                     attr = V3_Malloc(4 * sizeof(char *)); 
571                     if (!attr) {
572                         PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml parse string\n");
573                         return NULL;
574                     }
575                     attr[last_idx] = V3_Malloc(2);
576                     if (!attr[last_idx]) {
577                         PrintError(VM_NONE, VCORE_NONE, "Cannot alloocate in xml parse string\n");
578                         return NULL;
579                     }
580                 }
581
582                 attr[attr_idx] = buf; // set attribute name
583                 attr[val_idx] = ""; // temporary attribute value
584                 attr[term_idx] = NULL; // null terminate list
585                 strcpy(attr[last_idx] + attr_cnt, " "); // value is not malloc'd, offset into the stringmap
586
587                 buf += strcspn(buf, V3_XML_WS "=/>");
588
589                 if ((*buf == '=') || isspace(*buf)) {
590
591                     *(buf++) = '\0'; // null terminate tag attribute name
592
593                     // eat whitespace (and more multiple '=' ?)
594                     buf += strspn(buf, V3_XML_WS "=");
595
596                     quote_char = *buf;
597
598                     if ((quote_char == '"') || (quote_char == '\'')) { // attribute value
599                         attr[val_idx] = ++buf;
600
601                         while ((*buf) && (*buf != quote_char)) {
602                             buf++;
603                         }
604
605                         if (*buf) {
606                             // null terminate attribute val
607                             *(buf++) = '\0';
608                         } else {
609                             char err_buf[2] = {quote_char,0};
610
611                             v3_xml_free_attr(attr);
612                             v3_xml_err(root, tag_ptr, "missing quote char", err_buf);
613                             return NULL;
614                         }
615
616                         attr[val_idx] = v3_xml_decode(attr[val_idx], ' ');
617                     }
618                 }
619
620                 while (isspace(*buf)) {
621                     buf++;
622                 }
623             }
624
625             if (*buf == '/') { 
626                 // self closing tag
627                 *(buf++) = '\0';
628                 
629                 if ( ((*buf) && (*buf != '>')) || 
630                      ((!*buf) && (last_char != '>'))) {
631
632                     if (attr_idx > 0) {
633                         v3_xml_free_attr(attr);
634                     }
635                     v3_xml_err(root, tag_ptr, "missing >", 0);
636                     return NULL;
637                 }
638                 v3_xml_open_tag(root, tag_ptr, attr);
639                 v3_xml_close_tag(root, tag_ptr, buf);
640             } else if (((quote_char = *buf) == '>') || 
641                        ((!*buf) && (last_char == '>'))) {
642                 // open tag
643                 *buf = '\0'; // temporarily null terminate tag name
644                 v3_xml_open_tag(root, tag_ptr, attr);
645                 *buf = quote_char;
646             } else {
647                 if (attr_idx > 0) {
648                     v3_xml_free_attr(attr);
649                 }
650                 v3_xml_err(root, tag_ptr, "missing >", 0); 
651                 return NULL;
652             }
653         } else if (*buf == '/') { 
654             // close tag
655             
656             buf += strcspn(tag_ptr = buf + 1, V3_XML_WS ">") + 1;
657             
658             quote_char = *buf;
659             if ((*buf == '\0') && (last_char != '>')) {
660                 v3_xml_err(root, tag_ptr, "missing >", 0);
661                 return NULL;
662             }
663
664             *buf = '\0'; // temporarily null terminate tag name
665             
666             if (v3_xml_close_tag(root, tag_ptr, buf) == -1) {
667                 return NULL;
668             }
669
670             *buf = quote_char;
671             if (isspace(*buf)) {
672                 buf += strspn(buf, V3_XML_WS);
673             }
674         } else if (strncmp(buf, "!--", 3) == 0) {
675             // xml comment
676             if ( ((buf = strstr(buf + 3, "--")) == 0) || 
677                  ((*(buf += 2) != '>') && (*buf)) ||
678                  ((!*buf) && (last_char != '>'))) {
679                 v3_xml_err(root, tag_ptr, "unclosed <!--", 0);
680                 return NULL;
681             }
682         } else if (! strncmp(buf, "![CDATA[", 8)) { 
683             // cdata
684             if ((buf = strstr(buf, "]]>"))) {
685                 v3_xml_char_content(root, tag_ptr + 8, (buf += 2) - tag_ptr - 10, 'c');
686             } else {
687                 v3_xml_err(root, tag_ptr, "unclosed <![CDATA[", 0);
688                 return NULL;
689             }
690         } else {
691             v3_xml_err(root, tag_ptr, "unexpected <",0);
692             return NULL;
693         }
694
695         if (! buf || ! *buf) {
696             break;
697         }
698
699         *buf = '\0';
700         tag_ptr = ++buf;
701
702         /* Eat leading whitespace */
703         while (*buf && isspace(*buf)) {
704             buf++;
705         }
706
707         if (*buf && (*buf != '<')) { 
708             // tag character content
709             while (*buf && (*buf != '<')) {
710                 buf++;
711             }
712
713             if (*buf) { 
714                 v3_xml_char_content(root, tag_ptr, buf - tag_ptr, '&');
715             } else {
716                 break;
717             }
718         } else if (*buf == '\0') {
719             break;
720         }
721     }
722
723     if (root->cur == NULL) {
724         return &root->xml;
725     } else if (root->cur->name == NULL) {
726         v3_xml_err(root, tag_ptr, "root tag missing",0);
727         return NULL;
728     } else {
729         v3_xml_err(root, tag_ptr, "unclosed tag", root->cur->name);
730         return NULL;
731     }
732 }
733
734
735 struct v3_xml * v3_xml_parse(char * buf) {
736     int str_len = 0;
737     char * xml_buf = NULL;
738
739     if (!buf) {
740         return NULL;
741     }
742
743     str_len = strlen(buf);
744     xml_buf = (char *)V3_Malloc(str_len + 1);
745
746     if (!xml_buf) {
747         PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml parse\n");
748         return NULL;
749     }
750
751     strcpy(xml_buf, buf);
752
753     return parse_str(xml_buf, str_len);
754 }
755
756
757
758 // free the memory allocated for the v3_xml structure
759 void v3_xml_free(struct v3_xml * xml) {
760     struct v3_xml_root * root = (struct v3_xml_root *)xml;
761
762     if (xml == NULL) {
763         return;
764     }
765
766     v3_xml_free(xml->child);
767     v3_xml_free(xml->ordered);
768
769     if (xml->parent == NULL) { 
770         // free root tag allocations
771         V3_Free(root->str_ptr); // malloced xml data
772     }
773
774     v3_xml_free_attr(xml->attr); // tag attributes
775
776
777
778     if ((xml->flags & V3_XML_TXTM)) {
779         // character content
780         V3_Free(xml->txt); 
781     }
782
783     if ((xml->flags & V3_XML_NAMEM)) {
784         // tag name
785         V3_Free(xml->name);
786     }
787
788     V3_Free(xml);
789 }
790
791
792
793
794
795 /* Adding XML data */
796
797
798
799
800 // sets the character content for the given tag and returns the tag
801 struct v3_xml *  v3_xml_set_txt(struct v3_xml * xml, const char *txt) {
802     if (! xml) {
803         return NULL;
804     }
805
806     if (xml->flags & V3_XML_TXTM) {
807         // existing txt was malloced
808         V3_Free(xml->txt); 
809     }
810
811     xml->flags &= ~V3_XML_TXTM;
812     xml->txt = (char *)txt;
813     return xml;
814 }
815
816 // Sets the given tag attribute or adds a new attribute if not found. A value
817 // of NULL will remove the specified attribute. Returns the tag given.
818 struct v3_xml * v3_xml_set_attr(struct v3_xml * xml, const char * name, const char * value) {
819     int l = 0;
820     int c;
821
822     if (! xml) {
823         return NULL;
824     }
825
826     while (xml->attr[l] && strcmp(xml->attr[l], name)) {
827         l += 2;
828     }
829
830     if (! xml->attr[l]) { 
831         // not found, add as new attribute
832         
833         if (! value) {
834             // nothing to do
835             return xml;
836         }
837        
838         if (xml->attr == V3_XML_NIL) { 
839             // first attribute
840             xml->attr = V3_Malloc(4 * sizeof(char *));
841
842             if (!xml->attr) {
843                 PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml set attr\n");
844                 return NULL;
845             }
846
847             // empty list of malloced names/vals
848             xml->attr[1] = strdup(""); 
849
850             if (!xml->attr[1]) {
851                 PrintError(VM_NONE, VCORE_NONE, "Cannot strdup in xml set attr\n");
852                 return NULL;
853             }
854
855         } else {
856             xml->attr = tmp_realloc(xml->attr, l * sizeof(char *), (l + 4) * sizeof(char *));
857
858             if (!xml->attr) {
859                 PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml set attr\n");
860                 return NULL;
861             }
862         }
863
864         // set attribute name
865         xml->attr[l] = (char *)name; 
866
867         // null terminate attribute list
868         xml->attr[l + 2] = NULL; 
869
870         xml->attr[l + 3] = tmp_realloc(xml->attr[l + 1],
871                                        strlen(xml->attr[l + 1]),
872                                        (c = strlen(xml->attr[l + 1])) + 2);
873
874
875         if (!xml->attr[l + 3]) {
876             PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml set attr\n");
877             return NULL;
878         }
879
880         // set name/value as not malloced
881         strcpy(xml->attr[l + 3] + c, " "); 
882
883         if (xml->flags & V3_XML_DUP) {
884             xml->attr[l + 3][c] = V3_XML_NAMEM;
885         }
886     } else if (xml->flags & V3_XML_DUP) {
887         // name was strduped
888         V3_Free((char *)name); 
889     }
890
891
892     // find end of attribute list
893     for (c = l; xml->attr[c]; c += 2); 
894
895     if (xml->attr[c + 1][l / 2] & V3_XML_TXTM) {
896         //old val
897         V3_Free(xml->attr[l + 1]); 
898     }
899
900     if (xml->flags & V3_XML_DUP) {
901         xml->attr[c + 1][l / 2] |= V3_XML_TXTM;
902     } else {
903         xml->attr[c + 1][l / 2] &= ~V3_XML_TXTM;
904     }
905
906
907     if (value) {
908         // set attribute value
909         xml->attr[l + 1] = (char *)value; 
910     } else { 
911         // remove attribute
912         
913         if (xml->attr[c + 1][l / 2] & V3_XML_NAMEM) {
914             V3_Free(xml->attr[l]);
915         }
916
917         memmove(xml->attr + l, xml->attr + l + 2, (c - l + 2) * sizeof(char*));
918
919         xml->attr = tmp_realloc(xml->attr, c * sizeof(char *), (c + 2) * sizeof(char *));
920
921         // fix list of which name/vals are malloced
922         memmove(xml->attr[c + 1] + (l / 2), xml->attr[c + 1] + (l / 2) + 1,
923                 (c / 2) - (l / 2)); 
924     }
925
926     // clear strdup() flag
927     xml->flags &= ~V3_XML_DUP; 
928
929     return xml;
930 }
931
932 // removes a tag along with its subtags without freeing its memory
933 struct v3_xml * v3_xml_cut(struct v3_xml * xml) {
934     struct v3_xml * cur;
935
936     if (! xml) {
937         // nothing to do
938         return NULL; 
939     }
940
941     if (xml->next) {
942         // patch sibling list
943         xml->next->sibling = xml->sibling; 
944     }
945
946
947     if (xml->parent) { 
948         // not root tag
949
950         // find head of subtag list
951         cur = xml->parent->child; 
952
953         if (cur == xml) {
954             // first subtag
955             xml->parent->child = xml->ordered; 
956         } else { 
957         // not first subtag
958
959             while (cur->ordered != xml) {
960                 cur = cur->ordered;
961             }
962
963             // patch ordered list
964             cur->ordered = cur->ordered->ordered; 
965
966             // go back to head of subtag list
967             cur = xml->parent->child; 
968
969             if (strcmp(cur->name, xml->name)) {
970                 // not in first sibling list
971
972                 while (strcmp(cur->sibling->name, xml->name)) {
973                     cur = cur->sibling;
974                 }
975
976                 if (cur->sibling == xml) { 
977                     // first of a sibling list
978                     cur->sibling = (xml->next) ? xml->next
979                                                : cur->sibling->sibling;
980                 } else {
981                     // not first of a sibling list
982                     cur = cur->sibling;
983                 }
984             }
985
986             while (cur->next && cur->next != xml) {
987                 cur = cur->next;
988             }
989
990             if (cur->next) {
991                 // patch next list
992                 cur->next = cur->next->next; 
993             }
994         } 
995    }
996     xml->ordered = xml->sibling = xml->next = NULL;
997     return xml;
998 }
999
1000
1001
1002
1003 /* ************************** */
1004 /* *** XML ENCODING       *** */
1005 /* ************************** */
1006
1007 // Encodes ampersand sequences appending the results to *dst, reallocating *dst
1008 // if length excedes max. a is non-zero for attribute encoding. Returns *dst
1009 static char *ampencode(const char *s, size_t len, char **dst, size_t *dlen,
1010                       size_t * max, short a)
1011 {
1012     const char * e;
1013     
1014     for (e = s + len; s != e; s++) {
1015         while (*dlen + 10 > *max) {
1016             *dst = tmp_realloc(*dst, *max, *max + V3_XML_BUFSIZE);
1017             *max += V3_XML_BUFSIZE;
1018         }
1019
1020         switch (*s) {
1021         case '\0': return *dst;
1022         case '&': *dlen += sprintf(*dst + *dlen, "&amp;"); break;
1023         case '<': *dlen += sprintf(*dst + *dlen, "&lt;"); break;
1024         case '>': *dlen += sprintf(*dst + *dlen, "&gt;"); break;
1025         case '"': *dlen += sprintf(*dst + *dlen, (a) ? "&quot;" : "\""); break;
1026         case '\n': *dlen += sprintf(*dst + *dlen, (a) ? "&#xA;" : "\n"); break;
1027         case '\t': *dlen += sprintf(*dst + *dlen, (a) ? "&#x9;" : "\t"); break;
1028         case '\r': *dlen += sprintf(*dst + *dlen, "&#xD;"); break;
1029         default: (*dst)[(*dlen)++] = *s;
1030         }
1031     }
1032     return *dst;
1033 }
1034
1035
1036
1037 // Recursively converts each tag to xml appending it to *s. Reallocates *s if
1038 // its length excedes max. start is the location of the previous tag in the
1039 // parent tag's character content. Returns *s.
1040 static char *toxml_r(struct v3_xml * xml, char **s, size_t *len, size_t *max,
1041                     size_t start) {
1042     int i;
1043     char *txt = (xml->parent) ? xml->parent->txt : "";
1044     size_t off = 0;
1045
1046     // parent character content up to this tag
1047     *s = ampencode(txt + start, xml->off - start, s, len, max, 0);
1048
1049     while (*len + strlen(xml->name) + 4 > *max) {
1050         // reallocate s
1051         *s = tmp_realloc(*s, *max, *max + V3_XML_BUFSIZE);
1052         *max += V3_XML_BUFSIZE;
1053     }
1054
1055
1056     *len += sprintf(*s + *len, "<%s", xml->name); // open tag
1057     for (i = 0; xml->attr[i]; i += 2) { // tag attributes
1058         if (v3_xml_attr(xml, xml->attr[i]) != xml->attr[i + 1]) continue;
1059         while (*len + strlen(xml->attr[i]) + 7 > *max) {
1060             // reallocate s
1061             *s = tmp_realloc(*s, *max, *max + V3_XML_BUFSIZE);
1062             *max += V3_XML_BUFSIZE;
1063         }
1064
1065         *len += sprintf(*s + *len, " %s=\"", xml->attr[i]);
1066         ampencode(xml->attr[i + 1], -1, s, len, max, 1);
1067         *len += sprintf(*s + *len, "\"");
1068     }
1069
1070   
1071     *len += sprintf(*s + *len, ">");
1072
1073     *s = (xml->child) ? toxml_r(xml->child, s, len, max, 0) //child
1074                       : ampencode(xml->txt, -1, s, len, max, 0);  //data
1075     
1076     while (*len + strlen(xml->name) + 4 > *max) {
1077         // reallocate s
1078         *s = tmp_realloc(*s, *max, *max + V3_XML_BUFSIZE);
1079         *max += V3_XML_BUFSIZE;
1080     }
1081
1082     *len += sprintf(*s + *len, "</%s>", xml->name); // close tag
1083
1084     while (off < xml->off && txt[off]) off++; // make sure off is within bounds
1085     return (xml->ordered) ? toxml_r(xml->ordered, s, len, max, off)
1086                           : ampencode(txt + off, -1, s, len, max, 0);
1087 }
1088
1089 // Converts an xml structure back to xml. Returns a string of xml data that
1090 // must be freed.
1091 char * v3_xml_tostr(struct v3_xml * xml) {
1092     struct v3_xml * p = (xml) ? xml->parent : NULL;
1093     struct v3_xml * o = (xml) ? xml->ordered : NULL;
1094     struct v3_xml_root * root = (struct v3_xml_root *)xml;
1095     size_t len = 0, max = V3_XML_BUFSIZE;
1096     char *s = V3_Malloc(max);
1097
1098     if (!s) {
1099         PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml tostrr\n");
1100         return NULL;
1101     }
1102
1103     strcpy(s, "");
1104
1105     if (! xml || ! xml->name) return tmp_realloc(s, max, len + 1);
1106     while (root->xml.parent) root = (struct v3_xml_root *)root->xml.parent; // root tag
1107
1108
1109     xml->parent = xml->ordered = NULL;
1110     s = toxml_r(xml, &s, &len, &max, 0);
1111     xml->parent = p;
1112     xml->ordered = o;
1113
1114
1115     return tmp_realloc(s, max, len + 1);
1116 }