/* * File loading code for mini-XML, a small XML-like file parsing library. * * Copyright 2003 by Michael Sweet. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Contents: * * stp_mxmlLoadFile() - Load a file into an XML node tree. * stp_mxmlLoadFromFile() - Load a file into an XML node tree. * stp_mxmlLoadString() - Load a string into an XML node tree. * stp_mxmlSaveAllocString() - Save an XML node tree to an allocated string. * stp_mxmlSaveFile() - Save an XML tree to a file. * stp_mxmlSaveString() - Save an XML node tree to a string. * mxml_add_char() - Add a character to a buffer, expanding as needed. * mxml_file_getc() - Get a character from a file. * mxml_load_data() - Load data into an XML node tree. * mxml_parse_element() - Parse an element for any attributes... * mxml_string_getc() - Get a character from a string. * mxml_write_node() - Save an XML node to a file. * mxml_write_string() - Write a string, escaping & and < as needed. * mxml_write_ws() - Do whitespace callback... */ /* * Include necessary headers... */ #include #include "config.h" #define MXML_BUFSIZE (64) #define ENTITY_BUFSIZE (64) /* * Local functions... */ static int mxml_add_char(int ch, char **ptr, char **buffer, int *bufsize); static int mxml_file_getc(void *p); static int mxml_file_putc(int ch, void *p); static stp_mxml_node_t *mxml_load_data(stp_mxml_node_t *top, void *p, stp_mxml_type_t (*cb)(stp_mxml_node_t *), int (*getc_cb)(void *)); static int mxml_parse_element(stp_mxml_node_t *node, void *p, int (*getc_cb)(void *)); static int mxml_string_getc(void *p); static int mxml_string_putc(int ch, void *p); static int mxml_write_node(stp_mxml_node_t *node, void *p, int (*cb)(stp_mxml_node_t *, int), int col, int (*putc_cb)(int, void *)); static int mxml_write_string(const char *s, void *p, int (*putc_cb)(int, void *)); static int mxml_write_ws(stp_mxml_node_t *node, void *p, int (*cb)(stp_mxml_node_t *, int), int ws, int col, int (*putc_cb)(int, void *)); /* * 'stp_mxmlLoadFile()' - Load a file into an XML node tree. * * The nodes in the specified file are added to the specified top node. * If no top node is provided, the XML file MUST be well-formed with a * single parent node like for the entire file. The callback * function returns the value type that should be used for child nodes. * If STP_MXML_NO_CALLBACK is specified then all child nodes will be either * STP_MXML_ELEMENT or STP_MXML_TEXT nodes. */ stp_mxml_node_t * /* O - First node or NULL if the file could not be read. */ stp_mxmlLoadFile(stp_mxml_node_t *top, /* I - Top node */ FILE *fp, /* I - File to read from */ stp_mxml_type_t (*cb)(stp_mxml_node_t *)) /* I - Callback function or STP_MXML_NO_CALLBACK */ { return (mxml_load_data(top, fp, cb, mxml_file_getc)); } /* * 'stp_mxmlLoadFromFile()' - Load a named file into an XML node tree. * * The nodes in the specified file are added to the specified top node. * If no top node is provided, the XML file MUST be well-formed with a * single parent node like for the entire file. The callback * function returns the value type that should be used for child nodes. * If STP_MXML_NO_CALLBACK is specified then all child nodes will be either * STP_MXML_ELEMENT or STP_MXML_TEXT nodes. */ stp_mxml_node_t * /* O - First node or NULL if the file could not be read. */ stp_mxmlLoadFromFile(stp_mxml_node_t *top, /* I - Top node */ const char *file, /* I - File to read from */ stp_mxml_type_t (*cb)(stp_mxml_node_t *)) /* I - Callback function or STP_MXML_NO_CALLBACK */ { FILE *fp = fopen(file, "r"); stp_mxml_node_t *doc; if (! fp) return NULL; doc = stp_mxmlLoadFile(top, fp, cb); fclose(fp); return doc; } /* * 'stp_mxmlLoadString()' - Load a string into an XML node tree. * * The nodes in the specified string are added to the specified top node. * If no top node is provided, the XML string MUST be well-formed with a * single parent node like for the entire string. The callback * function returns the value type that should be used for child nodes. * If STP_MXML_NO_CALLBACK is specified then all child nodes will be either * STP_MXML_ELEMENT or STP_MXML_TEXT nodes. */ stp_mxml_node_t * /* O - First node or NULL if the string has errors. */ stp_mxmlLoadString(stp_mxml_node_t *top, /* I - Top node */ const char *s, /* I - String to load */ stp_mxml_type_t (*cb)(stp_mxml_node_t *)) /* I - Callback function or STP_MXML_NO_CALLBACK */ { return (mxml_load_data(top, &s, cb, mxml_string_getc)); } /* * 'stp_mxmlSaveAllocString()' - Save an XML node tree to an allocated string. * * This function returns a pointer to a string containing the textual * representation of the XML node tree. The string should be freed * using the free() function when you are done with it. NULL is returned * if the node would produce an empty string or if the string cannot be * allocated. */ char * /* O - Allocated string or NULL */ stp_mxmlSaveAllocString(stp_mxml_node_t *node, /* I - Node to write */ int (*cb)(stp_mxml_node_t *, int)) /* I - Whitespace callback or STP_MXML_NO_CALLBACK */ { int bytes; /* Required bytes */ char buffer[8192]; /* Temporary buffer */ char *s; /* Allocated string */ /* * Write the node to the temporary buffer... */ bytes = stp_mxmlSaveString(node, buffer, sizeof(buffer), cb); if (bytes <= 0) return (NULL); if (bytes < (int)(sizeof(buffer) - 1)) { /* * Node fit inside the buffer, so just duplicate that string and * return... */ return (strdup(buffer)); } /* * Allocate a buffer of the required size and save the node to the * new buffer... */ if ((s = malloc(bytes + 1)) == NULL) return (NULL); stp_mxmlSaveString(node, s, bytes + 1, cb); /* * Return the allocated string... */ return (s); } /* * 'stp_mxmlSaveFile()' - Save an XML tree to a file. * * The callback argument specifies a function that returns a whitespace * character or nul (0) before and after each element. If STP_MXML_NO_CALLBACK * is specified, whitespace will only be added before STP_MXML_TEXT nodes * with leading whitespace and before attribute names inside opening * element tags. */ int /* O - 0 on success, -1 on error. */ stp_mxmlSaveFile(stp_mxml_node_t *node, /* I - Node to write */ FILE *fp, /* I - File to write to */ int (*cb)(stp_mxml_node_t *, int)) /* I - Whitespace callback or STP_MXML_NO_CALLBACK */ { int col; /* Final column */ /* * Write the node... */ if ((col = mxml_write_node(node, fp, cb, 0, mxml_file_putc)) < 0) return (-1); if (col > 0) if (putc('\n', fp) < 0) return (-1); /* * Return 0 (success)... */ return (0); } int /* O - 0 on success, -1 on error. */ stp_mxmlSaveToFile(stp_mxml_node_t *node, /* I - Node to write */ const char *file, /* I - File to write to */ int (*cb)(stp_mxml_node_t *, int)) /* I - Whitespace callback or STP_MXML_NO_CALLBACK */ { FILE *fp = fopen(file, "w"); int answer; int status; if (!fp) return -1; answer = stp_mxmlSaveFile(node, fp, cb); status = fclose(fp); if (status != 0) return -1; else return answer; } /* * 'stp_mxmlSaveString()' - Save an XML node tree to a string. * * This function returns the total number of bytes that would be * required for the string but only copies (bufsize - 1) characters * into the specified buffer. */ int /* O - Size of string */ stp_mxmlSaveString(stp_mxml_node_t *node, /* I - Node to write */ char *buffer, /* I - String buffer */ int bufsize, /* I - Size of string buffer */ int (*cb)(stp_mxml_node_t *, int)) /* I - Whitespace callback or STP_MXML_NO_CALLBACK */ { int col; /* Final column */ char *ptr[2]; /* Pointers for putc_cb */ /* * Write the node... */ ptr[0] = buffer; ptr[1] = buffer + bufsize; if ((col = mxml_write_node(node, ptr, cb, 0, mxml_string_putc)) < 0) return (-1); if (col > 0) mxml_string_putc('\n', ptr); /* * Nul-terminate the buffer... */ if (ptr[0] >= ptr[1]) buffer[bufsize - 1] = '\0'; else ptr[0][0] = '\0'; /* * Return the number of characters... */ return (ptr[0] - buffer); } /* * 'mxml_add_char()' - Add a character to a buffer, expanding as needed. */ static int /* O - 0 on success, -1 on error */ mxml_add_char(int ch, /* I - Character to add */ char **bufptr, /* IO - Current position in buffer */ char **buffer, /* IO - Current buffer */ int *bufsize) /* IO - Current buffer size */ { char *newbuffer; /* New buffer value */ if (*bufptr >= (*buffer + *bufsize - 1)) { /* * Increase the size of the buffer... */ if (*bufsize < 1024) (*bufsize) *= 2; else (*bufsize) += 1024; if ((newbuffer = realloc(*buffer, *bufsize)) == NULL) { free(*buffer); fprintf(stderr, "Unable to expand string buffer to %d bytes!\n", *bufsize); return (-1); } *bufptr = newbuffer + (*bufptr - *buffer); *buffer = newbuffer; } *(*bufptr)++ = ch; return (0); } /* * 'mxml_file_getc()' - Get a character from a file. */ static int /* O - Character or EOF */ mxml_file_getc(void *p) /* I - Pointer to file */ { return (getc((FILE *)p)); } /* * 'mxml_file_putc()' - Write a character to a file. */ static int /* O - 0 on success, -1 on failure */ mxml_file_putc(int ch, /* I - Character to write */ void *p) /* I - Pointer to file */ { return (putc(ch, (FILE *)p)); } /* * 'mxml_load_data()' - Load data into an XML node tree. */ static stp_mxml_node_t * /* O - First node or NULL if the file could not be read. */ mxml_load_data(stp_mxml_node_t *top, /* I - Top node */ void *p, /* I - Pointer to data */ stp_mxml_type_t (*cb)(stp_mxml_node_t *), /* I - Callback function or STP_MXML_NO_CALLBACK */ int (*getc_cb)(void *)) /* I - Read function */ { stp_mxml_node_t *node, /* Current node */ *parent; /* Current parent node */ int ch, /* Character from file */ whitespace; /* Non-zero if whitespace seen */ char *buffer, /* String buffer */ *bufptr; /* Pointer into buffer */ int bufsize; /* Size of buffer */ stp_mxml_type_t type; /* Current node type */ /* * Read elements and other nodes from the file... */ if ((buffer = malloc(MXML_BUFSIZE)) == NULL) { fputs("Unable to allocate string buffer!\n", stderr); return (NULL); } bufsize = MXML_BUFSIZE; bufptr = buffer; parent = top; whitespace = 0; if (cb && parent) type = (*cb)(parent); else type = STP_MXML_TEXT; while ((ch = (*getc_cb)(p)) != EOF) { if ((ch == '<' || (isspace(ch) && type != STP_MXML_OPAQUE)) && bufptr > buffer) { /* * Add a new value node... */ *bufptr = '\0'; switch (type) { case STP_MXML_INTEGER : node = stp_mxmlNewInteger(parent, strtol(buffer, &bufptr, 0)); break; case STP_MXML_OPAQUE : node = stp_mxmlNewOpaque(parent, buffer); break; case STP_MXML_REAL : node = stp_mxmlNewReal(parent, strtod(buffer, &bufptr)); break; case STP_MXML_DIMENSION : node = stp_mxmlNewDimension(parent, strtod(buffer, &bufptr)); break; case STP_MXML_TEXT : node = stp_mxmlNewText(parent, whitespace, buffer); break; default : /* Should never happen... */ node = NULL; break; } if (*bufptr) { /* * Bad integer/real number value... */ fprintf(stderr, "Bad %s value '%s' in parent <%s>!\n", type == STP_MXML_INTEGER ? "integer" : "real", buffer, parent ? parent->value.element.name : "null"); break; } bufptr = buffer; whitespace = isspace(ch) && type == STP_MXML_TEXT; if (!node) { /* * Just print error for now... */ fprintf(stderr, "Unable to add value node of type %d to parent <%s>!\n", type, parent ? parent->value.element.name : "null"); break; } } else if (isspace(ch) && type == STP_MXML_TEXT) whitespace = 1; /* * Add lone whitespace node if we have an element and existing * whitespace... */ if (ch == '<' && whitespace && type == STP_MXML_TEXT) { stp_mxmlNewText(parent, whitespace, ""); whitespace = 0; } if (ch == '<') { /* * Start of open/close tag... */ bufptr = buffer; while ((ch = (*getc_cb)(p)) != EOF) if (isspace(ch) || ch == '>' || (ch == '/' && bufptr > buffer)) break; else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) { return (NULL); } else if ((bufptr - buffer) == 3 && !strncmp(buffer, "!--", 3)) break; *bufptr = '\0'; if (!strcmp(buffer, "!--")) { /* * Gather rest of comment... */ while ((ch = (*getc_cb)(p)) != EOF) { if (ch == '>' && bufptr > (buffer + 4) && !strncmp(bufptr - 2, "--", 2)) break; else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) { return (NULL); } } /* * Error out if we didn't get the whole comment... */ if (ch != '>') break; /* * Otherwise add this as an element under the current parent... */ *bufptr = '\0'; if (!stp_mxmlNewElement(parent, buffer)) { /* * Just print error for now... */ fprintf(stderr, "Unable to add comment node to parent <%s>!\n", parent ? parent->value.element.name : "null"); break; } } else if (buffer[0] == '!') { /* * Gather rest of declaration... */ do { if (ch == '>') break; else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) { return (NULL); } } while ((ch = (*getc_cb)(p)) != EOF); /* * Error out if we didn't get the whole declaration... */ if (ch != '>') break; /* * Otherwise add this as an element under the current parent... */ *bufptr = '\0'; node = stp_mxmlNewElement(parent, buffer); if (!node) { /* * Just print error for now... */ fprintf(stderr, "Unable to add declaration node to parent <%s>!\n", parent ? parent->value.element.name : "null"); break; } /* * Descend into this node, setting the value type as needed... */ parent = node; if (cb && parent) type = (*cb)(parent); } else if (buffer[0] == '/') { /* * Handle close tag... */ if (!parent || strcmp(buffer + 1, parent->value.element.name)) { /* * Close tag doesn't match tree; print an error for now... */ fprintf(stderr, "Mismatched close tag <%s> under parent <%s>!\n", buffer, parent ? parent->value.element.name : "(null)"); break; } /* * Keep reading until we see >... */ while (ch != '>' && ch != EOF) ch = (*getc_cb)(p); /* * Ascend into the parent and set the value type as needed... */ parent = parent->parent; if (cb && parent) type = (*cb)(parent); } else { /* * Handle open tag... */ node = stp_mxmlNewElement(parent, buffer); if (!node) { /* * Just print error for now... */ fprintf(stderr, "Unable to add element node to parent <%s>!\n", parent ? parent->value.element.name : "null"); break; } if (isspace(ch)) ch = mxml_parse_element(node, p, getc_cb); else if (ch == '/') { if ((ch = (*getc_cb)(p)) != '>') { fprintf(stderr, "Expected > but got '%c' instead for element <%s/>!\n", ch, buffer); break; } ch = '/'; } if (ch == EOF) break; if (ch != '/') { /* * Descend into this node, setting the value type as needed... */ parent = node; if (cb && parent) type = (*cb)(parent); } } bufptr = buffer; } else if (ch == '&') { /* * Add character entity to current buffer... Currently we only * support <, &, >,  , ", &#nnn;, and &#xXXXX;... */ char entity[ENTITY_BUFSIZE], /* Entity string */ *entptr; /* Pointer into entity */ entity[0] = ch; entptr = entity + 1; while ((ch = (*getc_cb)(p)) != EOF) if (!isalnum(ch) && ch != '#') break; else if (entptr < (entity + sizeof(entity) - 1)) *entptr++ = ch; else { fprintf(stderr, "Entity name too long under parent <%s>!\n", parent ? parent->value.element.name : "null"); break; } *entptr = '\0'; if (ch != ';') { fprintf(stderr, "Entity name \"%s\" not terminated under parent <%s>!\n", entity, parent ? parent->value.element.name : "null"); break; } if (entity[1] == '#') { if (entity[2] == 'x') ch = strtol(entity + 3, NULL, 16); else ch = strtol(entity + 2, NULL, 10); } else if (!strcmp(entity, "&")) ch = '&'; else if (!strcmp(entity, ">")) ch = '>'; else if (!strcmp(entity, "<")) ch = '<'; else if (!strcmp(entity, " ")) ch = 0xa0; else if (!strcmp(entity, """)) ch = '\"'; else { fprintf(stderr, "Entity name \"%s;\" not supported under parent <%s>!\n", entity, parent ? parent->value.element.name : "null"); break; } if (ch < 128) { /* * Plain ASCII doesn't need special encoding... */ if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) { return (NULL); } } else { /* * Use UTF-8 encoding for the Unicode char... */ if (ch < 2048) { if (mxml_add_char(0xc0 | (ch >> 6), &bufptr, &buffer, &bufsize)) { return (NULL); } if (mxml_add_char(0x80 | (ch & 63), &bufptr, &buffer, &bufsize)) { return (NULL); } } else if (ch < 65536) { if (mxml_add_char(0xe0 | (ch >> 12), &bufptr, &buffer, &bufsize)) { return (NULL); } if (mxml_add_char(0x80 | ((ch >> 6) & 63), &bufptr, &buffer, &bufsize)) { return (NULL); } if (mxml_add_char(0x80 | (ch & 63), &bufptr, &buffer, &bufsize)) { return (NULL); } } else { if (mxml_add_char(0xf0 | (ch >> 18), &bufptr, &buffer, &bufsize)) { return (NULL); } if (mxml_add_char(0x80 | ((ch >> 12) & 63), &bufptr, &buffer, &bufsize)) { return (NULL); } if (mxml_add_char(0x80 | ((ch >> 6) & 63), &bufptr, &buffer, &bufsize)) { return (NULL); } if (mxml_add_char(0x80 | (ch & 63), &bufptr, &buffer, &bufsize)) { return (NULL); } } } } else if (type == STP_MXML_OPAQUE || !isspace(ch)) { /* * Add character to current buffer... */ if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) { return (NULL); } } } /* * Free the string buffer - we don't need it anymore... */ free(buffer); /* * Find the top element and return it... */ if (parent) { while (parent->parent != top) parent = parent->parent; } return (parent); } /* * 'mxml_parse_element()' - Parse an element for any attributes... */ static int /* O - Terminating character */ mxml_parse_element(stp_mxml_node_t *node, /* I - Element node */ void *p, /* I - Data to read from */ int (*getc_cb)(void *)) /* I - Data callback */ { int ch, /* Current character in file */ quote; /* Quoting character */ char *name, /* Attribute name */ *value, /* Attribute value */ *ptr; /* Pointer into name/value */ int namesize, /* Size of name string */ valsize; /* Size of value string */ /* * Initialize the name and value buffers... */ if ((name = malloc(MXML_BUFSIZE)) == NULL) { fputs("Unable to allocate memory for name!\n", stderr); return (EOF); } namesize = MXML_BUFSIZE; if ((value = malloc(MXML_BUFSIZE)) == NULL) { free(name); fputs("Unable to allocate memory for value!\n", stderr); return (EOF); } valsize = MXML_BUFSIZE; /* * Loop until we hit a >, /, ?, or EOF... */ while ((ch = (*getc_cb)(p)) != EOF) { #ifdef DEBUG fprintf(stderr, "parse_element: ch='%c'\n", ch); #endif /* DEBUG */ /* * Skip leading whitespace... */ if (isspace(ch)) continue; /* * Stop at /, ?, or >... */ if (ch == '/' || ch == '?') { /* * Grab the > character and print an error if it isn't there... */ quote = (*getc_cb)(p); if (quote != '>') { fprintf(stderr, "Expected '>' after '%c' for element %s, but got '%c'!\n", ch, node->value.element.name, quote); ch = EOF; } break; } else if (ch == '>') break; /* * Read the attribute name... */ name[0] = ch; ptr = name + 1; while ((ch = (*getc_cb)(p)) != EOF) if (isspace(ch) || ch == '=' || ch == '/' || ch == '>' || ch == '?') break; else if (mxml_add_char(ch, &ptr, &name, &namesize)) { free(value); return (EOF); } *ptr = '\0'; if (ch == '=') { /* * Read the attribute value... */ if ((ch = (*getc_cb)(p)) == EOF) { fprintf(stderr, "Missing value for attribute '%s' in element %s!\n", name, node->value.element.name); free(name); free(value); return (EOF); } if (ch == '\'' || ch == '\"') { /* * Read quoted value... */ quote = ch; ptr = value; while ((ch = (*getc_cb)(p)) != EOF) if (ch == quote) break; else if (mxml_add_char(ch, &ptr, &value, &valsize)) { free(name); return (EOF); } *ptr = '\0'; } else { /* * Read unquoted value... */ value[0] = ch; ptr = value + 1; while ((ch = (*getc_cb)(p)) != EOF) if (isspace(ch) || ch == '=' || ch == '/' || ch == '>') break; else if (mxml_add_char(ch, &ptr, &value, &valsize)) { free(name); return (EOF); } *ptr = '\0'; } } else value[0] = '\0'; /* * Save last character in case we need it... */ if (ch == '/' || ch == '?') { /* * Grab the > character and print an error if it isn't there... */ quote = (*getc_cb)(p); if (quote != '>') { fprintf(stderr, "Expected '>' after '%c' for element %s, but got '%c'!\n", ch, node->value.element.name, quote); ch = EOF; } break; } else if (ch == '>') break; /* * Set the attribute... */ stp_mxmlElementSetAttr(node, name, value); } /* * Free the name and value buffers and return... */ free(name); free(value); return (ch); } /* * 'mxml_string_getc()' - Get a character from a string. */ static int /* O - Character or EOF */ mxml_string_getc(void *p) /* I - Pointer to file */ { int ch; /* Character */ const char **s; /* Pointer to string pointer */ s = (const char **)p; if ((ch = *s[0]) != 0) { (*s)++; return (ch); } else return (EOF); } /* * 'mxml_string_putc()' - Write a character to a string. */ static int /* O - 0 on success, -1 on failure */ mxml_string_putc(int ch, /* I - Character to write */ void *p) /* I - Pointer to string pointers */ { char **pp; /* Pointer to string pointers */ pp = (char **)p; if (pp[0] < pp[1]) pp[0][0] = ch; pp[0] ++; return (0); } /* * 'mxml_write_node()' - Save an XML node to a file. */ static int /* O - Column or -1 on error */ mxml_write_node(stp_mxml_node_t *node, /* I - Node to write */ void *p, /* I - File to write to */ int (*cb)(stp_mxml_node_t *, int), /* I - Whitespace callback */ int col, /* I - Current column */ int (*putc_cb)(int, void *)) { int i; /* Looping var */ stp_mxml_attr_t *attr; /* Current attribute */ char s[255]; /* Temporary string */ while (node != NULL) { /* * Print the node value... */ switch (node->type) { case STP_MXML_ELEMENT : col = mxml_write_ws(node, p, cb, STP_MXML_WS_BEFORE_OPEN, col, putc_cb); if ((*putc_cb)('<', p) < 0) return (-1); if (mxml_write_string(node->value.element.name, p, putc_cb) < 0) return (-1); col += strlen(node->value.element.name) + 1; for (i = node->value.element.num_attrs, attr = node->value.element.attrs; i > 0; i --, attr ++) { if ((col + strlen(attr->name) + strlen(attr->value) + 3) > STP_MXML_WRAP) { if ((*putc_cb)('\n', p) < 0) return (-1); col = 0; } else { if ((*putc_cb)(' ', p) < 0) return (-1); col ++; } if (mxml_write_string(attr->name, p, putc_cb) < 0) return (-1); if ((*putc_cb)('=', p) < 0) return (-1); if ((*putc_cb)('\"', p) < 0) return (-1); if (mxml_write_string(attr->value, p, putc_cb) < 0) return (-1); if ((*putc_cb)('\"', p) < 0) return (-1); col += strlen(attr->name) + strlen(attr->value) + 3; } if (node->child) { /* * The ? and ! elements are special-cases and have no end tags... */ if (node->value.element.name[0] == '?') { if ((*putc_cb)('?', p) < 0) return (-1); if ((*putc_cb)('>', p) < 0) return (-1); if ((*putc_cb)('\n', p) < 0) return (-1); col = 0; } else if ((*putc_cb)('>', p) < 0) return (-1); else col ++; col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_OPEN, col, putc_cb); if ((col = mxml_write_node(node->child, p, cb, col, putc_cb)) < 0) return (-1); if (node->value.element.name[0] != '?' && node->value.element.name[0] != '!') { col = mxml_write_ws(node, p, cb, STP_MXML_WS_BEFORE_CLOSE, col, putc_cb); if ((*putc_cb)('<', p) < 0) return (-1); if ((*putc_cb)('/', p) < 0) return (-1); if (mxml_write_string(node->value.element.name, p, putc_cb) < 0) return (-1); if ((*putc_cb)('>', p) < 0) return (-1); col += strlen(node->value.element.name) + 3; col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_CLOSE, col, putc_cb); } } else if (node->value.element.name[0] == '!') { if ((*putc_cb)('>', p) < 0) return (-1); else col ++; col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_OPEN, col, putc_cb); } else { if ((*putc_cb)('/', p) < 0) return (-1); if ((*putc_cb)('>', p) < 0) return (-1); col += 2; col = mxml_write_ws(node, p, cb, STP_MXML_WS_AFTER_OPEN, col, putc_cb); } break; case STP_MXML_INTEGER : if (node->prev) { if (col > STP_MXML_WRAP) { if ((*putc_cb)('\n', p) < 0) return (-1); col = 0; } else if ((*putc_cb)(' ', p) < 0) return (-1); else col ++; } sprintf(s, "%d", node->value.integer); if (mxml_write_string(s, p, putc_cb) < 0) return (-1); col += strlen(s); break; case STP_MXML_OPAQUE : if (mxml_write_string(node->value.opaque, p, putc_cb) < 0) return (-1); col += strlen(node->value.opaque); break; case STP_MXML_REAL : case STP_MXML_DIMENSION : if (node->prev) { if (col > STP_MXML_WRAP) { if ((*putc_cb)('\n', p) < 0) return (-1); col = 0; } else if ((*putc_cb)(' ', p) < 0) return (-1); else col ++; } sprintf(s, "%f", node->value.real); if (mxml_write_string(s, p, putc_cb) < 0) return (-1); col += strlen(s); break; case STP_MXML_TEXT : if (node->value.text.whitespace && col > 0) { if (col > STP_MXML_WRAP) { if ((*putc_cb)('\n', p) < 0) return (-1); col = 0; } else if ((*putc_cb)(' ', p) < 0) return (-1); else col ++; } if (mxml_write_string(node->value.text.string, p, putc_cb) < 0) return (-1); col += strlen(node->value.text.string); break; } /* * Next node... */ node = node->next; } return (col); } /* * 'mxml_write_string()' - Write a string, escaping & and < as needed. */ static int /* O - 0 on success, -1 on failure */ mxml_write_string(const char *s, /* I - String to write */ void *p, /* I - Write pointer */ int (*putc_cb)(int, void *)) /* I - Write callback */ { char buf[255], /* Buffer */ *bufptr; /* Pointer into buffer */ while (*s) { if (*s == '&') { if ((*putc_cb)('&', p) < 0) return (-1); if ((*putc_cb)('a', p) < 0) return (-1); if ((*putc_cb)('m', p) < 0) return (-1); if ((*putc_cb)('p', p) < 0) return (-1); if ((*putc_cb)(';', p) < 0) return (-1); } else if (*s == '<') { if ((*putc_cb)('&', p) < 0) return (-1); if ((*putc_cb)('l', p) < 0) return (-1); if ((*putc_cb)('t', p) < 0) return (-1); if ((*putc_cb)(';', p) < 0) return (-1); } else if (*s == '>') { if ((*putc_cb)('&', p) < 0) return (-1); if ((*putc_cb)('g', p) < 0) return (-1); if ((*putc_cb)('t', p) < 0) return (-1); if ((*putc_cb)(';', p) < 0) return (-1); } else if (*s == '\"') { if ((*putc_cb)('&', p) < 0) return (-1); if ((*putc_cb)('q', p) < 0) return (-1); if ((*putc_cb)('u', p) < 0) return (-1); if ((*putc_cb)('o', p) < 0) return (-1); if ((*putc_cb)('t', p) < 0) return (-1); if ((*putc_cb)(';', p) < 0) return (-1); } else if (*s & 128) { /* * Convert UTF-8 to Unicode constant... */ int ch; /* Unicode character */ ch = *s & 255; if ((ch & 0xe0) == 0xc0) { ch = ((ch & 0x1f) << 6) | (s[1] & 0x3f); s ++; } else if ((ch & 0xf0) == 0xe0) { ch = ((((ch * 0x0f) << 6) | (s[1] & 0x3f)) << 6) | (s[2] & 0x3f); s += 2; } if (ch == 0xa0) { /* * Handle non-breaking space as-is... */ if ((*putc_cb)('&', p) < 0) return (-1); if ((*putc_cb)('n', p) < 0) return (-1); if ((*putc_cb)('b', p) < 0) return (-1); if ((*putc_cb)('s', p) < 0) return (-1); if ((*putc_cb)('p', p) < 0) return (-1); if ((*putc_cb)(';', p) < 0) return (-1); } else { sprintf(buf, "&#x%x;", ch); for (bufptr = buf; *bufptr; bufptr ++) if ((*putc_cb)(*bufptr, p) < 0) return (-1); } } else if ((*putc_cb)(*s, p) < 0) return (-1); s ++; } return (0); } /* * 'mxml_write_ws()' - Do whitespace callback... */ static int /* O - New column */ mxml_write_ws(stp_mxml_node_t *node, /* I - Current node */ void *p, /* I - Write pointer */ int (*cb)(stp_mxml_node_t *, int), /* I - Callback function */ int ws, /* I - Where value */ int col, /* I - Current column */ int (*putc_cb)(int, void *)) /* I - Write callback */ { int ch; /* Whitespace character */ if (cb && (ch = (*cb)(node, ws)) != 0) { if ((*putc_cb)(ch, p) < 0) return (-1); else if (ch == '\n') col = 0; else if (ch == '\t') { col += STP_MXML_TAB; col = col - (col % STP_MXML_TAB); } else col ++; } return (col); }