]> git.ozlabs.org Git - ccan/blobdiff - ccan/ttxml/ttxml.c
endian: add constant versions.
[ccan] / ccan / ttxml / ttxml.c
index 5a0d457d33644d5ac23ec585f57c75a3c3a079d3..410ac73a5eab8016bf217ed871f86fedf7f41842 100644 (file)
-/* Licensed under GPL - see LICENSE file for details */\r
-\r
-#include <stdlib.h>\r
-#include <string.h>\r
-#include <stdio.h>\r
-\r
-#include "ttxml.h"\r
-\r
-#ifndef BUFFER\r
-#define BUFFER 3264\r
-#endif\r
-\r
-\r
-#define XML_LETTER     1\r
-#define XML_NUMBER     2\r
-#define XML_SPACE      4\r
-#define XML_SLASH      8\r
-#define XML_OPEN       16\r
-#define XML_EQUALS     32\r
-#define XML_CLOSE      64\r
-#define XML_QUOTE      128\r
-#define XML_OTHER      256\r
-\r
-#define XML_ALL 0xFFFFFFFF\r
-\r
-\r
-typedef struct XMLBUF\r
-{\r
-       FILE * fptr;\r
-       char * buf;\r
-       int len;\r
-       int read_index;\r
-       int eof;\r
-} XMLBUF;\r
-\r
-\r
-/* Allocate a new XmlNode */\r
-static XmlNode* xml_new(char * name)\r
-{\r
-       XmlNode * ret = malloc(sizeof(XmlNode));\r
-       if(!ret)return NULL;\r
-\r
-       ret->attrib = NULL;\r
-       ret->nattrib = 0;\r
-       ret->child = ret->next = NULL;\r
-\r
-       ret->name = name;\r
-       return ret;\r
-}\r
-\r
-/* free a previously allocated XmlNode */\r
-void xml_free(XmlNode *target)\r
-{\r
-       int i;\r
-       for(i=0; i<target->nattrib*2; i++)\r
-               if(target->attrib[i])\r
-                       free(target->attrib[i]);\r
-\r
-       if(target->attrib)free(target->attrib);\r
-       if(target->child)xml_free(target->child);\r
-       if(target->next)xml_free(target->next);\r
-       free(target->name);\r
-       free(target);\r
-}\r
-\r
-/* Raise flags if we have a character of special meaning.\r
- * This is where I've hidden the switch statements :-p\r
- */\r
-static int is_special(char item)\r
-{\r
-       if((item >= 'a' && item <= 'z') || (item >= 'A' && item <='Z'))\r
-               return XML_LETTER;\r
-       if( item >= '0' && item <='9' )\r
-               return XML_NUMBER;\r
-       if( item == 0x20 || item == '\t' ||     item == 0x0D || item == 0x0A )\r
-               return XML_SPACE;\r
-       if( item == '/' )\r
-               return XML_SLASH;\r
-       if( item == '<' )\r
-               return XML_OPEN;\r
-       if( item == '=' )\r
-               return XML_EQUALS;\r
-       if( item == '>' )\r
-               return XML_CLOSE;\r
-       if( item == '"' || item == '\'' )\r
-               return XML_QUOTE;\r
-       return 128;\r
-}\r
-\r
-/* Refresh the buffer, if possible */\r
-static void xml_read_file(XMLBUF *xml)\r
-{\r
-       int size;\r
-       \r
-       if(xml->eof)return;\r
-         \r
-       size = fread( xml->buf, 1, xml->len, xml->fptr);\r
-       if( size != xml->len )\r
-       {\r
-               xml->len = size;\r
-               xml->buf[size]=0;\r
-               xml->eof = 1;\r
-       }\r
-}\r
-\r
-\r
-/* All reading of the XML buffer done through these two functions */\r
-/*** read a byte without advancing the offset */\r
-static char xml_peek(XMLBUF *xml)\r
-{\r
-       return xml->buf[xml->read_index];\r
-}\r
-\r
-/*** read a byte and advance the offset */\r
-static char xml_read_byte(XMLBUF *xml)\r
-{\r
-       char ret = xml_peek(xml);\r
-       xml->read_index++;\r
-       if(xml->read_index >= xml->len)\r
-       {\r
-               if(xml->eof)\r
-               {\r
-                 xml->read_index = xml->len;\r
-                 return ret;\r
-               }\r
-               xml->read_index = 0 ;\r
-               xml_read_file(xml);\r
-       }\r
-       return ret;\r
-}\r
-\r
-\r
-/* skip over bytes matching the is_special mask */\r
-static void xml_skip( XMLBUF *xml, int mask)\r
-{\r
-       while( is_special(xml_peek(xml)) & mask && !(xml->eof && xml->read_index >= xml->len) )\r
-               xml_read_byte(xml);\r
-}\r
-\r
-\r
-/* character matching tests for the feed functions */\r
-static char quotechar = 0;\r
-static int test_quote(const char x)\r
-{\r
-       static int escaped=0;\r
-       if( escaped || '\\' == x )\r
-       {\r
-               escaped = !escaped;\r
-               return 1;\r
-       }\r
-       if( x != quotechar )\r
-               return 1;\r
-       return 0;\r
-}\r
-\r
-static int feed_mask = 0;\r
-static int test_mask(const char x)\r
-{\r
-       return !(is_special(x) & feed_mask);\r
-}\r
-\r
-/*\r
- * char* xml_feed(x, test)\r
- *\r
- * Reads as many contiguous chars that pass test() into a newly allocated\r
- * string.\r
- *\r
- * Instead of calling xml_read_byte and flogging realloc() for each byte,\r
- * it checks the buffer itself.\r
-*/\r
-static char* xml_feed( XMLBUF *xml, int (*test)(char) )\r
-{\r
-       int offset = xml->read_index;\r
-       int delta;\r
-       char *ret = NULL;\r
-       int size = 0;\r
-\r
-       /* perform first and N middle realloc()'s */\r
-       while( test(xml->buf[offset]) )\r
-       {\r
-               offset ++;\r
-\r
-               if(offset >= xml->len)\r
-               {\r
-                       delta = offset - xml->read_index;\r
-                       ret = realloc(ret, size + delta + 1);\r
-                       memcpy(ret+size, xml->buf + xml->read_index, delta);\r
-                       size += delta;\r
-                       ret[size]=0;\r
-                       if(xml->eof)return ret;\r
-                       xml_read_file(xml);\r
-                       xml->read_index = 0;\r
-                       offset = 0;\r
-               }\r
-       }\r
-       /* perform final realloc() if needed */\r
-       if(offset > xml->read_index)\r
-       {\r
-               delta = offset - xml->read_index;\r
-               ret = realloc(ret, size + delta + 1);\r
-               memcpy(ret+size, xml->buf + xml->read_index, delta);\r
-               xml->read_index = offset;\r
-               size += delta;\r
-               ret[size]=0;\r
-       }\r
-       return ret;\r
-}\r
-\r
-/* this reads attributes from tags, of the form...\r
- *\r
- * <tag attr1="some arguments" attr2=argument>\r
- *\r
- * It is aware of quotes, and will allow anything inside quoted arguments\r
- */\r
-static void xml_read_attr(struct XMLBUF *xml, XmlNode *node)\r
-{\r
-       int n=0;\r
-\r
-       // how does this tag finish?\r
-       while(xml->len)\r
-       {\r
-               if( is_special(xml_peek(xml)) & (XML_CLOSE | XML_SLASH) )\r
-                       return;\r
-\r
-               n = ++node->nattrib;\r
-               node->attrib = realloc(node->attrib, n * 2 * sizeof(char*) );\r
-               node->attrib[--n*2+1] = 0;\r
-               \r
-               feed_mask = XML_EQUALS | XML_SPACE | XML_CLOSE | XML_SLASH;\r
-               node->attrib[n*2] = xml_feed(xml, test_mask );\r
-               if( xml_peek(xml) == '=' )\r
-               {\r
-                       xml_read_byte(xml);\r
-                       if( is_special(xml_peek(xml)) & XML_QUOTE )\r
-                       {\r
-                               quotechar = xml_read_byte(xml);\r
-                               node->attrib[n*2+1] = xml_feed(xml, test_quote);\r
-                               xml_read_byte(xml);\r
-                       }\r
-                       else\r
-                       {\r
-                               feed_mask = XML_SPACE | XML_CLOSE | XML_SLASH;\r
-                               node->attrib[n*2+1] = xml_feed(xml, test_mask);\r
-                       }\r
-               }\r
-               xml_skip(xml, XML_SPACE);\r
-       }\r
-}\r
-\r
-/* The big decision maker, is it a regular node, or a text node.\r
- * If it's a node, it will check if it should have children, and if so\r
- * will recurse over them.\r
- * Text nodes don't have children, so no recursing.\r
- */\r
-static XmlNode* xml_parse(struct XMLBUF *xml)\r
-{\r
-       int offset;\r
-       int toff;\r
-       char *tmp;\r
-       XmlNode **this, *ret = NULL;\r
-       \r
-       this = &ret;\r
-\r
-       xml_skip(xml, XML_SPACE);       // skip whitespace\r
-       offset=0;\r
-       while( (xml->read_index < xml->len) || !xml->eof )\r
-       {\r
-               switch(is_special(xml_peek(xml)))\r
-               {\r
-                       case XML_OPEN:\r
-                               xml_read_byte(xml);\r
-                               if(xml_peek(xml) == '/')\r
-                                       return ret;             // parents close tag\r
-                               // read the tag name\r
-                               feed_mask = XML_SPACE | XML_SLASH | XML_CLOSE;\r
-                               *this = xml_new( xml_feed(xml, test_mask));\r
-                               xml_skip(xml, XML_SPACE);       // skip any whitespace\r
-\r
-                               xml_read_attr(xml, *this);      // read attributes\r
-\r
-                               // how does this tag finish?\r
-                               switch(is_special(xml_peek(xml)))\r
-                               {\r
-                                       case XML_CLOSE:         // child-nodes ahead\r
-                                               xml_read_byte(xml);\r
-                                               (*this)->child = xml_parse(xml);\r
-                                               xml_skip(xml, XML_ALL ^ XML_CLOSE);\r
-                                               xml_read_byte(xml);\r
-                                               break;\r
-                                       case XML_SLASH:         // self closing tag\r
-                                               xml_read_byte(xml);\r
-                                               xml_read_byte(xml);\r
-                                               break;\r
-                               }\r
-                               break;\r
-\r
-                       default:        // text node\r
-                               *this = xml_new(0);\r
-                               xml_skip(xml, XML_SPACE);       // skip any whitespace\r
-                               feed_mask = XML_OPEN;\r
-                               (*this)->nattrib=1;\r
-                               (*this)->attrib = malloc(sizeof(char*)*2);\r
-                               (*this)->attrib[1] = NULL;\r
-                               tmp = (*this)->attrib[0] = xml_feed(xml, test_mask);\r
-\r
-                               /* trim the whitespace off the end of text nodes,\r
-                                * by overwriting the spaces will null termination. */\r
-                               toff = strlen(tmp)-1;\r
-                               while( ( is_special(tmp[toff]) & XML_SPACE ) )\r
-                               {\r
-                                       tmp[toff] = 0;\r
-                                       toff --;\r
-                               }\r
-\r
-                               break;\r
-               }\r
-               this = &(*this)->next; \r
-               xml_skip(xml, XML_SPACE);       // skip whitespace\r
-       }       \r
-\r
-       return ret;\r
-}\r
-\r
-\r
-/* bootstrap the structures for xml_parse() to be able to get started */\r
-XmlNode* xml_load(const char * filename)\r
-{\r
-       struct XMLBUF xml;\r
-       XmlNode *ret = NULL;\r
-\r
-//     printf("xml_load(\"%s\");\n", filename);\r
-\r
-       xml.eof = 0;\r
-       xml.read_index = 0;\r
-       xml.fptr = fopen(filename, "rb");\r
-       if(!xml.fptr)\r
-               return NULL;\r
-\r
-       xml.buf = malloc(BUFFER+1);\r
-       xml.buf[BUFFER]=0;\r
-       xml.len = BUFFER;\r
-       if(!xml.buf)\r
-               goto xml_load_fail_malloc_buf;\r
-       \r
-       xml_read_file(&xml);\r
-\r
-       ret = xml_parse(&xml);\r
-\r
-       free(xml.buf);\r
-xml_load_fail_malloc_buf:\r
-       fclose(xml.fptr);\r
-       return ret;\r
-}\r
-\r
-/* very basic function that will get you the first node with a given name */\r
-XmlNode * xml_find(XmlNode *xml, const char *name)\r
-{\r
-       XmlNode * ret;\r
-       if(xml->name)if(!strcmp(xml->name, name))return xml;\r
-       if(xml->child)\r
-       {\r
-               ret = xml_find(xml->child, name);\r
-               if(ret)return ret;\r
-       }\r
-       if(xml->next)\r
-       {\r
-               ret = xml_find(xml->next, name);\r
-               if(ret)return ret;\r
-       }\r
-       return NULL;\r
-}\r
-\r
-/* very basic attribute lookup function */\r
-char* xml_attr(XmlNode *x, const char *name)\r
-{\r
-       int i;\r
-       for(i=0; i<x->nattrib; i++)\r
-               if(x->attrib[i*2])\r
-                       if(!strcmp(x->attrib[i*2], name))\r
-                               return x->attrib[i*2+1];\r
-       return 0;\r
-}\r
-\r
-\r
+/* Licensed under GPL - see LICENSE file for details */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "ttxml.h"
+
+#ifndef BUFFER
+#define BUFFER 3264
+#endif
+
+
+#define XML_LETTER     1
+#define XML_NUMBER     2
+#define XML_SPACE      4
+#define XML_SLASH      8
+#define XML_OPEN       16
+#define XML_EQUALS     32
+#define XML_CLOSE      64
+#define XML_QUOTE      128
+#define XML_OTHER      256
+
+#define XML_ALL 0xFFFFFFFF
+
+
+typedef struct XMLBUF
+{
+       FILE * fptr;
+       char * buf;
+       int len;
+       int read_index;
+       int eof;
+       int error;
+} XMLBUF;
+
+
+/* Allocate a new XmlNode */
+static XmlNode* xml_new(char * name)
+{
+       XmlNode * ret = malloc(sizeof(XmlNode));
+       if(!ret)return NULL;
+
+       ret->attrib = NULL;
+       ret->nattrib = 0;
+       ret->child = ret->next = NULL;
+
+       ret->name = name;
+       return ret;
+}
+
+/* free a previously allocated XmlNode */
+void xml_free(XmlNode *target)
+{
+       int i;
+       for(i=0; i<target->nattrib*2; i++)
+               if(target->attrib[i])
+                       free(target->attrib[i]);
+
+       if(target->attrib)free(target->attrib);
+       if(target->child)xml_free(target->child);
+       if(target->next)xml_free(target->next);
+       free(target->name);
+       free(target);
+}
+
+/* Raise flags if we have a character of special meaning.
+ * This is where I've hidden the switch statements :-p
+ */
+static int is_special(char item)
+{
+       if((item >= 'a' && item <= 'z') || (item >= 'A' && item <='Z'))
+               return XML_LETTER;
+       if( item >= '0' && item <='9' )
+               return XML_NUMBER;
+       if( item == 0x20 || item == '\t' ||     item == 0x0D || item == 0x0A )
+               return XML_SPACE;
+       if( item == '/' )
+               return XML_SLASH;
+       if( item == '<' )
+               return XML_OPEN;
+       if( item == '=' )
+               return XML_EQUALS;
+       if( item == '>' )
+               return XML_CLOSE;
+       if( item == '"' || item == '\'' )
+               return XML_QUOTE;
+       return 128;
+}
+
+/* Refresh the buffer, if possible */
+static void xml_read_file(XMLBUF *xml)
+{
+       int size;
+       
+       if(xml->eof)return;
+         
+       size = fread( xml->buf, 1, xml->len, xml->fptr);
+       if( size != xml->len )
+       {
+               xml->len = size;
+               xml->buf[size]=0;
+               xml->eof = 1;
+       }
+}
+
+
+static void xml_end_file(XMLBUF *xml)
+{
+       xml->len = 0;
+       xml->eof = 1;
+       xml->read_index = 0 ;
+       xml->error = 1;
+}
+
+/* All reading of the XML buffer done through these two functions */
+/*** read a byte without advancing the offset */
+static char xml_peek(XMLBUF *xml)
+{
+       return xml->buf[xml->read_index];
+}
+
+/*** read a byte and advance the offset */
+static char xml_read_byte(XMLBUF *xml)
+{
+       char ret = xml_peek(xml);
+       xml->read_index++;
+       if(xml->read_index >= xml->len)
+       {
+               if(xml->eof)
+               {
+                 xml->read_index = xml->len;
+                 return ret;
+               }
+               xml->read_index = 0 ;
+               xml_read_file(xml);
+       }
+       return ret;
+}
+
+
+/* skip over bytes matching the is_special mask */
+static void xml_skip( XMLBUF *xml, int mask)
+{
+       while( is_special(xml_peek(xml)) & mask && !(xml->eof && xml->read_index >= xml->len) )
+               xml_read_byte(xml);
+}
+
+
+/* character matching tests for the feed functions */
+static char quotechar = 0;
+static int test_quote(const char x)
+{
+       static int escaped=0;
+       if( escaped || '\\' == x )
+       {
+               escaped = !escaped;
+               return 1;
+       }
+       if( x != quotechar )
+               return 1;
+       return 0;
+}
+
+static int feed_mask = 0;
+static int test_mask(const char x)
+{
+       return !(is_special(x) & feed_mask);
+}
+
+/*
+ * char* xml_feed(x, test)
+ *
+ * Reads as many contiguous chars that pass test() into a newly allocated
+ * string.
+ *
+ * Instead of calling xml_read_byte and flogging realloc() for each byte,
+ * it checks the buffer itself.
+*/
+static char* xml_feed( XMLBUF *xml, int (*test)(char) )
+{
+       int offset = xml->read_index;
+       int delta;
+       char *ret = NULL;
+       char *tmp = NULL;
+       int size = 0;
+
+       /* perform first and N middle realloc()'s */
+       while( test(xml->buf[offset]) )
+       {
+               offset ++;
+
+               if(offset >= xml->len)
+               {
+                       delta = offset - xml->read_index;
+                       tmp = realloc(ret, size + delta + 1);
+                       if(!tmp)goto xml_feed_malloc;
+                       ret = tmp;
+                       memcpy(ret+size, xml->buf + xml->read_index, delta);
+                       size += delta;
+                       ret[size]=0;
+                       if(xml->eof)return ret;
+                       xml_read_file(xml);
+                       xml->read_index = 0;
+                       offset = 0;
+               }
+       }
+       /* perform final realloc() if needed */
+       if(offset > xml->read_index)
+       {
+               delta = offset - xml->read_index;
+               tmp = realloc(ret, size + delta + 1);
+               if(!tmp)goto xml_feed_malloc;
+               ret = tmp;
+               memcpy(ret+size, xml->buf + xml->read_index, delta);
+               xml->read_index = offset;
+               size += delta;
+               ret[size]=0;
+       }
+       return ret;
+xml_feed_malloc:
+       free(ret);
+       xml_end_file(xml);
+       return 0;
+}
+
+/* this reads attributes from tags, of the form...
+ *
+ * <tag attr1="some arguments" attr2=argument>
+ *
+ * It is aware of quotes, and will allow anything inside quoted arguments
+ */
+static void xml_read_attr(struct XMLBUF *xml, XmlNode *node)
+{
+       int n=0;
+       char **tmp;
+
+       // how does this tag finish?
+       while(xml->len)
+       {
+               if( is_special(xml_peek(xml)) & (XML_CLOSE | XML_SLASH) )
+                       return;
+
+               n = ++node->nattrib;
+               tmp = realloc(node->attrib, n * 2 * sizeof(char*) );
+               if(!tmp)goto xml_read_attr_malloc;
+               node->attrib = tmp;
+               node->attrib[--n*2+1] = 0;
+               
+               feed_mask = XML_EQUALS | XML_SPACE | XML_CLOSE | XML_SLASH;
+               node->attrib[n*2] = xml_feed(xml, test_mask );
+               if( xml_peek(xml) == '=' )
+               {
+                       xml_read_byte(xml);
+                       if( is_special(xml_peek(xml)) & XML_QUOTE )
+                       {
+                               quotechar = xml_read_byte(xml);
+                               node->attrib[n*2+1] = xml_feed(xml, test_quote);
+                               xml_read_byte(xml);
+                       }
+                       else
+                       {
+                               feed_mask = XML_SPACE | XML_CLOSE | XML_SLASH;
+                               node->attrib[n*2+1] = xml_feed(xml, test_mask);
+                       }
+               }
+               xml_skip(xml, XML_SPACE);
+       }
+       return;
+xml_read_attr_malloc:
+       xml_end_file(xml);
+}
+
+/* The big decision maker, is it a regular node, or a text node.
+ * If it's a node, it will check if it should have children, and if so
+ * will recurse over them.
+ * Text nodes don't have children, so no recursing.
+ */
+static XmlNode* xml_parse(struct XMLBUF *xml)
+{
+       int toff;
+       char **tmp;
+       char *stmp;
+       XmlNode **this, *ret = NULL;
+
+       this = &ret;
+
+       xml_skip(xml, XML_SPACE);       // skip whitespace
+       while( (xml->read_index < xml->len) || !xml->eof )
+       {
+               switch(is_special(xml_peek(xml)))
+               {
+                       case XML_OPEN:
+                               xml_read_byte(xml);
+                               if(xml_peek(xml) == '/')
+                                       return ret;             // parents close tag
+                               // read the tag name
+                               feed_mask = XML_SPACE | XML_SLASH | XML_CLOSE;
+                               *this = xml_new( xml_feed(xml, test_mask));
+                               if(xml->error)goto xml_parse_malloc;
+                               xml_skip(xml, XML_SPACE);       // skip any whitespace
+
+                               xml_read_attr(xml, *this);      // read attributes
+
+                               // how does this tag finish?
+                               switch(is_special(xml_peek(xml)))
+                               {
+                                       case XML_CLOSE:         // child-nodes ahead
+                                               xml_read_byte(xml);
+                                               (*this)->child = xml_parse(xml);
+                                               xml_skip(xml, XML_ALL ^ XML_CLOSE);
+                                               xml_read_byte(xml);
+                                               break;
+                                       case XML_SLASH:         // self closing tag
+                                               xml_read_byte(xml);
+                                               xml_read_byte(xml);
+                                               break;
+                               }
+                               break;
+
+                       default:        // text node
+                               *this = xml_new(0);
+                               xml_skip(xml, XML_SPACE);       // skip any whitespace
+                               feed_mask = XML_OPEN;
+                               (*this)->nattrib=1;
+                               tmp = malloc(sizeof(char*)*2);
+                               if(!tmp)goto xml_parse_malloc;
+                               (*this)->attrib = tmp;
+                               (*this)->attrib[1] = NULL;
+                               stmp = (*this)->attrib[0] = xml_feed(xml, test_mask);
+
+                               /* trim the whitespace off the end of text nodes,
+                                * by overwriting the spaces will null termination. */
+                               toff = strlen(stmp)-1;
+                               while( ( is_special(stmp[toff]) & XML_SPACE ) )
+                               {
+                                       stmp[toff] = 0;
+                                       toff --;
+                               }
+
+                               break;
+               }
+               this = &(*this)->next;
+               xml_skip(xml, XML_SPACE);       // skip whitespace
+       }
+
+       return ret;
+xml_parse_malloc:
+       xml_end_file(xml);
+       if(ret)xml_free(ret);
+       return 0;
+}
+
+
+/* bootstrap the structures for xml_parse() to be able to get started */
+XmlNode* xml_load(const char * filename)
+{
+       struct XMLBUF xml;
+       XmlNode *ret = NULL;
+
+//     printf("xml_load(\"%s\");\n", filename);
+
+       xml.error = 0;
+       xml.eof = 0;
+       xml.read_index = 0;
+       xml.fptr = fopen(filename, "rb");
+       if(!xml.fptr)
+               return NULL;
+
+       xml.buf = malloc(BUFFER+1);
+       if(!xml.buf)
+               goto xml_load_fail_malloc_buf;
+       xml.buf[BUFFER]=0;
+       xml.len = BUFFER;
+       
+       xml_read_file(&xml);
+
+       ret = xml_parse(&xml);
+
+       if(xml.error)
+       {
+               xml_free(ret);
+               ret = NULL;
+       }
+
+       free(xml.buf);
+xml_load_fail_malloc_buf:
+       fclose(xml.fptr);
+       return ret;
+}
+
+/* very basic function that will get you the first node with a given name */
+XmlNode * xml_find(XmlNode *xml, const char *name)
+{
+       XmlNode * ret;
+       if(xml->name)if(!strcmp(xml->name, name))return xml;
+       if(xml->child)
+       {
+               ret = xml_find(xml->child, name);
+               if(ret)return ret;
+       }
+       if(xml->next)
+       {
+               ret = xml_find(xml->next, name);
+               if(ret)return ret;
+       }
+       return NULL;
+}
+
+/* very basic attribute lookup function */
+char* xml_attr(XmlNode *x, const char *name)
+{
+       int i;
+       for(i=0; i<x->nattrib; i++)
+               if(x->attrib[i*2])
+                       if(!strcmp(x->attrib[i*2], name))
+                               return x->attrib[i*2+1];
+       return 0;
+}
+
+