// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** xmlfile.c XML file parsing code. ***************************************************************************/ #include #include "xmlfile.h" #include #include /*************************************************************************** CONSTANTS ***************************************************************************/ #define TEMP_BUFFER_SIZE 4096 /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ struct xml_parse_info { XML_Parser parser; xml_data_node * rootnode; xml_data_node * curnode; UINT32 flags; }; /*************************************************************************** PROTOTYPES ***************************************************************************/ /* expat interfaces */ static int expat_setup_parser(xml_parse_info *parse_info, xml_parse_options *opts); static void expat_element_start(void *data, const XML_Char *name, const XML_Char **attributes); static void expat_data(void *data, const XML_Char *s, int len); static void expat_element_end(void *data, const XML_Char *name); /* node/attributes additions */ static xml_data_node *add_child(xml_data_node *parent, const char *name, const char *value); static xml_attribute_node *add_attribute(xml_data_node *node, const char *name, const char *value); /* recursive tree operations */ static void write_node_recursive(xml_data_node *node, int indent, util::core_file &file); static void free_node_recursive(xml_data_node *node); /*************************************************************************** CORE IMPLEMENTATION ***************************************************************************/ /*------------------------------------------------- copystring - make an allocated copy of a string -------------------------------------------------*/ static const char *copystring(const char *input) { char *newstr; /* nullptr just passes through */ if (input == nullptr) return nullptr; /* make a lower-case copy if the allocation worked */ newstr = (char *)malloc(strlen(input) + 1); if (newstr != nullptr) strcpy(newstr, input); return newstr; } /*------------------------------------------------- copystring_lower - make an allocated copy of a string and convert it to lowercase along the way -------------------------------------------------*/ static const char *copystring_lower(const char *input) { char *newstr; int i; /* nullptr just passes through */ if (input == nullptr) return nullptr; /* make a lower-case copy if the allocation worked */ newstr = (char *)malloc(strlen(input) + 1); if (newstr != nullptr) { for (i = 0; input[i] != 0; i++) newstr[i] = tolower((UINT8)input[i]); newstr[i] = 0; } return newstr; } /*************************************************************************** XML FILE OBJECTS ***************************************************************************/ /*------------------------------------------------- xml_file_create - create a new xml file object -------------------------------------------------*/ xml_data_node *xml_file_create(void) { xml_data_node *rootnode; /* create a root node */ rootnode = (xml_data_node *)malloc(sizeof(*rootnode)); if (rootnode == nullptr) return nullptr; memset(rootnode, 0, sizeof(*rootnode)); return rootnode; } /*------------------------------------------------- xml_file_read - parse an XML file into its nodes -------------------------------------------------*/ xml_data_node *xml_file_read(util::core_file &file, xml_parse_options *opts) { xml_parse_info parse_info; int done; /* set up the parser */ if (!expat_setup_parser(&parse_info, opts)) return nullptr; /* loop through the file and parse it */ do { char tempbuf[TEMP_BUFFER_SIZE]; /* read as much as we can */ int bytes = file.read(tempbuf, sizeof(tempbuf)); done = file.eof(); /* parse the data */ if (XML_Parse(parse_info.parser, tempbuf, bytes, done) == XML_STATUS_ERROR) { if (opts != nullptr && opts->error != nullptr) { opts->error->error_message = XML_ErrorString(XML_GetErrorCode(parse_info.parser)); opts->error->error_line = XML_GetCurrentLineNumber(parse_info.parser); opts->error->error_column = XML_GetCurrentColumnNumber(parse_info.parser); } xml_file_free(parse_info.rootnode); XML_ParserFree(parse_info.parser); return nullptr; } } while (!done); /* free the parser */ XML_ParserFree(parse_info.parser); /* return the root node */ return parse_info.rootnode; } /*------------------------------------------------- xml_string_read - parse an XML string into its nodes -------------------------------------------------*/ xml_data_node *xml_string_read(const char *string, xml_parse_options *opts) { xml_parse_info parse_info; int length = (int)strlen(string); /* set up the parser */ if (!expat_setup_parser(&parse_info, opts)) return nullptr; /* parse the data */ if (XML_Parse(parse_info.parser, string, length, TRUE) == XML_STATUS_ERROR) { if (opts != nullptr && opts->error != nullptr) { opts->error->error_message = XML_ErrorString(XML_GetErrorCode(parse_info.parser)); opts->error->error_line = XML_GetCurrentLineNumber(parse_info.parser); opts->error->error_column = XML_GetCurrentColumnNumber(parse_info.parser); } xml_file_free(parse_info.rootnode); XML_ParserFree(parse_info.parser); return nullptr; } /* free the parser */ XML_ParserFree(parse_info.parser); /* return the root node */ return parse_info.rootnode; } /*------------------------------------------------- xml_file_write - write an XML tree to a file -------------------------------------------------*/ void xml_file_write(xml_data_node *node, util::core_file &file) { /* ensure this is a root node */ if (node->name != nullptr) return; /* output a simple header */ file.printf("\n"); file.printf("\n"); /* loop over children of the root and output */ for (node = node->child; node; node = node->next) write_node_recursive(node, 0, file); } /*------------------------------------------------- xml_file_free - free an XML file object -------------------------------------------------*/ void xml_file_free(xml_data_node *node) { /* ensure this is a root node */ if (node->name != nullptr) return; free_node_recursive(node); } /*************************************************************************** XML NODE MANAGEMENT ***************************************************************************/ /*------------------------------------------------- xml_count_children - count the number of child nodes -------------------------------------------------*/ int xml_count_children(xml_data_node *node) { int count = 0; /* loop over children and count */ for (node = node->child; node; node = node->next) count++; return count; } /*------------------------------------------------- xml_get_sibling - find the next sibling of the specified node with the specified tag -------------------------------------------------*/ xml_data_node *xml_get_sibling(xml_data_node *node, const char *name) { /* loop over siblings and find a matching name */ for ( ; node; node = node->next) if (strcmp(node->name, name) == 0) return node; return nullptr; } /*------------------------------------------------- xml_find_matching_sibling - find the next sibling of the specified node with the specified tag or attribute/value pair -------------------------------------------------*/ xml_data_node *xml_find_matching_sibling(xml_data_node *node, const char *name, const char *attribute, const char *matchval) { /* loop over siblings and find a matching attribute */ for ( ; node; node = node->next) { /* can pass nullptr as a wildcard for the node name */ if (name == nullptr || strcmp(name, node->name) == 0) { /* find a matching attribute */ xml_attribute_node *attr = xml_get_attribute(node, attribute); if (attr != nullptr && strcmp(attr->value, matchval) == 0) return node; } } return nullptr; } /*------------------------------------------------- xml_add_child - add a new child node to the given node -------------------------------------------------*/ xml_data_node *xml_add_child(xml_data_node *node, const char *name, const char *value) { /* just a standard add child */ return add_child(node, name, value); } /*------------------------------------------------- xml_get_or_add_child - find a child node of the specified type; if not found, add one -------------------------------------------------*/ xml_data_node *xml_get_or_add_child(xml_data_node *node, const char *name, const char *value) { xml_data_node *child; /* find the child first */ child = xml_get_sibling(node->child, name); if (child != nullptr) return child; /* if not found, do a standard add child */ return add_child(node, name, value); } /*------------------------------------------------- xml_delete_node - delete a node and its children -------------------------------------------------*/ void xml_delete_node(xml_data_node *node) { xml_data_node **pnode; /* first unhook us from the list of children of our parent */ for (pnode = &node->parent->child; *pnode; pnode = &(*pnode)->next) if (*pnode == node) { *pnode = node->next; break; } /* now free ourselves and our children */ free_node_recursive(node); } /*************************************************************************** XML ATTRIBUTE MANAGEMENT ***************************************************************************/ /*------------------------------------------------- xml_get_attribute - get the value of the specified attribute, or nullptr if not found -------------------------------------------------*/ xml_attribute_node *xml_get_attribute(xml_data_node *node, const char *attribute) { xml_attribute_node *anode; /* loop over attributes and find a match */ for (anode = node->attribute; anode; anode = anode->next) if (strcmp(anode->name, attribute) == 0) return anode; return nullptr; } /*------------------------------------------------- xml_get_attribute_string - get the string value of the specified attribute; if not found, return = the provided default -------------------------------------------------*/ const char *xml_get_attribute_string(xml_data_node *node, const char *attribute, const char *defvalue) { xml_attribute_node *attr = xml_get_attribute(node, attribute); return attr ? attr->value : defvalue; } /*------------------------------------------------- xml_get_attribute_int - get the integer value of the specified attribute; if not found, return = the provided default -------------------------------------------------*/ int xml_get_attribute_int(xml_data_node *node, const char *attribute, int defvalue) { const char *string = xml_get_attribute_string(node, attribute, nullptr); int value; unsigned int uvalue; if (string == nullptr) return defvalue; if (string[0] == '$') return (sscanf(&string[1], "%X", &uvalue) == 1) ? uvalue : defvalue; if (string[0] == '0' && string[1] == 'x') return (sscanf(&string[2], "%X", &uvalue) == 1) ? uvalue : defvalue; if (string[0] == '#') return (sscanf(&string[1], "%d", &value) == 1) ? value : defvalue; return (sscanf(&string[0], "%d", &value) == 1) ? value : defvalue; } /*------------------------------------------------- xml_get_attribute_int_format - return the format of the given integer attribute -------------------------------------------------*/ int xml_get_attribute_int_format(xml_data_node *node, const char *attribute) { const char *string = xml_get_attribute_string(node, attribute, nullptr); if (string == nullptr) return XML_INT_FORMAT_DECIMAL; if (string[0] == '$') return XML_INT_FORMAT_HEX_DOLLAR; if (string[0] == '0' && string[1] == 'x') return XML_INT_FORMAT_HEX_C; if (string[0] == '#') return XML_INT_FORMAT_DECIMAL_POUND; return XML_INT_FORMAT_DECIMAL; } /*------------------------------------------------- xml_get_attribute_float - get the float value of the specified attribute; if not found, return = the provided default -------------------------------------------------*/ /** * @fn float xml_get_attribute_float(xml_data_node *node, const char *attribute, float defvalue) * * @brief XML get attribute float. * * @param [in,out] node If non-null, the node. * @param attribute The attribute. * @param defvalue The defvalue. * * @return A float. */ float xml_get_attribute_float(xml_data_node *node, const char *attribute, float defvalue) { const char *string = xml_get_attribute_string(node, attribute, nullptr); float value; if (string == nullptr || sscanf(string, "%f", &value) != 1) return defvalue; return value; } /*------------------------------------------------- xml_set_attribute - set a new attribute and string value on the node -------------------------------------------------*/ /** * @fn xml_attribute_node *xml_set_attribute(xml_data_node *node, const char *name, const char *value) * * @brief XML set attribute. * * @param [in,out] node If non-null, the node. * @param name The name. * @param value The value. * * @return null if it fails, else an xml_attribute_node*. */ xml_attribute_node *xml_set_attribute(xml_data_node *node, const char *name, const char *value) { xml_attribute_node *anode; /* first find an existing one to replace */ anode = xml_get_attribute(node, name); /* if we found it, free the old value and replace it */ if (anode != nullptr) { if (anode->value != nullptr) free((void *)anode->value); anode->value = copystring(value); } /* otherwise, create a new node */ else anode = add_attribute(node, name, value); return anode; } /*------------------------------------------------- xml_set_attribute_int - set a new attribute and integer value on the node -------------------------------------------------*/ /** * @fn xml_attribute_node *xml_set_attribute_int(xml_data_node *node, const char *name, int value) * * @brief XML set attribute int. * * @param [in,out] node If non-null, the node. * @param name The name. * @param value The value. * * @return null if it fails, else an xml_attribute_node*. */ xml_attribute_node *xml_set_attribute_int(xml_data_node *node, const char *name, int value) { char buffer[100]; sprintf(buffer, "%d", value); return xml_set_attribute(node, name, buffer); } /*------------------------------------------------- xml_set_attribute_int - set a new attribute and float value on the node -------------------------------------------------*/ /** * @fn xml_attribute_node *xml_set_attribute_float(xml_data_node *node, const char *name, float value) * * @brief XML set attribute float. * * @param [in,out] node If non-null, the node. * @param name The name. * @param value The value. * * @return null if it fails, else an xml_attribute_node*. */ xml_attribute_node *xml_set_attribute_float(xml_data_node *node, const char *name, float value) { char buffer[100]; sprintf(buffer, "%f", (double) value); return xml_set_attribute(node, name, buffer); } /*************************************************************************** MISCELLANEOUS INTERFACES ***************************************************************************/ /*------------------------------------------------- xml_normalize_string - normalize a string to ensure it doesn't contain embedded tags -------------------------------------------------*/ /** * @fn const char *xml_normalize_string(const char *string) * * @brief XML normalize string. * * @param string The string. * * @return null if it fails, else a char*. */ const char *xml_normalize_string(const char *string) { static char buffer[1024]; char *d = &buffer[0]; if (string != nullptr) { while (*string) { switch (*string) { case '\"' : d += sprintf(d, """); break; case '&' : d += sprintf(d, "&"); break; case '<' : d += sprintf(d, "<"); break; case '>' : d += sprintf(d, ">"); break; default: *d++ = *string; } ++string; } } *d++ = 0; return buffer; } /*************************************************************************** EXPAT INTERFACES ***************************************************************************/ /*------------------------------------------------- expat_malloc/expat_realloc/expat_free - wrappers for memory allocation functions so that they pass through out memory tracking systems -------------------------------------------------*/ /** * @fn static void *expat_malloc(size_t size) * * @brief Expat malloc. * * @param size The size. * * @return null if it fails, else a void*. */ static void *expat_malloc(size_t size) { UINT32 *result = (UINT32 *)malloc(size + 4 * sizeof(UINT32)); *result = size; return &result[4]; } /** * @fn static void expat_free(void *ptr) * * @brief Expat free. * * @param [in,out] ptr If non-null, the pointer. */ static void expat_free(void *ptr) { if (ptr != nullptr) free(&((UINT32 *)ptr)[-4]); } /** * @fn static void *expat_realloc(void *ptr, size_t size) * * @brief Expat realloc. * * @param [in,out] ptr If non-null, the pointer. * @param size The size. * * @return null if it fails, else a void*. */ static void *expat_realloc(void *ptr, size_t size) { void *newptr = expat_malloc(size); if (newptr == nullptr) return nullptr; if (ptr != nullptr) { UINT32 oldsize = ((UINT32 *)ptr)[-4]; memcpy(newptr, ptr, oldsize); expat_free(ptr); } return newptr; } /*------------------------------------------------- expat_setup_parser - set up expat for parsing -------------------------------------------------*/ /** * @fn static int expat_setup_parser(xml_parse_info *parse_info, xml_parse_options *opts) * * @brief Expat setup parser. * * @param [in,out] parse_info If non-null, information describing the parse. * @param [in,out] opts If non-null, options for controlling the operation. * * @return An int. */ static int expat_setup_parser(xml_parse_info *parse_info, xml_parse_options *opts) { XML_Memory_Handling_Suite memcallbacks; /* setup parse_info structure */ memset(parse_info, 0, sizeof(*parse_info)); if (opts != nullptr) { parse_info->flags = opts->flags; if (opts->error != nullptr) { opts->error->error_message = nullptr; opts->error->error_line = 0; opts->error->error_column = 0; } } /* create a root node */ parse_info->rootnode = xml_file_create(); if (parse_info->rootnode == nullptr) return FALSE; parse_info->curnode = parse_info->rootnode; /* create the XML parser */ memcallbacks.malloc_fcn = expat_malloc; memcallbacks.realloc_fcn = expat_realloc; memcallbacks.free_fcn = expat_free; parse_info->parser = XML_ParserCreate_MM(nullptr, &memcallbacks, nullptr); if (parse_info->parser == nullptr) { free(parse_info->rootnode); return FALSE; } /* configure the parser */ XML_SetElementHandler(parse_info->parser, expat_element_start, expat_element_end); XML_SetCharacterDataHandler(parse_info->parser, expat_data); XML_SetUserData(parse_info->parser, parse_info); /* optional parser initialization step */ if (opts != nullptr && opts->init_parser != nullptr) (*opts->init_parser)(parse_info->parser); return TRUE; } /*------------------------------------------------- expat_element_start - expat callback for a new element -------------------------------------------------*/ /** * @fn static void expat_element_start(void *data, const XML_Char *name, const XML_Char **attributes) * * @brief Expat element start. * * @param [in,out] data If non-null, the data. * @param name The name. * @param attributes The attributes. */ static void expat_element_start(void *data, const XML_Char *name, const XML_Char **attributes) { xml_parse_info *parse_info = (xml_parse_info *) data; xml_data_node **curnode = &parse_info->curnode; xml_data_node *newnode; int attr; /* add a new child node to the current node */ newnode = add_child(*curnode, name, nullptr); if (newnode == nullptr) return; /* remember the line number */ newnode->line = XML_GetCurrentLineNumber(parse_info->parser); /* add all the attributes as well */ for (attr = 0; attributes[attr]; attr += 2) add_attribute(newnode, attributes[attr+0], attributes[attr+1]); /* set us up as the current node */ *curnode = newnode; } /*------------------------------------------------- expat_data - expat callback for an additional element data -------------------------------------------------*/ /** * @fn static void expat_data(void *data, const XML_Char *s, int len) * * @brief Expat data. * * @param [in,out] data If non-null, the data. * @param s The const XML_Char * to process. * @param len The length. */ static void expat_data(void *data, const XML_Char *s, int len) { xml_parse_info *parse_info = (xml_parse_info *) data; xml_data_node **curnode = &parse_info->curnode; int oldlen = 0; char *newdata; /* if no data, skip */ if (len == 0) return; /* determine how much data we currently have */ if ((*curnode)->value != nullptr) oldlen = (int)strlen((*curnode)->value); /* realloc */ newdata = (char *)malloc(oldlen + len + 1); if (newdata == nullptr) return; if ((*curnode)->value != nullptr) { memcpy(newdata, (*curnode)->value, oldlen); free((void *)(*curnode)->value); } (*curnode)->value = newdata; /* copy in the new data a NULL-terminate */ memcpy(&newdata[oldlen], s, len); newdata[oldlen + len] = 0; (*curnode)->value = newdata; } /*------------------------------------------------- expat_element_end - expat callback for the end of an element -------------------------------------------------*/ /** * @fn static void expat_element_end(void *data, const XML_Char *name) * * @brief Expat element end. * * @param [in,out] data If non-null, the data. * @param name The name. */ static void expat_element_end(void *data, const XML_Char *name) { xml_parse_info *parse_info = (xml_parse_info *) data; xml_data_node **curnode = &parse_info->curnode; char *orig; /* strip leading/trailing spaces from the value data */ orig = (char *)(*curnode)->value; if (orig != nullptr && !(parse_info->flags & XML_PARSE_FLAG_WHITESPACE_SIGNIFICANT)) { char *start = orig; char *end = start + strlen(start); /* first strip leading spaces */ while (*start && isspace((UINT8)*start)) start++; /* then strip trailing spaces */ while (end > start && isspace((UINT8)end[-1])) end--; /* if nothing left, just free it */ if (start == end) { free(orig); (*curnode)->value = nullptr; } /* otherwise, memmove the data */ else { memmove(orig, start, end - start); orig[end - start] = 0; } } /* back us up a node */ *curnode = (*curnode)->parent; } /*************************************************************************** NODE/ATTRIBUTE ADDITIONS ***************************************************************************/ /*------------------------------------------------- add_child - add a new node to the parent -------------------------------------------------*/ /** * @fn static xml_data_node *add_child(xml_data_node *parent, const char *name, const char *value) * * @brief Adds a child. * * @param [in,out] parent If non-null, the parent. * @param name The name. * @param value The value. * * @return null if it fails, else an xml_data_node*. */ static xml_data_node *add_child(xml_data_node *parent, const char *name, const char *value) { xml_data_node **pnode; xml_data_node *node; /* new element: create a new node */ node = (xml_data_node *)malloc(sizeof(*node)); if (node == nullptr) return nullptr; /* initialize the members */ node->next = nullptr; node->parent = parent; node->child = nullptr; node->name = copystring_lower(name); if (node->name == nullptr) { free(node); return nullptr; } node->value = copystring(value); if (node->value == nullptr && value != nullptr) { free((void *)node->name); free(node); return nullptr; } node->attribute = nullptr; /* add us to the end of the list of siblings */ for (pnode = &parent->child; *pnode; pnode = &(*pnode)->next) { } *pnode = node; return node; } /*------------------------------------------------- add_attribute - add a new attribute to the given node -------------------------------------------------*/ /** * @fn static xml_attribute_node *add_attribute(xml_data_node *node, const char *name, const char *value) * * @brief Adds an attribute. * * @param [in,out] node If non-null, the node. * @param name The name. * @param value The value. * * @return null if it fails, else an xml_attribute_node*. */ static xml_attribute_node *add_attribute(xml_data_node *node, const char *name, const char *value) { xml_attribute_node *anode, **panode; /* allocate a new attribute node */ anode = (xml_attribute_node *)malloc(sizeof(*anode)); if (anode == nullptr) return nullptr; /* fill it in */ anode->next = nullptr; anode->name = copystring_lower(name); if (anode->name == nullptr) { free(anode); return nullptr; } anode->value = copystring(value); if (anode->value == nullptr) { free((void *)anode->name); free(anode); return nullptr; } /* add us to the end of the list of attributes */ for (panode = &node->attribute; *panode; panode = &(*panode)->next) { } *panode = anode; return anode; } /*************************************************************************** RECURSIVE TREE OPERATIONS ***************************************************************************/ /*------------------------------------------------- write_node_recursive - recursively write an XML node and its children to a file -------------------------------------------------*/ /** * @fn static void write_node_recursive(xml_data_node *node, int indent, util::core_file &file) * * @brief Writes a node recursive. * * @param [in,out] node If non-null, the node. * @param indent The indent. * @param [in,out] file If non-null, the file. */ static void write_node_recursive(xml_data_node *node, int indent, util::core_file &file) { xml_attribute_node *anode; xml_data_node *child; /* output this tag */ file.printf("%*s<%s", indent, "", node->name); /* output any attributes */ for (anode = node->attribute; anode; anode = anode->next) file.printf(" %s=\"%s\"", anode->name, anode->value); /* if there are no children and no value, end the tag here */ if (node->child == nullptr && node->value == nullptr) file.printf(" />\n"); /* otherwise, close this tag and output more stuff */ else { file.printf(">\n"); /* if there is a value, output that here */ if (node->value != nullptr) file.printf("%*s%s\n", indent + 4, "", node->value); /* loop over children and output them as well */ if (node->child != nullptr) { for (child = node->child; child; child = child->next) write_node_recursive(child, indent + 4, file); } /* write a closing tag */ file.printf("%*s\n", indent, "", node->name); } } /*------------------------------------------------- free_node_recursive - recursively free the data allocated to an XML node -------------------------------------------------*/ /** * @fn static void free_node_recursive(xml_data_node *node) * * @brief Free node recursive. * * @param [in,out] node If non-null, the node. */ static void free_node_recursive(xml_data_node *node) { xml_attribute_node *anode, *nanode; xml_data_node *child, *nchild; /* free name/value */ if (node->name != nullptr) free((void *)node->name); if (node->value != nullptr) free((void *)node->value); /* free attributes */ for (anode = node->attribute; anode; anode = nanode) { /* free name/value */ if (anode->name != nullptr) free((void *)anode->name); if (anode->value != nullptr) free((void *)anode->value); /* note the next node and free this node */ nanode = anode->next; free(anode); } /* free the children */ for (child = node->child; child; child = nchild) { /* note the next node and free this node */ nchild = child->next; free_node_recursive(child); } /* finally free ourself */ free(node); }