e8f90373af98ca9dc91c10be2b261faa2fb5f24a
[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, 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         l = read(fd, m = MALLOC(st.st_size), st.st_size);
1074         if (l > 0)
1075         {
1076             root = (ezxml_root_t)ezxml_parse_str(m, l);
1077             root->len = (size_t) - 1; // so we know to free s in ezxml_free()
1078         }
1079 #ifndef EZXML_NOMMAP
1080     }
1081 #endif // EZXML_NOMMAP
1082
1083     return &root->xml;
1084 }
1085
1086 // a wrapper for ezxml_parse_fd that accepts a file name
1087 ezxml_t ezxml_parse_file(const char *file)
1088 {
1089     int fd = open(file, O_RDONLY, 0);
1090     ezxml_t xml = ezxml_parse_fd(fd);
1091     if (fd >= 0)
1092     {
1093         close(fd);
1094     }
1095     return xml;
1096 }
1097
1098 // Encodes ampersand sequences appending the results to *dst, reallocating *dst
1099 // if length exceeds max. a is non-zero for attribute encoding. Returns *dst
1100 char *ezxml_ampencode(const char *s, size_t len, char **dst, size_t *dlen,
1101                       size_t *max, short a)
1102 {
1103     const char *e;
1104
1105     for (e = s + len; s != e; s++)
1106     {
1107         while (*dlen + 10 > *max)
1108         {
1109             *dst = REALLOC(*dst, *max += EZXML_BUFSIZE);
1110         }
1111
1112         switch (*s)
1113         {
1114             case '\0':
1115                 return *dst;
1116             case '&':
1117                 *dlen += sprintf(*dst + *dlen, "&amp;");
1118                 break;
1119             case '<':
1120                 *dlen += sprintf(*dst + *dlen, "&lt;");
1121                 break;
1122             case '>':
1123                 *dlen += sprintf(*dst + *dlen, "&gt;");
1124                 break;
1125             case '"':
1126                 *dlen += sprintf(*dst + *dlen, (a) ? "&quot;" : "\"");
1127                 break;
1128             case '\n':
1129                 *dlen += sprintf(*dst + *dlen, (a) ? "&#xA;" : "\n");
1130                 break;
1131             case '\t':
1132                 *dlen += sprintf(*dst + *dlen, (a) ? "&#x9;" : "\t");
1133                 break;
1134             case '\r':
1135                 *dlen += sprintf(*dst + *dlen, "&#xD;");
1136                 break;
1137             default:
1138                 (*dst)[(*dlen)++] = *s;
1139         }
1140     }
1141     return *dst;
1142 }
1143
1144 // Recursively converts each tag to xml appending it to *s. Reallocates *s if
1145 // its length excedes max. start is the location of the previous tag in the
1146 // parent tag's character content. Returns *s.
1147 char *ezxml_toxml_r(ezxml_t xml, char **s, size_t *len, size_t *max,
1148                     size_t start, char ***attr)
1149 {
1150     int i, j;
1151     char *txt = (xml->parent) ? xml->parent->txt : "";
1152     size_t off = 0;
1153
1154     // parent character content up to this tag
1155     *s = ezxml_ampencode(txt + start, xml->off - start, s, len, max, 0);
1156
1157     while (*len + strlen(xml->name) + 4 > *max) // reallocate s
1158     {
1159         *s = REALLOC(*s, *max += EZXML_BUFSIZE);
1160     }
1161
1162     *len += sprintf(*s + *len, "<%s", xml->name); // open tag
1163     for (i = 0; xml->attr[i]; i += 2)   // tag attributes
1164     {
1165         if (ezxml_attr(xml, xml->attr[i]) != xml->attr[i + 1])
1166         {
1167             continue;
1168         }
1169         while (*len + strlen(xml->attr[i]) + 7 > *max) // reallocate s
1170         {
1171             *s = REALLOC(*s, *max += EZXML_BUFSIZE);
1172         }
1173
1174         *len += sprintf(*s + *len, " %s=\"", xml->attr[i]);
1175         ezxml_ampencode(xml->attr[i + 1], (size_t)(-1), s, len, max, 1);
1176         *len += sprintf(*s + *len, "\"");
1177     }
1178
1179     for (i = 0; attr[i] && strcmp(attr[i][0], xml->name); i++)
1180     {
1181         ;
1182     }
1183     for (j = 1; attr[i] && attr[i][j]; j += 3)   // default attributes
1184     {
1185         if (! attr[i][j + 1] || ezxml_attr(xml, attr[i][j]) != attr[i][j + 1])
1186         {
1187             continue;    // skip duplicates and non-values
1188         }
1189         while (*len + strlen(attr[i][j]) + 7 > *max) // reallocate s
1190         {
1191             *s = REALLOC(*s, *max += EZXML_BUFSIZE);
1192         }
1193
1194         *len += sprintf(*s + *len, " %s=\"", attr[i][j]);
1195         ezxml_ampencode(attr[i][j + 1], (size_t)(-1), s, len, max, 1);
1196         *len += sprintf(*s + *len, "\"");
1197     }
1198     *len += sprintf(*s + *len, ">");
1199
1200     *s = (xml->child) ? ezxml_toxml_r(xml->child, s, len, max, 0, attr) //child
1201          : ezxml_ampencode(xml->txt, (size_t)(-1), s, len, max, 0);  //data
1202
1203     while (*len + strlen(xml->name) + 4 > *max) // reallocate s
1204     {
1205         *s = REALLOC(*s, *max += EZXML_BUFSIZE);
1206     }
1207
1208     *len += sprintf(*s + *len, "</%s>", xml->name); // close tag
1209
1210     while (txt[off] && off < xml->off)
1211     {
1212         off++;    // make sure off is within bounds
1213     }
1214     return (xml->ordered) ? ezxml_toxml_r(xml->ordered, s, len, max, off, attr)
1215            : ezxml_ampencode(txt + off, (size_t)(-1), s, len, max, 0);
1216 }
1217
1218 // Converts an ezxml structure back to xml. Returns a string of xml data that
1219 // must be freed.
1220 char *ezxml_toxml(ezxml_t xml)
1221 {
1222     ezxml_t p = (xml) ? xml->parent : NULL, o = (xml) ? xml->ordered : NULL;
1223     ezxml_root_t root = (ezxml_root_t)xml;
1224     size_t len = 0, max = EZXML_BUFSIZE;
1225     char *s = strcpy(MALLOC(max), ""), *t, *n;
1226     int i, j, k;
1227
1228     if (! xml || ! xml->name)
1229     {
1230         return REALLOC(s, len + 1);
1231     }
1232     while (root->xml.parent)
1233     {
1234         root = (ezxml_root_t)root->xml.parent;    // root tag
1235     }
1236
1237     for (i = 0; ! p && root->pi[i]; i++)   // pre-root processing instructions
1238     {
1239         for (k = 2; root->pi[i][k - 1]; k++)
1240         {
1241             ;
1242         }
1243         for (j = 1; (n = root->pi[i][j]); j++)
1244         {
1245             if (root->pi[i][k][j - 1] == '>')
1246             {
1247                 continue;    // not pre-root
1248             }
1249             while (len + strlen(t = root->pi[i][0]) + strlen(n) + 7 > max)
1250             {
1251                 s = REALLOC(s, max += EZXML_BUFSIZE);
1252             }
1253             len += sprintf(s + len, "<?%s%s%s?>\n", t, *n ? " " : "", n);
1254         }
1255     }
1256
1257     xml->parent = xml->ordered = NULL;
1258     s = ezxml_toxml_r(xml, &s, &len, &max, 0, root->attr);
1259     xml->parent = p;
1260     xml->ordered = o;
1261
1262     for (i = 0; ! p && root->pi[i]; i++)   // post-root processing instructions
1263     {
1264         for (k = 2; root->pi[i][k - 1]; k++)
1265         {
1266             ;
1267         }
1268         for (j = 1; (n = root->pi[i][j]); j++)
1269         {
1270             if (root->pi[i][k][j - 1] == '<')
1271             {
1272                 continue;    // not post-root
1273             }
1274             while (len + strlen(t = root->pi[i][0]) + strlen(n) + 7 > max)
1275             {
1276                 s = REALLOC(s, max += EZXML_BUFSIZE);
1277             }
1278             len += sprintf(s + len, "\n<?%s%s%s?>", t, *n ? " " : "", n);
1279         }
1280     }
1281     return REALLOC(s, len + 1);
1282 }
1283
1284 // free the memory allocated for the ezxml structure
1285 void ezxml_free(ezxml_t xml)
1286 {
1287     ezxml_root_t root = (ezxml_root_t)xml;
1288     int i, j;
1289     char **a, *s;
1290
1291     if (! xml)
1292     {
1293         return;
1294     }
1295     ezxml_free(xml->child);
1296     ezxml_free(xml->ordered);
1297
1298     if (! xml->parent)   // free root tag allocations
1299     {
1300         for (i = 10; root->ent[i]; i += 2) // 0 - 9 are default entites (<>&"')
1301             if ((s = root->ent[i + 1]) < root->s || s > root->e)
1302             {
1303                 free(s);
1304             }
1305         FREE(root->ent); // free list of general entities
1306
1307         for (i = 0; (a = root->attr[i]); i++)
1308         {
1309             for (j = 1; a[j++]; j += 2) // free malloced attribute values
1310                 if (a[j] && (a[j] < root->s || a[j] > root->e))
1311                 {
1312                     FREE(a[j]);
1313                 }
1314             FREE(a);
1315         }
1316         if (root->attr[0])
1317         {
1318             FREE(root->attr);    // free default attribute list
1319         }
1320
1321         for (i = 0; root->pi[i]; i++)
1322         {
1323             for (j = 1; root->pi[i][j]; j++)
1324             {
1325                 ;
1326             }
1327             FREE(root->pi[i][j + 1]);
1328             FREE(root->pi[i]);
1329         }
1330         if (root->pi[0])
1331         {
1332             FREE(root->pi);    // free processing instructions
1333         }
1334
1335         if (root->len == -1)
1336         {
1337             FREE(root->m);    // malloced xml data
1338         }
1339 #ifndef EZXML_NOMMAP
1340         else if (root->len)
1341         {
1342             munmap(root->m, root->len);    // mem mapped xml data
1343         }
1344 #endif // EZXML_NOMMAP
1345         if (root->u)
1346         {
1347             FREE(root->u);    // utf8 conversion
1348         }
1349     }
1350
1351     ezxml_free_attr(xml->attr); // tag attributes
1352     if ((xml->flags & EZXML_TXTM))
1353     {
1354         FREE(xml->txt);    // character content
1355     }
1356     if ((xml->flags & EZXML_NAMEM))
1357     {
1358         FREE(xml->name);    // tag name
1359     }
1360     FREE(xml);
1361 }
1362
1363 // return parser error message or empty string if none
1364 const char *ezxml_error(ezxml_t xml)
1365 {
1366     while (xml && xml->parent)
1367     {
1368         xml = xml->parent;    // find root tag
1369     }
1370     return (xml) ? ((ezxml_root_t)xml)->err : "";
1371 }
1372
1373 // returns a new empty ezxml structure with the given root tag name
1374 ezxml_t ezxml_new(const char *name)
1375 {
1376     static char *ent[] = { "lt;", "&#60;", "gt;", "&#62;", "quot;", "&#34;",
1377                            "apos;", "&#39;", "amp;", "&#38;", NULL
1378                          };
1379     ezxml_root_t root = (ezxml_root_t)memset(MALLOC(sizeof(struct ezxml_root)),
1380                         '\0', sizeof(struct ezxml_root));
1381     root->xml.name = (char *)name;
1382     root->cur = &root->xml;
1383     strcpy(root->err, root->xml.txt = "");
1384     root->ent = memcpy(MALLOC(sizeof(ent)), ent, sizeof(ent));
1385     root->attr = root->pi = (char ***)(root->xml.attr = EZXML_NIL);
1386     return &root->xml;
1387 }
1388
1389 // inserts an existing tag into an ezxml structure
1390 ezxml_t ezxml_insert(ezxml_t xml, ezxml_t dest, size_t off)
1391 {
1392     ezxml_t cur, prev, head;
1393
1394     xml->next = xml->sibling = xml->ordered = NULL;
1395     xml->off = off;
1396     xml->parent = dest;
1397
1398     if ((head = dest->child))   // already have sub tags
1399     {
1400         if (head->off <= off)   // not first subtag
1401         {
1402             for (cur = head; cur->ordered && cur->ordered->off <= off;
1403                     cur = cur->ordered)
1404             {
1405                 ;
1406             }
1407             xml->ordered = cur->ordered;
1408             cur->ordered = xml;
1409         }
1410         else   // first subtag
1411         {
1412             xml->ordered = head;
1413             dest->child = xml;
1414         }
1415
1416         for (cur = head, prev = NULL; cur && strcmp(cur->name, xml->name);
1417                 prev = cur, cur = cur->sibling)
1418         {
1419             ;    // find tag type
1420         }
1421         if (cur && cur->off <= off)   // not first of type
1422         {
1423             while (cur->next && cur->next->off <= off)
1424             {
1425                 cur = cur->next;
1426             }
1427             xml->next = cur->next;
1428             cur->next = xml;
1429         }
1430         else   // first tag of this type
1431         {
1432             if (prev && cur)
1433             {
1434                 prev->sibling = cur->sibling;    // remove old first
1435             }
1436             xml->next = cur; // old first tag is now next
1437             for (cur = head, prev = NULL; cur && cur->off <= off;
1438                     prev = cur, cur = cur->sibling)
1439             {
1440                 ;    // new sibling insert point
1441             }
1442             xml->sibling = cur;
1443             if (prev)
1444             {
1445                 prev->sibling = xml;
1446             }
1447         }
1448     }
1449     else
1450     {
1451         dest->child = xml;    // only sub tag
1452     }
1453
1454     return xml;
1455 }
1456
1457 // Adds a child tag. off is the offset of the child tag relative to the start
1458 // of the parent tag's character content. Returns the child tag.
1459 ezxml_t ezxml_add_child(ezxml_t xml, const char *name, size_t off)
1460 {
1461     ezxml_t child;
1462
1463     if (! xml)
1464     {
1465         return NULL;
1466     }
1467     child = (ezxml_t)memset(MALLOC(sizeof(struct ezxml)), '\0',
1468                             sizeof(struct ezxml));
1469     child->name = (char *)name;
1470     child->attr = EZXML_NIL;
1471     child->txt = "";
1472
1473     return ezxml_insert(child, xml, off);
1474 }
1475
1476 // sets the character content for the given tag and returns the tag
1477 ezxml_t ezxml_set_txt(ezxml_t xml, const char *txt)
1478 {
1479     if (! xml)
1480     {
1481         return NULL;
1482     }
1483     if (xml->flags & EZXML_TXTM)
1484     {
1485         FREE(xml->txt);    // existing txt was malloced
1486     }
1487     xml->flags &= ~EZXML_TXTM;
1488     xml->txt = (char *)txt;
1489     return xml;
1490 }
1491
1492 // Sets the given tag attribute or adds a new attribute if not found. A value
1493 // of NULL will remove the specified attribute. Returns the tag given.
1494 ezxml_t ezxml_set_attr(ezxml_t xml, const char *name, char *value)
1495 {
1496     int l = 0, c;
1497     if (! xml)
1498     {
1499         return NULL;
1500     }
1501     while (xml->attr[l] && strcmp(xml->attr[l], name))
1502     {
1503         l += 2;
1504     }
1505
1506     if (! xml->attr[l])   // not found, add as new attribute
1507     {
1508         if (! value)
1509         {
1510             return xml;    // nothing to do
1511         }
1512         if (xml->attr == EZXML_NIL)   // first attribute
1513         {
1514             xml->attr = MALLOC(4 * sizeof(char *));
1515             xml->attr[1] = os_strdup(""); // empty list of malloced names/vals
1516         }
1517         else
1518         {
1519             xml->attr = REALLOC(xml->attr, (l + 4) * sizeof(char *));
1520         }
1521
1522         xml->attr[l] = (char *)name; // set attribute name
1523         xml->attr[l + 2] = NULL; // null terminate attribute list
1524         xml->attr[l + 3] = REALLOC(xml->attr[l + 1], (c = (int)strlen(xml->attr[l + 1])) + 2);
1525         strcpy(xml->attr[l + 3] + c, " "); // set name/value as not malloced
1526         if (xml->flags & EZXML_DUP)
1527         {
1528             xml->attr[l + 3][c] = EZXML_NAMEM;
1529         }
1530     }
1531     else if (xml->flags & EZXML_DUP)
1532     {
1533         FREE((char *)name);    // name was os_strduped
1534     }
1535
1536     for (c = l; xml->attr[c]; c += 2)
1537     {
1538         ;    // find end of attribute list
1539     }
1540     if (xml->attr[c + 1][l / 2] & EZXML_TXTM)
1541     {
1542         FREE(xml->attr[l + 1]);    //old val
1543     }
1544     if (xml->flags & EZXML_DUP)
1545     {
1546         xml->attr[c + 1][l / 2] |= EZXML_TXTM;
1547     }
1548     else
1549     {
1550         xml->attr[c + 1][l / 2] &= ~EZXML_TXTM;
1551     }
1552
1553     if (value)
1554     {
1555         xml->attr[l + 1] = (char *)value;    // set attribute value
1556     }
1557     else   // remove attribute
1558     {
1559         if (xml->attr[c + 1][l / 2] & EZXML_NAMEM)
1560         {
1561             FREE(xml->attr[l]);
1562         }
1563         memmove(xml->attr + l, xml->attr + l + 2, (c - l + 2) * sizeof(char*));
1564         xml->attr = REALLOC(xml->attr, (c + 2) * sizeof(char *));
1565         memmove(xml->attr[c + 1] + (l / 2), xml->attr[c + 1] + (l / 2) + 1,
1566                 (c / 2) - (l / 2)); // fix list of which name/vals are malloced
1567     }
1568     xml->flags &= ~EZXML_DUP; // clear os_strdup() flag
1569     return xml;
1570 }
1571
1572 // sets a flag for the given tag and returns the tag
1573 ezxml_t ezxml_set_flag(ezxml_t xml, short flag)
1574 {
1575     if (xml)
1576     {
1577         xml->flags |= flag;
1578     }
1579     return xml;
1580 }
1581
1582 // removes a tag along with its subtags without freeing its memory
1583 ezxml_t ezxml_cut(ezxml_t xml)
1584 {
1585     ezxml_t cur;
1586
1587     if (! xml)
1588     {
1589         return NULL;    // nothing to do
1590     }
1591     if (xml->next)
1592     {
1593         xml->next->sibling = xml->sibling;    // patch sibling list
1594     }
1595
1596     if (xml->parent)   // not root tag
1597     {
1598         cur = xml->parent->child; // find head of subtag list
1599         if (cur == xml)
1600         {
1601             xml->parent->child = xml->ordered;    // first subtag
1602         }
1603         else   // not first subtag
1604         {
1605             while (cur->ordered != xml)
1606             {
1607                 cur = cur->ordered;
1608             }
1609             cur->ordered = cur->ordered->ordered; // patch ordered list
1610
1611             cur = xml->parent->child; // go back to head of subtag list
1612             if (strcmp(cur->name, xml->name))   // not in first sibling list
1613             {
1614                 while (strcmp(cur->sibling->name, xml->name))
1615                 {
1616                     cur = cur->sibling;
1617                 }
1618                 if (cur->sibling == xml)   // first of a sibling list
1619                 {
1620                     cur->sibling = (xml->next) ? xml->next
1621                                    : cur->sibling->sibling;
1622                 }
1623                 else
1624                 {
1625                     cur = cur->sibling;    // not first of a sibling list
1626                 }
1627             }
1628
1629             while (cur->next && cur->next != xml)
1630             {
1631                 cur = cur->next;
1632             }
1633             if (cur->next)
1634             {
1635                 cur->next = cur->next->next;    // patch next list
1636             }
1637         }
1638     }
1639     xml->ordered = xml->sibling = xml->next = NULL;
1640     return xml;
1641 }
1642 /*----------------------------------------------------------*/
1643 int write_in_child(ezxml_t *parent, char *idx, char *w)
1644 {
1645     ezxml_t terminal, structx, subnode, i_value;
1646
1647     int result;
1648
1649
1650     for (terminal = ezxml_child(*parent, "terminal"); terminal; terminal = terminal->next)
1651     {
1652         if (strcmp(ezxml_child(terminal, "id")->txt, idx) == 0)
1653         {
1654             i_value = ezxml_child(terminal, "initial_value");
1655             ezxml_set_attr(i_value, "value", w);
1656             return 1; /*  found */
1657         }
1658     }
1659
1660     for (structx = ezxml_child(*parent, "struct"); structx; structx = structx->next)
1661     {
1662         for (subnode = ezxml_child(structx, "subnodes"); subnode; subnode = subnode->next)
1663         {
1664             result = write_in_child(&subnode, idx, w);
1665             if (result == 1 )
1666             {
1667                 return result;
1668             }
1669         }
1670     }
1671
1672     return 0; /* not found*/
1673 }
1674 /*----------------------------------------------------------*/
1675 int search_in_child(ezxml_t *parent, char *idx, char *value)
1676 {
1677     ezxml_t terminal, structx, subnode, i_value;
1678     const char *teamname;
1679     int result;
1680
1681     for (terminal = ezxml_child(*parent, "terminal"); terminal; terminal = terminal->next)
1682     {
1683         if (strcmp(ezxml_child(terminal, "id")->txt, idx) == 0)
1684         {
1685             i_value = ezxml_child(terminal, "initial_value");
1686             teamname = ezxml_attr(i_value, "value");
1687             strcpy (value, teamname);
1688             return 1; /*  found */
1689         }
1690     }
1691
1692     for (structx = ezxml_child(*parent, "struct"); structx; structx = structx->next)
1693     {
1694         for (subnode = ezxml_child(structx, "subnodes"); subnode; subnode = subnode->next)
1695         {
1696             result = search_in_child(&subnode, idx, value);
1697             if (result == 1 )
1698             {
1699                 return result;
1700             }
1701         }
1702     }
1703
1704     return 0; /* not found*/
1705 }
1706
1707 /*----------------------------------------------------------*/