// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** xmlfile.c XML file parsing code. ***************************************************************************/ #include #include "xmlfile.h" #include #include #include #include #include namespace util { namespace xml { /*************************************************************************** CONSTANTS ***************************************************************************/ #define TEMP_BUFFER_SIZE 4096 /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ struct parse_info { XML_Parser parser; data_node * rootnode; data_node * curnode; uint32_t flags; }; /*************************************************************************** PROTOTYPES ***************************************************************************/ /* expat interfaces */ static bool expat_setup_parser(parse_info &info, parse_options const *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); /*************************************************************************** XML FILE OBJECTS ***************************************************************************/ /*------------------------------------------------- file_create - create a new xml file object -------------------------------------------------*/ data_node *data_node::file_create() { try { return new data_node(); } catch (...) { return nullptr; } } /*------------------------------------------------- file_read - parse an XML file into its nodes -------------------------------------------------*/ data_node *data_node::file_read(util::core_file &file, parse_options const *opts) { parse_info info; int done; /* set up the parser */ if (!expat_setup_parser(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(info.parser, tempbuf, bytes, done) == XML_STATUS_ERROR) { if (opts != nullptr && opts->error != nullptr) { opts->error->error_message = XML_ErrorString(XML_GetErrorCode(info.parser)); opts->error->error_line = XML_GetCurrentLineNumber(info.parser); opts->error->error_column = XML_GetCurrentColumnNumber(info.parser); } info.rootnode->file_free(); XML_ParserFree(info.parser); return nullptr; } } while (!done); /* free the parser */ XML_ParserFree(info.parser); /* return the root node */ return info.rootnode; } /*------------------------------------------------- string_read - parse an XML string into its nodes -------------------------------------------------*/ data_node *data_node::string_read(const char *string, parse_options const *opts) { parse_info info; int length = (int)strlen(string); /* set up the parser */ if (!expat_setup_parser(info, opts)) return nullptr; /* parse the data */ if (XML_Parse(info.parser, string, length, 1) == XML_STATUS_ERROR) { if (opts != nullptr && opts->error != nullptr) { opts->error->error_message = XML_ErrorString(XML_GetErrorCode(info.parser)); opts->error->error_line = XML_GetCurrentLineNumber(info.parser); opts->error->error_column = XML_GetCurrentColumnNumber(info.parser); } info.rootnode->file_free(); XML_ParserFree(info.parser); return nullptr; } /* free the parser */ XML_ParserFree(info.parser); /* return the root node */ return info.rootnode; } /*------------------------------------------------- file_write - write an XML tree to a file -------------------------------------------------*/ void data_node::file_write(util::core_file &file) const { /* ensure this is a root node */ if (get_name()) return; /* output a simple header */ file.printf("\n"); file.printf("\n"); /* loop over children of the root and output */ for (data_node const *node = get_first_child(); node; node = node->get_next_sibling()) node->write_recursive(0, file); } /*------------------------------------------------- file_free - free an XML file object -------------------------------------------------*/ void data_node::file_free() { /* ensure this is a root node */ if (get_name()) return; delete this; } /*************************************************************************** XML NODE MANAGEMENT ***************************************************************************/ data_node::data_node() : line(0) , m_next(nullptr) , m_first_child(nullptr) , m_name() , m_value() , m_parent(nullptr) , m_attributes() { } data_node::data_node(data_node *parent, const char *name, const char *value) : line(0) , m_next(nullptr) , m_first_child(nullptr) , m_name(name ? name : "") , m_value(value ? value : "") , m_parent(parent) , m_attributes() { std::transform(m_name.begin(), m_name.end(), m_name.begin(), [] (char ch) { return std::tolower(uint8_t(ch)); }); } /*------------------------------------------------- free_node_recursive - recursively free the data allocated to an XML node -------------------------------------------------*/ data_node::~data_node() { /* free the children */ for (data_node *nchild = nullptr; m_first_child; m_first_child = nchild) { /* note the next node and free this node */ nchild = m_first_child->get_next_sibling(); delete m_first_child; } } void data_node::set_value(char const *value) { m_value.assign(value ? value : ""); } void data_node::append_value(char const *value, int length) { m_value.append(value, length); } void data_node::trim_whitespace() { /* first strip leading spaces */ std::string::iterator start = m_value.begin(); while ((m_value.end() != start) && std::isspace(uint8_t(*start))) ++start; m_value.replace(m_value.begin(), start, 0U, '\0'); /* then strip trailing spaces */ std::string::iterator end = m_value.end(); while ((m_value.begin() != end) && std::isspace(uint8_t(*std::prev(end)))) --end; m_value.replace(end, m_value.end(), 0U, '\0'); } /*------------------------------------------------- data_node::count_children - count the number of child nodes -------------------------------------------------*/ int data_node::count_children() const { int count = 0; /* loop over children and count */ for (data_node const *node = get_first_child(); node; node = node->get_next_sibling()) count++; return count; } /*------------------------------------------------- data_node::get_child - find the first child of the specified node with the specified tag -------------------------------------------------*/ data_node *data_node::get_child(const char *name) { return m_first_child ? m_first_child->get_sibling(name) : nullptr; } data_node const *data_node::get_child(const char *name) const { return m_first_child ? m_first_child->get_sibling(name) : nullptr; } /*------------------------------------------------- find_first_matching_child - find the first child of the specified node with the specified tag or attribute/value pair -------------------------------------------------*/ data_node *data_node::find_first_matching_child(const char *name, const char *attribute, const char *matchval) { return m_first_child ? m_first_child->find_matching_sibling(name, attribute, matchval) : nullptr; } data_node const *data_node::find_first_matching_child(const char *name, const char *attribute, const char *matchval) const { return m_first_child ? m_first_child->find_matching_sibling(name, attribute, matchval) : nullptr; } /*------------------------------------------------- data_node::get_next_sibling - find the next sibling of the specified node with the specified tag -------------------------------------------------*/ data_node *data_node::get_next_sibling(const char *name) { return m_next ? m_next->get_sibling(name) : nullptr; } data_node const *data_node::get_next_sibling(const char *name) const { return m_next ? m_next->get_sibling(name) : nullptr; } /*------------------------------------------------- find_next_matching_sibling - find the next sibling of the specified node with the specified tag or attribute/value pair -------------------------------------------------*/ data_node *data_node::find_next_matching_sibling(const char *name, const char *attribute, const char *matchval) { return m_next ? m_next->find_matching_sibling(name, attribute, matchval) : nullptr; } data_node const *data_node::find_next_matching_sibling(const char *name, const char *attribute, const char *matchval) const { return m_next ? m_next->find_matching_sibling(name, attribute, matchval) : nullptr; } /*------------------------------------------------- add_child - add a new child node to the given node -------------------------------------------------*/ data_node *data_node::add_child(const char *name, const char *value) { /* new element: create a new node */ data_node *node; try { node = new data_node(this, name, value); } catch (...) { return nullptr; } if (!node->get_name() || (!node->get_value() && value)) { delete node; return nullptr; } /* add us to the end of the list of siblings */ data_node **pnode; for (pnode = &m_first_child; *pnode; pnode = &(*pnode)->m_next) { } *pnode = node; return node; } /*------------------------------------------------- get_or_add_child - find a child node of the specified type; if not found, add one -------------------------------------------------*/ data_node *data_node::get_or_add_child(const char *name, const char *value) { /* find the child first */ data_node *const child = m_first_child->get_sibling(name); if (child) return child; /* if not found, do a standard add child */ return add_child(name, value); } /*------------------------------------------------- delete_node - delete a node and its children -------------------------------------------------*/ void data_node::delete_node() { /* first unhook us from the list of children of our parent */ if (m_parent) { for (data_node **pnode = &m_parent->m_first_child; *pnode; pnode = &(*pnode)->m_next) { if (*pnode == this) { *pnode = this->m_next; break; } } } /* now free ourselves and our children */ delete this; } /*------------------------------------------------- get_next_sibling - find the next sibling of the specified node with the specified tag -------------------------------------------------*/ data_node *data_node::get_sibling(const char *name) { /* loop over siblings and find a matching name */ for (data_node *node = this; node; node = node->get_next_sibling()) if (strcmp(node->get_name(), name) == 0) return node; return nullptr; } data_node const *data_node::get_sibling(const char *name) const { /* loop over siblings and find a matching name */ for (data_node const *node = this; node; node = node->get_next_sibling()) if (strcmp(node->get_name(), name) == 0) return node; return nullptr; } /*------------------------------------------------- find_matching_sibling - find the next sibling of the specified node with the specified tag or attribute/value pair -------------------------------------------------*/ data_node *data_node::find_matching_sibling(const char *name, const char *attribute, const char *matchval) { /* loop over siblings and find a matching attribute */ for (data_node *node = this; node; node = node->get_next_sibling()) { /* can pass nullptr as a wildcard for the node name */ if (!name || !strcmp(name, node->get_name())) { /* find a matching attribute */ attribute_node const *const attr = node->get_attribute(attribute); if (attr && !strcmp(attr->value.c_str(), matchval)) return node; } } return nullptr; } data_node const *data_node::find_matching_sibling(const char *name, const char *attribute, const char *matchval) const { /* loop over siblings and find a matching attribute */ for (data_node const *node = this; node; node = node->get_next_sibling()) { /* can pass nullptr as a wildcard for the node name */ if (!name || !strcmp(name, node->get_name())) { /* find a matching attribute */ attribute_node const *const attr = node->get_attribute(attribute); if (attr && !strcmp(attr->value.c_str(), matchval)) return node; } } return nullptr; } /*************************************************************************** XML ATTRIBUTE MANAGEMENT ***************************************************************************/ bool data_node::has_attribute(const char *attribute) const { return get_attribute(attribute) != nullptr; } /*------------------------------------------------- get_attribute - get the value of the specified attribute, or nullptr if not found -------------------------------------------------*/ data_node::attribute_node *data_node::get_attribute(const char *attribute) { /* loop over attributes and find a match */ for (attribute_node &anode : m_attributes) if (strcmp(anode.name.c_str(), attribute) == 0) return &anode; return nullptr; } data_node::attribute_node const *data_node::get_attribute(const char *attribute) const { /* loop over attributes and find a match */ for (attribute_node const &anode : m_attributes) if (strcmp(anode.name.c_str(), attribute) == 0) return &anode; return nullptr; } /*------------------------------------------------- get_attribute_string - get the string value of the specified attribute; if not found, return = the provided default -------------------------------------------------*/ const char *data_node::get_attribute_string(const char *attribute, const char *defvalue) const { attribute_node const *attr = get_attribute(attribute); return attr ? attr->value.c_str() : defvalue; } /*------------------------------------------------- get_attribute_int - get the integer value of the specified attribute; if not found, return = the provided default -------------------------------------------------*/ int data_node::get_attribute_int(const char *attribute, int defvalue) const { char const *const string = get_attribute_string(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; } /*------------------------------------------------- get_attribute_int_format - return the format of the given integer attribute -------------------------------------------------*/ data_node::int_format data_node::get_attribute_int_format(const char *attribute) const { char const *const string = get_attribute_string(attribute, nullptr); if (!string) return int_format::DECIMAL; else if (string[0] == '$') return int_format::HEX_DOLLAR; else if (string[0] == '0' && string[1] == 'x') return int_format::HEX_C; if (string[0] == '#') return int_format::DECIMAL_HASH; else return int_format::DECIMAL; } /*------------------------------------------------- get_attribute_float - get the float value of the specified attribute; if not found, return = the provided default -------------------------------------------------*/ float data_node::get_attribute_float(const char *attribute, float defvalue) const { char const *const string = get_attribute_string(attribute, nullptr); float value; if (string == nullptr || sscanf(string, "%f", &value) != 1) return defvalue; return value; } /*------------------------------------------------- set_attribute - set a new attribute and string value on the node -------------------------------------------------*/ void data_node::set_attribute(const char *name, const char *value) { attribute_node *anode; /* first find an existing one to replace */ anode = get_attribute(name); if (anode != nullptr) { /* if we found it, free the old value and replace it */ anode->value = value; } else { /* otherwise, create a new node */ add_attribute(name, value); } } /*------------------------------------------------- set_attribute_int - set a new attribute and integer value on the node -------------------------------------------------*/ void data_node::set_attribute_int(const char *name, int value) { char buffer[100]; sprintf(buffer, "%d", value); set_attribute(name, buffer); } /*------------------------------------------------- set_attribute_int - set a new attribute and float value on the node -------------------------------------------------*/ void data_node::set_attribute_float(const char *name, float value) { char buffer[100]; sprintf(buffer, "%f", (double) value); set_attribute(name, buffer); } /*************************************************************************** MISCELLANEOUS INTERFACES ***************************************************************************/ /*------------------------------------------------- normalize_string - normalize a string to ensure it doesn't contain embedded tags -------------------------------------------------*/ const char *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 -------------------------------------------------*/ static void *expat_malloc(size_t size) { uint32_t *result = (uint32_t *)malloc(size + 4 * sizeof(uint32_t)); *result = size; return &result[4]; } static void expat_free(void *ptr) { if (ptr != nullptr) free(&((uint32_t *)ptr)[-4]); } static void *expat_realloc(void *ptr, size_t size) { void *newptr = expat_malloc(size); if (newptr == nullptr) return nullptr; if (ptr != nullptr) { uint32_t oldsize = ((uint32_t *)ptr)[-4]; memcpy(newptr, ptr, oldsize); expat_free(ptr); } return newptr; } /*------------------------------------------------- expat_setup_parser - set up expat for parsing -------------------------------------------------*/ static bool expat_setup_parser(parse_info &info, parse_options const *opts) { XML_Memory_Handling_Suite memcallbacks; /* setup info structure */ memset(&info, 0, sizeof(info)); if (opts != nullptr) { 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 */ info.rootnode = data_node::file_create(); if (info.rootnode == nullptr) return false; info.curnode = info.rootnode; /* create the XML parser */ memcallbacks.malloc_fcn = expat_malloc; memcallbacks.realloc_fcn = expat_realloc; memcallbacks.free_fcn = expat_free; info.parser = XML_ParserCreate_MM(nullptr, &memcallbacks, nullptr); if (info.parser == nullptr) { info.rootnode->file_free(); return false; } /* configure the parser */ XML_SetElementHandler(info.parser, expat_element_start, expat_element_end); XML_SetCharacterDataHandler(info.parser, expat_data); XML_SetUserData(info.parser, &info); /* optional parser initialization step */ if (opts != nullptr && opts->init_parser != nullptr) (*opts->init_parser)(info.parser); return true; } /*------------------------------------------------- expat_element_start - expat callback for a new element -------------------------------------------------*/ static void expat_element_start(void *data, const XML_Char *name, const XML_Char **attributes) { parse_info *info = (parse_info *) data; data_node **curnode = &info->curnode; data_node *newnode; int attr; /* add a new child node to the current node */ newnode = (*curnode)->add_child(name, nullptr); if (newnode == nullptr) return; /* remember the line number */ newnode->line = XML_GetCurrentLineNumber(info->parser); /* add all the attributes as well */ for (attr = 0; attributes[attr]; attr += 2) newnode->add_attribute(attributes[attr+0], attributes[attr+1]); /* set us up as the current node */ *curnode = newnode; } /*------------------------------------------------- expat_data - expat callback for an additional element data -------------------------------------------------*/ static void expat_data(void *data, const XML_Char *s, int len) { parse_info *info = (parse_info *) data; data_node **curnode = &info->curnode; (*curnode)->append_value(s, len); } /*------------------------------------------------- expat_element_end - expat callback for the end of an element -------------------------------------------------*/ static void expat_element_end(void *data, const XML_Char *name) { parse_info *info = (parse_info *) data; data_node **curnode = &info->curnode; /* strip leading/trailing spaces from the value data */ if (!(info->flags & PARSE_FLAG_WHITESPACE_SIGNIFICANT)) (*curnode)->trim_whitespace(); /* back us up a node */ *curnode = (*curnode)->get_parent(); } /*************************************************************************** NODE/ATTRIBUTE ADDITIONS ***************************************************************************/ /*------------------------------------------------- add_attribute - add a new attribute to the given node -------------------------------------------------*/ void data_node::add_attribute(const char *name, const char *value) { try { attribute_node &anode = *m_attributes.emplace(m_attributes.end(), name, value); std::transform(anode.name.begin(), anode.name.end(), anode.name.begin(), [] (char ch) { return std::tolower(uint8_t(ch)); }); } catch (...) { } } /*************************************************************************** RECURSIVE TREE OPERATIONS ***************************************************************************/ /*------------------------------------------------- write_node_recursive - recursively write an XML node and its children to a file -------------------------------------------------*/ void data_node::write_recursive(int indent, util::core_file &file) const { /* output this tag */ file.printf("%*s<%s", indent, "", get_name()); /* output any attributes */ for (attribute_node const &anode : m_attributes) file.printf(" %s=\"%s\"", anode.name, anode.value); if (!get_first_child() && !get_value()) { /* if there are no children and no value, end the tag here */ file.printf(" />\n"); } else { /* otherwise, close this tag and output more stuff */ file.printf(">\n"); /* if there is a value, output that here */ if (get_value()) file.printf("%*s%s\n", indent + 4, "", get_value()); /* loop over children and output them as well */ if (get_first_child()) { for (data_node const *child = this->get_first_child(); child; child = child->get_next_sibling()) child->write_recursive(indent + 4, file); } /* write a closing tag */ file.printf("%*s\n", indent, "", get_name()); } } } } // namespace util::xml