1 /* Licensed under GPL - see LICENSE file for details */
24 #define XML_ALL 0xFFFFFFFF
38 /* Allocate a new XmlNode */
39 static XmlNode* xml_new(char * name)
41 XmlNode * ret = malloc(sizeof(XmlNode));
46 ret->child = ret->next = NULL;
52 /* free a previously allocated XmlNode */
53 void xml_free(XmlNode *target)
56 for(i=0; i<target->nattrib*2; i++)
58 free(target->attrib[i]);
60 if(target->attrib)free(target->attrib);
61 if(target->child)xml_free(target->child);
62 if(target->next)xml_free(target->next);
67 /* Raise flags if we have a character of special meaning.
68 * This is where I've hidden the switch statements :-p
70 static int is_special(char item)
72 if((item >= 'a' && item <= 'z') || (item >= 'A' && item <='Z'))
74 if( item >= '0' && item <='9' )
76 if( item == 0x20 || item == '\t' || item == 0x0D || item == 0x0A )
86 if( item == '"' || item == '\'' )
91 /* Refresh the buffer, if possible */
92 static void xml_read_file(XMLBUF *xml)
98 size = fread( xml->buf, 1, xml->len, xml->fptr);
99 if( size != xml->len )
108 static void xml_end_file(XMLBUF *xml)
112 xml->read_index = 0 ;
116 /* All reading of the XML buffer done through these two functions */
117 /*** read a byte without advancing the offset */
118 static char xml_peek(XMLBUF *xml)
120 return xml->buf[xml->read_index];
123 /*** read a byte and advance the offset */
124 static char xml_read_byte(XMLBUF *xml)
126 char ret = xml_peek(xml);
128 if(xml->read_index >= xml->len)
132 xml->read_index = xml->len;
135 xml->read_index = 0 ;
142 /* skip over bytes matching the is_special mask */
143 static void xml_skip( XMLBUF *xml, int mask)
145 while( is_special(xml_peek(xml)) & mask && !(xml->eof && xml->read_index >= xml->len) )
150 /* character matching tests for the feed functions */
151 static char quotechar = 0;
152 static int test_quote(const char x)
154 static int escaped=0;
155 if( escaped || '\\' == x )
165 static int feed_mask = 0;
166 static int test_mask(const char x)
168 return !(is_special(x) & feed_mask);
172 * char* xml_feed(x, test)
174 * Reads as many contiguous chars that pass test() into a newly allocated
177 * Instead of calling xml_read_byte and flogging realloc() for each byte,
178 * it checks the buffer itself.
180 static char* xml_feed( XMLBUF *xml, int (*test)(char) )
182 int offset = xml->read_index;
188 /* perform first and N middle realloc()'s */
189 while( test(xml->buf[offset]) )
193 if(offset >= xml->len)
195 delta = offset - xml->read_index;
196 tmp = realloc(ret, size + delta + 1);
197 if(!tmp)goto xml_feed_malloc;
199 memcpy(ret+size, xml->buf + xml->read_index, delta);
202 if(xml->eof)return ret;
208 /* perform final realloc() if needed */
209 if(offset > xml->read_index)
211 delta = offset - xml->read_index;
212 tmp = realloc(ret, size + delta + 1);
213 if(!tmp)goto xml_feed_malloc;
215 memcpy(ret+size, xml->buf + xml->read_index, delta);
216 xml->read_index = offset;
227 /* this reads attributes from tags, of the form...
229 * <tag attr1="some arguments" attr2=argument>
231 * It is aware of quotes, and will allow anything inside quoted arguments
233 static void xml_read_attr(struct XMLBUF *xml, XmlNode *node)
238 // how does this tag finish?
241 if( is_special(xml_peek(xml)) & (XML_CLOSE | XML_SLASH) )
245 tmp = realloc(node->attrib, n * 2 * sizeof(char*) );
246 if(!tmp)goto xml_read_attr_malloc;
248 node->attrib[--n*2+1] = 0;
250 feed_mask = XML_EQUALS | XML_SPACE | XML_CLOSE | XML_SLASH;
251 node->attrib[n*2] = xml_feed(xml, test_mask );
252 if( xml_peek(xml) == '=' )
255 if( is_special(xml_peek(xml)) & XML_QUOTE )
257 quotechar = xml_read_byte(xml);
258 node->attrib[n*2+1] = xml_feed(xml, test_quote);
263 feed_mask = XML_SPACE | XML_CLOSE | XML_SLASH;
264 node->attrib[n*2+1] = xml_feed(xml, test_mask);
267 xml_skip(xml, XML_SPACE);
270 xml_read_attr_malloc:
274 /* The big decision maker, is it a regular node, or a text node.
275 * If it's a node, it will check if it should have children, and if so
276 * will recurse over them.
277 * Text nodes don't have children, so no recursing.
279 static XmlNode* xml_parse(struct XMLBUF *xml)
284 XmlNode **this, *ret = NULL;
288 xml_skip(xml, XML_SPACE); // skip whitespace
289 while( (xml->read_index < xml->len) || !xml->eof )
291 switch(is_special(xml_peek(xml)))
295 if(xml_peek(xml) == '/')
296 return ret; // parents close tag
298 feed_mask = XML_SPACE | XML_SLASH | XML_CLOSE;
299 *this = xml_new( xml_feed(xml, test_mask));
300 if(xml->error)goto xml_parse_malloc;
301 xml_skip(xml, XML_SPACE); // skip any whitespace
303 xml_read_attr(xml, *this); // read attributes
305 // how does this tag finish?
306 switch(is_special(xml_peek(xml)))
308 case XML_CLOSE: // child-nodes ahead
310 (*this)->child = xml_parse(xml);
311 xml_skip(xml, XML_ALL ^ XML_CLOSE);
314 case XML_SLASH: // self closing tag
321 default: // text node
323 xml_skip(xml, XML_SPACE); // skip any whitespace
324 feed_mask = XML_OPEN;
326 tmp = malloc(sizeof(char*)*2);
327 if(!tmp)goto xml_parse_malloc;
328 (*this)->attrib = tmp;
329 (*this)->attrib[1] = NULL;
330 stmp = (*this)->attrib[0] = xml_feed(xml, test_mask);
332 /* trim the whitespace off the end of text nodes,
333 * by overwriting the spaces will null termination. */
334 toff = strlen(stmp)-1;
335 while( ( is_special(stmp[toff]) & XML_SPACE ) )
343 this = &(*this)->next;
344 xml_skip(xml, XML_SPACE); // skip whitespace
350 if(ret)xml_free(ret);
355 /* bootstrap the structures for xml_parse() to be able to get started */
356 XmlNode* xml_load(const char * filename)
361 // printf("xml_load(\"%s\");\n", filename);
366 xml.fptr = fopen(filename, "rb");
370 xml.buf = malloc(BUFFER+1);
372 goto xml_load_fail_malloc_buf;
378 ret = xml_parse(&xml);
387 xml_load_fail_malloc_buf:
392 /* very basic function that will get you the first node with a given name */
393 XmlNode * xml_find(XmlNode *xml, const char *name)
396 if(xml->name)if(!strcmp(xml->name, name))return xml;
399 ret = xml_find(xml->child, name);
404 ret = xml_find(xml->next, name);
410 /* very basic attribute lookup function */
411 char* xml_attr(XmlNode *x, const char *name)
414 for(i=0; i<x->nattrib; i++)
416 if(!strcmp(x->attrib[i*2], name))
417 return x->attrib[i*2+1];