X-Git-Url: http://v3vee.org/palacios/gitweb/gitweb.cgi?a=blobdiff_plain;f=palacios%2Fsrc%2Fpalacios%2Fvmm_xml.c;h=c2213ca2c71fdfd55568772954d42d85f770c852;hb=88a3605446744969abe6f193a7bc20e62d5aa555;hp=e632a4ffdcddeab9ef273f517ec4f33457095c4e;hpb=123a1ba27ea09c8fa77a1b36ce625b43d7c48b14;p=palacios.git diff --git a/palacios/src/palacios/vmm_xml.c b/palacios/src/palacios/vmm_xml.c index e632a4f..c2213ca 100644 --- a/palacios/src/palacios/vmm_xml.c +++ b/palacios/src/palacios/vmm_xml.c @@ -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 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,35 +338,6 @@ static int v3_xml_close_tag(struct v3_xml_root * root, char * name, char * s) { return 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; - } - } -} - - // frees a tag attribute list static void v3_xml_free_attr(char **attr) { @@ -410,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); } @@ -431,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;", "<", "gt;", ">", "quot;", """, - "apos;", "'", "amp;", "&", 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; @@ -442,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; @@ -535,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; @@ -573,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; @@ -620,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) @@ -654,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 @@ -672,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 "="); @@ -696,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], ' '); } } @@ -795,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 != '<')) { @@ -833,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); @@ -843,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; @@ -855,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, "&"); break; + case '<': *dlen += sprintf(*dst + *dlen, "<"); break; + case '>': *dlen += sprintf(*dst + *dlen, ">"); break; + case '"': *dlen += sprintf(*dst + *dlen, (a) ? """ : "\""); break; + case '\n': *dlen += sprintf(*dst + *dlen, (a) ? " " : "\n"); break; + case '\t': *dlen += sprintf(*dst + *dlen, (a) ? " " : "\t"); break; + case '\r': *dlen += sprintf(*dst + *dlen, " "); 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, "", 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); +}