diff --git a/src/common/util/xml.c b/src/common/util/xml.c new file mode 100644 index 000000000..dc1d5237c --- /dev/null +++ b/src/common/util/xml.c @@ -0,0 +1,237 @@ +#include +#include "xml.h" + +/* libc includes */ +#include +#include +#include +#include +#include + +static int +__cberror(const struct xml_stack * stack, const char* parsed, unsigned int line, int error) +{ + fprintf(stderr, "Error #%d in line %u while parsing \"%s\"\n", -error, line, parsed); + return error; +} + +static xml_tag * +make_tag(const char * name) +{ + xml_tag * tag = calloc(sizeof(xml_tag), 1); + tag->name = strdup(name); + return tag; +} + +static void +push_tag(xml_stack ** ostack, xml_tag * tag) +{ + xml_stack * stack = calloc(sizeof(xml_stack), 1); + stack->next = *ostack; + stack->tag = tag; + *ostack = stack; +} + +static void +free_attribs(xml_attrib * xa) +{ + free(xa->name); + free(xa->value); + free(xa); +} + +static void +free_tag(xml_tag * tag) +{ + while (tag->attribs) { + xml_attrib * p = tag->attribs; + tag->attribs = tag->attribs->next; + free_attribs(p); + } + free(tag->name); + free(tag); +} + +static xml_attrib * +make_attrib(xml_tag * tag, const char * name) +{ + xml_attrib * xa = calloc(sizeof(xml_attrib), 1); + xa->name = strdup(name); + xa->next = tag->attribs; + return tag->attribs = xa; +} + +static xml_tag * +pop_tag(xml_stack ** ostack) +{ + xml_stack * stack = *ostack; + xml_tag * tag = stack->tag; + *ostack = stack->next; + free(stack); + return tag; +} + +int +xml_parse(FILE * stream, struct xml_callbacks * cb) +{ + xml_stack * stack = NULL; + enum { TAG, ENDTAG, ATNAME, ATVALUE, PLAIN } state = PLAIN; + char tokbuffer[1024]; + char * pos = tokbuffer; + int quoted = 0; + unsigned int line = 0; + xml_tag * tag = NULL; + xml_attrib * attrib = NULL; + int (*cb_error)(const struct xml_stack*, const char*, unsigned int, int) = __cberror; + if (cb && cb->error) cb_error = cb->error; + + for (;;) { + int reparse; + int c = fgetc(stream); + if (c=='\n') { + ++line; + } else if (c==EOF) { + if (state==PLAIN) { + *pos='\0'; + if (cb && cb->plaintext && pos!=tokbuffer) cb->plaintext(stack, tokbuffer); + break; + } else { + *pos='\0'; + return cb_error(stack, tokbuffer, line, XML_BROKENSTREAM); + } + } + do { + reparse = 0; + switch (state) { + case ATVALUE: + switch (c) { + case '<': + case '/': + *pos='\0'; + return cb_error(stack, tokbuffer, line, XML_INVALIDCHAR); + case '"': + quoted = !quoted; + break; + case '>': + if (quoted) { + *pos='\0'; + return cb_error(stack, tokbuffer, line, XML_INVALIDCHAR); + } + state = TAG; + /* intentional fallthrough */ + default: + if (quoted) *pos++ = (char)c; + else { + if (isspace(c) || c=='>') { + assert(attrib || !"internal error"); + *pos='\0'; + attrib->value = strdup(tokbuffer); + state = TAG; + pos = tokbuffer; + if (c=='>') reparse = 1; + } + } + } + break; /* case ATVALUE */ + case ATNAME: + switch (c) { + case '=': + *pos='\0'; + assert(tag || !"internal error"); + attrib = make_attrib(tag, tokbuffer); + state = ATVALUE; + pos = tokbuffer; + break; + case '>': + case '<': + case '/': + *pos='\0'; + return cb_error(stack, tokbuffer, line, XML_INVALIDCHAR); + default: + if (isspace(c)) { + *pos='\0'; + return cb_error(stack, tokbuffer, line, XML_INVALIDCHAR); + } + *pos++ = (char)c; + } + break; /* case ATNAME */ + case PLAIN: + switch (c) { + case '<': + if (cb && cb->plaintext && pos!=tokbuffer) { + *pos = '\0'; + cb->plaintext(stack, tokbuffer); + } + state = TAG; + tag = NULL; + pos = tokbuffer; + break; + case '>': + *pos='\0'; + return cb_error(stack, tokbuffer, line, XML_INVALIDCHAR); + default: + *pos++ = (char)c; + } + break; /* case PLAIN */ + case TAG: + switch (c) { + case '/': + if (pos==tokbuffer) state = ENDTAG; + else { + *pos='\0'; + return cb_error(stack, tokbuffer, line, XML_INVALIDCHAR); + } + break; + case '>': + if (tag==NULL) { + *pos='\0'; + push_tag(&stack, make_tag(tokbuffer)); + } + if (cb && cb->tagbegin) cb->tagbegin(stack); + state = PLAIN; + pos = tokbuffer; + break; + default: + if (isspace(c)) { + if (tag==NULL) { + *pos='\0'; + push_tag(&stack, tag = make_tag(tokbuffer)); + state = ATNAME; + pos = tokbuffer; + } + } else { + if (tag!=NULL) { + state = ATNAME; + pos = tokbuffer; + reparse = 1; + } + else *pos++ = (char)c; + } + } + break; /* case TAG */ + case ENDTAG: + switch (c) { + case '>': + *pos = '\0'; + while (stack && strcmp(stack->tag->name, tokbuffer)!=0) free_tag(pop_tag(&stack)); + if (stack==NULL) return cb_error(stack, tokbuffer, line, XML_NESTINGERROR); + if (cb && cb->tagend) cb->tagend(stack); + free_tag(pop_tag(&stack)); + state = PLAIN; + pos = tokbuffer; + break; + case '<': + case ' ': + case '=': + case '/': + *pos='\0'; + return cb_error(stack, tokbuffer, line, XML_INVALIDCHAR); + default: + *pos++ = (char)c; + } + break; /* case ENDTAG */ + } /* switch(state) */ + } while (reparse); + } /* for(;;) */ + return XML_OK; /* SUCCESS */ +} diff --git a/src/common/util/xml.h b/src/common/util/xml.h new file mode 100644 index 000000000..75051960c --- /dev/null +++ b/src/common/util/xml.h @@ -0,0 +1,33 @@ +typedef struct xml_stack { + struct xml_stack * next; + struct xml_tag * tag; +} xml_stack; + +typedef struct xml_tag { + char * name; + struct xml_attrib * attribs; +} xml_tag; + +typedef struct xml_attrib { + struct xml_attrib * next; + char * name; + char * value; +} xml_attrib; + +#define XML_OK 0 +#define XML_INVALIDCHAR -1 +#define XML_NESTINGERROR -2 +#define XML_BROKENSTREAM -3 + +/* callbacks */ +typedef struct xml_callbacks { + int (*plaintext)(const struct xml_stack *, const char*); + int (*tagbegin)(const struct xml_stack *); + int (*tagend)(const struct xml_stack *); + int (*error)(const struct xml_stack *, const char*, unsigned int, int); +} xml_callbacks; + +/* parser */ +#include +extern int xml_parse(FILE * stream, struct xml_callbacks *); +