Palacios Public Git Repository

To checkout Palacios execute

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


Memory checking enhancements
[palacios.releases.git] / linux_usr / 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 #include <unistd.h>
31 #include <sys/types.h>
32 #ifndef EZXML_NOMMAP
33 #include <sys/mman.h>
34 #endif // EZXML_NOMMAP
35 #include <sys/stat.h>
36 #include "ezxml.h"
37
38 #define EZXML_WS   "\t\r\n "  // whitespace
39 #define EZXML_ERRL 128        // maximum error string length
40
41 typedef struct ezxml_root *ezxml_root_t;
42 struct ezxml_root {       // additional data for the root tag
43     struct ezxml xml;     // is a super-struct built on top of ezxml struct
44     ezxml_t cur;          // current xml tree insertion point
45     char *m;              // original xml string
46     size_t len;           // length of allocated memory for mmap, -1 for malloc
47     char *u;              // UTF-8 conversion of string if original was UTF-16
48     char *s;              // start of work area
49     char *e;              // end of work area
50     char **ent;           // general entities (ampersand sequences)
51     char ***attr;         // default attributes
52     char ***pi;           // processing instructions
53     short standalone;     // non-zero if <?xml standalone="yes"?>
54     char err[EZXML_ERRL]; // error string
55 };
56
57 char *EZXML_NIL[] = { NULL }; // empty, null terminated array of strings
58
59 // returns the first child tag with the given name or NULL if not found
60 ezxml_t ezxml_child(ezxml_t xml, const char *name)
61 {
62     xml = (xml) ? xml->child : NULL;
63     while (xml && strcmp(name, xml->name)) xml = xml->sibling;
64     return xml;
65 }
66
67 // returns the Nth tag with the same name in the same subsection or NULL if not
68 // found
69 ezxml_t ezxml_idx(ezxml_t xml, int idx)
70 {
71     for (; xml && idx; idx--) xml = xml->next;
72     return xml;
73 }
74
75 // returns the value of the requested tag attribute or NULL if not found
76 const char *ezxml_attr(ezxml_t xml, const char *attr)
77 {
78     int i = 0, j = 1;
79     ezxml_root_t root = (ezxml_root_t)xml;
80
81     if (! xml || ! xml->attr) return NULL;
82     while (xml->attr[i] && strcmp(attr, xml->attr[i])) i += 2;
83     if (xml->attr[i]) return xml->attr[i + 1]; // found attribute
84
85     while (root->xml.parent) root = (ezxml_root_t)root->xml.parent; // root tag
86     for (i = 0; root->attr[i] && strcmp(xml->name, root->attr[i][0]); i++);
87     if (! root->attr[i]) return NULL; // no matching default attributes
88     while (root->attr[i][j] && strcmp(attr, root->attr[i][j])) j += 3;
89     return (root->attr[i][j]) ? root->attr[i][j + 1] : NULL; // found default
90 }
91
92 // same as ezxml_get but takes an already initialized va_list
93 ezxml_t ezxml_vget(ezxml_t xml, va_list ap)
94 {
95     char *name = va_arg(ap, char *);
96     int idx = -1;
97
98     if (name && *name) {
99         idx = va_arg(ap, int);    
100         xml = ezxml_child(xml, name);
101     }
102     return (idx < 0) ? xml : ezxml_vget(ezxml_idx(xml, idx), ap);
103 }
104
105 // Traverses the xml tree to retrieve a specific subtag. Takes a variable
106 // length list of tag names and indexes. The argument list must be terminated
107 // by either an index of -1 or an empty string tag name. Example: 
108 // title = ezxml_get(library, "shelf", 0, "book", 2, "title", -1);
109 // This retrieves the title of the 3rd book on the 1st shelf of library.
110 // Returns NULL if not found.
111 ezxml_t ezxml_get(ezxml_t xml, ...)
112 {
113     va_list ap;
114     ezxml_t r;
115
116     va_start(ap, xml);
117     r = ezxml_vget(xml, ap);
118     va_end(ap);
119     return r;
120 }
121
122 // returns a null terminated array of processing instructions for the given
123 // target
124 const char **ezxml_pi(ezxml_t xml, const char *target)
125 {
126     ezxml_root_t root = (ezxml_root_t)xml;
127     int i = 0;
128
129     if (! root) return (const char **)EZXML_NIL;
130     while (root->xml.parent) root = (ezxml_root_t)root->xml.parent; // root tag
131     while (root->pi[i] && strcmp(target, root->pi[i][0])) i++; // find target
132     return (const char **)((root->pi[i]) ? root->pi[i] + 1 : EZXML_NIL);
133 }
134
135 // set an error string and return root
136 ezxml_t ezxml_err(ezxml_root_t root, char *s, const char *err, ...)
137 {
138     va_list ap;
139     int line = 1;
140     char *t, fmt[EZXML_ERRL];
141     
142     for (t = root->s; t < s; t++) if (*t == '\n') line++;
143     snprintf(fmt, EZXML_ERRL, "[error near line %d]: %s", line, err);
144
145     va_start(ap, err);
146     vsnprintf(root->err, EZXML_ERRL, fmt, ap);
147     va_end(ap);
148
149     return &root->xml;
150 }
151
152 // Recursively decodes entity and character references and normalizes new lines
153 // ent is a null terminated array of alternating entity names and values. set t
154 // to '&' for general entity decoding, '%' for parameter entity decoding, 'c'
155 // for cdata sections, ' ' for attribute normalization, or '*' for non-cdata
156 // attribute normalization. Returns s, or if the decoded string is longer than
157 // s, returns a malloced string that must be freed.
158 char *ezxml_decode(char *s, char **ent, char t)
159 {
160     char *e, *r = s, *m = s;
161     long b, c, d, l;
162
163     for (; *s; s++) { // normalize line endings
164         while (*s == '\r') {
165             *(s++) = '\n';
166             if (*s == '\n') memmove(s, (s + 1), strlen(s));
167         }
168     }
169     
170     for (s = r; ; ) {
171         while (*s && *s != '&' && (*s != '%' || t != '%') && !isspace(*s)) s++;
172
173         if (! *s) break;
174         else if (t != 'c' && ! strncmp(s, "&#", 2)) { // character reference
175             if (s[2] == 'x') c = strtol(s + 3, &e, 16); // base 16
176             else c = strtol(s + 2, &e, 10); // base 10
177             if (! c || *e != ';') { s++; continue; } // not a character ref
178
179             if (c < 0x80) *(s++) = c; // US-ASCII subset
180             else { // multi-byte UTF-8 sequence
181                 for (b = 0, d = c; d; d /= 2) b++; // number of bits in c
182                 b = (b - 2) / 5; // number of bytes in payload
183                 *(s++) = (0xFF << (7 - b)) | (c >> (6 * b)); // head
184                 while (b) *(s++) = 0x80 | ((c >> (6 * --b)) & 0x3F); // payload
185             }
186
187             memmove(s, strchr(s, ';') + 1, strlen(strchr(s, ';')));
188         }
189         else if ((*s == '&' && (t == '&' || t == ' ' || t == '*')) ||
190                  (*s == '%' && t == '%')) { // entity reference
191             for (b = 0; ent[b] && strncmp(s + 1, ent[b], strlen(ent[b]));
192                  b += 2); // find entity in entity list
193
194             if (ent[b++]) { // found a match
195                 if ((c = strlen(ent[b])) - 1 > (e = strchr(s, ';')) - s) {
196                     l = (d = (s - r)) + c + strlen(e); // new length
197                     r = (r == m) ? strcpy(malloc(l), r) : realloc(r, l);
198                     e = strchr((s = r + d), ';'); // fix up pointers
199                 }
200
201                 memmove(s + c, e + 1, strlen(e)); // shift rest of string
202                 strncpy(s, ent[b], c); // copy in replacement text
203             }
204             else s++; // not a known entity
205         }
206         else if ((t == ' ' || t == '*') && isspace(*s)) *(s++) = ' ';
207         else s++; // no decoding needed
208     }
209
210     if (t == '*') { // normalize spaces for non-cdata attributes
211         for (s = r; *s; s++) {
212             if ((l = strspn(s, " "))) memmove(s, s + l, strlen(s + l) + 1);
213             while (*s && *s != ' ') s++;
214         }
215         if (--s >= r && *s == ' ') *s = '\0'; // trim any trailing space
216     }
217     return r;
218 }
219
220 // called when parser finds start of new tag
221 void ezxml_open_tag(ezxml_root_t root, char *name, char **attr)
222 {
223     ezxml_t xml = root->cur;
224     
225     if (xml->name) xml = ezxml_add_child(xml, name, strlen(xml->txt));
226     else xml->name = name; // first open tag
227
228     xml->attr = attr;
229     root->cur = xml; // update tag insertion point
230 }
231
232 // called when parser finds character content between open and closing tag
233 void ezxml_char_content(ezxml_root_t root, char *s, size_t len, char t)
234 {
235     ezxml_t xml = root->cur;
236     char *m = s;
237     size_t l;
238
239     if (! xml || ! xml->name || ! len) return; // sanity check
240
241     s[len] = '\0'; // null terminate text (calling functions anticipate this)
242     len = strlen(s = ezxml_decode(s, root->ent, t)) + 1;
243
244     if (! *(xml->txt)) xml->txt = s; // initial character content
245     else { // allocate our own memory and make a copy
246         xml->txt = (xml->flags & EZXML_TXTM) // allocate some space
247                    ? realloc(xml->txt, (l = strlen(xml->txt)) + len)
248                    : strcpy(malloc((l = strlen(xml->txt)) + len), xml->txt);
249         strcpy(xml->txt + l, s); // add new char content
250         if (s != m) free(s); // free s if it was malloced by ezxml_decode()
251     }
252
253     if (xml->txt != m) ezxml_set_flag(xml, EZXML_TXTM);
254 }
255
256 // called when parser finds closing tag
257 ezxml_t ezxml_close_tag(ezxml_root_t root, char *name, char *s)
258 {
259     if (! root->cur || ! root->cur->name || strcmp(name, root->cur->name))
260         return ezxml_err(root, s, "unexpected closing tag </%s>", name);
261
262     root->cur = root->cur->parent;
263     return NULL;
264 }
265
266 // checks for circular entity references, returns non-zero if no circular
267 // references are found, zero otherwise
268 int ezxml_ent_ok(char *name, char *s, char **ent)
269 {
270     int i;
271
272     for (; ; s++) {
273         while (*s && *s != '&') s++; // find next entity reference
274         if (! *s) return 1;
275         if (! strncmp(s + 1, name, strlen(name))) return 0; // circular ref.
276         for (i = 0; ent[i] && strncmp(ent[i], s + 1, strlen(ent[i])); i += 2);
277         if (ent[i] && ! ezxml_ent_ok(name, ent[i + 1], ent)) return 0;
278     }
279 }
280
281 // called when the parser finds a processing instruction
282 void ezxml_proc_inst(ezxml_root_t root, char *s, size_t len)
283 {
284     int i = 0, j = 1;
285     char *target = s;
286
287     s[len] = '\0'; // null terminate instruction
288     if (*(s += strcspn(s, EZXML_WS))) {
289         *s = '\0'; // null terminate target
290         s += strspn(s + 1, EZXML_WS) + 1; // skip whitespace after target
291     }
292
293     if (! strcmp(target, "xml")) { // <?xml ... ?>
294         if ((s = strstr(s, "standalone")) && ! strncmp(s + strspn(s + 10,
295             EZXML_WS "='\"") + 10, "yes", 3)) root->standalone = 1;
296         return;
297     }
298
299     if (! root->pi[0]) *(root->pi = malloc(sizeof(char **))) = NULL; //first pi
300
301     while (root->pi[i] && strcmp(target, root->pi[i][0])) i++; // find target
302     if (! root->pi[i]) { // new target
303         root->pi = realloc(root->pi, sizeof(char **) * (i + 2));
304         root->pi[i] = malloc(sizeof(char *) * 3);
305         root->pi[i][0] = target;
306         root->pi[i][1] = (char *)(root->pi[i + 1] = NULL); // terminate pi list
307         root->pi[i][2] = strdup(""); // empty document position list
308     }
309
310     while (root->pi[i][j]) j++; // find end of instruction list for this target
311     root->pi[i] = realloc(root->pi[i], sizeof(char *) * (j + 3));
312     root->pi[i][j + 2] = realloc(root->pi[i][j + 1], j + 1);
313     strcpy(root->pi[i][j + 2] + j - 1, (root->xml.name) ? ">" : "<");
314     root->pi[i][j + 1] = NULL; // null terminate pi list for this target
315     root->pi[i][j] = s; // set instruction
316 }
317
318 // called when the parser finds an internal doctype subset
319 short ezxml_internal_dtd(ezxml_root_t root, char *s, size_t len)
320 {
321     char q, *c, *t, *n = NULL, *v, **ent, **pe;
322     int i, j;
323     
324     pe = memcpy(malloc(sizeof(EZXML_NIL)), EZXML_NIL, sizeof(EZXML_NIL));
325
326     for (s[len] = '\0'; s; ) {
327         while (*s && *s != '<' && *s != '%') s++; // find next declaration
328
329         if (! *s) break;
330         else if (! strncmp(s, "<!ENTITY", 8)) { // parse entity definitions
331             c = s += strspn(s + 8, EZXML_WS) + 8; // skip white space separator
332             n = s + strspn(s, EZXML_WS "%"); // find name
333             *(s = n + strcspn(n, EZXML_WS)) = ';'; // append ; to name
334
335             v = s + strspn(s + 1, EZXML_WS) + 1; // find value
336             if ((q = *(v++)) != '"' && q != '\'') { // skip externals
337                 s = strchr(s, '>');
338                 continue;
339             }
340
341             for (i = 0, ent = (*c == '%') ? pe : root->ent; ent[i]; i++);
342             ent = realloc(ent, (i + 3) * sizeof(char *)); // space for next ent
343             if (*c == '%') pe = ent;
344             else root->ent = ent;
345
346             *(++s) = '\0'; // null terminate name
347             if ((s = strchr(v, q))) *(s++) = '\0'; // null terminate value
348             ent[i + 1] = ezxml_decode(v, pe, '%'); // set value
349             ent[i + 2] = NULL; // null terminate entity list
350             if (! ezxml_ent_ok(n, ent[i + 1], ent)) { // circular reference
351                 if (ent[i + 1] != v) free(ent[i + 1]);
352                 ezxml_err(root, v, "circular entity declaration &%s", n);
353                 break;
354             }
355             else ent[i] = n; // set entity name
356         }
357         else if (! strncmp(s, "<!ATTLIST", 9)) { // parse default attributes
358             t = s + strspn(s + 9, EZXML_WS) + 9; // skip whitespace separator
359             if (! *t) { ezxml_err(root, t, "unclosed <!ATTLIST"); break; }
360             if (*(s = t + strcspn(t, EZXML_WS ">")) == '>') continue;
361             else *s = '\0'; // null terminate tag name
362             for (i = 0; root->attr[i] && strcmp(n, root->attr[i][0]); i++);
363
364             while (*(n = ++s + strspn(s, EZXML_WS)) && *n != '>') {
365                 if (*(s = n + strcspn(n, EZXML_WS))) *s = '\0'; // attr name
366                 else { ezxml_err(root, t, "malformed <!ATTLIST"); break; }
367
368                 s += strspn(s + 1, EZXML_WS) + 1; // find next token
369                 c = (strncmp(s, "CDATA", 5)) ? "*" : " "; // is it cdata?
370                 if (! strncmp(s, "NOTATION", 8))
371                     s += strspn(s + 8, EZXML_WS) + 8;
372                 s = (*s == '(') ? strchr(s, ')') : s + strcspn(s, EZXML_WS);
373                 if (! s) { ezxml_err(root, t, "malformed <!ATTLIST"); break; }
374
375                 s += strspn(s, EZXML_WS ")"); // skip white space separator
376                 if (! strncmp(s, "#FIXED", 6))
377                     s += strspn(s + 6, EZXML_WS) + 6;
378                 if (*s == '#') { // no default value
379                     s += strcspn(s, EZXML_WS ">") - 1;
380                     if (*c == ' ') continue; // cdata is default, nothing to do
381                     v = NULL;
382                 }
383                 else if ((*s == '"' || *s == '\'')  &&  // default value
384                          (s = strchr(v = s + 1, *s))) *s = '\0';
385                 else { ezxml_err(root, t, "malformed <!ATTLIST"); break; }
386
387                 if (! root->attr[i]) { // new tag name
388                     root->attr = (! i) ? malloc(2 * sizeof(char **))
389                                        : realloc(root->attr,
390                                                  (i + 2) * sizeof(char **));
391                     root->attr[i] = malloc(2 * sizeof(char *));
392                     root->attr[i][0] = t; // set tag name
393                     root->attr[i][1] = (char *)(root->attr[i + 1] = NULL);
394                 }
395
396                 for (j = 1; root->attr[i][j]; j += 3); // find end of list
397                 root->attr[i] = realloc(root->attr[i],
398                                         (j + 4) * sizeof(char *));
399
400                 root->attr[i][j + 3] = NULL; // null terminate list
401                 root->attr[i][j + 2] = c; // is it cdata?
402                 root->attr[i][j + 1] = (v) ? ezxml_decode(v, root->ent, *c)
403                                            : NULL;
404                 root->attr[i][j] = n; // attribute name 
405             }
406         }
407         else if (! strncmp(s, "<!--", 4)) s = strstr(s + 4, "-->"); // comments
408         else if (! strncmp(s, "<?", 2)) { // processing instructions
409             if ((s = strstr(c = s + 2, "?>")))
410                 ezxml_proc_inst(root, c, s++ - c);
411         }
412         else if (*s == '<') s = strchr(s, '>'); // skip other declarations
413         else if (*(s++) == '%' && ! root->standalone) break;
414     }
415
416     free(pe);
417     return ! *root->err;
418 }
419
420 // Converts a UTF-16 string to UTF-8. Returns a new string that must be freed
421 // or NULL if no conversion was needed.
422 char *ezxml_str2utf8(char **s, size_t *len)
423 {
424     char *u;
425     size_t l = 0, sl, max = *len;
426     long c, d;
427     int b, be = (**s == '\xFE') ? 1 : (**s == '\xFF') ? 0 : -1;
428
429     if (be == -1) return NULL; // not UTF-16
430
431     u = malloc(max);
432     for (sl = 2; sl < *len - 1; sl += 2) {
433         c = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF)  //UTF-16BE
434                  : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); //UTF-16LE
435         if (c >= 0xD800 && c <= 0xDFFF && (sl += 2) < *len - 1) { // high-half
436             d = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF)
437                      : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF);
438             c = (((c & 0x3FF) << 10) | (d & 0x3FF)) + 0x10000;
439         }
440
441         while (l + 6 > max) u = realloc(u, max += EZXML_BUFSIZE);
442         if (c < 0x80) u[l++] = c; // US-ASCII subset
443         else { // multi-byte UTF-8 sequence
444             for (b = 0, d = c; d; d /= 2) b++; // bits in c
445             b = (b - 2) / 5; // bytes in payload
446             u[l++] = (0xFF << (7 - b)) | (c >> (6 * b)); // head
447             while (b) u[l++] = 0x80 | ((c >> (6 * --b)) & 0x3F); // payload
448         }
449     }
450     return *s = realloc(u, *len = l);
451 }
452
453 // frees a tag attribute list
454 void ezxml_free_attr(char **attr) {
455     int i = 0;
456     char *m;
457     
458     if (! attr || attr == EZXML_NIL) return; // nothing to free
459     while (attr[i]) i += 2; // find end of attribute list
460     m = attr[i + 1]; // list of which names and values are malloced
461     for (i = 0; m[i]; i++) {
462         if (m[i] & EZXML_NAMEM) free(attr[i * 2]);
463         if (m[i] & EZXML_TXTM) free(attr[(i * 2) + 1]);
464     }
465     free(m);
466     free(attr);
467 }
468
469 // parse the given xml string and return an ezxml structure
470 ezxml_t ezxml_parse_str(char *s, size_t len)
471 {
472     ezxml_root_t root = (ezxml_root_t)ezxml_new(NULL);
473     char q, e, *d, **attr, **a = NULL; // initialize a to avoid compile warning
474     int l, i, j;
475
476     root->m = s;
477     if (! len) return ezxml_err(root, NULL, "root tag missing");
478     root->u = ezxml_str2utf8(&s, &len); // convert utf-16 to utf-8
479     root->e = (root->s = s) + len; // record start and end of work area
480     
481     e = s[len - 1]; // save end char
482     s[len - 1] = '\0'; // turn end char into null terminator
483
484     while (*s && *s != '<') s++; // find first tag
485     if (! *s) return ezxml_err(root, s, "root tag missing");
486
487     for (; ; ) {
488         attr = (char **)EZXML_NIL;
489         d = ++s;
490         
491         if (isalpha(*s) || *s == '_' || *s == ':' || *s < '\0') { // new tag
492             if (! root->cur)
493                 return ezxml_err(root, d, "markup outside of root element");
494
495             s += strcspn(s, EZXML_WS "/>");
496             while (isspace(*s)) *(s++) = '\0'; // null terminate tag name
497   
498             if (*s && *s != '/' && *s != '>') // find tag in default attr list
499                 for (i = 0; (a = root->attr[i]) && strcmp(a[0], d); i++);
500
501             for (l = 0; *s && *s != '/' && *s != '>'; l += 2) { // new attrib
502                 attr = (l) ? realloc(attr, (l + 4) * sizeof(char *))
503                            : malloc(4 * sizeof(char *)); // allocate space
504                 attr[l + 3] = (l) ? realloc(attr[l + 1], (l / 2) + 2)
505                                   : malloc(2); // mem for list of maloced vals
506                 strcpy(attr[l + 3] + (l / 2), " "); // value is not malloced
507                 attr[l + 2] = NULL; // null terminate list
508                 attr[l + 1] = ""; // temporary attribute value
509                 attr[l] = s; // set attribute name
510
511                 s += strcspn(s, EZXML_WS "=/>");
512                 if (*s == '=' || isspace(*s)) { 
513                     *(s++) = '\0'; // null terminate tag attribute name
514                     q = *(s += strspn(s, EZXML_WS "="));
515                     if (q == '"' || q == '\'') { // attribute value
516                         attr[l + 1] = ++s;
517                         while (*s && *s != q) s++;
518                         if (*s) *(s++) = '\0'; // null terminate attribute val
519                         else {
520                             ezxml_free_attr(attr);
521                             return ezxml_err(root, d, "missing %c", q);
522                         }
523
524                         for (j = 1; a && a[j] && strcmp(a[j], attr[l]); j +=3);
525                         attr[l + 1] = ezxml_decode(attr[l + 1], root->ent, (a
526                                                    && a[j]) ? *a[j + 2] : ' ');
527                         if (attr[l + 1] < d || attr[l + 1] > s)
528                             attr[l + 3][l / 2] = EZXML_TXTM; // value malloced
529                     }
530                 }
531                 while (isspace(*s)) s++;
532             }
533
534             if (*s == '/') { // self closing tag
535                 *(s++) = '\0';
536                 if ((*s && *s != '>') || (! *s && e != '>')) {
537                     if (l) ezxml_free_attr(attr);
538                     return ezxml_err(root, d, "missing >");
539                 }
540                 ezxml_open_tag(root, d, attr);
541                 ezxml_close_tag(root, d, s);
542             }
543             else if ((q = *s) == '>' || (! *s && e == '>')) { // open tag
544                 *s = '\0'; // temporarily null terminate tag name
545                 ezxml_open_tag(root, d, attr);
546                 *s = q;
547             }
548             else {
549                 if (l) ezxml_free_attr(attr);
550                 return ezxml_err(root, d, "missing >"); 
551             }
552         }
553         else if (*s == '/') { // close tag
554             s += strcspn(d = s + 1, EZXML_WS ">") + 1;
555             if (! (q = *s) && e != '>') return ezxml_err(root, d, "missing >");
556             *s = '\0'; // temporarily null terminate tag name
557             if (ezxml_close_tag(root, d, s)) return &root->xml;
558             if (isspace(*s = q)) s += strspn(s, EZXML_WS);
559         }
560         else if (! strncmp(s, "!--", 3)) { // xml comment
561             if (! (s = strstr(s + 3, "--")) || (*(s += 2) != '>' && *s) ||
562                 (! *s && e != '>')) return ezxml_err(root, d, "unclosed <!--");
563         }
564         else if (! strncmp(s, "![CDATA[", 8)) { // cdata
565             if ((s = strstr(s, "]]>")))
566                 ezxml_char_content(root, d + 8, (s += 2) - d - 10, 'c');
567             else return ezxml_err(root, d, "unclosed <![CDATA[");
568         }
569         else if (! strncmp(s, "!DOCTYPE", 8)) { // dtd
570             for (l = 0; *s && ((! l && *s != '>') || (l && (*s != ']' || 
571                  *(s + strspn(s + 1, EZXML_WS) + 1) != '>')));
572                  l = (*s == '[') ? 1 : l) s += strcspn(s + 1, "[]>") + 1;
573             if (! *s && e != '>')
574                 return ezxml_err(root, d, "unclosed <!DOCTYPE");
575             d = (l) ? strchr(d, '[') + 1 : d;
576             if (l && ! ezxml_internal_dtd(root, d, s++ - d)) return &root->xml;
577         }
578         else if (*s == '?') { // <?...?> processing instructions
579             do { s = strchr(s, '?'); } while (s && *(++s) && *s != '>');
580             if (! s || (! *s && e != '>')) 
581                 return ezxml_err(root, d, "unclosed <?");
582             else ezxml_proc_inst(root, d + 1, s - d - 2);
583         }
584         else return ezxml_err(root, d, "unexpected <");
585         
586         if (! s || ! *s) break;
587         *s = '\0';
588         d = ++s;
589         if (*s && *s != '<') { // tag character content
590             while (*s && *s != '<') s++;
591             if (*s) ezxml_char_content(root, d, s - d, '&');
592             else break;
593         }
594         else if (! *s) break;
595     }
596
597     if (! root->cur) return &root->xml;
598     else if (! root->cur->name) return ezxml_err(root, d, "root tag missing");
599     else return ezxml_err(root, d, "unclosed tag <%s>", root->cur->name);
600 }
601
602 // Wrapper for ezxml_parse_str() that accepts a file stream. Reads the entire
603 // stream into memory and then parses it. For xml files, use ezxml_parse_file()
604 // or ezxml_parse_fd()
605 ezxml_t ezxml_parse_fp(FILE *fp)
606 {
607     ezxml_root_t root;
608     size_t l, len = 0;
609     char *s;
610
611     if (! (s = malloc(EZXML_BUFSIZE))) return NULL;
612     do {
613         len += (l = fread((s + len), 1, EZXML_BUFSIZE, fp));
614         if (l == EZXML_BUFSIZE) s = realloc(s, len + EZXML_BUFSIZE);
615     } while (s && l == EZXML_BUFSIZE);
616
617     if (! s) return NULL;
618     root = (ezxml_root_t)ezxml_parse_str(s, len);
619     root->len = -1; // so we know to free s in ezxml_free()
620     return &root->xml;
621 }
622
623 // A wrapper for ezxml_parse_str() that accepts a file descriptor. First
624 // attempts to mem map the file. Failing that, reads the file into memory.
625 // Returns NULL on failure.
626 ezxml_t ezxml_parse_fd(int fd)
627 {
628     ezxml_root_t root;
629     struct stat st;
630     size_t l;
631     void *m;
632
633     if (fd < 0) return NULL;
634     fstat(fd, &st);
635
636 #ifndef EZXML_NOMMAP
637     l = (st.st_size + sysconf(_SC_PAGESIZE) - 1) & ~(sysconf(_SC_PAGESIZE) -1);
638     if ((m = mmap(NULL, l, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) !=
639         MAP_FAILED) {
640         madvise(m, l, MADV_SEQUENTIAL); // optimize for sequential access
641         root = (ezxml_root_t)ezxml_parse_str(m, st.st_size);
642         madvise(m, root->len = l, MADV_NORMAL); // put it back to normal
643     }
644     else { // mmap failed, read file into memory
645 #endif // EZXML_NOMMAP
646         l = read(fd, m = malloc(st.st_size), st.st_size);
647         root = (ezxml_root_t)ezxml_parse_str(m, l);
648         root->len = -1; // so we know to free s in ezxml_free()
649 #ifndef EZXML_NOMMAP
650     }
651 #endif // EZXML_NOMMAP
652     return &root->xml;
653 }
654
655 // a wrapper for ezxml_parse_fd that accepts a file name
656 ezxml_t ezxml_parse_file(const char *file)
657 {
658     int fd = open(file, O_RDONLY, 0);
659     ezxml_t xml = ezxml_parse_fd(fd);
660     
661     if (fd >= 0) close(fd);
662     return xml;
663 }
664
665 // Encodes ampersand sequences appending the results to *dst, reallocating *dst
666 // if length excedes max. a is non-zero for attribute encoding. Returns *dst
667 char *ezxml_ampencode(const char *s, size_t len, char **dst, size_t *dlen,
668                       size_t *max, short a)
669 {
670     const char *e;
671     
672     for (e = s + len; s != e; s++) {
673         while (*dlen + 10 > *max) *dst = realloc(*dst, *max += EZXML_BUFSIZE);
674
675         switch (*s) {
676         case '\0': return *dst;
677         case '&': *dlen += sprintf(*dst + *dlen, "&amp;"); break;
678         case '<': *dlen += sprintf(*dst + *dlen, "&lt;"); break;
679         case '>': *dlen += sprintf(*dst + *dlen, "&gt;"); break;
680         case '"': *dlen += sprintf(*dst + *dlen, (a) ? "&quot;" : "\""); break;
681         case '\n': *dlen += sprintf(*dst + *dlen, (a) ? "&#xA;" : "\n"); break;
682         case '\t': *dlen += sprintf(*dst + *dlen, (a) ? "&#x9;" : "\t"); break;
683         case '\r': *dlen += sprintf(*dst + *dlen, "&#xD;"); break;
684         default: (*dst)[(*dlen)++] = *s;
685         }
686     }
687     return *dst;
688 }
689
690 // Recursively converts each tag to xml appending it to *s. Reallocates *s if
691 // its length excedes max. start is the location of the previous tag in the
692 // parent tag's character content. Returns *s.
693 char *ezxml_toxml_r(ezxml_t xml, char **s, size_t *len, size_t *max,
694                     size_t start, char ***attr)
695 {
696     int i, j;
697     char *txt = (xml->parent) ? xml->parent->txt : "";
698     size_t off = 0;
699
700     // parent character content up to this tag
701     *s = ezxml_ampencode(txt + start, xml->off - start, s, len, max, 0);
702
703     while (*len + strlen(xml->name) + 4 > *max) // reallocate s
704         *s = realloc(*s, *max += EZXML_BUFSIZE);
705
706     *len += sprintf(*s + *len, "<%s", xml->name); // open tag
707     for (i = 0; xml->attr[i]; i += 2) { // tag attributes
708         if (ezxml_attr(xml, xml->attr[i]) != xml->attr[i + 1]) continue;
709         while (*len + strlen(xml->attr[i]) + 7 > *max) // reallocate s
710             *s = realloc(*s, *max += EZXML_BUFSIZE);
711
712         *len += sprintf(*s + *len, " %s=\"", xml->attr[i]);
713         ezxml_ampencode(xml->attr[i + 1], -1, s, len, max, 1);
714         *len += sprintf(*s + *len, "\"");
715     }
716
717     for (i = 0; attr[i] && strcmp(attr[i][0], xml->name); i++);
718     for (j = 1; attr[i] && attr[i][j]; j += 3) { // default attributes
719         if (! attr[i][j + 1] || ezxml_attr(xml, attr[i][j]) != attr[i][j + 1])
720             continue; // skip duplicates and non-values
721         while (*len + strlen(attr[i][j]) + 7 > *max) // reallocate s
722             *s = realloc(*s, *max += EZXML_BUFSIZE);
723
724         *len += sprintf(*s + *len, " %s=\"", attr[i][j]);
725         ezxml_ampencode(attr[i][j + 1], -1, s, len, max, 1);
726         *len += sprintf(*s + *len, "\"");
727     }
728     *len += sprintf(*s + *len, ">");
729
730     *s = (xml->child) ? ezxml_toxml_r(xml->child, s, len, max, 0, attr) //child
731                       : ezxml_ampencode(xml->txt, -1, s, len, max, 0);  //data
732     
733     while (*len + strlen(xml->name) + 4 > *max) // reallocate s
734         *s = realloc(*s, *max += EZXML_BUFSIZE);
735
736     *len += sprintf(*s + *len, "</%s>", xml->name); // close tag
737
738     while (txt[off] && off < xml->off) off++; // make sure off is within bounds
739     return (xml->ordered) ? ezxml_toxml_r(xml->ordered, s, len, max, off, attr)
740                           : ezxml_ampencode(txt + off, -1, s, len, max, 0);
741 }
742
743 // Converts an ezxml structure back to xml. Returns a string of xml data that
744 // must be freed.
745 char *ezxml_toxml(ezxml_t xml)
746 {
747     ezxml_t p = (xml) ? xml->parent : NULL, o = (xml) ? xml->ordered : NULL;
748     ezxml_root_t root = (ezxml_root_t)xml;
749     size_t len = 0, max = EZXML_BUFSIZE;
750     char *s = strcpy(malloc(max), ""), *t, *n;
751     int i, j, k;
752
753     if (! xml || ! xml->name) return realloc(s, len + 1);
754     while (root->xml.parent) root = (ezxml_root_t)root->xml.parent; // root tag
755
756     for (i = 0; ! p && root->pi[i]; i++) { // pre-root processing instructions
757         for (k = 2; root->pi[i][k - 1]; k++);
758         for (j = 1; (n = root->pi[i][j]); j++) {
759             if (root->pi[i][k][j - 1] == '>') continue; // not pre-root
760             while (len + strlen(t = root->pi[i][0]) + strlen(n) + 7 > max)
761                 s = realloc(s, max += EZXML_BUFSIZE);
762             len += sprintf(s + len, "<?%s%s%s?>\n", t, *n ? " " : "", n);
763         }
764     }
765
766     xml->parent = xml->ordered = NULL;
767     s = ezxml_toxml_r(xml, &s, &len, &max, 0, root->attr);
768     xml->parent = p;
769     xml->ordered = o;
770
771     for (i = 0; ! p && root->pi[i]; i++) { // post-root processing instructions
772         for (k = 2; root->pi[i][k - 1]; k++);
773         for (j = 1; (n = root->pi[i][j]); j++) {
774             if (root->pi[i][k][j - 1] == '<') continue; // not post-root
775             while (len + strlen(t = root->pi[i][0]) + strlen(n) + 7 > max)
776                 s = realloc(s, max += EZXML_BUFSIZE);
777             len += sprintf(s + len, "\n<?%s%s%s?>", t, *n ? " " : "", n);
778         }
779     }
780     return realloc(s, len + 1);
781 }
782
783 // free the memory allocated for the ezxml structure
784 void ezxml_free(ezxml_t xml)
785 {
786     ezxml_root_t root = (ezxml_root_t)xml;
787     int i, j;
788     char **a, *s;
789
790     if (! xml) return;
791     ezxml_free(xml->child);
792     ezxml_free(xml->ordered);
793
794     if (! xml->parent) { // free root tag allocations
795         for (i = 10; root->ent[i]; i += 2) // 0 - 9 are default entites (<>&"')
796             if ((s = root->ent[i + 1]) < root->s || s > root->e) free(s);
797         free(root->ent); // free list of general entities
798
799         for (i = 0; (a = root->attr[i]); i++) {
800             for (j = 1; a[j++]; j += 2) // free malloced attribute values
801                 if (a[j] && (a[j] < root->s || a[j] > root->e)) free(a[j]);
802             free(a);
803         }
804         if (root->attr[0]) free(root->attr); // free default attribute list
805
806         for (i = 0; root->pi[i]; i++) {
807             for (j = 1; root->pi[i][j]; j++);
808             free(root->pi[i][j + 1]);
809             free(root->pi[i]);
810         }            
811         if (root->pi[0]) free(root->pi); // free processing instructions
812
813         if (root->len == -1) free(root->m); // malloced xml data
814 #ifndef EZXML_NOMMAP
815         else if (root->len) munmap(root->m, root->len); // mem mapped xml data
816 #endif // EZXML_NOMMAP
817         if (root->u) free(root->u); // utf8 conversion
818     }
819
820     ezxml_free_attr(xml->attr); // tag attributes
821     if ((xml->flags & EZXML_TXTM)) free(xml->txt); // character content
822     if ((xml->flags & EZXML_NAMEM)) free(xml->name); // tag name
823     free(xml);
824 }
825
826 // return parser error message or empty string if none
827 const char *ezxml_error(ezxml_t xml)
828 {
829     while (xml && xml->parent) xml = xml->parent; // find root tag
830     return (xml) ? ((ezxml_root_t)xml)->err : "";
831 }
832
833 // returns a new empty ezxml structure with the given root tag name
834 ezxml_t ezxml_new(const char *name)
835 {
836     static char *ent[] = { "lt;", "&#60;", "gt;", "&#62;", "quot;", "&#34;",
837                            "apos;", "&#39;", "amp;", "&#38;", NULL };
838     ezxml_root_t root = (ezxml_root_t)memset(malloc(sizeof(struct ezxml_root)), 
839                                              '\0', sizeof(struct ezxml_root));
840     root->xml.name = (char *)name;
841     root->cur = &root->xml;
842     strcpy(root->err, root->xml.txt = "");
843     root->ent = memcpy(malloc(sizeof(ent)), ent, sizeof(ent));
844     root->attr = root->pi = (char ***)(root->xml.attr = EZXML_NIL);
845     return &root->xml;
846 }
847
848 // inserts an existing tag into an ezxml structure
849 ezxml_t ezxml_insert(ezxml_t xml, ezxml_t dest, size_t off)
850 {
851     ezxml_t cur, prev, head;
852
853     xml->next = xml->sibling = xml->ordered = NULL;
854     xml->off = off;
855     xml->parent = dest;
856
857     if ((head = dest->child)) { // already have sub tags
858         if (head->off <= off) { // not first subtag
859             for (cur = head; cur->ordered && cur->ordered->off <= off;
860                  cur = cur->ordered);
861             xml->ordered = cur->ordered;
862             cur->ordered = xml;
863         }
864         else { // first subtag
865             xml->ordered = head;
866             dest->child = xml;
867         }
868
869         for (cur = head, prev = NULL; cur && strcmp(cur->name, xml->name);
870              prev = cur, cur = cur->sibling); // find tag type
871         if (cur && cur->off <= off) { // not first of type
872             while (cur->next && cur->next->off <= off) cur = cur->next;
873             xml->next = cur->next;
874             cur->next = xml;
875         }
876         else { // first tag of this type
877             if (prev && cur) prev->sibling = cur->sibling; // remove old first
878             xml->next = cur; // old first tag is now next
879             for (cur = head, prev = NULL; cur && cur->off <= off;
880                  prev = cur, cur = cur->sibling); // new sibling insert point
881             xml->sibling = cur;
882             if (prev) prev->sibling = xml;
883         }
884     }
885     else dest->child = xml; // only sub tag
886
887     return xml;
888 }
889
890 // Adds a child tag. off is the offset of the child tag relative to the start
891 // of the parent tag's character content. Returns the child tag.
892 ezxml_t ezxml_add_child(ezxml_t xml, const char *name, size_t off)
893 {
894     ezxml_t child;
895
896     if (! xml) return NULL;
897     child = (ezxml_t)memset(malloc(sizeof(struct ezxml)), '\0',
898                             sizeof(struct ezxml));
899     child->name = (char *)name;
900     child->attr = EZXML_NIL;
901     child->txt = "";
902
903     return ezxml_insert(child, xml, off);
904 }
905
906 // sets the character content for the given tag and returns the tag
907 ezxml_t ezxml_set_txt(ezxml_t xml, const char *txt)
908 {
909     if (! xml) return NULL;
910     if (xml->flags & EZXML_TXTM) free(xml->txt); // existing txt was malloced
911     xml->flags &= ~EZXML_TXTM;
912     xml->txt = (char *)txt;
913     return xml;
914 }
915
916 // Sets the given tag attribute or adds a new attribute if not found. A value
917 // of NULL will remove the specified attribute. Returns the tag given.
918 ezxml_t ezxml_set_attr(ezxml_t xml, const char *name, const char *value)
919 {
920     int l = 0, c;
921
922     if (! xml) return NULL;
923     while (xml->attr[l] && strcmp(xml->attr[l], name)) l += 2;
924     if (! xml->attr[l]) { // not found, add as new attribute
925         if (! value) return xml; // nothing to do
926         if (xml->attr == EZXML_NIL) { // first attribute
927             xml->attr = malloc(4 * sizeof(char *));
928             xml->attr[1] = strdup(""); // empty list of malloced names/vals
929         }
930         else xml->attr = realloc(xml->attr, (l + 4) * sizeof(char *));
931
932         xml->attr[l] = (char *)name; // set attribute name
933         xml->attr[l + 2] = NULL; // null terminate attribute list
934         xml->attr[l + 3] = realloc(xml->attr[l + 1],
935                                    (c = strlen(xml->attr[l + 1])) + 2);
936         strcpy(xml->attr[l + 3] + c, " "); // set name/value as not malloced
937         if (xml->flags & EZXML_DUP) xml->attr[l + 3][c] = EZXML_NAMEM;
938     }
939     else if (xml->flags & EZXML_DUP) free((char *)name); // name was strduped
940
941     for (c = l; xml->attr[c]; c += 2); // find end of attribute list
942     if (xml->attr[c + 1][l / 2] & EZXML_TXTM) free(xml->attr[l + 1]); //old val
943     if (xml->flags & EZXML_DUP) xml->attr[c + 1][l / 2] |= EZXML_TXTM;
944     else xml->attr[c + 1][l / 2] &= ~EZXML_TXTM;
945
946     if (value) xml->attr[l + 1] = (char *)value; // set attribute value
947     else { // remove attribute
948         if (xml->attr[c + 1][l / 2] & EZXML_NAMEM) free(xml->attr[l]);
949         memmove(xml->attr + l, xml->attr + l + 2, (c - l + 2) * sizeof(char*));
950         xml->attr = realloc(xml->attr, (c + 2) * sizeof(char *));
951         memmove(xml->attr[c + 1] + (l / 2), xml->attr[c + 1] + (l / 2) + 1,
952                 (c / 2) - (l / 2)); // fix list of which name/vals are malloced
953     }
954     xml->flags &= ~EZXML_DUP; // clear strdup() flag
955     return xml;
956 }
957
958 // sets a flag for the given tag and returns the tag
959 ezxml_t ezxml_set_flag(ezxml_t xml, short flag)
960 {
961     if (xml) xml->flags |= flag;
962     return xml;
963 }
964
965 // removes a tag along with its subtags without freeing its memory
966 ezxml_t ezxml_cut(ezxml_t xml)
967 {
968     ezxml_t cur;
969
970     if (! xml) return NULL; // nothing to do
971     if (xml->next) xml->next->sibling = xml->sibling; // patch sibling list
972
973     if (xml->parent) { // not root tag
974         cur = xml->parent->child; // find head of subtag list
975         if (cur == xml) xml->parent->child = xml->ordered; // first subtag
976         else { // not first subtag
977             while (cur->ordered != xml) cur = cur->ordered;
978             cur->ordered = cur->ordered->ordered; // patch ordered list
979
980             cur = xml->parent->child; // go back to head of subtag list
981             if (strcmp(cur->name, xml->name)) { // not in first sibling list
982                 while (strcmp(cur->sibling->name, xml->name))
983                     cur = cur->sibling;
984                 if (cur->sibling == xml) { // first of a sibling list
985                     cur->sibling = (xml->next) ? xml->next
986                                                : cur->sibling->sibling;
987                 }
988                 else cur = cur->sibling; // not first of a sibling list
989             }
990
991             while (cur->next && cur->next != xml) cur = cur->next;
992             if (cur->next) cur->next = cur->next->next; // patch next list
993         }        
994     }
995     xml->ordered = xml->sibling = xml->next = NULL;
996     return xml;
997 }
998
999 #ifdef EZXML_TEST // test harness
1000 int main(int argc, char **argv)
1001 {
1002     ezxml_t xml;
1003     char *s;
1004     int i;
1005
1006     if (argc != 2) return fprintf(stderr, "usage: %s xmlfile\n", argv[0]);
1007
1008     xml = ezxml_parse_file(argv[1]);
1009     printf("%s\n", (s = ezxml_toxml(xml)));
1010     free(s);
1011     i = fprintf(stderr, "%s", ezxml_error(xml));
1012     ezxml_free(xml);
1013     return (i) ? 1 : 0;
1014 }
1015 #endif // EZXML_TEST