3
* Copyright 2004-2006 Aaron Voisine <aaron@voisine.org>
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:
13
* The above copyright notice and this permission notice shall be included
14
* in all copies or substantial portions of the Software.
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.
26
#define _BSD_SOURCE /* required by strdup() */
27
#define _DARWIN_C_SOURCE /* required by strdup() on OS X */
36
#include <sys/types.h>
40
#define EZXML_WS "\t\r\n " // whitespace
41
#define EZXML_ERRL 128 // maximum error string length
43
typedef struct ezxml_root *ezxml_root_t;
44
struct ezxml_root { // additional data for the root tag
45
struct ezxml xml; // is a super-struct built on top of ezxml struct
46
ezxml_t cur; // current xml tree insertion point
47
char *m; // original xml string
48
size_t len; // length of allocated memory for mmap, -1 for malloc
49
char *u; // UTF-8 conversion of string if original was UTF-16
50
char *s; // start of work area
51
char *e; // end of work area
52
char **ent; // general entities (ampersand sequences)
53
char ***attr; // default attributes
54
char ***pi; // processing instructions
55
short standalone; // non-zero if <?xml standalone="yes"?>
56
char err[EZXML_ERRL]; // error string
59
char *EZXML_NIL[] = { NULL }; // empty, null terminated array of strings
61
// sets a flag for the given tag and returns the tag
62
static ezxml_t ezxml_set_flag(ezxml_t xml, short flag) {
63
if (xml) xml->flags |= flag;
67
// inserts an existing tag into an ezxml structure
68
static ezxml_t ezxml_insert(ezxml_t xml, ezxml_t dest, size_t off)
70
ezxml_t cur, prev, head;
72
xml->next = xml->sibling = xml->ordered = NULL;
76
if ((head = dest->child)) { // already have sub tags
77
if (head->off <= off) { // not first subtag
78
for (cur = head; cur->ordered && cur->ordered->off <= off;
80
xml->ordered = cur->ordered;
83
else { // first subtag
88
for (cur = head, prev = NULL; cur && strcmp(cur->name, xml->name);
89
prev = cur, cur = cur->sibling); // find tag type
90
if (cur && cur->off <= off) { // not first of type
91
while (cur->next && cur->next->off <= off) cur = cur->next;
92
xml->next = cur->next;
95
else { // first tag of this type
96
if (prev && cur) prev->sibling = cur->sibling; // remove old first
97
xml->next = cur; // old first tag is now next
98
for (cur = head, prev = NULL; cur && cur->off <= off;
99
prev = cur, cur = cur->sibling); // new sibling insert point
101
if (prev) prev->sibling = xml;
104
else dest->child = xml; // only sub tag
109
// Adds a child tag. off is the offset of the child tag relative to the start
110
// of the parent tag's character content. Returns the child tag.
111
static ezxml_t ezxml_add_child(ezxml_t xml, char *name, size_t off)
115
if (! xml) return NULL;
116
child = (ezxml_t)memset(malloc(sizeof(struct ezxml)), '\0',
117
sizeof(struct ezxml));
118
child->name = (char *)name;
119
child->attr = EZXML_NIL;
122
return ezxml_insert(child, xml, off);
125
// returns the first child tag with the given name or NULL if not found
126
ezxml_t ezxml_child(ezxml_t xml, const char *name)
128
xml = (xml) ? xml->child : NULL;
129
while (xml && strcmp(name, xml->name)) xml = xml->sibling;
133
// returns a new empty ezxml structure with the given root tag name
134
static ezxml_t ezxml_new(char *name)
136
static char *ent[] = { "lt;", "<", "gt;", ">", "quot;", """,
137
"apos;", "'", "amp;", "&", NULL };
138
ezxml_root_t root = (ezxml_root_t)memset(malloc(sizeof(struct ezxml_root)),
139
'\0', sizeof(struct ezxml_root));
140
root->xml.name = (char *)name;
141
root->cur = &root->xml;
142
strcpy(root->err, root->xml.txt = "");
143
root->ent = memcpy(malloc(sizeof(ent)), ent, sizeof(ent));
144
root->attr = root->pi = (char ***)(root->xml.attr = EZXML_NIL);
148
// returns the Nth tag with the same name in the same subsection or NULL if not
150
ezxml_t ezxml_idx(ezxml_t xml, int idx)
152
for (; xml && idx; idx--) xml = xml->next;
156
// returns the value of the requested tag attribute or NULL if not found
157
const char *ezxml_attr(ezxml_t xml, const char *attr)
160
ezxml_root_t root = (ezxml_root_t)xml;
162
if (! xml || ! xml->attr) return NULL;
163
while (xml->attr[i] && strcmp(attr, xml->attr[i])) i += 2;
164
if (xml->attr[i]) return xml->attr[i + 1]; // found attribute
166
while (root->xml.parent) root = (ezxml_root_t)root->xml.parent; // root tag
167
for (i = 0; root->attr[i] && strcmp(xml->name, root->attr[i][0]); i++);
168
if (! root->attr[i]) return NULL; // no matching default attributes
169
while (root->attr[i][j] && strcmp(attr, root->attr[i][j])) j += 3;
170
return (root->attr[i][j]) ? root->attr[i][j + 1] : NULL; // found default
173
// same as ezxml_get but takes an already initialized va_list
174
static ezxml_t ezxml_vget(ezxml_t xml, va_list ap)
176
char *name = va_arg(ap, char *);
180
idx = va_arg(ap, int);
181
xml = ezxml_child(xml, name);
183
return (idx < 0) ? xml : ezxml_vget(ezxml_idx(xml, idx), ap);
186
// Traverses the xml tree to retrieve a specific subtag. Takes a variable
187
// length list of tag names and indexes. The argument list must be terminated
188
// by either an index of -1 or an empty string tag name. Example:
189
// title = ezxml_get(library, "shelf", 0, "book", 2, "title", -1);
190
// This retrieves the title of the 3rd book on the 1st shelf of library.
191
// Returns NULL if not found.
192
ezxml_t ezxml_get(ezxml_t xml, ...)
198
r = ezxml_vget(xml, ap);
203
// set an error string and return root
204
static ezxml_t ezxml_err(ezxml_root_t root, char *s, const char *err, ...)
208
char *t, fmt[EZXML_ERRL];
210
for (t = root->s; t < s; t++) if (*t == '\n') line++;
211
snprintf(fmt, EZXML_ERRL, "[error near line %d]: %s", line, err);
214
vsnprintf(root->err, EZXML_ERRL, fmt, ap);
220
// Recursively decodes entity and character references and normalizes new lines
221
// ent is a null terminated array of alternating entity names and values. set t
222
// to '&' for general entity decoding, '%' for parameter entity decoding, 'c'
223
// for cdata sections, ' ' for attribute normalization, or '*' for non-cdata
224
// attribute normalization. Returns s, or if the decoded string is longer than
225
// s, returns a malloced string that must be freed.
226
static char *ezxml_decode(char *s, char **ent, char t)
228
char *e, *r = s, *m = s;
231
for (; *s; s++) { // normalize line endings
234
if (*s == '\n') memmove(s, (s + 1), strlen(s));
239
while (*s && *s != '&' && (*s != '%' || t != '%') && !isspace(*s)) s++;
242
else if (t != 'c' && ! strncmp(s, "&#", 2)) { // character reference
243
if (s[2] == 'x') c = strtol(s + 3, &e, 16); // base 16
244
else c = strtol(s + 2, &e, 10); // base 10
245
if (! c || *e != ';') { s++; continue; } // not a character ref
247
if (c < 0x80) *(s++) = c; // US-ASCII subset
248
else { // multi-byte UTF-8 sequence
249
for (b = 0, d = c; d; d /= 2) b++; // number of bits in c
250
b = (b - 2) / 5; // number of bytes in payload
251
*(s++) = (0xFF << (7 - b)) | (c >> (6 * b)); // head
252
while (b) *(s++) = 0x80 | ((c >> (6 * --b)) & 0x3F); // payload
255
memmove(s, strchr(s, ';') + 1, strlen(strchr(s, ';')));
257
else if ((*s == '&' && (t == '&' || t == ' ' || t == '*')) ||
258
(*s == '%' && t == '%')) { // entity reference
259
for (b = 0; ent[b] && strncmp(s + 1, ent[b], strlen(ent[b]));
260
b += 2); // find entity in entity list
262
if (ent[b++]) { // found a match
263
if ((c = strlen(ent[b])) - 1 > (e = strchr(s, ';')) - s) {
264
l = (d = (s - r)) + c + strlen(e); // new length
265
r = (r == m) ? strcpy(malloc(l), r) : realloc(r, l);
266
e = strchr((s = r + d), ';'); // fix up pointers
269
memmove(s + c, e + 1, strlen(e)); // shift rest of string
270
strncpy(s, ent[b], c); // copy in replacement text
272
else s++; // not a known entity
274
else if ((t == ' ' || t == '*') && isspace(*s)) *(s++) = ' ';
275
else s++; // no decoding needed
278
if (t == '*') { // normalize spaces for non-cdata attributes
279
for (s = r; *s; s++) {
280
if ((l = strspn(s, " "))) memmove(s, s + l, strlen(s + l) + 1);
281
while (*s && *s != ' ') s++;
283
if (--s >= r && *s == ' ') *s = '\0'; // trim any trailing space
288
// called when parser finds start of new tag
289
static void ezxml_open_tag(ezxml_root_t root, char *name, char **attr)
291
ezxml_t xml = root->cur;
293
if (xml->name) xml = ezxml_add_child(xml, name, strlen(xml->txt));
294
else xml->name = name; // first open tag
297
root->cur = xml; // update tag insertion point
300
// called when parser finds character content between open and closing tag
301
static void ezxml_char_content(ezxml_root_t root, char *s, size_t len, char t)
303
ezxml_t xml = root->cur;
307
if (! xml || ! xml->name || ! len) return; // sanity check
309
s[len] = '\0'; // null terminate text (calling functions anticipate this)
310
len = strlen(s = ezxml_decode(s, root->ent, t)) + 1;
312
if (! *(xml->txt)) xml->txt = s; // initial character content
313
else { // allocate our own memory and make a copy
314
xml->txt = (xml->flags & EZXML_TXTM) // allocate some space
315
? realloc(xml->txt, (l = strlen(xml->txt)) + len)
316
: strcpy(malloc((l = strlen(xml->txt)) + len), xml->txt);
317
strcpy(xml->txt + l, s); // add new char content
318
if (s != m) free(s); // free s if it was malloced by ezxml_decode()
321
if (xml->txt != m) ezxml_set_flag(xml, EZXML_TXTM);
324
// called when parser finds closing tag
325
static ezxml_t ezxml_close_tag(ezxml_root_t root, char *name, char *s)
327
if (! root->cur || ! root->cur->name || strcmp(name, root->cur->name))
328
return ezxml_err(root, s, "unexpected closing tag </%s>", name);
330
root->cur = root->cur->parent;
334
// checks for circular entity references, returns non-zero if no circular
335
// references are found, zero otherwise
336
static int ezxml_ent_ok(char *name, char *s, char **ent)
341
while (*s && *s != '&') s++; // find next entity reference
343
if (! strncmp(s + 1, name, strlen(name))) return 0; // circular ref.
344
for (i = 0; ent[i] && strncmp(ent[i], s + 1, strlen(ent[i])); i += 2);
345
if (ent[i] && ! ezxml_ent_ok(name, ent[i + 1], ent)) return 0;
349
// called when the parser finds a processing instruction
350
static void ezxml_proc_inst(ezxml_root_t root, char *s, size_t len)
355
s[len] = '\0'; // null terminate instruction
356
if (*(s += strcspn(s, EZXML_WS))) {
357
*s = '\0'; // null terminate target
358
s += strspn(s + 1, EZXML_WS) + 1; // skip whitespace after target
361
if (! strcmp(target, "xml")) { // <?xml ... ?>
362
if ((s = strstr(s, "standalone")) && ! strncmp(s + strspn(s + 10,
363
EZXML_WS "='\"") + 10, "yes", 3)) root->standalone = 1;
367
if (! root->pi[0]) *(root->pi = malloc(sizeof(char **))) = NULL; //first pi
369
while (root->pi[i] && strcmp(target, root->pi[i][0])) i++; // find target
370
if (! root->pi[i]) { // new target
371
root->pi = realloc(root->pi, sizeof(char **) * (i + 2));
372
root->pi[i] = malloc(sizeof(char *) * 3);
373
root->pi[i][0] = target;
374
root->pi[i][1] = (char *)(root->pi[i + 1] = NULL); // terminate pi list
375
root->pi[i][2] = strdup(""); // empty document position list
378
while (root->pi[i][j]) j++; // find end of instruction list for this target
379
root->pi[i] = realloc(root->pi[i], sizeof(char *) * (j + 3));
380
root->pi[i][j + 2] = realloc(root->pi[i][j + 1], j + 1);
381
strcpy(root->pi[i][j + 2] + j - 1, (root->xml.name) ? ">" : "<");
382
root->pi[i][j + 1] = NULL; // null terminate pi list for this target
383
root->pi[i][j] = s; // set instruction
386
// called when the parser finds an internal doctype subset
387
static short ezxml_internal_dtd(ezxml_root_t root, char *s, size_t len)
389
char q, *c, *t, *n = NULL, *v, **ent, **pe;
392
pe = memcpy(malloc(sizeof(EZXML_NIL)), EZXML_NIL, sizeof(EZXML_NIL));
394
for (s[len] = '\0'; s; ) {
395
while (*s && *s != '<' && *s != '%') s++; // find next declaration
398
else if (! strncmp(s, "<!ENTITY", 8)) { // parse entity definitions
399
c = s += strspn(s + 8, EZXML_WS) + 8; // skip white space separator
400
n = s + strspn(s, EZXML_WS "%"); // find name
401
*(s = n + strcspn(n, EZXML_WS)) = ';'; // append ; to name
403
v = s + strspn(s + 1, EZXML_WS) + 1; // find value
404
if ((q = *(v++)) != '"' && q != '\'') { // skip externals
409
for (i = 0, ent = (*c == '%') ? pe : root->ent; ent[i]; i++);
410
ent = realloc(ent, (i + 3) * sizeof(char *)); // space for next ent
411
if (*c == '%') pe = ent;
412
else root->ent = ent;
414
*(++s) = '\0'; // null terminate name
415
if ((s = strchr(v, q))) *(s++) = '\0'; // null terminate value
416
ent[i + 1] = ezxml_decode(v, pe, '%'); // set value
417
ent[i + 2] = NULL; // null terminate entity list
418
if (! ezxml_ent_ok(n, ent[i + 1], ent)) { // circular reference
419
if (ent[i + 1] != v) free(ent[i + 1]);
420
ezxml_err(root, v, "circular entity declaration &%s", n);
423
else ent[i] = n; // set entity name
425
else if (! strncmp(s, "<!ATTLIST", 9)) { // parse default attributes
426
t = s + strspn(s + 9, EZXML_WS) + 9; // skip whitespace separator
427
if (! *t) { ezxml_err(root, t, "unclosed <!ATTLIST"); break; }
428
if (*(s = t + strcspn(t, EZXML_WS ">")) == '>') continue;
429
else *s = '\0'; // null terminate tag name
430
for (i = 0; root->attr[i] && strcmp(n, root->attr[i][0]); i++);
433
while (*(n = s + strspn(s, EZXML_WS)) && *n != '>') {
434
if (*(s = n + strcspn(n, EZXML_WS))) *s = '\0'; // attr name
435
else { ezxml_err(root, t, "malformed <!ATTLIST"); break; }
437
s += strspn(s + 1, EZXML_WS) + 1; // find next token
438
c = (strncmp(s, "CDATA", 5)) ? "*" : " "; // is it cdata?
439
if (! strncmp(s, "NOTATION", 8))
440
s += strspn(s + 8, EZXML_WS) + 8;
441
s = (*s == '(') ? strchr(s, ')') : s + strcspn(s, EZXML_WS);
442
if (! s) { ezxml_err(root, t, "malformed <!ATTLIST"); break; }
444
s += strspn(s, EZXML_WS ")"); // skip white space separator
445
if (! strncmp(s, "#FIXED", 6))
446
s += strspn(s + 6, EZXML_WS) + 6;
447
if (*s == '#') { // no default value
448
s += strcspn(s, EZXML_WS ">") - 1;
449
if (*c == ' ') continue; // cdata is default, nothing to do
452
else if ((*s == '"' || *s == '\'') && // default value
453
(s = strchr(v = s + 1, *s))) *s = '\0';
454
else { ezxml_err(root, t, "malformed <!ATTLIST"); break; }
456
if (! root->attr[i]) { // new tag name
457
root->attr = (! i) ? malloc(2 * sizeof(char **))
458
: realloc(root->attr,
459
(i + 2) * sizeof(char **));
460
root->attr[i] = malloc(2 * sizeof(char *));
461
root->attr[i][0] = t; // set tag name
462
root->attr[i][1] = (char *)(root->attr[i + 1] = NULL);
465
for (j = 1; root->attr[i][j]; j += 3); // find end of list
466
root->attr[i] = realloc(root->attr[i],
467
(j + 4) * sizeof(char *));
469
root->attr[i][j + 3] = NULL; // null terminate list
470
root->attr[i][j + 2] = c; // is it cdata?
471
root->attr[i][j + 1] = (v) ? ezxml_decode(v, root->ent, *c)
473
root->attr[i][j] = n; // attribute name
477
else if (! strncmp(s, "<!--", 4)) s = strstr(s + 4, "-->"); // comments
478
else if (! strncmp(s, "<?", 2)) { // processing instructions
479
if ((s = strstr(c = s + 2, "?>")))
480
ezxml_proc_inst(root, c, s++ - c);
482
else if (*s == '<') s = strchr(s, '>'); // skip other declarations
483
else if (*(s++) == '%' && ! root->standalone) break;
490
// Converts a UTF-16 string to UTF-8. Returns a new string that must be freed
491
// or NULL if no conversion was needed.
492
static char *ezxml_str2utf8(char **s, size_t *len)
495
size_t l = 0, sl, max = *len;
497
int b, be = (**s == '\xFE') ? 1 : (**s == '\xFF') ? 0 : -1;
499
if (be == -1) return NULL; // not UTF-16
502
for (sl = 2; sl < *len - 1; sl += 2) {
503
c = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF) //UTF-16BE
504
: (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); //UTF-16LE
505
if (c >= 0xD800 && c <= 0xDFFF && (sl += 2) < *len - 1) { // high-half
506
d = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF)
507
: (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF);
508
c = (((c & 0x3FF) << 10) | (d & 0x3FF)) + 0x10000;
511
while (l + 6 > max) u = realloc(u, max += EZXML_BUFSIZE);
512
if (c < 0x80) u[l++] = c; // US-ASCII subset
513
else { // multi-byte UTF-8 sequence
514
for (b = 0, d = c; d; d /= 2) b++; // bits in c
515
b = (b - 2) / 5; // bytes in payload
516
u[l++] = (0xFF << (7 - b)) | (c >> (6 * b)); // head
517
while (b) u[l++] = 0x80 | ((c >> (6 * --b)) & 0x3F); // payload
520
return *s = realloc(u, *len = l);
523
// frees a tag attribute list
524
static void ezxml_free_attr(char **attr) {
528
if (! attr || attr == EZXML_NIL) return; // nothing to free
529
while (attr[i]) i += 2; // find end of attribute list
530
m = attr[i + 1]; // list of which names and values are malloced
531
for (i = 0; m[i]; i++) {
532
if (m[i] & EZXML_NAMEM) free(attr[i * 2]);
533
if (m[i] & EZXML_TXTM) free(attr[(i * 2) + 1]);
539
// parse the given xml string and return an ezxml structure
540
ezxml_t ezxml_parse_str(char *s, size_t len)
542
ezxml_root_t root = (ezxml_root_t)ezxml_new(NULL);
543
char q, e, *d, **attr, **a = NULL; // initialize a to avoid compile warning
547
if (! len) return ezxml_err(root, NULL, "root tag missing");
548
root->u = ezxml_str2utf8(&s, &len); // convert utf-16 to utf-8
549
root->e = (root->s = s) + len; // record start and end of work area
551
e = s[len - 1]; // save end char
552
s[len - 1] = '\0'; // turn end char into null terminator
554
while (*s && *s != '<') s++; // find first tag
555
if (! *s) return ezxml_err(root, s, "root tag missing");
558
attr = (char **)EZXML_NIL;
561
if (isalpha(*s) || *s == '_' || *s == ':' || *s < '\0') { // new tag
563
return ezxml_err(root, d, "markup outside of root element");
565
s += strcspn(s, EZXML_WS "/>");
566
while (isspace(*s)) *(s++) = '\0'; // null terminate tag name
568
if (*s && *s != '/' && *s != '>') // find tag in default attr list
569
for (i = 0; (a = root->attr[i]) && strcmp(a[0], d); i++);
571
for (l = 0; *s && *s != '/' && *s != '>'; l += 2) { // new attrib
572
attr = (l) ? realloc(attr, (l + 4) * sizeof(char *))
573
: malloc(4 * sizeof(char *)); // allocate space
574
attr[l + 3] = (l) ? realloc(attr[l + 1], (l / 2) + 2)
575
: malloc(2); // mem for list of maloced vals
576
strcpy(attr[l + 3] + (l / 2), " "); // value is not malloced
577
attr[l + 2] = NULL; // null terminate list
578
attr[l + 1] = ""; // temporary attribute value
579
attr[l] = s; // set attribute name
581
s += strcspn(s, EZXML_WS "=/>");
582
if (*s == '=' || isspace(*s)) {
583
*(s++) = '\0'; // null terminate tag attribute name
584
q = *(s += strspn(s, EZXML_WS "="));
585
if (q == '"' || q == '\'') { // attribute value
587
while (*s && *s != q) s++;
588
if (*s) *(s++) = '\0'; // null terminate attribute val
590
ezxml_free_attr(attr);
591
return ezxml_err(root, d, "missing %c", q);
594
for (j = 1; a && a[j] && strcmp(a[j], attr[l]); j +=3);
595
attr[l + 1] = ezxml_decode(attr[l + 1], root->ent, (a
596
&& a[j]) ? *a[j + 2] : ' ');
597
if (attr[l + 1] < d || attr[l + 1] > s)
598
attr[l + 3][l / 2] = EZXML_TXTM; // value malloced
601
while (isspace(*s)) s++;
604
if (*s == '/') { // self closing tag
606
if ((*s && *s != '>') || (! *s && e != '>')) {
607
if (l) ezxml_free_attr(attr);
608
return ezxml_err(root, d, "missing >");
610
ezxml_open_tag(root, d, attr);
611
ezxml_close_tag(root, d, s);
613
else if ((q = *s) == '>' || (! *s && e == '>')) { // open tag
614
*s = '\0'; // temporarily null terminate tag name
615
ezxml_open_tag(root, d, attr);
619
if (l) ezxml_free_attr(attr);
620
return ezxml_err(root, d, "missing >");
623
else if (*s == '/') { // close tag
624
s += strcspn(d = s + 1, EZXML_WS ">") + 1;
625
if (! (q = *s) && e != '>') return ezxml_err(root, d, "missing >");
626
*s = '\0'; // temporarily null terminate tag name
627
if (ezxml_close_tag(root, d, s)) return &root->xml;
628
if (isspace(*s = q)) s += strspn(s, EZXML_WS);
630
else if (! strncmp(s, "!--", 3)) { // xml comment
631
if (! (s = strstr(s + 3, "--")) || (*(s += 2) != '>' && *s) ||
632
(! *s && e != '>')) return ezxml_err(root, d, "unclosed <!--");
634
else if (! strncmp(s, "![CDATA[", 8)) { // cdata
635
if ((s = strstr(s, "]]>")))
636
ezxml_char_content(root, d + 8, (s += 2) - d - 10, 'c');
637
else return ezxml_err(root, d, "unclosed <![CDATA[");
639
else if (! strncmp(s, "!DOCTYPE", 8)) { // dtd
640
for (l = 0; *s && ((! l && *s != '>') || (l && (*s != ']' ||
641
*(s + strspn(s + 1, EZXML_WS) + 1) != '>')));
642
l = (*s == '[') ? 1 : l) s += strcspn(s + 1, "[]>") + 1;
643
if (! *s && e != '>')
644
return ezxml_err(root, d, "unclosed <!DOCTYPE");
645
d = (l) ? strchr(d, '[') + 1 : d;
646
if (l && ! ezxml_internal_dtd(root, d, s++ - d)) return &root->xml;
648
else if (*s == '?') { // <?...?> processing instructions
649
do { s = strchr(s, '?'); } while (s && *(++s) && *s != '>');
650
if (! s || (! *s && e != '>'))
651
return ezxml_err(root, d, "unclosed <?");
652
else ezxml_proc_inst(root, d + 1, s - d - 2);
654
else return ezxml_err(root, d, "unexpected <");
656
if (! s || ! *s) break;
659
if (*s && *s != '<') { // tag character content
660
while (*s && *s != '<') s++;
661
if (*s) ezxml_char_content(root, d, s - d, '&');
664
else if (! *s) break;
667
if (! root->cur) return &root->xml;
668
else if (! root->cur->name) return ezxml_err(root, d, "root tag missing");
669
else return ezxml_err(root, d, "unclosed tag <%s>", root->cur->name);
672
// free the memory allocated for the ezxml structure
673
void ezxml_free(ezxml_t xml)
675
ezxml_root_t root = (ezxml_root_t)xml;
680
ezxml_free(xml->child);
681
ezxml_free(xml->ordered);
683
if (! xml->parent) { // free root tag allocations
684
for (i = 10; root->ent[i]; i += 2) // 0 - 9 are default entites (<>&"')
685
if ((s = root->ent[i + 1]) < root->s || s > root->e) free(s);
686
free(root->ent); // free list of general entities
688
for (i = 0; (a = root->attr[i]); i++) {
689
for (j = 1; a[j++]; j += 2) // free malloced attribute values
690
if (a[j] && (a[j] < root->s || a[j] > root->e)) free(a[j]);
693
if (root->attr[0]) free(root->attr); // free default attribute list
695
for (i = 0; root->pi[i]; i++) {
696
for (j = 1; root->pi[i][j]; j++);
697
free(root->pi[i][j + 1]);
700
if (root->pi[0]) free(root->pi); // free processing instructions
702
if (root->len == -1) free(root->m); // malloced xml data
703
if (root->u) free(root->u); // utf8 conversion
706
ezxml_free_attr(xml->attr); // tag attributes
707
if ((xml->flags & EZXML_TXTM)) free(xml->txt); // character content
708
if ((xml->flags & EZXML_NAMEM)) free(xml->name); // tag name
712
// return parser error message or empty string if none
713
const char *ezxml_error(ezxml_t xml)
715
while (xml && xml->parent) xml = xml->parent; // find root tag
716
return (xml) ? ((ezxml_root_t)xml)->err : "";