Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


Cleanup and sanity-checking of before/after null-check and copy+paste errors (Coverit...
[palacios.git] / palacios / src / palacios / vmm_xml.c
index f3641be..c2213ca 100644 (file)
@@ -41,6 +41,8 @@
 #define V3_XML_DUP     0x20 // attribute name and value are strduped
 //
 
+static char * V3_XML_NIL[] = { NULL }; // empty, null terminated array of strings
+
 
 #define V3_XML_WS   "\t\r\n "  // whitespace
 #define V3_XML_ERRL 128        // maximum error string length
@@ -51,8 +53,6 @@ struct v3_xml_root {       // additional data for the root tag
     char *str_ptr;         // original xml string
     char *tmp_start;              // start of work area
     char *tmp_end;              // end of work area
-    char **ent;           // general entities (ampersand sequences)
-    char ***attr;         // default attributes
     short standalone;     // non-zero if <?xml standalone="yes"?>
     char err[V3_XML_ERRL]; // error string
 };
@@ -60,23 +60,25 @@ struct v3_xml_root {       // additional data for the root tag
 static char * empty_attrib_list[] = { NULL }; // empty, null terminated array of strings
 
 
-static void * tmp_realloc(void * old_ptr, uint_t old_size, uint_t new_size) {
-    void * new_buf = V3_Malloc(new_size);
 
+static void * tmp_realloc(void * old_ptr, size_t old_size, size_t new_size) {
+    void * new_buf = NULL; 
+
+    new_buf = V3_Malloc(new_size);
+    
     if (new_buf == NULL) {
+       PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in tmp_realloc in xml\n");
         return NULL;
     }
 
+    memset(new_buf, 0, new_size);
+
     memcpy(new_buf, old_ptr, old_size);
     V3_Free(old_ptr);
 
     return new_buf;
 }
 
-
-
-
-
 // set an error string and return root
 static void v3_xml_err(struct v3_xml_root * root, char * xml_str, const char * err, ...) {
     va_list ap;
@@ -96,7 +98,7 @@ static void v3_xml_err(struct v3_xml_root * root, char * xml_str, const char * e
     vsnprintf(root->err, V3_XML_ERRL, fmt, ap);
     va_end(ap);
 
-    PrintError("XML Error: %s\n", root->err);
+    PrintError(VM_NONE, VCORE_NONE, "XML Error: %s\n", root->err);
 
     // free memory
     v3_xml_free(&(root->xml));
@@ -134,8 +136,6 @@ struct v3_xml * v3_xml_idx(struct v3_xml * xml, int idx) {
 // returns the value of the requested tag attribute or NULL if not found
 const char * v3_xml_attr(struct v3_xml * xml, const char * attr) {
     int i = 0;
-    int j = 1;
-    struct v3_xml_root * root = (struct v3_xml_root *)xml;
 
     if ((!xml) || (!xml->attr)) {
        return NULL;
@@ -149,24 +149,7 @@ const char * v3_xml_attr(struct v3_xml * xml, const char * attr) {
        return xml->attr[i + 1]; // found attribute
     }
 
-    while (root->xml.parent != NULL) {
-       root = (struct v3_xml_root *)root->xml.parent; // root tag
-    }
-
-    for (i = 0; 
-        ( (root->attr[i] != NULL) && 
-          (strcasecmp(xml->name, root->attr[i][0]) != 0) ); 
-        i++);
-
-    if (! root->attr[i]) {
-       return NULL; // no matching default attributes
-    }
-
-    while ((root->attr[i][j] != NULL) && (strcasecmp(attr, root->attr[i][j]) != 0)) {
-       j += 3;
-    }
-
-    return (root->attr[i][j] != NULL) ? root->attr[i][j + 1] : NULL; // found default
+    return NULL; // found default
 }
 
 // same as v3_xml_get but takes an already initialized va_list
@@ -201,7 +184,9 @@ struct v3_xml * v3_xml_get(struct v3_xml * xml, ...) {
 // sets a flag for the given tag and returns the tag
 static struct v3_xml * v3_xml_set_flag(struct v3_xml * xml, short flag)
 {
-    if (xml) xml->flags |= flag;
+    if (xml) {
+       xml->flags |= flag;
+    }
     return xml;
 }
 
@@ -215,11 +200,10 @@ static struct v3_xml * v3_xml_set_flag(struct v3_xml * xml, short flag)
 // 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.
-static char * v3_xml_decode(char * s, char ** ent, char t) {
+static char * v3_xml_decode(char * s, char t) {
     char * e;
     char * r = s;
-    char * m = s;
-    long b, c, d, l;
+    long c, l;
 
     // normalize line endings
     for (; *s; s++) { 
@@ -262,28 +246,6 @@ static char * v3_xml_decode(char * s, char ** ent, char t) {
            *(s++) = c; 
 
             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])) != 0);
-                 (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(V3_Malloc(l), r) : tmp_realloc(r, strlen(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 {
-               // not a known entity
-               s++;
-           }
         } else if ( ( (t == ' ') || (t == '*')) && 
                    (isspace(*s))) {
            *(s++) = ' ';
@@ -327,16 +289,29 @@ static void v3_xml_char_content(struct v3_xml_root * root, char * s, size_t len,
     }
 
     s[len] = '\0'; // null terminate text (calling functions anticipate this)
-    len = strlen(s = v3_xml_decode(s, root->ent, t)) + 1;
+    len = strlen(s = v3_xml_decode(s, t)) + 1;
 
-    if (! *(xml->txt)) {
+    if (xml->txt[0] == '\0') { // empty string
        // initial character content
        xml->txt = s;
     } else { 
+
        // allocate our own memory and make a copy
-        xml->txt = (xml->flags & V3_XML_TXTM) ? 
-           (tmp_realloc(xml->txt, strlen(xml->txt), (l = strlen(xml->txt)) + len)) : 
-           (strcpy(V3_Malloc((l = strlen(xml->txt)) + len), xml->txt));
+       if (xml->flags & V3_XML_TXTM) {
+           xml->txt = (tmp_realloc(xml->txt, strlen(xml->txt), (l = strlen(xml->txt)) + len));
+       } else {
+           char * tmp = NULL;
+
+           tmp = V3_Malloc((l = strlen(xml->txt)) + len);
+
+           if (!tmp) {
+               PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml char content\n");
+               return ;
+           }
+
+           strcpy(tmp, xml->txt);
+           xml->txt = tmp;
+       }
 
         strcpy(xml->txt + l, s); // add new char content
        
@@ -363,37 +338,6 @@ static int v3_xml_close_tag(struct v3_xml_root * root, char * name, char * s) {
     return 0;
 }
 
-#if 0
-// checks for circular entity references, returns non-zero if no circular
-// references are found, zero otherwise
-static int v3_xml_ent_ok(char * name, char * s, char ** ent) {
-    int i;
-
-    for (; ; s++) {
-        while ((*s != '\0') && (*s != '&')) {
-           // find next entity reference
-           s++; 
-       }
-
-        if (*s == '\0') {
-           return 1;
-       }
-
-        if (strncmp(s + 1, name, strlen(name)) == 0) {
-           // circular ref.
-           return 0;
-       }
-
-        for (i = 0; (ent[i]) && (strncmp(ent[i], s + 1, strlen(ent[i]))); i += 2);
-
-        if ((ent[i] != NULL) && (v3_xml_ent_ok(name, ent[i + 1], ent) == 0)) {
-           return 0;
-       }
-    }
-}
-#endif
-
-
 
 // frees a tag attribute list
 static void v3_xml_free_attr(char **attr) {
@@ -412,16 +356,6 @@ static void v3_xml_free_attr(char **attr) {
 
     m = attr[i + 1]; // list of which names and values are malloced
 
-    for (i = 0; m[i]; i++) {
-        if (m[i] & V3_XML_NAMEM) {
-           V3_Free(attr[i * 2]);
-       }
-
-        if (m[i] & V3_XML_TXTM) {
-           V3_Free(attr[(i * 2) + 1]);
-       }
-    }
-
     V3_Free(m);
     V3_Free(attr);
 }
@@ -433,10 +367,14 @@ static void v3_xml_free_attr(char **attr) {
 
 // returns a new empty v3_xml structure with the given root tag name
 static struct v3_xml * v3_xml_new(const char * name) {
-    static char * ent[] = { "lt;", "&#60;", "gt;", "&#62;", "quot;", "&#34;",
-                           "apos;", "&#39;", "amp;", "&#38;", NULL };
 
     struct v3_xml_root * root = (struct v3_xml_root *)V3_Malloc(sizeof(struct v3_xml_root));
+
+    if (!root) {
+       PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml_new\n");
+       return NULL;
+    }
+
     memset(root, 0, sizeof(struct v3_xml_root));
 
     root->xml.name = (char *)name;
@@ -444,17 +382,12 @@ static struct v3_xml * v3_xml_new(const char * name) {
     root->xml.txt = "";
     memset(root->err, 0, V3_XML_ERRL);
 
-    root->ent = V3_Malloc(sizeof(ent));
-    memcpy(root->ent, ent, sizeof(ent));
-
-    root->xml.attr = empty_attrib_list;
-    root->attr = (char ***)(empty_attrib_list);
 
     return &root->xml;
 }
 
 // inserts an existing tag into an v3_xml structure
-static struct v3_xml * v3_xml_insert(struct v3_xml * xml, struct v3_xml * dest, size_t off) {
+struct v3_xml * v3_xml_insert(struct v3_xml * xml, struct v3_xml * dest, size_t off) {
     struct v3_xml * cur, * prev, * head;
 
     xml->next = NULL;
@@ -537,6 +470,12 @@ static struct v3_xml * v3_xml_add_child(struct v3_xml * xml, const char * name,
     }
 
     child = (struct v3_xml *)V3_Malloc(sizeof(struct v3_xml));
+
+    if (!child) {
+       PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml_add_child\n");
+       return NULL;
+    }
+
     memset(child, 0, sizeof(struct v3_xml));
 
     child->name = (char *)name;
@@ -575,9 +514,11 @@ static struct v3_xml * parse_str(char * buf, size_t len) {
     char last_char; 
     char * tag_ptr;
     char ** attr; 
-    char ** tmp_attr = NULL; // initialize a to avoid compile warning
     int attr_idx;
-    int i, j;
+
+    if (!buf) {
+       return NULL;
+    }
 
     root->str_ptr = buf;
 
@@ -622,17 +563,7 @@ static struct v3_xml * parse_str(char * buf, size_t len) {
                *(buf++) = '\0';
            }
 
-           // check if attribute follows tag
-            if ((*buf) && (*buf != '/') && (*buf != '>')) {
-               // there is an attribute
-               // find attributes for correct tag
-                for ((i = 0); 
-                    ((tmp_attr = root->attr[i]) && 
-                     (strcasecmp(tmp_attr[0], tag_ptr) != 0)); 
-                    (i++)) ;
-               
-               // 'tmp_attr' now points to the attribute list associated with 'tag_ptr'
-           }
+       
 
            // attributes are name value pairs, 
            //     2nd to last entry is null  (end of list)
@@ -656,15 +587,33 @@ static struct v3_xml * parse_str(char * buf, size_t len) {
                                        (2 * sizeof(char *))), 
                                       ((attr_cnt * (2 * sizeof(char *))) + 
                                        (2 * sizeof(char *))));
-               
+
+                   if (!attr) {
+                       PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml parse string\n");
+                       return NULL;
+                   }
+
                    attr[last_idx] = tmp_realloc(attr[last_idx - 2], 
                                                 attr_cnt,
                                                 (attr_cnt + 1)); 
+
+                   if (!attr[last_idx]) {
+                       PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml parse string\n");
+                       return NULL;
+                   }
+
                } else {
                    attr = V3_Malloc(4 * sizeof(char *)); 
+                   if (!attr) {
+                       PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml parse string\n");
+                       return NULL;
+                   }
                    attr[last_idx] = V3_Malloc(2);
+                   if (!attr[last_idx]) {
+                       PrintError(VM_NONE, VCORE_NONE, "Cannot alloocate in xml parse string\n");
+                       return NULL;
+                   }
                }
-               
 
                 attr[attr_idx] = buf; // set attribute name
                 attr[val_idx] = ""; // temporary attribute value
@@ -674,9 +623,9 @@ static struct v3_xml * parse_str(char * buf, size_t len) {
                 buf += strcspn(buf, V3_XML_WS "=/>");
 
                 if ((*buf == '=') || isspace(*buf)) {
-                    
+
                    *(buf++) = '\0'; // null terminate tag attribute name
-                   
+
                    // eat whitespace (and more multiple '=' ?)
                    buf += strspn(buf, V3_XML_WS "=");
 
@@ -698,20 +647,7 @@ static struct v3_xml * parse_str(char * buf, size_t len) {
                            return NULL;
                         }
 
-                        for (j = 1; 
-                            ( (tmp_attr) && (tmp_attr[j]) && 
-                              (strcasecmp(tmp_attr[j], attr[attr_idx]) != 0)); 
-                            j += 3);
-
-                        attr[val_idx] = v3_xml_decode(attr[val_idx], root->ent, 
-                                                     ((tmp_attr && tmp_attr[j]) ? 
-                                                      *tmp_attr[j + 2] : 
-                                                      ' '));
-                       
-                        if ( (attr[val_idx] < tag_ptr) || 
-                            (attr[val_idx] > buf) ) {
-                            attr[last_idx][attr_cnt - 1] = V3_XML_TXTM; // value malloced
-                       }
+                        attr[val_idx] = v3_xml_decode(attr[val_idx], ' ');
                     }
                 }
 
@@ -797,6 +733,11 @@ static struct v3_xml * parse_str(char * buf, size_t len) {
         *buf = '\0';
         tag_ptr = ++buf;
 
+       /* Eat leading whitespace */
+       while (*buf && isspace(*buf)) {
+           buf++;
+       }
+
         if (*buf && (*buf != '<')) { 
            // tag character content
             while (*buf && (*buf != '<')) {
@@ -835,6 +776,12 @@ struct v3_xml * v3_xml_parse(char * buf) {
 
     str_len = strlen(buf);
     xml_buf = (char *)V3_Malloc(str_len + 1);
+
+    if (!xml_buf) {
+       PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml parse\n");
+       return NULL;
+    }
+
     strcpy(xml_buf, buf);
 
     return parse_str(xml_buf, str_len);
@@ -845,8 +792,6 @@ struct v3_xml * v3_xml_parse(char * buf) {
 // free the memory allocated for the v3_xml structure
 void v3_xml_free(struct v3_xml * xml) {
     struct v3_xml_root * root = (struct v3_xml_root *)xml;
-    int i, j;
-    char **a, *s;
 
     if (xml == NULL) {
         return;
@@ -857,46 +802,349 @@ void v3_xml_free(struct v3_xml * xml) {
 
     if (xml->parent == NULL) { 
        // free root tag allocations
+       V3_Free(root->str_ptr); // malloced xml data
+    }
+
+    v3_xml_free_attr(xml->attr); // tag attributes
+
+
+
+    if ((xml->flags & V3_XML_TXTM)) {
+       // character content
+       V3_Free(xml->txt); 
+    }
+
+    if ((xml->flags & V3_XML_NAMEM)) {
+       // tag name
+       V3_Free(xml->name);
+    }
+
+    V3_Free(xml);
+}
+
+
+
+
+
+/* Adding XML data */
+
+
+
+
+// sets the character content for the given tag and returns the tag
+struct v3_xml *  v3_xml_set_txt(struct v3_xml * xml, const char *txt) {
+    if (! xml) {
+       return NULL;
+    }
+
+    if (xml->flags & V3_XML_TXTM) {
+       // existing txt was malloced
+       V3_Free(xml->txt); 
+    }
+
+    xml->flags &= ~V3_XML_TXTM;
+    xml->txt = (char *)txt;
+    return xml;
+}
+
+// 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.
+struct v3_xml * v3_xml_set_attr(struct v3_xml * xml, const char * name, const char * value) {
+    int l = 0;
+    int c;
+
+    if (! xml) {
+       return NULL;
+    }
+
+    while (xml->attr[l] && strcmp(xml->attr[l], name)) {
+       l += 2;
+    }
+
+    if (! xml->attr[l]) { 
+       // not found, add as new attribute
         
-       for (i = 10; root->ent[i]; i += 2) {
-           // 0 - 9 are default entites (<>&"')
-            if ((s = root->ent[i + 1]) < root->tmp_start || s > root->tmp_end) {
-               V3_Free(s);
+       if (! value) {
+           // nothing to do
+           return xml;
+       }
+       
+       if (xml->attr == V3_XML_NIL) { 
+           // first attribute
+            xml->attr = V3_Malloc(4 * sizeof(char *));
+
+           if (!xml->attr) {
+               PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml set attr\n");
+               return NULL;
            }
+
+           // empty list of malloced names/vals
+            xml->attr[1] = strdup(""); 
+
+           if (!xml->attr[1]) {
+               PrintError(VM_NONE, VCORE_NONE, "Cannot strdup in xml set attr\n");
+               return NULL;
+           }
+
+        } else {
+           xml->attr = tmp_realloc(xml->attr, l * sizeof(char *), (l + 4) * sizeof(char *));
+
+           if (!xml->attr) {
+               PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml set attr\n");
+               return NULL;
+           }
+       }
+
+       // set attribute name
+        xml->attr[l] = (char *)name; 
+
+       // null terminate attribute list
+        xml->attr[l + 2] = NULL; 
+
+        xml->attr[l + 3] = tmp_realloc(xml->attr[l + 1],
+                                      strlen(xml->attr[l + 1]),
+                                      (c = strlen(xml->attr[l + 1])) + 2);
+
+
+       if (!xml->attr[l + 3]) {
+           PrintError(VM_NONE, VCORE_NONE, "Cannot reallocate in xml set attr\n");
+           return NULL;
+       }
+
+       // set name/value as not malloced
+        strcpy(xml->attr[l + 3] + c, " "); 
+
+        if (xml->flags & V3_XML_DUP) {
+           xml->attr[l + 3][c] = V3_XML_NAMEM;
        }
+    } else if (xml->flags & V3_XML_DUP) {
+       // name was strduped
+       V3_Free((char *)name); 
+    }
+
+
+    // find end of attribute list
+    for (c = l; xml->attr[c]; c += 2); 
+
+    if (xml->attr[c + 1][l / 2] & V3_XML_TXTM) {
+       //old val
+       V3_Free(xml->attr[l + 1]); 
+    }
+
+    if (xml->flags & V3_XML_DUP) {
+       xml->attr[c + 1][l / 2] |= V3_XML_TXTM;
+    } else {
+       xml->attr[c + 1][l / 2] &= ~V3_XML_TXTM;
+    }
+
+
+    if (value) {
+       // set attribute value
+       xml->attr[l + 1] = (char *)value; 
+    } else { 
+       // remove attribute
+        
+       if (xml->attr[c + 1][l / 2] & V3_XML_NAMEM) {
+           V3_Free(xml->attr[l]);
+       }
+
+        memmove(xml->attr + l, xml->attr + l + 2, (c - l + 2) * sizeof(char*));
+
+        xml->attr = tmp_realloc(xml->attr, c * sizeof(char *), (c + 2) * sizeof(char *));
+
+       // fix list of which name/vals are malloced
+        memmove(xml->attr[c + 1] + (l / 2), xml->attr[c + 1] + (l / 2) + 1,
+                (c / 2) - (l / 2)); 
+    }
+
+    // clear strdup() flag
+    xml->flags &= ~V3_XML_DUP; 
+
+    return xml;
+}
+
+// removes a tag along with its subtags without freeing its memory
+struct v3_xml * v3_xml_cut(struct v3_xml * xml) {
+    struct v3_xml * cur;
+
+    if (! xml) {
+       // nothing to do
+       return NULL; 
+    }
+
+    if (xml->next) {
+       // patch sibling list
+       xml->next->sibling = xml->sibling; 
+    }
+
+
+    if (xml->parent) { 
+       // not root tag
 
-       V3_Free(root->ent); // free list of general entities
+       // find head of subtag list
+        cur = xml->parent->child; 
 
-        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->tmp_start || a[j] > root->tmp_end)) {
-                   V3_Free(a[j]);
+        if (cur == xml) {
+           // first subtag
+           xml->parent->child = xml->ordered; 
+       } else { 
+       // not first subtag
+
+            while (cur->ordered != xml) {
+               cur = cur->ordered;
+           }
+
+           // patch ordered list
+            cur->ordered = cur->ordered->ordered; 
+
+           // go back to head of subtag list
+            cur = xml->parent->child; 
+
+            if (strcmp(cur->name, xml->name)) {
+               // not in first sibling list
+
+                while (strcmp(cur->sibling->name, xml->name)) {
+                    cur = cur->sibling;
                }
+
+                if (cur->sibling == xml) { 
+                   // first of a sibling list
+                    cur->sibling = (xml->next) ? xml->next
+                                               : cur->sibling->sibling;
+                } else {
+                   // not first of a sibling list
+                   cur = cur->sibling;
+               }
+            }
+
+            while (cur->next && cur->next != xml) {
+               cur = cur->next;
            }
-            V3_Free(a);
-        }
 
-        if (root->attr[0]) {
-           // free default attribute list
-           V3_Free(root->attr);
+            if (cur->next) {
+               // patch next list
+               cur->next = cur->next->next; 
+           }
+        } 
+   }
+    xml->ordered = xml->sibling = xml->next = NULL;
+    return xml;
+}
+
+
+
+
+/* ************************** */
+/* *** XML ENCODING       *** */
+/* ************************** */
+
+// Encodes ampersand sequences appending the results to *dst, reallocating *dst
+// if length excedes max. a is non-zero for attribute encoding. Returns *dst
+static char *ampencode(const char *s, size_t len, char **dst, size_t *dlen,
+                      size_t * max, short a)
+{
+    const char * e;
+    
+    for (e = s + len; s != e; s++) {
+        while (*dlen + 10 > *max) {
+           *dst = tmp_realloc(*dst, *max, *max + V3_XML_BUFSIZE);
+           *max += V3_XML_BUFSIZE;
        }
 
-       V3_Free(root->str_ptr); // malloced xml data
+        switch (*s) {
+        case '\0': return *dst;
+        case '&': *dlen += sprintf(*dst + *dlen, "&amp;"); break;
+        case '<': *dlen += sprintf(*dst + *dlen, "&lt;"); break;
+        case '>': *dlen += sprintf(*dst + *dlen, "&gt;"); break;
+        case '"': *dlen += sprintf(*dst + *dlen, (a) ? "&quot;" : "\""); break;
+        case '\n': *dlen += sprintf(*dst + *dlen, (a) ? "&#xA;" : "\n"); break;
+        case '\t': *dlen += sprintf(*dst + *dlen, (a) ? "&#x9;" : "\t"); break;
+        case '\r': *dlen += sprintf(*dst + *dlen, "&#xD;"); break;
+        default: (*dst)[(*dlen)++] = *s;
+        }
     }
+    return *dst;
+}
 
-    v3_xml_free_attr(xml->attr); // tag attributes
 
-    if ((xml->flags & V3_XML_TXTM)) {
-       // character content
-       V3_Free(xml->txt); 
+
+// Recursively converts each tag to xml appending it to *s. Reallocates *s if
+// its length excedes max. start is the location of the previous tag in the
+// parent tag's character content. Returns *s.
+static char *toxml_r(struct v3_xml * xml, char **s, size_t *len, size_t *max,
+                    size_t start) {
+    int i;
+    char *txt = (xml->parent) ? xml->parent->txt : "";
+    size_t off = 0;
+
+    // parent character content up to this tag
+    *s = ampencode(txt + start, xml->off - start, s, len, max, 0);
+
+    while (*len + strlen(xml->name) + 4 > *max) {
+       // reallocate s
+        *s = tmp_realloc(*s, *max, *max + V3_XML_BUFSIZE);
+       *max += V3_XML_BUFSIZE;
     }
 
-    if ((xml->flags & V3_XML_NAMEM)) {
-       // tag name
-       V3_Free(xml->name);
+
+    *len += sprintf(*s + *len, "<%s", xml->name); // open tag
+    for (i = 0; xml->attr[i]; i += 2) { // tag attributes
+        if (v3_xml_attr(xml, xml->attr[i]) != xml->attr[i + 1]) continue;
+        while (*len + strlen(xml->attr[i]) + 7 > *max) {
+           // reallocate s
+            *s = tmp_realloc(*s, *max, *max + V3_XML_BUFSIZE);
+           *max += V3_XML_BUFSIZE;
+       }
+
+        *len += sprintf(*s + *len, " %s=\"", xml->attr[i]);
+        ampencode(xml->attr[i + 1], -1, s, len, max, 1);
+        *len += sprintf(*s + *len, "\"");
     }
 
-    V3_Free(xml);
+  
+    *len += sprintf(*s + *len, ">");
+
+    *s = (xml->child) ? toxml_r(xml->child, s, len, max, 0) //child
+                      : ampencode(xml->txt, -1, s, len, max, 0);  //data
+    
+    while (*len + strlen(xml->name) + 4 > *max) {
+       // reallocate s
+        *s = tmp_realloc(*s, *max, *max + V3_XML_BUFSIZE);
+       *max += V3_XML_BUFSIZE;
+    }
+
+    *len += sprintf(*s + *len, "</%s>", xml->name); // close tag
+
+    while (off < xml->off && txt[off]) off++; // make sure off is within bounds
+    return (xml->ordered) ? toxml_r(xml->ordered, s, len, max, off)
+                          : ampencode(txt + off, -1, s, len, max, 0);
 }
 
+// Converts an xml structure back to xml. Returns a string of xml data that
+// must be freed.
+char * v3_xml_tostr(struct v3_xml * xml) {
+    struct v3_xml * p = (xml) ? xml->parent : NULL;
+    struct v3_xml * o = (xml) ? xml->ordered : NULL;
+    struct v3_xml_root * root = (struct v3_xml_root *)xml;
+    size_t len = 0, max = V3_XML_BUFSIZE;
+    char *s = V3_Malloc(max);
+
+    if (!s) {
+       PrintError(VM_NONE, VCORE_NONE, "Cannot allocate in xml tostrr\n");
+       return NULL;
+    }
+
+    strcpy(s, "");
+
+    if (! xml || ! xml->name) return tmp_realloc(s, max, len + 1);
+    while (root->xml.parent) root = (struct v3_xml_root *)root->xml.parent; // root tag
+
+
+    xml->parent = xml->ordered = NULL;
+    s = toxml_r(xml, &s, &len, &max, 0);
+    xml->parent = p;
+    xml->ordered = o;
+
+
+    return tmp_realloc(s, max, len + 1);
+}