fix after https://codereview.scilab.org/#/c/19277/
[scilab.git] / scilab / modules / scicos / src / c / ezxml.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 #include <stdlib.h>
26 #include <stdio.h>
27 #include <stdarg.h>
28 #include <string.h>
29 #include <ctype.h>
30 #ifndef _MSC_VER
31 #include <unistd.h>
32 #else
33 #include <io.h>
34 #endif
35 #include <sys/types.h>
36 #if defined(_MSC_VER)
37 #define EZXML_NOMMAP
38 #endif
39
40 #ifndef EZXML_NOMMAP
41 #include <sys/mman.h>
42 #endif // EZXML_NOMMAP
43 #include <sys/stat.h>
44 #include "ezxml.h"
45
46 #include "sci_malloc.h"
47
48 #include "os_string.h"
49
50 #ifdef _MSC_VER
51 #define snprintf _snprintf
52 #define open _open
53 #define read _read
54 #define close _close
55 #endif
56
57 #define EZXML_WS   "\t\n "  // whitespace
58 #define EZXML_ERRL 128        // maximum error string length
59
60 typedef struct ezxml_root *ezxml_root_t;
61 struct ezxml_root         // additional data for the root tag
62 {
63     struct ezxml xml;     // is a super-struct built on top of ezxml struct
64     ezxml_t cur;          // current xml tree insertion point
65     char *m;              // original xml string
66     size_t len;           // length of allocated memory for mmap, -1 for malloc
67     char *u;              // UTF-8 conversion of string if original was UTF-16
68     char *s;              // start of work area
69     char *e;              // end of work area
70     char **ent;           // general entities (ampersand sequences)
71     char ***attr;         // default attributes
72     char ***pi;           // processing instructions
73     short standalone;     // non-zero if <?xml standalone="yes"?>
74     char err[EZXML_ERRL]; // error string
75 };
76
77 char *EZXML_NIL[] = { NULL }; // empty, null terminated array of strings
78
79 // returns the first child tag with the given name or NULL if not found
80 ezxml_t ezxml_child(ezxml_t xml, const char *name)
81 {
82     xml = (xml) ? xml->child : NULL;
83     while (xml && strcmp(name, xml->name))
84     {
85         xml = xml->sibling;
86     }
87     return xml;
88 }
89
90 // returns the Nth tag with the same name in the same subsection or NULL if not
91 // found
92 ezxml_t ezxml_idx(ezxml_t xml, int idx)
93 {
94     for (; xml && idx; idx--)
95     {
96         xml = xml->next;
97     }
98     return xml;
99 }
100
101 // returns the value of the requested tag attribute or NULL if not found
102 const char *ezxml_attr(ezxml_t xml, const char *attr)
103 {
104     int i = 0, j = 1;
105     ezxml_root_t root = (ezxml_root_t)xml;
106
107     if (! xml || ! xml->attr)
108     {
109         return NULL;
110     }
111     while (xml->attr[i] && strcmp(attr, xml->attr[i]))
112     {
113         i += 2;
114     }
115     if (xml->attr[i])
116     {
117         return xml->attr[i + 1];    // found attribute
118     }
119
120     while (root->xml.parent)
121     {
122         root = (ezxml_root_t)root->xml.parent;    // root tag
123     }
124     for (i = 0; root->attr[i] && strcmp(xml->name, root->attr[i][0]); i++)
125     {
126         ;
127     }
128     if (! root->attr[i])
129     {
130         return NULL;    // no matching default attributes
131     }
132     while (root->attr[i][j] && strcmp(attr, root->attr[i][j]))
133     {
134         j += 3;
135     }
136     return (root->attr[i][j]) ? root->attr[i][j + 1] : NULL; // found default
137 }
138
139 // same as ezxml_get but takes an already initialized va_list
140 ezxml_t ezxml_vget(ezxml_t xml, va_list ap)
141 {
142     char *name = va_arg(ap, char *);
143     int idx = -1;
144
145     if (name && *name)
146     {
147         idx = va_arg(ap, int);
148         xml = ezxml_child(xml, name);
149     }
150     return (idx < 0) ? xml : ezxml_vget(ezxml_idx(xml, idx), ap);
151 }
152
153 // Traverses the xml tree to retrieve a specific subtag. Takes a variable
154 // length list of tag names and indexes. The argument list must be terminated
155 // by either an index of -1 or an empty string tag name. Example:
156 // title = ezxml_get(library, "shelf", 0, "book", 2, "title", -1);
157 // This retrieves the title of the 3rd book on the 1st shelf of library.
158 // Returns NULL if not found.
159 ezxml_t ezxml_get(ezxml_t xml, ...)
160 {
161     va_list ap;
162     ezxml_t r;
163
164     va_start(ap, xml);
165     r = ezxml_vget(xml, ap);
166     va_end(ap);
167     return r;
168 }
169
170 // returns a null terminated array of processing instructions for the given
171 // target
172 const char **ezxml_pi(ezxml_t xml, const char *target)
173 {
174     ezxml_root_t root = (ezxml_root_t)xml;
175     int i = 0;
176
177     if (! root)
178     {
179         return (const char **)EZXML_NIL;
180     }
181     while (root->xml.parent)
182     {
183         root = (ezxml_root_t)root->xml.parent;    // root tag
184     }
185     while (root->pi[i] && strcmp(target, root->pi[i][0]))
186     {
187         i++;    // find target
188     }
189     return (const char **)((root->pi[i]) ? root->pi[i] + 1 : EZXML_NIL);
190 }
191
192 // set an error string and return root
193 ezxml_t ezxml_err(ezxml_root_t root, char *s, const char *err, ...)
194 {
195     va_list ap;
196     int line = 1;
197     char *t, fmt[EZXML_ERRL];
198
199     for (t = root->s; t < s; t++) if (*t == '\n')
200         {
201             line++;
202         }
203     snprintf(fmt, EZXML_ERRL, "[error near line %d]: %s", line, err);
204
205     va_start(ap, err);
206     vsnprintf(root->err, EZXML_ERRL, fmt, ap);
207     va_end(ap);
208
209     return &root->xml;
210 }
211
212 // Recursively decodes entity and character references and normalizes new lines
213 // ent is a null terminated array of alternating entity names and values. set t
214 // to '&' for general entity decoding, '%' for parameter entity decoding, 'c'
215 // for cdata sections, ' ' for attribute normalization, or '*' for non-cdata
216 // attribute normalization. Returns s, or if the decoded string is longer than
217 // s, returns a malloced string that must be freed.
218 char *ezxml_decode(char *s, char **ent, char t)
219 {
220     char *e, *r = s, *m = s;
221     long b, c, d, l;
222
223     for (; *s; s++)   // normalize line endings
224     {
225         while (*s == '\r')
226         {
227             *(s++) = '\n';
228             if (*s == '\n')
229             {
230                 memmove(s, (s + 1), strlen(s));
231             }
232         }
233     }
234
235     for (s = r; ; )
236     {
237         while (*s && *s != '&' && (*s != '%' || t != '%') && !isspace(*s))
238         {
239             s++;
240         }
241
242         if (! *s)
243         {
244             break;
245         }
246         else if (t != 'c' && ! strncmp(s, "&#", 2))   // character reference
247         {
248             if (s[2] == 'x')
249             {
250                 c = strtol(s + 3, &e, 16);    // base 16
251             }
252             else
253             {
254                 c = strtol(s + 2, &e, 10);    // base 10
255             }
256             if (! c || *e != ';')
257             {
258                 s++;    // not a character ref
259                 continue;
260             }
261
262             if ((long)c < 0x80)
263             {
264                 *(s++) = (char)c;    // US-ASCII subset
265             }
266             else   // multi-byte UTF-8 sequence
267             {
268                 for (b = 0, d = c; d; d /= 2)
269                 {
270                     b++;    // number of bits in c
271                 }
272                 b = (b - 2) / 5; // number of bytes in payload
273                 *(s++) = (char)(0xFF << (7 - b)) | (char)(c >> (6 * b)); // head
274                 while (b)
275                 {
276                     *(s++) = 0x80 | ((c >> (6 * --b)) & 0x3F);    // payload
277                 }
278             }
279
280             memmove(s, strchr(s, ';') + 1, strlen(strchr(s, ';')));
281         }
282         else if ((*s == '&' && (t == '&' || t == ' ' || t == '*')) ||
283                  (*s == '%' && t == '%'))   // entity reference
284         {
285             for (b = 0; ent[b] && strncmp(s + 1, ent[b], strlen(ent[b]));
286                     b += 2)
287             {
288                 ;    // find entity in entity list
289             }
290
291             if (ent[b++])   // found a match
292             {
293                 if ((c = (long)strlen(ent[b])) - 1 > (e = strchr(s, ';')) - s)
294                 {
295                     l = (long)(d = (long)(s - r)) + c + (long)strlen(e); // new length
296                     r = (r == m) ? strcpy(MALLOC(l), r) : REALLOC(r, l);
297                     e = strchr((s = r + d), ';'); // fix up pointers
298                 }
299
300                 if (e)
301                 {
302                     memmove(s + c, e + 1, strlen(e)); // shift rest of string
303                     strncpy(s, ent[b], c); // copy in replacement text
304                 }
305             }
306             else
307             {
308                 s++;    // not a known entity
309             }
310         }
311         else if ((t == ' ' || t == '*') && isspace(*s))
312         {
313             *(s++) = ' ';
314         }
315         else
316         {
317             s++;    // no decoding needed
318         }
319     }
320
321     if (t == '*')   // normalize spaces for non-cdata attributes
322     {
323         for (s = r; *s; s++)
324         {
325             if ((l = (long)strspn(s, " ")))
326             {
327                 memmove(s, s + l, (long)strlen(s + l) + 1);
328             }
329             while (*s && *s != ' ')
330             {
331                 s++;
332             }
333         }
334         if (--s >= r && *s == ' ')
335         {
336             *s = '\0';    // trim any trailing space
337         }
338     }
339     return r;
340 }
341
342 // called when parser finds start of new tag
343 void ezxml_open_tag(ezxml_root_t root, char *name, char **attr)
344 {
345     ezxml_t xml = root->cur;
346
347     if (xml->name)
348     {
349         xml = ezxml_add_child(xml, name, strlen(xml->txt));
350     }
351     else
352     {
353         xml->name = name;    // first open tag
354     }
355
356     xml->attr = attr;
357     root->cur = xml; // update tag insertion point
358 }
359
360 // called when parser finds character content between open and closing tag
361 void ezxml_char_content(ezxml_root_t root, char *s, size_t len, char t)
362 {
363     ezxml_t xml = root->cur;
364     char *m = s;
365     size_t l;
366
367     if (! xml || ! xml->name || ! len)
368     {
369         return;    // sanity check
370     }
371
372     s[len] = '\0'; // null terminate text (calling functions anticipate this)
373     len = strlen(s = ezxml_decode(s, root->ent, t)) + 1;
374
375     if (! *(xml->txt))
376     {
377         xml->txt = s;    // initial character content
378     }
379     else   // allocate our own memory and make a copy
380     {
381         xml->txt = (xml->flags & EZXML_TXTM) // allocate some space
382                    ? REALLOC(xml->txt, (l = strlen(xml->txt)) + len)
383                    : strcpy(MALLOC((l = strlen(xml->txt)) + len), xml->txt);
384         strcpy(xml->txt + l, s); // add new char content
385         if (s != m)
386         {
387             FREE(s);    // free s if it was malloced by ezxml_decode()
388         }
389     }
390
391     if (xml->txt != m)
392     {
393         ezxml_set_flag(xml, EZXML_TXTM);
394     }
395 }
396
397 // called when parser finds closing tag
398 ezxml_t ezxml_close_tag(ezxml_root_t root, char *name, char *s)
399 {
400     if (! root->cur || ! root->cur->name || strcmp(name, root->cur->name))
401     {
402         return ezxml_err(root, s, "unexpected closing tag </%s>", name);
403     }
404
405     root->cur = root->cur->parent;
406     return NULL;
407 }
408
409 // checks for circular entity references, returns non-zero if no circular
410 // references are found, zero otherwise
411 int ezxml_ent_ok(char *name, char *s, char **ent)
412 {
413     int i;
414
415     for (; ; s++)
416     {
417         while (*s && *s != '&')
418         {
419             s++;    // find next entity reference
420         }
421         if (! *s)
422         {
423             return 1;
424         }
425         if (! strncmp(s + 1, name, strlen(name)))
426         {
427             return 0;    // circular ref.
428         }
429         for (i = 0; ent[i] && strncmp(ent[i], s + 1, strlen(ent[i])); i += 2)
430         {
431             ;
432         }
433         if (ent[i] && ! ezxml_ent_ok(name, ent[i + 1], ent))
434         {
435             return 0;
436         }
437     }
438 }
439
440 // called when the parser finds a processing instruction
441 void ezxml_proc_inst(ezxml_root_t root, char *s, size_t len)
442 {
443     int i = 0, j = 1;
444     char *target = s;
445
446     s[len] = '\0'; // null terminate instruction
447     if (*(s += strcspn(s, EZXML_WS)))
448     {
449         *s = '\0'; // null terminate target
450         s += strspn(s + 1, EZXML_WS) + 1; // skip whitespace after target
451     }
452
453     if (! strcmp(target, "xml"))   // <?xml ... ?>
454     {
455         if ((s = strstr(s, "standalone")) && ! strncmp(s + strspn(s + 10,
456                 EZXML_WS "='\"") + 10, "yes", 3))
457         {
458             root->standalone = 1;
459         }
460         return;
461     }
462
463     if (! root->pi[0])
464     {
465         *(root->pi = MALLOC(sizeof(char **))) = NULL;    //first pi
466     }
467
468     while (root->pi[i] && strcmp(target, root->pi[i][0]))
469     {
470         i++;    // find target
471     }
472     if (! root->pi[i])   // new target
473     {
474         root->pi = REALLOC(root->pi, sizeof(char **) * (i + 2));
475         root->pi[i] = MALLOC(sizeof(char *) * 3);
476         root->pi[i][0] = target;
477         root->pi[i][1] = (char *)(root->pi[i + 1] = NULL); // terminate pi list
478         root->pi[i][2] = os_strdup(""); // empty document position list
479     }
480
481     while (root->pi[i][j])
482     {
483         j++;    // find end of instruction list for this target
484     }
485     root->pi[i] = REALLOC(root->pi[i], sizeof(char *) * (j + 3));
486     root->pi[i][j + 2] = REALLOC(root->pi[i][j + 1], j + 1);
487     strcpy(root->pi[i][j + 2] + j - 1, (root->xml.name) ? ">" : "<");
488     root->pi[i][j + 1] = NULL; // null terminate pi list for this target
489     root->pi[i][j] = s; // set instruction
490 }
491
492 // called when the parser finds an internal doctype subset
493 short ezxml_internal_dtd(ezxml_root_t root, char *s, size_t len)
494 {
495     char q, *c, *t, *n = NULL, *v, **ent, **pe;
496     int i = 0, j;
497
498     pe = memcpy(MALLOC(sizeof(EZXML_NIL)), EZXML_NIL, sizeof(EZXML_NIL));
499
500     for (s[len] = '\0'; s; )
501     {
502         while (*s && *s != '<' && *s != '%')
503         {
504             s++;    // find next declaration
505         }
506
507         if (! *s)
508         {
509             break;
510         }
511         else if (! strncmp(s, "<!ENTITY", 8))   // parse entity definitions
512         {
513             c = s += strspn(s + 8, EZXML_WS) + 8; // skip white space separator
514             n = s + strspn(s, EZXML_WS "%"); // find name
515             *(s = n + strcspn(n, EZXML_WS)) = ';'; // append ; to name
516
517             v = s + strspn(s + 1, EZXML_WS) + 1; // find value
518             if ((q = *(v++)) != '"' && q != '\'')   // skip externals
519             {
520                 s = strchr(s, '>');
521                 continue;
522             }
523
524             for (i = 0, ent = (*c == '%') ? pe : root->ent; ent[i]; i++)
525             {
526                 ;
527             }
528             ent = REALLOC(ent, (i + 3) * sizeof(char *)); // space for next ent
529             if (*c == '%')
530             {
531                 pe = ent;
532             }
533             else
534             {
535                 root->ent = ent;
536             }
537
538             *(++s) = '\0'; // null terminate name
539             if ((s = strchr(v, q)))
540             {
541                 *(s++) = '\0';    // null terminate value
542             }
543             ent[i + 1] = ezxml_decode(v, pe, '%'); // set value
544             ent[i + 2] = NULL; // null terminate entity list
545             if (! ezxml_ent_ok(n, ent[i + 1], ent))   // circular reference
546             {
547                 if (ent[i + 1] != v)
548                 {
549                     FREE(ent[i + 1]);
550                 }
551                 ezxml_err(root, v, "circular entity declaration &%s", n);
552                 break;
553             }
554             else
555             {
556                 ent[i] = n;    // set entity name
557             }
558         }
559         else if (! strncmp(s, "<!ATTLIST", 9))   // parse default attributes
560         {
561             t = s + strspn(s + 9, EZXML_WS) + 9; // skip whitespace separator
562             if (! *t)
563             {
564                 ezxml_err(root, t, "unclosed <!ATTLIST");
565                 break;
566             }
567             if (*(s = t + strcspn(t, EZXML_WS ">")) == '>')
568             {
569                 continue;
570             }
571             else
572             {
573                 *s = '\0';    // null terminate tag name
574             }
575
576             if (n)
577             {
578                 for (i = 0; root->attr[i] && strcmp(n, root->attr[i][0]); i++)
579                 {
580                     ;
581                 }
582             }
583
584             while (*(n = ++s + strspn(s, EZXML_WS)) && *n != '>')
585             {
586                 if (*(s = n + strcspn(n, EZXML_WS)))
587                 {
588                     *s = '\0';    // attr name
589                 }
590                 else
591                 {
592                     ezxml_err(root, t, "malformed <!ATTLIST");
593                     break;
594                 }
595
596                 s += strspn(s + 1, EZXML_WS) + 1; // find next token
597                 c = (strncmp(s, "CDATA", 5)) ? "*" : " "; // is it cdata?
598                 if (! strncmp(s, "NOTATION", 8))
599                 {
600                     s += strspn(s + 8, EZXML_WS) + 8;
601                 }
602                 s = (*s == '(') ? strchr(s, ')') : s + strcspn(s, EZXML_WS);
603                 if (! s)
604                 {
605                     ezxml_err(root, t, "malformed <!ATTLIST");
606                     break;
607                 }
608
609                 s += strspn(s, EZXML_WS ")"); // skip white space separator
610                 if (! strncmp(s, "#FIXED", 6))
611                 {
612                     s += strspn(s + 6, EZXML_WS) + 6;
613                 }
614                 if (*s == '#')   // no default value
615                 {
616                     s += strcspn(s, EZXML_WS ">") - 1;
617                     if (*c == ' ')
618                     {
619                         continue;    // cdata is default, nothing to do
620                     }
621                     v = NULL;
622                 }
623                 else if ((*s == '"' || *s == '\'')  &&  // default value
624                          (s = strchr(v = s + 1, *s)))
625                 {
626                     *s = '\0';
627                 }
628                 else
629                 {
630                     ezxml_err(root, t, "malformed <!ATTLIST");
631                     break;
632                 }
633
634                 if (! root->attr[i])   // new tag name
635                 {
636                     root->attr = (! i) ? MALLOC(2 * sizeof(char **))
637                                  : REALLOC(root->attr,
638                                            (i + 2) * sizeof(char **));
639                     root->attr[i] = MALLOC(2 * sizeof(char *));
640                     root->attr[i][0] = t; // set tag name
641                     root->attr[i][1] = (char *)(root->attr[i + 1] = NULL);
642                 }
643
644                 for (j = 1; root->attr[i][j]; j += 3)
645                 {
646                     ;    // find end of list
647                 }
648                 root->attr[i] = REALLOC(root->attr[i],
649                                         (j + 4) * sizeof(char *));
650
651                 root->attr[i][j + 3] = NULL; // null terminate list
652                 root->attr[i][j + 2] = c; // is it cdata?
653                 root->attr[i][j + 1] = (v) ? ezxml_decode(v, root->ent, *c)
654                                        : NULL;
655                 root->attr[i][j] = n; // attribute name
656             }
657         }
658         else if (! strncmp(s, "<!--", 4))
659         {
660             s = strstr(s + 4, "-->");    // comments
661         }
662         else if (! strncmp(s, "<?", 2))   // processing instructions
663         {
664             if ((s = strstr(c = s + 2, "?>")))
665             {
666                 ezxml_proc_inst(root, c, s++ - c);
667             }
668         }
669         else if (*s == '<')
670         {
671             s = strchr(s, '>');    // skip other declarations
672         }
673         else if (*(s++) == '%' && ! root->standalone)
674         {
675             break;
676         }
677     }
678
679     FREE(pe);
680     return ! *root->err;
681 }
682
683 // Converts a UTF-16 string to UTF-8. Returns a new string that must be freed
684 // or NULL if no conversion was needed.
685 char *ezxml_str2utf8(char **s, size_t *len)
686 {
687     char *u;
688     size_t l = 0, sl, max = *len;
689     long c, d;
690     int b, be = (**s == '\xFE') ? 1 : (**s == '\xFF') ? 0 : -1;
691
692     if (be == -1)
693     {
694         return NULL;    // not UTF-16
695     }
696
697     u = MALLOC(max);
698     for (sl = 2; sl < *len - 1; sl += 2)
699     {
700         c = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF)  //UTF-16BE
701             : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); //UTF-16LE
702         if (c >= 0xD800 && c <= 0xDFFF && (sl += 2) < *len - 1)   // high-half
703         {
704             d = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF)
705                 : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF);
706             c = (((c & 0x3FF) << 10) | (d & 0x3FF)) + 0x10000;
707         }
708
709         while (l + 6 > max)
710         {
711             u = REALLOC(u, max += EZXML_BUFSIZE);
712         }
713         if (c < 0x80)
714         {
715             u[l++] = (char)c;    // US-ASCII subset
716         }
717         else   // multi-byte UTF-8 sequence
718         {
719             for (b = 0, d = c; d; d /= 2)
720             {
721                 b++;    // bits in c
722             }
723             b = (b - 2) / 5; // bytes in payload
724             u[l++] = (char)(0xFF << (7 - b)) | (char)(c >> (6 * b)); // head
725             while (b)
726             {
727                 u[l++] = 0x80 | ((c >> (6 * --b)) & 0x3F);    // payload
728             }
729         }
730     }
731     return *s = REALLOC(u, *len = l);
732 }
733
734 // frees a tag attribute list
735 void ezxml_free_attr(char **attr)
736 {
737     int i = 0;
738     char *m;
739
740     if (! attr || attr == EZXML_NIL)
741     {
742         return;    // nothing to free
743     }
744     while (attr[i])
745     {
746         i += 2;    // find end of attribute list
747     }
748     m = attr[i + 1]; // list of which names and values are malloced
749     for (i = 0; m[i]; i++)
750     {
751         if (m[i] & EZXML_NAMEM)
752         {
753             FREE(attr[i * 2]);
754         }
755         if (m[i] & EZXML_TXTM)
756         {
757             FREE(attr[(i * 2) + 1]);
758         }
759     }
760     FREE(m);
761     FREE(attr);
762 }
763
764 // parse the given xml string and return an ezxml structure
765 ezxml_t ezxml_parse_str(char *s, size_t len)
766 {
767     ezxml_root_t root = (ezxml_root_t)ezxml_new(NULL);
768     char q, e, *d, **attr, **a = NULL; // initialize a to avoid compile warning
769     int l, i, j;
770
771     root->m = s;
772     if (! len)
773     {
774         return ezxml_err(root, NULL, "root tag missing");
775     }
776     root->u = ezxml_str2utf8(&s, &len); // convert utf-16 to utf-8
777     root->e = (root->s = s) + len; // record start and end of work area
778
779     e = s[len - 1]; // save end char
780     s[len - 1] = '\0'; // turn end char into null terminator
781
782     while (*s && *s != '<')
783     {
784         s++;    // find first tag
785     }
786     if (! *s)
787     {
788         return ezxml_err(root, s, "root tag missing");
789     }
790
791     for (; ; )
792     {
793         attr = (char **)EZXML_NIL;
794         d = ++s;
795
796         if (isalpha(*s) || *s == '_' || *s == ':' || *s < '\0')   // new tag
797         {
798             if (! root->cur)
799             {
800                 return ezxml_err(root, d, "markup outside of root element");
801             }
802
803             s += strcspn(s, EZXML_WS "/>");
804             while (isspace(*s))
805             {
806                 *(s++) = '\0';    // null terminate tag name
807             }
808
809             if (*s && *s != '/' && *s != '>') // find tag in default attr list
810                 for (i = 0; (a = root->attr[i]) && strcmp(a[0], d); i++)
811                 {
812                     ;
813                 }
814
815             for (l = 0; *s && *s != '/' && *s != '>'; l += 2)   // new attrib
816             {
817                 attr = (l) ? REALLOC(attr, (l + 4) * sizeof(char *))
818                        : MALLOC(4 * sizeof(char *)); // allocate space
819                 attr[l + 3] = (l) ? REALLOC(attr[l + 1], (l / 2) + 2)
820                               : MALLOC(2); // mem for list of maloced vals
821                 strcpy(attr[l + 3] + (l / 2), " "); // value is not malloced
822                 attr[l + 2] = NULL; // null terminate list
823                 attr[l + 1] = ""; // temporary attribute value
824                 attr[l] = s; // set attribute name
825
826                 s += strcspn(s, EZXML_WS "=/>");
827                 if (*s == '=' || isspace(*s))
828                 {
829                     *(s++) = '\0'; // null terminate tag attribute name
830                     q = *(s += strspn(s, EZXML_WS "="));
831                     if (q == '"' || q == '\'')   // attribute value
832                     {
833                         attr[l + 1] = ++s;
834                         while (*s && *s != q)
835                         {
836                             s++;
837                         }
838                         if (*s)
839                         {
840                             *(s++) = '\0';    // null terminate attribute val
841                         }
842                         else
843                         {
844                             ezxml_free_attr(attr);
845                             return ezxml_err(root, d, "missing %c", q);
846                         }
847
848                         for (j = 1; a && a[j] && strcmp(a[j], attr[l]); j += 3)
849                         {
850                             ;
851                         }
852                         attr[l + 1] = ezxml_decode(attr[l + 1], root->ent, (a
853                                                    && a[j]) ? *a[j + 2] : ' ');
854                         if (attr[l + 1] < d || attr[l + 1] > s)
855                         {
856                             attr[l + 3][l / 2] = EZXML_TXTM;    // value malloced
857                         }
858                     }
859                 }
860                 while (isspace(*s))
861                 {
862                     s++;
863                 }
864             }
865
866             if (*s == '/')   // self closing tag
867             {
868                 *(s++) = '\0';
869                 if ((*s && *s != '>') || (! *s && e != '>'))
870                 {
871                     if (l)
872                     {
873                         ezxml_free_attr(attr);
874                     }
875                     return ezxml_err(root, d, "missing >");
876                 }
877                 ezxml_open_tag(root, d, attr);
878                 ezxml_close_tag(root, d, s);
879             }
880             else if ((q = *s) == '>' || (! *s && e == '>'))   // open tag
881             {
882                 *s = '\0'; // temporarily null terminate tag name
883                 ezxml_open_tag(root, d, attr);
884                 *s = q;
885             }
886             else
887             {
888                 if (l)
889                 {
890                     ezxml_free_attr(attr);
891                 }
892                 return ezxml_err(root, d, "missing >");
893             }
894         }
895         else if (*s == '/')   // close tag
896         {
897             s += strcspn(d = s + 1, EZXML_WS ">") + 1;
898             if (! (q = *s) && e != '>')
899             {
900                 return ezxml_err(root, d, "missing >");
901             }
902             *s = '\0'; // temporarily null terminate tag name
903             if (ezxml_close_tag(root, d, s))
904             {
905                 return &root->xml;
906             }
907             if (isspace(*s = q))
908             {
909                 s += strspn(s, EZXML_WS);
910             }
911         }
912         else if (! strncmp(s, "!--", 3))   // xml comment
913         {
914             if (! (s = strstr(s + 3, "--")) || (*(s += 2) != '>' && *s) ||
915                     (! *s && e != '>'))
916             {
917                 return ezxml_err(root, d, "unclosed <!--");
918             }
919         }
920         else if (! strncmp(s, "![CDATA[", 8))   // cdata
921         {
922             if ((s = strstr(s, "]]>")))
923             {
924                 ezxml_char_content(root, d + 8, (s += 2) - d - 10, 'c');
925             }
926             else
927             {
928                 return ezxml_err(root, d, "unclosed <![CDATA[");
929             }
930         }
931         else if (! strncmp(s, "!DOCTYPE", 8))   // dtd
932         {
933             for (l = 0; *s && ((! l && *s != '>') || (l && (*s != ']' ||
934                                *(s + strspn(s + 1, EZXML_WS) + 1) != '>')));
935                     l = (*s == '[') ? 1 : l)
936             {
937                 s += strcspn(s + 1, "[]>") + 1;
938             }
939             if (! *s && e != '>')
940             {
941                 return ezxml_err(root, d, "unclosed <!DOCTYPE");
942             }
943             d = (l) ? strchr(d, '[') + 1 : d;
944             if (l && ! ezxml_internal_dtd(root, d, s++ - d))
945             {
946                 return &root->xml;
947             }
948         }
949         else if (*s == '?')   // <?...?> processing instructions
950         {
951             do
952             {
953                 s = strchr(s, '?');
954             }
955             while (s && *(++s) && *s != '>');
956             if (! s || (! *s && e != '>'))
957             {
958                 return ezxml_err(root, d, "unclosed <?");
959             }
960             else
961             {
962                 ezxml_proc_inst(root, d + 1, s - d - 2);
963             }
964         }
965         else
966         {
967             return ezxml_err(root, d, "unexpected <");
968         }
969
970         if (! s || ! *s)
971         {
972             break;
973         }
974         *s = '\0';
975         d = ++s;
976         if (*s && *s != '<')   // tag character content
977         {
978             while (*s && *s != '<')
979             {
980                 s++;
981             }
982             if (*s)
983             {
984                 ezxml_char_content(root, d, s - d, '&');
985             }
986             else
987             {
988                 break;
989             }
990         }
991         else if (! *s)
992         {
993             break;
994         }
995     }
996
997     if (! root->cur)
998     {
999         return &root->xml;
1000     }
1001     else if (! root->cur->name)
1002     {
1003         return ezxml_err(root, d, "root tag missing");
1004     }
1005     else
1006     {
1007         return ezxml_err(root, d, "unclosed tag <%s>", root->cur->name);
1008     }
1009 }
1010
1011 // Wrapper for ezxml_parse_str() that accepts a file stream. Reads the entire
1012 // stream into memory and then parses it. For xml files, use ezxml_parse_file()
1013 // or ezxml_parse_fd()
1014 ezxml_t ezxml_parse_fp(FILE *fp)
1015 {
1016     ezxml_root_t root;
1017     size_t l, len = 0;
1018     char *s;
1019
1020     if (! (s = MALLOC(EZXML_BUFSIZE)))
1021     {
1022         return NULL;
1023     }
1024     do
1025     {
1026         len += (l = fread((s + len), 1, EZXML_BUFSIZE, fp));
1027         if (l == EZXML_BUFSIZE)
1028         {
1029             s = REALLOC(s, len + EZXML_BUFSIZE);
1030         }
1031     }
1032     while (s && l == EZXML_BUFSIZE);
1033
1034     if (! s)
1035     {
1036         return NULL;
1037     }
1038     root = (ezxml_root_t)ezxml_parse_str(s, len);
1039     root->len = (size_t) - 1; // so we know to free s in ezxml_free()
1040     return &root->xml;
1041 }
1042
1043 // A wrapper for ezxml_parse_str() that accepts a file descriptor. First
1044 // attempts to mem map the file. Failing that, reads the file into memory.
1045 // Returns NULL on failure.
1046 ezxml_t ezxml_parse_fd(int fd)
1047 {
1048     ezxml_root_t root;
1049     struct stat st;
1050     size_t l;
1051     void *m;
1052
1053     if (fd < 0)
1054     {
1055         return NULL;
1056     }
1057     if (fstat(fd, &st) < 0)
1058     {
1059         return NULL;
1060     }
1061
1062 #ifndef EZXML_NOMMAP
1063     l = (st.st_size + sysconf(_SC_PAGESIZE) - 1) & ~(sysconf(_SC_PAGESIZE) - 1);
1064     if ((m = mmap(NULL, l, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) != MAP_FAILED)
1065     {
1066         madvise(m, l, MADV_SEQUENTIAL); // optimize for sequential access
1067         root = (ezxml_root_t)ezxml_parse_str(m, st.st_size);
1068         madvise(m, root->len = l, MADV_NORMAL); // put it back to normal
1069     }
1070     else   // mmap failed, read file into memory
1071     {
1072 #endif // EZXML_NOMMAP
1073         int l_read = read(fd, m = MALLOC(st.st_size), st.st_size);
1074         if (l_read <= 0)
1075         {
1076             free(m);
1077             return NULL;
1078         }
1079         l = l_read;
1080
1081         root = (ezxml_root_t)ezxml_parse_str(m, l);
1082         root->len = (size_t) - 1; // so we know to free s in ezxml_free()
1083 #ifndef EZXML_NOMMAP
1084     }
1085 #endif // EZXML_NOMMAP
1086
1087     return &root->xml;
1088 }
1089
1090 // a wrapper for ezxml_parse_fd that accepts a file name
1091 ezxml_t ezxml_parse_file(const char *file)
1092 {
1093     int fd = open(file, O_RDONLY, 0);
1094     ezxml_t xml = ezxml_parse_fd(fd);
1095     if (fd >= 0)
1096     {
1097         close(fd);
1098     }
1099     return xml;
1100 }
1101
1102 // Encodes ampersand sequences appending the results to *dst, reallocating *dst
1103 // if length exceeds max. a is non-zero for attribute encoding. Returns *dst
1104 char *ezxml_ampencode(const char *s, size_t len, char **dst, size_t *dlen,
1105                       size_t *max, short a)
1106 {
1107     const char *e;
1108
1109     for (e = s + len; s != e; s++)
1110     {
1111         while (*dlen + 10 > *max)
1112         {
1113             *dst = REALLOC(*dst, *max += EZXML_BUFSIZE);
1114         }
1115
1116         switch (*s)
1117         {
1118             case '\0':
1119                 return *dst;
1120             case '&':
1121                 *dlen += sprintf(*dst + *dlen, "&amp;");
1122                 break;
1123             case '<':
1124                 *dlen += sprintf(*dst + *dlen, "&lt;");
1125                 break;
1126             case '>':
1127                 *dlen += sprintf(*dst + *dlen, "&gt;");
1128                 break;
1129             case '"':
1130                 *dlen += sprintf(*dst + *dlen, (a) ? "&quot;" : "\"");
1131                 break;
1132             case '\n':
1133                 *dlen += sprintf(*dst + *dlen, (a) ? "&#xA;" : "\n");
1134                 break;
1135             case '\t':
1136                 *dlen += sprintf(*dst + *dlen, (a) ? "&#x9;" : "\t");
1137                 break;
1138             case '\r':
1139                 *dlen += sprintf(*dst + *dlen, "&#xD;");
1140                 break;
1141             default:
1142                 (*dst)[(*dlen)++] = *s;
1143         }
1144     }
1145     return *dst;
1146 }
1147
1148 // Recursively converts each tag to xml appending it to *s. Reallocates *s if
1149 // its length excedes max. start is the location of the previous tag in the
1150 // parent tag's character content. Returns *s.
1151 char *ezxml_toxml_r(ezxml_t xml, char **s, size_t *len, size_t *max,
1152                     size_t start, char ***attr)
1153 {
1154     int i, j;
1155     char *txt = (xml->parent) ? xml->parent->txt : "";
1156     size_t off = 0;
1157
1158     // parent character content up to this tag
1159     *s = ezxml_ampencode(txt + start, xml->off - start, s, len, max, 0);
1160
1161     while (*len + strlen(xml->name) + 4 > *max) // reallocate s
1162     {
1163         *s = REALLOC(*s, *max += EZXML_BUFSIZE);
1164     }
1165
1166     *len += sprintf(*s + *len, "<%s", xml->name); // open tag
1167     for (i = 0; xml->attr[i]; i += 2)   // tag attributes
1168     {
1169         if (ezxml_attr(xml, xml->attr[i]) != xml->attr[i + 1])
1170         {
1171             continue;
1172         }
1173         while (*len + strlen(xml->attr[i]) + 7 > *max) // reallocate s
1174         {
1175             *s = REALLOC(*s, *max += EZXML_BUFSIZE);
1176         }
1177
1178         *len += sprintf(*s + *len, " %s=\"", xml->attr[i]);
1179         ezxml_ampencode(xml->attr[i + 1], (size_t)(-1), s, len, max, 1);
1180         *len += sprintf(*s + *len, "\"");
1181     }
1182
1183     for (i = 0; attr[i] && strcmp(attr[i][0], xml->name); i++)
1184     {
1185         ;
1186     }
1187     for (j = 1; attr[i] && attr[i][j]; j += 3)   // default attributes
1188     {
1189         if (! attr[i][j + 1] || ezxml_attr(xml, attr[i][j]) != attr[i][j + 1])
1190         {
1191             continue;    // skip duplicates and non-values
1192         }
1193         while (*len + strlen(attr[i][j]) + 7 > *max) // reallocate s
1194         {
1195             *s = REALLOC(*s, *max += EZXML_BUFSIZE);
1196         }
1197
1198         *len += sprintf(*s + *len, " %s=\"", attr[i][j]);
1199         ezxml_ampencode(attr[i][j + 1], (size_t)(-1), s, len, max, 1);
1200         *len += sprintf(*s + *len, "\"");
1201     }
1202     *len += sprintf(*s + *len, ">");
1203
1204     *s = (xml->child) ? ezxml_toxml_r(xml->child, s, len, max, 0, attr) //child
1205          : ezxml_ampencode(xml->txt, (size_t)(-1), s, len, max, 0);  //data
1206
1207     while (*len + strlen(xml->name) + 4 > *max) // reallocate s
1208     {
1209         *s = REALLOC(*s, *max += EZXML_BUFSIZE);
1210     }
1211
1212     *len += sprintf(*s + *len, "</%s>", xml->name); // close tag
1213
1214     while (txt[off] && off < xml->off)
1215     {
1216         off++;    // make sure off is within bounds
1217     }
1218     return (xml->ordered) ? ezxml_toxml_r(xml->ordered, s, len, max, off, attr)
1219            : ezxml_ampencode(txt + off, (size_t)(-1), s, len, max, 0);
1220 }
1221
1222 // Converts an ezxml structure back to xml. Returns a string of xml data that
1223 // must be freed.
1224 char *ezxml_toxml(ezxml_t xml)
1225 {
1226     ezxml_t p = (xml) ? xml->parent : NULL, o = (xml) ? xml->ordered : NULL;
1227     ezxml_root_t root = (ezxml_root_t)xml;
1228     size_t len = 0, max = EZXML_BUFSIZE;
1229     char *s = strcpy(MALLOC(max), ""), *t, *n;
1230     int i, j, k;
1231
1232     if (! xml || ! xml->name)
1233     {
1234         return REALLOC(s, len + 1);
1235     }
1236     while (root->xml.parent)
1237     {
1238         root = (ezxml_root_t)root->xml.parent;    // root tag
1239     }
1240
1241     for (i = 0; ! p && root->pi[i]; i++)   // pre-root processing instructions
1242     {
1243         for (k = 2; root->pi[i][k - 1]; k++)
1244         {
1245             ;
1246         }
1247         for (j = 1; (n = root->pi[i][j]); j++)
1248         {
1249             if (root->pi[i][k][j - 1] == '>')
1250             {
1251                 continue;    // not pre-root
1252             }
1253             while (len + strlen(t = root->pi[i][0]) + strlen(n) + 7 > max)
1254             {
1255                 s = REALLOC(s, max += EZXML_BUFSIZE);
1256             }
1257             len += sprintf(s + len, "<?%s%s%s?>\n", t, *n ? " " : "", n);
1258         }
1259     }
1260
1261     xml->parent = xml->ordered = NULL;
1262     s = ezxml_toxml_r(xml, &s, &len, &max, 0, root->attr);
1263     xml->parent = p;
1264     xml->ordered = o;
1265
1266     for (i = 0; ! p && root->pi[i]; i++)   // post-root processing instructions
1267     {
1268         for (k = 2; root->pi[i][k - 1]; k++)
1269         {
1270             ;
1271         }
1272         for (j = 1; (n = root->pi[i][j]); j++)
1273         {
1274             if (root->pi[i][k][j - 1] == '<')
1275             {
1276                 continue;    // not post-root
1277             }
1278             while (len + strlen(t = root->pi[i][0]) + strlen(n) + 7 > max)
1279             {
1280                 s = REALLOC(s, max += EZXML_BUFSIZE);
1281             }
1282             len += sprintf(s + len, "\n<?%s%s%s?>", t, *n ? " " : "", n);
1283         }
1284     }
1285     return REALLOC(s, len + 1);
1286 }
1287
1288 // free the memory allocated for the ezxml structure
1289 void ezxml_free(ezxml_t xml)
1290 {
1291     ezxml_root_t root = (ezxml_root_t)xml;
1292     int i, j;
1293     char **a, *s;
1294
1295     if (! xml)
1296     {
1297         return;
1298     }
1299     ezxml_free(xml->child);
1300     ezxml_free(xml->ordered);
1301
1302     if (! xml->parent)   // free root tag allocations
1303     {
1304         for (i = 10; root->ent[i]; i += 2) // 0 - 9 are default entites (<>&"')
1305             if ((s = root->ent[i + 1]) < root->s || s > root->e)
1306             {
1307                 free(s);
1308             }
1309         FREE(root->ent); // free list of general entities
1310
1311         for (i = 0; (a = root->attr[i]); i++)
1312         {
1313             for (j = 1; a[j++]; j += 2) // free malloced attribute values
1314                 if (a[j] && (a[j] < root->s || a[j] > root->e))
1315                 {
1316                     FREE(a[j]);
1317                 }
1318             FREE(a);
1319         }
1320         if (root->attr[0])
1321         {
1322             FREE(root->attr);    // free default attribute list
1323         }
1324
1325         for (i = 0; root->pi[i]; i++)
1326         {
1327             for (j = 1; root->pi[i][j]; j++)
1328             {
1329                 ;
1330             }
1331             FREE(root->pi[i][j + 1]);
1332             FREE(root->pi[i]);
1333         }
1334         if (root->pi[0])
1335         {
1336             FREE(root->pi);    // free processing instructions
1337         }
1338
1339         if (root->len == -1)
1340         {
1341             FREE(root->m);    // malloced xml data
1342         }
1343 #ifndef EZXML_NOMMAP
1344         else if (root->len)
1345         {
1346             munmap(root->m, root->len);    // mem mapped xml data
1347         }
1348 #endif // EZXML_NOMMAP
1349         if (root->u)
1350         {
1351             FREE(root->u);    // utf8 conversion
1352         }
1353     }
1354
1355     ezxml_free_attr(xml->attr); // tag attributes
1356     if ((xml->flags & EZXML_TXTM))
1357     {
1358         FREE(xml->txt);    // character content
1359     }
1360     if ((xml->flags & EZXML_NAMEM))
1361     {
1362         FREE(xml->name);    // tag name
1363     }
1364     FREE(xml);
1365 }
1366
1367 // return parser error message or empty string if none
1368 const char *ezxml_error(ezxml_t xml)
1369 {
1370     while (xml && xml->parent)
1371     {
1372         xml = xml->parent;    // find root tag
1373     }
1374     return (xml) ? ((ezxml_root_t)xml)->err : "";
1375 }
1376
1377 // returns a new empty ezxml structure with the given root tag name
1378 ezxml_t ezxml_new(const char *name)
1379 {
1380     static char *ent[] = { "lt;", "&#60;", "gt;", "&#62;", "quot;", "&#34;",
1381                            "apos;", "&#39;", "amp;", "&#38;", NULL
1382                          };
1383     ezxml_root_t root = (ezxml_root_t)memset(MALLOC(sizeof(struct ezxml_root)),
1384                         '\0', sizeof(struct ezxml_root));
1385     root->xml.name = (char *)name;
1386     root->cur = &root->xml;
1387     strcpy(root->err, root->xml.txt = "");
1388     root->ent = memcpy(MALLOC(sizeof(ent)), ent, sizeof(ent));
1389     root->attr = root->pi = (char ***)(root->xml.attr = EZXML_NIL);
1390     return &root->xml;
1391 }
1392
1393 // inserts an existing tag into an ezxml structure
1394 ezxml_t ezxml_insert(ezxml_t xml, ezxml_t dest, size_t off)
1395 {
1396     ezxml_t cur, prev, head;
1397
1398     xml->next = xml->sibling = xml->ordered = NULL;
1399     xml->off = off;
1400     xml->parent = dest;
1401
1402     if ((head = dest->child))   // already have sub tags
1403     {
1404         if (head->off <= off)   // not first subtag
1405         {
1406             for (cur = head; cur->ordered && cur->ordered->off <= off;
1407                     cur = cur->ordered)
1408             {
1409                 ;
1410             }
1411             xml->ordered = cur->ordered;
1412             cur->ordered = xml;
1413         }
1414         else   // first subtag
1415         {
1416             xml->ordered = head;
1417             dest->child = xml;
1418         }
1419
1420         for (cur = head, prev = NULL; cur && strcmp(cur->name, xml->name);
1421                 prev = cur, cur = cur->sibling)
1422         {
1423             ;    // find tag type
1424         }
1425         if (cur && cur->off <= off)   // not first of type
1426         {
1427             while (cur->next && cur->next->off <= off)
1428             {
1429                 cur = cur->next;
1430             }
1431             xml->next = cur->next;
1432             cur->next = xml;
1433         }
1434         else   // first tag of this type
1435         {
1436             if (prev && cur)
1437             {
1438                 prev->sibling = cur->sibling;    // remove old first
1439             }
1440             xml->next = cur; // old first tag is now next
1441             for (cur = head, prev = NULL; cur && cur->off <= off;
1442                     prev = cur, cur = cur->sibling)
1443             {
1444                 ;    // new sibling insert point
1445             }
1446             xml->sibling = cur;
1447             if (prev)
1448             {
1449                 prev->sibling = xml;
1450             }
1451         }
1452     }
1453     else
1454     {
1455         dest->child = xml;    // only sub tag
1456     }
1457
1458     return xml;
1459 }
1460
1461 // Adds a child tag. off is the offset of the child tag relative to the start
1462 // of the parent tag's character content. Returns the child tag.
1463 ezxml_t ezxml_add_child(ezxml_t xml, const char *name, size_t off)
1464 {
1465     ezxml_t child;
1466
1467     if (! xml)
1468     {
1469         return NULL;
1470     }
1471     child = (ezxml_t)memset(MALLOC(sizeof(struct ezxml)), '\0',
1472                             sizeof(struct ezxml));
1473     child->name = (char *)name;
1474     child->attr = EZXML_NIL;
1475     child->txt = "";
1476
1477     return ezxml_insert(child, xml, off);
1478 }
1479
1480 // sets the character content for the given tag and returns the tag
1481 ezxml_t ezxml_set_txt(ezxml_t xml, const char *txt)
1482 {
1483     if (! xml)
1484     {
1485         return NULL;
1486     }
1487     if (xml->flags & EZXML_TXTM)
1488     {
1489         FREE(xml->txt);    // existing txt was malloced
1490     }
1491     xml->flags &= ~EZXML_TXTM;
1492     xml->txt = (char *)txt;
1493     return xml;
1494 }
1495
1496 // Sets the given tag attribute or adds a new attribute if not found. A value
1497 // of NULL will remove the specified attribute. Returns the tag given.
1498 ezxml_t ezxml_set_attr(ezxml_t xml, const char *name, char *value)
1499 {
1500     int l = 0, c;
1501     if (! xml)
1502     {
1503         return NULL;
1504     }
1505     while (xml->attr[l] && strcmp(xml->attr[l], name))
1506     {
1507         l += 2;
1508     }
1509
1510     if (! xml->attr[l])   // not found, add as new attribute
1511     {
1512         if (! value)
1513         {
1514             return xml;    // nothing to do
1515         }
1516         if (xml->attr == EZXML_NIL)   // first attribute
1517         {
1518             xml->attr = MALLOC(4 * sizeof(char *));
1519             xml->attr[1] = os_strdup(""); // empty list of malloced names/vals
1520         }
1521         else
1522         {
1523             xml->attr = REALLOC(xml->attr, (l + 4) * sizeof(char *));
1524         }
1525
1526         xml->attr[l] = (char *)name; // set attribute name
1527         xml->attr[l + 2] = NULL; // null terminate attribute list
1528         xml->attr[l + 3] = REALLOC(xml->attr[l + 1], (c = (int)strlen(xml->attr[l + 1])) + 2);
1529         strcpy(xml->attr[l + 3] + c, " "); // set name/value as not malloced
1530         if (xml->flags & EZXML_DUP)
1531         {
1532             xml->attr[l + 3][c] = EZXML_NAMEM;
1533         }
1534     }
1535     else if (xml->flags & EZXML_DUP)
1536     {
1537         FREE((char *)name);    // name was os_strduped
1538     }
1539
1540     for (c = l; xml->attr[c]; c += 2)
1541     {
1542         ;    // find end of attribute list
1543     }
1544     if (xml->attr[c + 1][l / 2] & EZXML_TXTM)
1545     {
1546         FREE(xml->attr[l + 1]);    //old val
1547     }
1548     if (xml->flags & EZXML_DUP)
1549     {
1550         xml->attr[c + 1][l / 2] |= EZXML_TXTM;
1551     }
1552     else
1553     {
1554         xml->attr[c + 1][l / 2] &= ~EZXML_TXTM;
1555     }
1556
1557     if (value)
1558     {
1559         xml->attr[l + 1] = (char *)value;    // set attribute value
1560     }
1561     else   // remove attribute
1562     {
1563         if (xml->attr[c + 1][l / 2] & EZXML_NAMEM)
1564         {
1565             FREE(xml->attr[l]);
1566         }
1567         memmove(xml->attr + l, xml->attr + l + 2, (c - l + 2) * sizeof(char*));
1568         xml->attr = REALLOC(xml->attr, (c + 2) * sizeof(char *));
1569         memmove(xml->attr[c + 1] + (l / 2), xml->attr[c + 1] + (l / 2) + 1,
1570                 (c / 2) - (l / 2)); // fix list of which name/vals are malloced
1571     }
1572     xml->flags &= ~EZXML_DUP; // clear os_strdup() flag
1573     return xml;
1574 }
1575
1576 // sets a flag for the given tag and returns the tag
1577 ezxml_t ezxml_set_flag(ezxml_t xml, short flag)
1578 {
1579     if (xml)
1580     {
1581         xml->flags |= flag;
1582     }
1583     return xml;
1584 }
1585
1586 // removes a tag along with its subtags without freeing its memory
1587 ezxml_t ezxml_cut(ezxml_t xml)
1588 {
1589     ezxml_t cur;
1590
1591     if (! xml)
1592     {
1593         return NULL;    // nothing to do
1594     }
1595     if (xml->next)
1596     {
1597         xml->next->sibling = xml->sibling;    // patch sibling list
1598     }
1599
1600     if (xml->parent)   // not root tag
1601     {
1602         cur = xml->parent->child; // find head of subtag list
1603         if (cur == xml)
1604         {
1605             xml->parent->child = xml->ordered;    // first subtag
1606         }
1607         else   // not first subtag
1608         {
1609             while (cur->ordered != xml)
1610             {
1611                 cur = cur->ordered;
1612             }
1613             cur->ordered = cur->ordered->ordered; // patch ordered list
1614
1615             cur = xml->parent->child; // go back to head of subtag list
1616             if (strcmp(cur->name, xml->name))   // not in first sibling list
1617             {
1618                 while (strcmp(cur->sibling->name, xml->name))
1619                 {
1620                     cur = cur->sibling;
1621                 }
1622                 if (cur->sibling == xml)   // first of a sibling list
1623                 {
1624                     cur->sibling = (xml->next) ? xml->next
1625                                    : cur->sibling->sibling;
1626                 }
1627                 else
1628                 {
1629                     cur = cur->sibling;    // not first of a sibling list
1630                 }
1631             }
1632
1633             while (cur->next && cur->next != xml)
1634             {
1635                 cur = cur->next;
1636             }
1637             if (cur->next)
1638             {
1639                 cur->next = cur->next->next;    // patch next list
1640             }
1641         }
1642     }
1643     xml->ordered = xml->sibling = xml->next = NULL;
1644     return xml;
1645 }
1646 /*----------------------------------------------------------*/
1647 int write_in_child(ezxml_t *parent, char *idx, char *w)
1648 {
1649     ezxml_t terminal, structx, subnode, i_value;
1650
1651     int result;
1652
1653
1654     for (terminal = ezxml_child(*parent, "terminal"); terminal; terminal = terminal->next)
1655     {
1656         if (strcmp(ezxml_child(terminal, "id")->txt, idx) == 0)
1657         {
1658             i_value = ezxml_child(terminal, "initial_value");
1659             ezxml_set_attr(i_value, "value", w);
1660             return 1; /*  found */
1661         }
1662     }
1663
1664     for (structx = ezxml_child(*parent, "struct"); structx; structx = structx->next)
1665     {
1666         for (subnode = ezxml_child(structx, "subnodes"); subnode; subnode = subnode->next)
1667         {
1668             result = write_in_child(&subnode, idx, w);
1669             if (result == 1 )
1670             {
1671                 return result;
1672             }
1673         }
1674     }
1675
1676     return 0; /* not found*/
1677 }
1678 /*----------------------------------------------------------*/
1679 int search_in_child(ezxml_t *parent, char *idx, char *value)
1680 {
1681     ezxml_t terminal, structx, subnode, i_value;
1682     const char *teamname;
1683     int result;
1684
1685     for (terminal = ezxml_child(*parent, "terminal"); terminal; terminal = terminal->next)
1686     {
1687         if (strcmp(ezxml_child(terminal, "id")->txt, idx) == 0)
1688         {
1689             i_value = ezxml_child(terminal, "initial_value");
1690             teamname = ezxml_attr(i_value, "value");
1691             strcpy (value, teamname);
1692             return 1; /*  found */
1693         }
1694     }
1695
1696     for (structx = ezxml_child(*parent, "struct"); structx; structx = structx->next)
1697     {
1698         for (subnode = ezxml_child(structx, "subnodes"); subnode; subnode = subnode->next)
1699         {
1700             result = search_in_child(&subnode, idx, value);
1701             if (result == 1 )
1702             {
1703                 return result;
1704             }
1705         }
1706     }
1707
1708     return 0; /* not found*/
1709 }
1710
1711 /*----------------------------------------------------------*/