diff options
| author | Lars-Dominik Braun <PromyLOPh@lavabit.com> | 2009-07-11 15:09:59 +0200 | 
|---|---|---|
| committer | Lars-Dominik Braun <PromyLOPh@lavabit.com> | 2009-07-11 15:12:37 +0200 | 
| commit | 7d76f4315eaa5ac7783c9cbb47c31a487904dbb2 (patch) | |
| tree | d1424dbaa705f6e6472168b0fd9caa29124953d2 /libezxml | |
| parent | decad112401877c2986ed04fb8f3e0084542f251 (diff) | |
| download | pianobar-7d76f4315eaa5ac7783c9cbb47c31a487904dbb2.tar.gz pianobar-7d76f4315eaa5ac7783c9cbb47c31a487904dbb2.tar.bz2 pianobar-7d76f4315eaa5ac7783c9cbb47c31a487904dbb2.zip | |
ezxml: Slightly modified version of ezxml added
ezxml is a light xml parser, homepage: http://ezxml.sourceforge.net/
Removed xml write and modify functions, added cmake build system
Diffstat (limited to 'libezxml')
| -rw-r--r-- | libezxml/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | libezxml/COPYING | 20 | ||||
| -rw-r--r-- | libezxml/src/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | libezxml/src/ezxml.c | 712 | ||||
| -rw-r--r-- | libezxml/src/ezxml.h | 128 | 
5 files changed, 871 insertions, 0 deletions
| diff --git a/libezxml/CMakeLists.txt b/libezxml/CMakeLists.txt new file mode 100644 index 0000000..edf52c7 --- /dev/null +++ b/libezxml/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required (VERSION 2.4) + +set (PACKAGE "libezxml") +project (${PACKAGE} C) + +add_subdirectory (src) + diff --git a/libezxml/COPYING b/libezxml/COPYING new file mode 100644 index 0000000..80e4e88 --- /dev/null +++ b/libezxml/COPYING @@ -0,0 +1,20 @@ +Copyright 2004-2006 Aaron Voisine <aaron@voisine.org> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/libezxml/src/CMakeLists.txt b/libezxml/src/CMakeLists.txt new file mode 100644 index 0000000..d1cf76d --- /dev/null +++ b/libezxml/src/CMakeLists.txt @@ -0,0 +1,4 @@ +set (CMAKE_C_FLAGS -Wall) + +add_library (ezxml STATIC ezxml.c) +target_link_libraries (ezxml) diff --git a/libezxml/src/ezxml.c b/libezxml/src/ezxml.c new file mode 100644 index 0000000..17c8997 --- /dev/null +++ b/libezxml/src/ezxml.c @@ -0,0 +1,712 @@ +/* ezxml.c + * + * Copyright 2004-2006 Aaron Voisine <aaron@voisine.org> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "ezxml.h" + +#define EZXML_WS   "\t\r\n "  // whitespace +#define EZXML_ERRL 128        // maximum error string length + +typedef struct ezxml_root *ezxml_root_t; +struct ezxml_root {       // additional data for the root tag +    struct ezxml xml;     // is a super-struct built on top of ezxml struct +    ezxml_t cur;          // current xml tree insertion point +    char *m;              // original xml string +    size_t len;           // length of allocated memory for mmap, -1 for malloc +    char *u;              // UTF-8 conversion of string if original was UTF-16 +    char *s;              // start of work area +    char *e;              // end of work area +    char **ent;           // general entities (ampersand sequences) +    char ***attr;         // default attributes +    char ***pi;           // processing instructions +    short standalone;     // non-zero if <?xml standalone="yes"?> +    char err[EZXML_ERRL]; // error string +}; + +char *EZXML_NIL[] = { NULL }; // empty, null terminated array of strings + +// returns the first child tag with the given name or NULL if not found +ezxml_t ezxml_child(ezxml_t xml, const char *name) +{ +    xml = (xml) ? xml->child : NULL; +    while (xml && strcmp(name, xml->name)) xml = xml->sibling; +    return xml; +} + +// returns the Nth tag with the same name in the same subsection or NULL if not +// found +ezxml_t ezxml_idx(ezxml_t xml, int idx) +{ +    for (; xml && idx; idx--) xml = xml->next; +    return xml; +} + +// returns the value of the requested tag attribute or NULL if not found +const char *ezxml_attr(ezxml_t xml, const char *attr) +{ +    int i = 0, j = 1; +    ezxml_root_t root = (ezxml_root_t)xml; + +    if (! xml || ! xml->attr) return NULL; +    while (xml->attr[i] && strcmp(attr, xml->attr[i])) i += 2; +    if (xml->attr[i]) return xml->attr[i + 1]; // found attribute + +    while (root->xml.parent) root = (ezxml_root_t)root->xml.parent; // root tag +    for (i = 0; root->attr[i] && strcmp(xml->name, root->attr[i][0]); i++); +    if (! root->attr[i]) return NULL; // no matching default attributes +    while (root->attr[i][j] && strcmp(attr, root->attr[i][j])) j += 3; +    return (root->attr[i][j]) ? root->attr[i][j + 1] : NULL; // found default +} + +// same as ezxml_get but takes an already initialized va_list +ezxml_t ezxml_vget(ezxml_t xml, va_list ap) +{ +    char *name = va_arg(ap, char *); +    int idx = -1; + +    if (name && *name) { +        idx = va_arg(ap, int);     +        xml = ezxml_child(xml, name); +    } +    return (idx < 0) ? xml : ezxml_vget(ezxml_idx(xml, idx), ap); +} + +// Traverses the xml tree to retrieve a specific subtag. Takes a variable +// length list of tag names and indexes. The argument list must be terminated +// by either an index of -1 or an empty string tag name. Example:  +// title = ezxml_get(library, "shelf", 0, "book", 2, "title", -1); +// This retrieves the title of the 3rd book on the 1st shelf of library. +// Returns NULL if not found. +ezxml_t ezxml_get(ezxml_t xml, ...) +{ +    va_list ap; +    ezxml_t r; + +    va_start(ap, xml); +    r = ezxml_vget(xml, ap); +    va_end(ap); +    return r; +} + +// set an error string and return root +ezxml_t ezxml_err(ezxml_root_t root, char *s, const char *err, ...) +{ +    va_list ap; +    int line = 1; +    char *t, fmt[EZXML_ERRL]; +     +    for (t = root->s; t < s; t++) if (*t == '\n') line++; +    snprintf(fmt, EZXML_ERRL, "[error near line %d]: %s", line, err); + +    va_start(ap, err); +    vsnprintf(root->err, EZXML_ERRL, fmt, ap); +    va_end(ap); + +    return &root->xml; +} + +// Recursively decodes entity and character references and normalizes new lines +// ent is a null terminated array of alternating entity names and values. set t +// to '&' for general entity decoding, '%' for parameter entity decoding, 'c' +// for cdata sections, ' ' for attribute normalization, or '*' for non-cdata +// attribute normalization. Returns s, or if the decoded string is longer than +// s, returns a malloced string that must be freed. +char *ezxml_decode(char *s, char **ent, char t) +{ +    char *e, *r = s, *m = s; +    long b, c, d, l; + +    for (; *s; s++) { // normalize line endings +        while (*s == '\r') { +            *(s++) = '\n'; +            if (*s == '\n') memmove(s, (s + 1), strlen(s)); +        } +    } +     +    for (s = r; ; ) { +        while (*s && *s != '&' && (*s != '%' || t != '%') && !isspace(*s)) s++; + +        if (! *s) break; +        else if (t != 'c' && ! strncmp(s, "&#", 2)) { // character reference +            if (s[2] == 'x') c = strtol(s + 3, &e, 16); // base 16 +            else c = strtol(s + 2, &e, 10); // base 10 +            if (! c || *e != ';') { s++; continue; } // not a character ref + +            if (c < 0x80) *(s++) = c; // US-ASCII subset +            else { // multi-byte UTF-8 sequence +                for (b = 0, d = c; d; d /= 2) b++; // number of bits in c +                b = (b - 2) / 5; // number of bytes in payload +                *(s++) = (0xFF << (7 - b)) | (c >> (6 * b)); // head +                while (b) *(s++) = 0x80 | ((c >> (6 * --b)) & 0x3F); // payload +            } + +            memmove(s, strchr(s, ';') + 1, strlen(strchr(s, ';'))); +        } +        else if ((*s == '&' && (t == '&' || t == ' ' || t == '*')) || +                 (*s == '%' && t == '%')) { // entity reference +            for (b = 0; ent[b] && strncmp(s + 1, ent[b], strlen(ent[b])); +                 b += 2); // find entity in entity list + +            if (ent[b++]) { // found a match +                if ((c = strlen(ent[b])) - 1 > (e = strchr(s, ';')) - s) { +                    l = (d = (s - r)) + c + strlen(e); // new length +                    r = (r == m) ? strcpy(malloc(l), r) : realloc(r, l); +                    e = strchr((s = r + d), ';'); // fix up pointers +                } + +                memmove(s + c, e + 1, strlen(e)); // shift rest of string +                strncpy(s, ent[b], c); // copy in replacement text +            } +            else s++; // not a known entity +        } +        else if ((t == ' ' || t == '*') && isspace(*s)) *(s++) = ' '; +        else s++; // no decoding needed +    } + +    if (t == '*') { // normalize spaces for non-cdata attributes +        for (s = r; *s; s++) { +            if ((l = strspn(s, " "))) memmove(s, s + l, strlen(s + l) + 1); +            while (*s && *s != ' ') s++; +        } +        if (--s >= r && *s == ' ') *s = '\0'; // trim any trailing space +    } +    return r; +} + +// called when parser finds start of new tag +void ezxml_open_tag(ezxml_root_t root, char *name, char **attr) +{ +    ezxml_t xml = root->cur; +     +    if (xml->name) xml = ezxml_add_child(xml, name, strlen(xml->txt)); +    else xml->name = name; // first open tag + +    xml->attr = attr; +    root->cur = xml; // update tag insertion point +} + +// called when parser finds character content between open and closing tag +void ezxml_char_content(ezxml_root_t root, char *s, size_t len, char t) +{ +    ezxml_t xml = root->cur; +    char *m = s; +    size_t l; + +    if (! xml || ! xml->name || ! len) return; // sanity check + +    s[len] = '\0'; // null terminate text (calling functions anticipate this) +    len = strlen(s = ezxml_decode(s, root->ent, t)) + 1; + +    if (! *(xml->txt)) xml->txt = s; // initial character content +    else { // allocate our own memory and make a copy +        xml->txt = (xml->flags & EZXML_TXTM) // allocate some space +                   ? realloc(xml->txt, (l = strlen(xml->txt)) + len) +                   : strcpy(malloc((l = strlen(xml->txt)) + len), xml->txt); +        strcpy(xml->txt + l, s); // add new char content +        if (s != m) free(s); // free s if it was malloced by ezxml_decode() +    } + +    if (xml->txt != m) ezxml_set_flag(xml, EZXML_TXTM); +} + +// called when parser finds closing tag +ezxml_t ezxml_close_tag(ezxml_root_t root, char *name, char *s) +{ +    if (! root->cur || ! root->cur->name || strcmp(name, root->cur->name)) +        return ezxml_err(root, s, "unexpected closing tag </%s>", name); + +    root->cur = root->cur->parent; +    return NULL; +} + +// checks for circular entity references, returns non-zero if no circular +// references are found, zero otherwise +int ezxml_ent_ok(char *name, char *s, char **ent) +{ +    int i; + +    for (; ; s++) { +        while (*s && *s != '&') s++; // find next entity reference +        if (! *s) return 1; +        if (! strncmp(s + 1, name, strlen(name))) return 0; // circular ref. +        for (i = 0; ent[i] && strncmp(ent[i], s + 1, strlen(ent[i])); i += 2); +        if (ent[i] && ! ezxml_ent_ok(name, ent[i + 1], ent)) return 0; +    } +} + +// called when the parser finds a processing instruction +void ezxml_proc_inst(ezxml_root_t root, char *s, size_t len) +{ +    int i = 0, j = 1; +    char *target = s; + +    s[len] = '\0'; // null terminate instruction +    if (*(s += strcspn(s, EZXML_WS))) { +        *s = '\0'; // null terminate target +        s += strspn(s + 1, EZXML_WS) + 1; // skip whitespace after target +    } + +    if (! strcmp(target, "xml")) { // <?xml ... ?> +        if ((s = strstr(s, "standalone")) && ! strncmp(s + strspn(s + 10, +            EZXML_WS "='\"") + 10, "yes", 3)) root->standalone = 1; +        return; +    } + +    if (! root->pi[0]) *(root->pi = malloc(sizeof(char **))) = NULL; //first pi + +    while (root->pi[i] && strcmp(target, root->pi[i][0])) i++; // find target +    if (! root->pi[i]) { // new target +        root->pi = realloc(root->pi, sizeof(char **) * (i + 2)); +        root->pi[i] = malloc(sizeof(char *) * 3); +        root->pi[i][0] = target; +        root->pi[i][1] = (char *)(root->pi[i + 1] = NULL); // terminate pi list +        root->pi[i][2] = strdup(""); // empty document position list +    } + +    while (root->pi[i][j]) j++; // find end of instruction list for this target +    root->pi[i] = realloc(root->pi[i], sizeof(char *) * (j + 3)); +    root->pi[i][j + 2] = realloc(root->pi[i][j + 1], j + 1); +    strcpy(root->pi[i][j + 2] + j - 1, (root->xml.name) ? ">" : "<"); +    root->pi[i][j + 1] = NULL; // null terminate pi list for this target +    root->pi[i][j] = s; // set instruction +} + +// called when the parser finds an internal doctype subset +short ezxml_internal_dtd(ezxml_root_t root, char *s, size_t len) +{ +    char q, *c, *t, *n = NULL, *v, **ent, **pe; +    int i, j; +     +    pe = memcpy(malloc(sizeof(EZXML_NIL)), EZXML_NIL, sizeof(EZXML_NIL)); + +    for (s[len] = '\0'; s; ) { +        while (*s && *s != '<' && *s != '%') s++; // find next declaration + +        if (! *s) break; +        else if (! strncmp(s, "<!ENTITY", 8)) { // parse entity definitions +            c = s += strspn(s + 8, EZXML_WS) + 8; // skip white space separator +            n = s + strspn(s, EZXML_WS "%"); // find name +            *(s = n + strcspn(n, EZXML_WS)) = ';'; // append ; to name + +            v = s + strspn(s + 1, EZXML_WS) + 1; // find value +            if ((q = *(v++)) != '"' && q != '\'') { // skip externals +                s = strchr(s, '>'); +                continue; +            } + +            for (i = 0, ent = (*c == '%') ? pe : root->ent; ent[i]; i++); +            ent = realloc(ent, (i + 3) * sizeof(char *)); // space for next ent +            if (*c == '%') pe = ent; +            else root->ent = ent; + +            *(++s) = '\0'; // null terminate name +            if ((s = strchr(v, q))) *(s++) = '\0'; // null terminate value +            ent[i + 1] = ezxml_decode(v, pe, '%'); // set value +            ent[i + 2] = NULL; // null terminate entity list +            if (! ezxml_ent_ok(n, ent[i + 1], ent)) { // circular reference +                if (ent[i + 1] != v) free(ent[i + 1]); +                ezxml_err(root, v, "circular entity declaration &%s", n); +                break; +            } +            else ent[i] = n; // set entity name +        } +        else if (! strncmp(s, "<!ATTLIST", 9)) { // parse default attributes +            t = s + strspn(s + 9, EZXML_WS) + 9; // skip whitespace separator +            if (! *t) { ezxml_err(root, t, "unclosed <!ATTLIST"); break; } +            if (*(s = t + strcspn(t, EZXML_WS ">")) == '>') continue; +            else *s = '\0'; // null terminate tag name +            for (i = 0; root->attr[i] && strcmp(n, root->attr[i][0]); i++); + +            while (*(n = ++s + strspn(s, EZXML_WS)) && *n != '>') { +                if (*(s = n + strcspn(n, EZXML_WS))) *s = '\0'; // attr name +                else { ezxml_err(root, t, "malformed <!ATTLIST"); break; } + +                s += strspn(s + 1, EZXML_WS) + 1; // find next token +                c = (strncmp(s, "CDATA", 5)) ? "*" : " "; // is it cdata? +                if (! strncmp(s, "NOTATION", 8)) +                    s += strspn(s + 8, EZXML_WS) + 8; +                s = (*s == '(') ? strchr(s, ')') : s + strcspn(s, EZXML_WS); +                if (! s) { ezxml_err(root, t, "malformed <!ATTLIST"); break; } + +                s += strspn(s, EZXML_WS ")"); // skip white space separator +                if (! strncmp(s, "#FIXED", 6)) +                    s += strspn(s + 6, EZXML_WS) + 6; +                if (*s == '#') { // no default value +                    s += strcspn(s, EZXML_WS ">") - 1; +                    if (*c == ' ') continue; // cdata is default, nothing to do +                    v = NULL; +                } +                else if ((*s == '"' || *s == '\'')  &&  // default value +                         (s = strchr(v = s + 1, *s))) *s = '\0'; +                else { ezxml_err(root, t, "malformed <!ATTLIST"); break; } + +                if (! root->attr[i]) { // new tag name +                    root->attr = (! i) ? malloc(2 * sizeof(char **)) +                                       : realloc(root->attr, +                                                 (i + 2) * sizeof(char **)); +                    root->attr[i] = malloc(2 * sizeof(char *)); +                    root->attr[i][0] = t; // set tag name +                    root->attr[i][1] = (char *)(root->attr[i + 1] = NULL); +                } + +                for (j = 1; root->attr[i][j]; j += 3); // find end of list +                root->attr[i] = realloc(root->attr[i], +                                        (j + 4) * sizeof(char *)); + +                root->attr[i][j + 3] = NULL; // null terminate list +                root->attr[i][j + 2] = c; // is it cdata? +                root->attr[i][j + 1] = (v) ? ezxml_decode(v, root->ent, *c) +                                           : NULL; +                root->attr[i][j] = n; // attribute name  +            } +        } +        else if (! strncmp(s, "<!--", 4)) s = strstr(s + 4, "-->"); // comments +        else if (! strncmp(s, "<?", 2)) { // processing instructions +            if ((s = strstr(c = s + 2, "?>"))) +                ezxml_proc_inst(root, c, s++ - c); +        } +        else if (*s == '<') s = strchr(s, '>'); // skip other declarations +        else if (*(s++) == '%' && ! root->standalone) break; +    } + +    free(pe); +    return ! *root->err; +} + +// Converts a UTF-16 string to UTF-8. Returns a new string that must be freed +// or NULL if no conversion was needed. +char *ezxml_str2utf8(char **s, size_t *len) +{ +    char *u; +    size_t l = 0, sl, max = *len; +    long c, d; +    int b, be = (**s == '\xFE') ? 1 : (**s == '\xFF') ? 0 : -1; + +    if (be == -1) return NULL; // not UTF-16 + +    u = malloc(max); +    for (sl = 2; sl < *len - 1; sl += 2) { +        c = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF)  //UTF-16BE +                 : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); //UTF-16LE +        if (c >= 0xD800 && c <= 0xDFFF && (sl += 2) < *len - 1) { // high-half +            d = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF) +                     : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); +            c = (((c & 0x3FF) << 10) | (d & 0x3FF)) + 0x10000; +        } + +        while (l + 6 > max) u = realloc(u, max += EZXML_BUFSIZE); +        if (c < 0x80) u[l++] = c; // US-ASCII subset +        else { // multi-byte UTF-8 sequence +            for (b = 0, d = c; d; d /= 2) b++; // bits in c +            b = (b - 2) / 5; // bytes in payload +            u[l++] = (0xFF << (7 - b)) | (c >> (6 * b)); // head +            while (b) u[l++] = 0x80 | ((c >> (6 * --b)) & 0x3F); // payload +        } +    } +    return *s = realloc(u, *len = l); +} + +// frees a tag attribute list +void ezxml_free_attr(char **attr) { +    int i = 0; +    char *m; +     +    if (! attr || attr == EZXML_NIL) return; // nothing to free +    while (attr[i]) i += 2; // find end of attribute list +    m = attr[i + 1]; // list of which names and values are malloced +    for (i = 0; m[i]; i++) { +        if (m[i] & EZXML_NAMEM) free(attr[i * 2]); +        if (m[i] & EZXML_TXTM) free(attr[(i * 2) + 1]); +    } +    free(m); +    free(attr); +} + +// parse the given xml string and return an ezxml structure +ezxml_t ezxml_parse_str(char *s, size_t len) +{ +    ezxml_root_t root = (ezxml_root_t)ezxml_new(NULL); +    char q, e, *d, **attr, **a = NULL; // initialize a to avoid compile warning +    int l, i, j; + +    root->m = s; +    if (! len) return ezxml_err(root, NULL, "root tag missing"); +    root->u = ezxml_str2utf8(&s, &len); // convert utf-16 to utf-8 +    root->e = (root->s = s) + len; // record start and end of work area +     +    e = s[len - 1]; // save end char +    s[len - 1] = '\0'; // turn end char into null terminator + +    while (*s && *s != '<') s++; // find first tag +    if (! *s) return ezxml_err(root, s, "root tag missing"); + +    for (; ; ) { +        attr = (char **)EZXML_NIL; +        d = ++s; +         +        if (isalpha(*s) || *s == '_' || *s == ':' || *s < '\0') { // new tag +            if (! root->cur) +                return ezxml_err(root, d, "markup outside of root element"); + +            s += strcspn(s, EZXML_WS "/>"); +            while (isspace(*s)) *(s++) = '\0'; // null terminate tag name +   +            if (*s && *s != '/' && *s != '>') // find tag in default attr list +                for (i = 0; (a = root->attr[i]) && strcmp(a[0], d); i++); + +            for (l = 0; *s && *s != '/' && *s != '>'; l += 2) { // new attrib +                attr = (l) ? realloc(attr, (l + 4) * sizeof(char *)) +                           : malloc(4 * sizeof(char *)); // allocate space +                attr[l + 3] = (l) ? realloc(attr[l + 1], (l / 2) + 2) +                                  : malloc(2); // mem for list of maloced vals +                strcpy(attr[l + 3] + (l / 2), " "); // value is not malloced +                attr[l + 2] = NULL; // null terminate list +                attr[l + 1] = ""; // temporary attribute value +                attr[l] = s; // set attribute name + +                s += strcspn(s, EZXML_WS "=/>"); +                if (*s == '=' || isspace(*s)) {  +                    *(s++) = '\0'; // null terminate tag attribute name +                    q = *(s += strspn(s, EZXML_WS "=")); +                    if (q == '"' || q == '\'') { // attribute value +                        attr[l + 1] = ++s; +                        while (*s && *s != q) s++; +                        if (*s) *(s++) = '\0'; // null terminate attribute val +                        else { +                            ezxml_free_attr(attr); +                            return ezxml_err(root, d, "missing %c", q); +                        } + +                        for (j = 1; a && a[j] && strcmp(a[j], attr[l]); j +=3); +                        attr[l + 1] = ezxml_decode(attr[l + 1], root->ent, (a +                                                   && a[j]) ? *a[j + 2] : ' '); +                        if (attr[l + 1] < d || attr[l + 1] > s) +                            attr[l + 3][l / 2] = EZXML_TXTM; // value malloced +                    } +                } +                while (isspace(*s)) s++; +            } + +            if (*s == '/') { // self closing tag +                *(s++) = '\0'; +                if ((*s && *s != '>') || (! *s && e != '>')) { +                    if (l) ezxml_free_attr(attr); +                    return ezxml_err(root, d, "missing >"); +                } +                ezxml_open_tag(root, d, attr); +                ezxml_close_tag(root, d, s); +            } +            else if ((q = *s) == '>' || (! *s && e == '>')) { // open tag +                *s = '\0'; // temporarily null terminate tag name +                ezxml_open_tag(root, d, attr); +                *s = q; +            } +            else { +                if (l) ezxml_free_attr(attr); +                return ezxml_err(root, d, "missing >");  +            } +        } +        else if (*s == '/') { // close tag +            s += strcspn(d = s + 1, EZXML_WS ">") + 1; +            if (! (q = *s) && e != '>') return ezxml_err(root, d, "missing >"); +            *s = '\0'; // temporarily null terminate tag name +            if (ezxml_close_tag(root, d, s)) return &root->xml; +            if (isspace(*s = q)) s += strspn(s, EZXML_WS); +        } +        else if (! strncmp(s, "!--", 3)) { // xml comment +            if (! (s = strstr(s + 3, "--")) || (*(s += 2) != '>' && *s) || +                (! *s && e != '>')) return ezxml_err(root, d, "unclosed <!--"); +        } +        else if (! strncmp(s, "![CDATA[", 8)) { // cdata +            if ((s = strstr(s, "]]>"))) +                ezxml_char_content(root, d + 8, (s += 2) - d - 10, 'c'); +            else return ezxml_err(root, d, "unclosed <![CDATA["); +        } +        else if (! strncmp(s, "!DOCTYPE", 8)) { // dtd +            for (l = 0; *s && ((! l && *s != '>') || (l && (*s != ']' ||  +                 *(s + strspn(s + 1, EZXML_WS) + 1) != '>'))); +                 l = (*s == '[') ? 1 : l) s += strcspn(s + 1, "[]>") + 1; +            if (! *s && e != '>') +                return ezxml_err(root, d, "unclosed <!DOCTYPE"); +            d = (l) ? strchr(d, '[') + 1 : d; +            if (l && ! ezxml_internal_dtd(root, d, s++ - d)) return &root->xml; +        } +        else if (*s == '?') { // <?...?> processing instructions +            do { s = strchr(s, '?'); } while (s && *(++s) && *s != '>'); +            if (! s || (! *s && e != '>'))  +                return ezxml_err(root, d, "unclosed <?"); +            else ezxml_proc_inst(root, d + 1, s - d - 2); +        } +        else return ezxml_err(root, d, "unexpected <"); +         +        if (! s || ! *s) break; +        *s = '\0'; +        d = ++s; +        if (*s && *s != '<') { // tag character content +            while (*s && *s != '<') s++; +            if (*s) ezxml_char_content(root, d, s - d, '&'); +            else break; +        } +        else if (! *s) break; +    } + +    if (! root->cur) return &root->xml; +    else if (! root->cur->name) return ezxml_err(root, d, "root tag missing"); +    else return ezxml_err(root, d, "unclosed tag <%s>", root->cur->name); +} + +// free the memory allocated for the ezxml structure +void ezxml_free(ezxml_t xml) +{ +    ezxml_root_t root = (ezxml_root_t)xml; +    int i, j; +    char **a, *s; + +    if (! xml) return; +    ezxml_free(xml->child); +    ezxml_free(xml->ordered); + +    if (! xml->parent) { // free root tag allocations +        for (i = 10; root->ent[i]; i += 2) // 0 - 9 are default entites (<>&"') +            if ((s = root->ent[i + 1]) < root->s || s > root->e) free(s); +        free(root->ent); // free list of general entities + +        for (i = 0; (a = root->attr[i]); i++) { +            for (j = 1; a[j++]; j += 2) // free malloced attribute values +                if (a[j] && (a[j] < root->s || a[j] > root->e)) free(a[j]); +            free(a); +        } +        if (root->attr[0]) free(root->attr); // free default attribute list + +        for (i = 0; root->pi[i]; i++) { +            for (j = 1; root->pi[i][j]; j++); +            free(root->pi[i][j + 1]); +            free(root->pi[i]); +        }             +        if (root->pi[0]) free(root->pi); // free processing instructions + +        if (root->len == -1) free(root->m); // malloced xml data +        if (root->u) free(root->u); // utf8 conversion +    } + +    ezxml_free_attr(xml->attr); // tag attributes +    if ((xml->flags & EZXML_TXTM)) free(xml->txt); // character content +    if ((xml->flags & EZXML_NAMEM)) free(xml->name); // tag name +    free(xml); +} + +// return parser error message or empty string if none +const char *ezxml_error(ezxml_t xml) +{ +    while (xml && xml->parent) xml = xml->parent; // find root tag +    return (xml) ? ((ezxml_root_t)xml)->err : ""; +} + +// returns a new empty ezxml structure with the given root tag name +ezxml_t ezxml_new(const char *name) +{ +    static char *ent[] = { "lt;", "<", "gt;", ">", "quot;", """, +                           "apos;", "'", "amp;", "&", NULL }; +    ezxml_root_t root = (ezxml_root_t)memset(malloc(sizeof(struct ezxml_root)),  +                                             '\0', sizeof(struct ezxml_root)); +    root->xml.name = (char *)name; +    root->cur = &root->xml; +    strcpy(root->err, root->xml.txt = ""); +    root->ent = memcpy(malloc(sizeof(ent)), ent, sizeof(ent)); +    root->attr = root->pi = (char ***)(root->xml.attr = EZXML_NIL); +    return &root->xml; +} + +// inserts an existing tag into an ezxml structure +ezxml_t ezxml_insert(ezxml_t xml, ezxml_t dest, size_t off) +{ +    ezxml_t cur, prev, head; + +    xml->next = xml->sibling = xml->ordered = NULL; +    xml->off = off; +    xml->parent = dest; + +    if ((head = dest->child)) { // already have sub tags +        if (head->off <= off) { // not first subtag +            for (cur = head; cur->ordered && cur->ordered->off <= off; +                 cur = cur->ordered); +            xml->ordered = cur->ordered; +            cur->ordered = xml; +        } +        else { // first subtag +            xml->ordered = head; +            dest->child = xml; +        } + +        for (cur = head, prev = NULL; cur && strcmp(cur->name, xml->name); +             prev = cur, cur = cur->sibling); // find tag type +        if (cur && cur->off <= off) { // not first of type +            while (cur->next && cur->next->off <= off) cur = cur->next; +            xml->next = cur->next; +            cur->next = xml; +        } +        else { // first tag of this type +            if (prev && cur) prev->sibling = cur->sibling; // remove old first +            xml->next = cur; // old first tag is now next +            for (cur = head, prev = NULL; cur && cur->off <= off; +                 prev = cur, cur = cur->sibling); // new sibling insert point +            xml->sibling = cur; +            if (prev) prev->sibling = xml; +        } +    } +    else dest->child = xml; // only sub tag + +    return xml; +} + +// Adds a child tag. off is the offset of the child tag relative to the start +// of the parent tag's character content. Returns the child tag. +ezxml_t ezxml_add_child(ezxml_t xml, const char *name, size_t off) +{ +    ezxml_t child; + +    if (! xml) return NULL; +    child = (ezxml_t)memset(malloc(sizeof(struct ezxml)), '\0', +                            sizeof(struct ezxml)); +    child->name = (char *)name; +    child->attr = EZXML_NIL; +    child->txt = ""; + +    return ezxml_insert(child, xml, off); +} + +// sets a flag for the given tag and returns the tag +ezxml_t ezxml_set_flag(ezxml_t xml, short flag) +{ +    if (xml) xml->flags |= flag; +    return xml; +} + diff --git a/libezxml/src/ezxml.h b/libezxml/src/ezxml.h new file mode 100644 index 0000000..edd0810 --- /dev/null +++ b/libezxml/src/ezxml.h @@ -0,0 +1,128 @@ +/* ezxml.h + * + * Copyright 2004-2006 Aaron Voisine <aaron@voisine.org> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _EZXML_H +#define _EZXML_H + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <fcntl.h> + +#define EZXML_BUFSIZE 1024 // size of internal memory buffers +#define EZXML_NAMEM   0x80 // name is malloced +#define EZXML_TXTM    0x40 // txt is malloced +#define EZXML_DUP     0x20 // attribute name and value are strduped + +typedef struct ezxml *ezxml_t; +struct ezxml { +    char *name;      // tag name +    char **attr;     // tag attributes { name, value, name, value, ... NULL } +    char *txt;       // tag character content, empty string if none +    size_t off;      // tag offset from start of parent tag character content +    ezxml_t next;    // next tag with same name in this section at this depth +    ezxml_t sibling; // next tag with different name in same section and depth +    ezxml_t ordered; // next tag, same section and depth, in original order +    ezxml_t child;   // head of sub tag list, NULL if none +    ezxml_t parent;  // parent tag, NULL if current tag is root tag +    short flags;     // additional information +}; + +// Given a string of xml data and its length, parses it and creates an ezxml +// structure. For efficiency, modifies the data by adding null terminators +// and decoding ampersand sequences. If you don't want this, copy the data and +// pass in the copy. Returns NULL on failure. +ezxml_t ezxml_parse_str(char *s, size_t len); + +// returns the first child tag (one level deeper) with the given name or NULL +// if not found +ezxml_t ezxml_child(ezxml_t xml, const char *name); + +// returns the next tag of the same name in the same section and depth or NULL +// if not found +#define ezxml_next(xml) ((xml) ? xml->next : NULL) + +// Returns the Nth tag with the same name in the same section at the same depth +// or NULL if not found. An index of 0 returns the tag given. +ezxml_t ezxml_idx(ezxml_t xml, int idx); + +// returns the name of the given tag +#define ezxml_name(xml) ((xml) ? xml->name : NULL) + +// returns the given tag's character content or empty string if none +#define ezxml_txt(xml) ((xml) ? xml->txt : "") + +// returns the value of the requested tag attribute, or NULL if not found +const char *ezxml_attr(ezxml_t xml, const char *attr); + +// Traverses the ezxml sturcture to retrieve a specific subtag. Takes a +// variable length list of tag names and indexes. The argument list must be +// terminated by either an index of -1 or an empty string tag name. Example:  +// title = ezxml_get(library, "shelf", 0, "book", 2, "title", -1); +// This retrieves the title of the 3rd book on the 1st shelf of library. +// Returns NULL if not found. +ezxml_t ezxml_get(ezxml_t xml, ...); + +// frees the memory allocated for an ezxml structure +void ezxml_free(ezxml_t xml); +     +// returns parser error message or empty string if none +const char *ezxml_error(ezxml_t xml); + +// returns a new empty ezxml structure with the given root tag name +ezxml_t ezxml_new(const char *name); + +// wrapper for ezxml_new() that strdup()s name +#define ezxml_new_d(name) ezxml_set_flag(ezxml_new(strdup(name)), EZXML_NAMEM) + +// Adds a child tag. off is the offset of the child tag relative to the start +// of the parent tag's character content. Returns the child tag. +ezxml_t ezxml_add_child(ezxml_t xml, const char *name, size_t off); + +// wrapper for ezxml_add_child() that strdup()s name +#define ezxml_add_child_d(xml, name, off) \ +    ezxml_set_flag(ezxml_add_child(xml, strdup(name), off), EZXML_NAMEM) + +// sets the character content for the given tag and returns the tag +ezxml_t ezxml_set_txt(ezxml_t xml, const char *txt); + +// wrapper for ezxml_set_txt() that strdup()s txt +#define ezxml_set_txt_d(xml, txt) \ +    ezxml_set_flag(ezxml_set_txt(xml, strdup(txt)), EZXML_TXTM) + +// Sets the given tag attribute or adds a new attribute if not found. A value +// of NULL will remove the specified attribute. Returns the tag given. +ezxml_t ezxml_set_attr(ezxml_t xml, const char *name, const char *value); + +// Wrapper for ezxml_set_attr() that strdup()s name/value. Value cannot be NULL +#define ezxml_set_attr_d(xml, name, value) \ +    ezxml_set_attr(ezxml_set_flag(xml, EZXML_DUP), strdup(name), strdup(value)) + +// sets a flag for the given tag and returns the tag +ezxml_t ezxml_set_flag(ezxml_t xml, short flag); + +// inserts an existing tag into an ezxml structure +ezxml_t ezxml_insert(ezxml_t xml, ezxml_t dest, size_t off); + +#endif // _EZXML_H | 
