XML Parser
Overview
Constants
Enumerations
Data Structures
Public Data Members
Functions
SAX Event Handlers
Internal Processing Functions
Data Structure Functions
Example
Overview
The gxsXML class is a small XML parser and writer. It is declared in gxsxml.h and implemented in gxsxml.cpp. The class is derived from the DiskFileB class, but it opens its own local file objects to load and write XML files.
The class can work in two ways. In DOM mode (the default) the LoadXMLFile() function reads the whole file into an in-memory tree. In SAX mode the parser does not store the tree. It only calls the virtual event handlers as nodes are read. The event handlers are called in both modes. The mode is set with the parser_type data member.
The in-memory tree is stored in the node_tree list. The first entry is the root node with a node type of gxsXML_ROOT_NODE. Each element directly inside the root follows as a separate entry with a node type of gxsXML_PARENT_NODE. A parent keeps its children in the child_nodes.elements list of gxsXMLNode_t objects. A child keeps everything below it in one flat, doubly linked list of gxsXMLSubElement objects. Deeper levels are not stored as separate lists. Instead, each sub-element has a depth member that gives its nesting level (1 = directly inside the child). An element that contains other elements is marked with the has_siblings flag, and the elements after it with a higher depth are written inside it. NOTE: 10/14/2026: Fixed the parser and writer so elements nested at any depth are all stored and written back with their original nesting. Before this fix, elements following a nested element at the same level were lost, and deeper levels were written flattened.
Processing instructions, document type declarations, CDATA sections and comments found before the root element are stored in the pi_list, dtd_list, cdata_list and comment_list members. For these nodes the complete tag text, including the brackets, is stored in the attributes member of the node. Comments inside the root element are removed and are not stored.
If the first root element has a name starting with "xs:" its nodes are moved to the xs (XML Schema) list when its closing tag is found. The parser then keeps reading to find the document root.
A tree can also be built in memory with AddRoot(), AddParent(), AddChild(), AddSubChild() and AddSibling(). The tree is written out with WriteXMLFile() or WriteTree().
Most functions report errors through an internal error string. The string always starts with "XML ERROR: " and can be read with GetError().
NOTE: This is not a validating parser. It does not decode entity references such as "&" while loading. Use DecodeString() on the data if needed. The file must start with an "<?xml ... ?>" header, and parsing stops at the closing tag of the root element. Anything after the root element is ignored.
NOTE: 10/14/2026: Fixed: the debug trace statements were removed, so loading a file no longer writes trace text to std::cout.
Mixed content. Text found between elements is kept in document order (10/24/2026, version 6.0). It used to be dropped. Inside a parent, a child or a sub-element, each piece of text becomes a gxsXMLNode_t with an empty name and the text in its data member. Text between the parent elements of the root becomes a node_tree entry with a node type of gxsXML_TEXT_NODE, an empty name and the text in its data member. In SAX mode these root level text nodes are passed to Handle_Parent_Event(), so check the node type there. Leading and trailing white space is removed from the text, and text that is only white space, such as the line breaks between elements, is not kept. An element with text and no elements keeps its text in its data member as before. The writer writes a node with no name as text. Code that walks a tree loaded from a file with mixed content will see these nodes and should skip nodes with an empty name if it only wants elements.
Constants
const unsigned gxsXML_DEFAULT_READ_LEN = 4096; // Optimum buffer size const unsigned gxsXML_MIN_READ_LEN = 256; // One line length const unsigned gxsXML_MAX_READ_LEN = gxsXML_DEFAULT_READ_LEN * 16;
These constants set the size of the buffer used to read the XML file. The read buffer length is set to gxsXML_DEFAULT_READ_LEN by Clear(). A length outside the minimum and maximum values is reset to the default before a file is read. The XML header must be found inside the first read buffer.
Enumerations
enum gxsXMLNodeType {
gxsXML_NODE_TYPE_NOT_SET = 0,
// Hierarchal tree node types
gxsXML_ROOT_NODE,
gxsXML_PARENT_NODE,
gxsXML_CHILD_NODE,
gxsXML_SUBELEMENT_NODE,
// XML document processing types
gxsXML_COMMENT_NODE, // Comment nodes
gxsXML_PROC_NODE, // Processing instruction nodes
gxsXML_DTD_NODE, // Document type declaration nodes
gxsXML_CDATA_NODE, // CDATA type
// CDATA contains information expressed in a non-XML format
// 10/24/2026: Text found between the parent elements of the root.
// Text between other elements is stored in nodes with no name.
gxsXML_TEXT_NODE
};
enum gxsXMLElementType {
gxsXML_ELEMENT_TYPE_NOT_SET = 0,
gxsXML_TEXT_ELEMENT, // Text nodes
gxsXML_ATTRIBUTE_ELEMENT, // Attribute nodes
gxsXML_COMMENT_ELEMENT, // Comment nodes
gxsXML_NAMESPACE_ELEMENT // Namespace nodes
};
enum gxsXMLParserType {
gxsXML_PARSER_TYPE_NOT_SET = 0,
gxsXML_PARSER_DOM, // DOM (Document Object Model) loads entire tree in memory
gxsXML_PARSER_SAX // SAX (Simple API for XML) event driven node processing
};
The parser only sets gxsXML_ROOT_NODE and gxsXML_PARENT_NODE in the node tree. Processing instruction, DTD, CDATA and comment nodes are stored with gxsXML_PROC_NODE, gxsXML_DTD_NODE, gxsXML_CDATA_NODE and gxsXML_COMMENT_NODE. NOTE: 10/14/2026: Fixed: DTD, CDATA and comment nodes were all stored as gxsXML_PROC_NODE. The gxsXMLElementType values are not set by the parser. The element_type member is always gxsXML_ELEMENT_TYPE_NOT_SET.
The parser stores nodes only when parser_type equals gxsXML_PARSER_DOM. Any other value, such as gxsXML_PARSER_SAX, turns off storage and leaves only the event handlers.
Data Structures
Document information. Filled in by LoadXMLFile() from the "<?xml ... ?>" header. The defaults are version "1.0", encoding "utf-8" and standalone "no". The style sheet is set when a processing instruction containing "xml-stylesheet" is found before the root. It holds the complete tag text.
struct gxsXMLDocInfo
{
gxsXMLDocInfo() { Clear(); }
~gxsXMLDocInfo() { }
gxsXMLDocInfo(const gxsXMLDocInfo &ob) {
Copy(ob);
}
gxsXMLDocInfo &operator=(const gxsXMLDocInfo &ob);
void Clear();
void Copy(const gxsXMLDocInfo &ob);
gxString fname;
gxString xml_version_str;
gxString xml_encoding;
gxString xml_style_sheet;
gxString xml_standalone;
};
Element node. Used for child elements of a parent node and for every element below a child. The name member holds the tag name. The data member holds the text between the start and end tags. The attributes member holds the attribute text from the start tag, for example: link="http://www.example.com". The has_siblings flag marks an element that contains other elements. The depth member (added 10/14/2026) is the nesting level of a sub-element below its child: 1 is directly inside the child. A depth of 0 means not set, and the writer then works out the level from has_siblings, is_closed and sub_child the way older versions did. The is_closed flag and the sub_child pointer let the caller close a nested level early on elements with no depth set. See CloseChild() and CloseSubChildAtSibling(). Each node owns its sub-element list and frees it when it is cleared or destroyed.
struct gxsXMLNode_t
{
gxsXMLNode_t();
~gxsXMLNode_t();
gxsXMLNode_t(const gxsXMLNode_t &ob);
gxsXMLNode_t &operator=(const gxsXMLNode_t &ob);
void Clear();
void Copy(const gxsXMLNode_t &ob);
gxsXMLSubElement *AddSubElement(gxsXMLNode_t &element);
gxsXMLSubElement *GetSubElements() { return subelements; }
gxsXMLSubElement *GetLastSubElement() { return last_subelement; }
int HasSubElements();
int HasSiblings();
// Subelement processing functions
void delete_last_subelement();
void destroy_subelements();
void delete_subelement(gxsXMLSubElement *n);
void detach_subelement(gxsXMLSubElement *n);
void insert_before(gxsXMLSubElement *pos, gxsXMLSubElement *n);
void insert_after(gxsXMLSubElement *pos, gxsXMLSubElement *n);
gxsXMLSubElement *add_sub_element(gxsXMLSubElement *n);
gxString name;
gxString data;
gxString attributes;
int has_siblings;
int is_closed;
gxsXMLSubElement *sub_child;
// 10/14/2026: Nesting level of a sub-element below its child element
// (1 = directly inside the child). The parser and AddSubChild()/AddSibling()
// set it and the writer uses it to rebuild the nesting. A value of 0
// means not set, and the writer works it out from has_siblings.
int depth;
private:
gxsXMLSubElement *subelements;
gxsXMLSubElement *last_subelement;
};
Sub-element list node. One link in the doubly linked sub-element list of a gxsXMLNode_t. The element pointer points to a gxsXMLNode_t that is allocated with new and owned by the list. It is deleted when the link is deleted through the owning node.
struct gxsXMLSubElement
{
gxsXMLSubElement() {
next = prev = 0;
element = 0;
}
gxsXMLSubElement *next;
gxsXMLSubElement *prev;
gxsXMLNode_t *element;
};
Element list. Holds the child elements of a parent node.
struct gxsXMLElement
{
gxsXMLElement() { Clear(); }
~gxsXMLElement() { Clear(); }
gxsXMLElement(const gxsXMLElement &ob) {
Copy(ob);
}
gxsXMLElement &operator=(const gxsXMLElement &ob);
void Clear();
void Copy(const gxsXMLElement &ob);
gxList<gxsXMLNode_t> elements;
};
Tree node. Used for the root node, the parent nodes and the nodes stored in pi_list, dtd_list, cdata_list, comment_list and xs. For the root node, data holds the text found inside the root before the first parent element. For a parent with no child elements, data holds its text. For a parent with child elements, data is empty and the children are in child_nodes, including any text between them as children with no name (see Mixed content). The sax_end_parse member can be set to 1 by an event handler to stop the load.
struct gxsXMLNode
{
gxsXMLNode() { Clear(); }
~gxsXMLNode() { }
gxsXMLNode(const gxsXMLNode &ob) {
Copy(ob);
}
gxsXMLNode &operator=(const gxsXMLNode &ob);
void Clear();
void Copy(const gxsXMLNode &ob);
gxString name; // Name of the node in the XML file
gxString data; // Data for nodes with no children
gxString attributes; // List of attributes for this node
gxsXMLNodeType node_type; // Type of node root, parent, child
gxsXMLElementType element_type; // Type of element
gxsXMLElement child_nodes; // Data for nodes with children
int sax_end_parse; // Signal from SAX event to stop file load
};
Public Data Members
class gxsXML : public DiskFileB
{
...
public:
gxsXMLDocInfo doc_info;
gxsXMLParserType parser_type;
gxList<gxsXMLNode> pi_list; // Process instruction list
gxList<gxsXMLNode> comment_list; // Comment list before root
gxList<gxsXMLNode> dtd_list; // Document type declaration list
gxList<gxsXMLNode> cdata_list; // CDATA list before root
gxList<gxsXMLNode> xs; // XML Schema
gxList<gxsXMLNode> node_tree; // Hierarchal node structure
protected:
unsigned read_buf_len;
gxString err_string;
};
doc_info - Document information read from the XML header. See gxsXMLDocInfo.
parser_type - Parser mode. Set to gxsXML_PARSER_DOM by Clear(). Set it to gxsXML_PARSER_SAX before loading a file to use the event handlers only.
pi_list, comment_list, dtd_list, cdata_list - Processing instructions, comments, document type declarations and CDATA sections found before the root element. The full tag text is stored in the attributes member of each node.
xs - Nodes of an XML Schema root element (a root named "xs:..."). Written with WriteXS().
node_tree - The document tree: the root node followed by one node per parent element.
read_buf_len - Protected. File read buffer length. There is no public function to change it.
err_string - Protected. The last error message.
Functions
gxsXML::gxsXML()
gxsXML::~gxsXML()
gxsXML::AddChild()
gxsXML::AddParent()
gxsXML::AddRoot()
gxsXML::AddSibling()
gxsXML::AddSubChild()
gxsXML::CleanAttributes()
gxsXML::CleanTagName()
gxsXML::Clear()
gxsXML::CloseChild()
gxsXML::CloseSubChildAtSibling()
gxsXML::Copy()
gxsXML::DecodeString()
gxsXML::EncodeString()
gxsXML::GetError()
gxsXML::GetXMLAttribute()
gxsXML::HasAttributes()
gxsXML::LoadXMLFile()
gxsXML::SetError()
gxsXML::WriteTree()
gxsXML::WriteXMLFile()
gxsXML::WriteXS()
gxsXML::gxsXML()Default class constructor. Calls Clear() to set the default values.
gxsXML::gxsXML(const gxsXML &ob)Class copy constructor. Calls Copy(). The DiskFileB base class is default constructed and is not copied.
gxsXML &gxsXML::operator=(const gxsXML &ob)Class assignment operator. Calls Copy() unless the object is assigned to itself.
gxsXML::~gxsXML()Class destructor. It does no work of its own. The lists free their nodes when they are destroyed.
gxsXMLNode_t *gxsXML::AddChild(gxsXMLNode *parent, const char *name, const char *attributes = 0, const char *data = 0)Public member function used to add a child element to the end of a parent node's child list. The "attributes" string is written as given inside the start tag. The "data" string is written between the start and end tags. Returns a pointer to the new child, which is stored inside the parent's list and owned by it. Returns a null value and sets the error string if "parent" or "name" is null or the node cannot be added.
gxsXMLNode *gxsXML::AddParent(const char *name, const char *attributes = 0, const char *data = 0)Public member function used to add a parent node to the end of the node tree. The tree must already have a root node. Returns a pointer to the new node, which is stored inside the node_tree list and owned by it. The pointer becomes invalid when the tree is cleared. Returns a null value and sets the error string if "name" is null, the tree is empty, or the node cannot be added.
gxsXMLNode *gxsXML::AddRoot(const char *name, const char *attributes = 0, const char *data = 0)Public member function used to start a new tree by adding the root node. The node tree must be empty. Call Clear() first to remove a tree that was loaded or built before. Returns a pointer to the root node, which is owned by the node_tree list. Returns a null value and sets the error string if "name" is null, the tree is not empty, or the node cannot be added.
gxsXMLSubElement *gxsXML::AddSibling(gxsXMLNode_t *child, gxsXMLSubElement *sibling, const char *name, const char *attributes = 0, const char *data = 0)Public member function used to add an element one level below an existing sub-element. The "sibling" sub-element is marked as having nested elements (has_siblings is set), and the new element is added to the end of the child's sub-element list with a depth one higher than "sibling". When the tree is written, the new element appears inside the "sibling" element. Despite its name, the "sibling" argument is the element that will contain the new one. Returns a pointer to the new sub-element, which is owned by the child. Returns a null value and sets the error string if any pointer argument is null or memory cannot be allocated.
gxsXMLSubElement *gxsXML::AddSubChild(gxsXMLNode_t *child, const char *name, const char *attributes = 0, const char *data = 0)Public member function used to add an element directly inside a child element. The element is added to the end of the child's sub-element list with a depth of 1, and the child's has_siblings flag is set. An element added this way after a nested level is written after that level is closed. Returns a pointer to the new sub-element, which is owned by the child. Returns a null value and sets the error string if "child" or "name" is null or memory cannot be allocated.
void gxsXML::CleanAttributes(const gxString &tag, gxString &attrib)Public member function used to pass back the attribute part of a tag in the "attrib" variable. For example, the tag <item id="1"/> gives id="1". Text up to the first space after the "<" and anything from the ">" on are removed, and a trailing "/" and the spaces before it are trimmed. NOTE: 10/14/2026: Fixed: the space before the "/" was kept, so each write added another space to empty element tags. If the tag has no "=" character, "attrib" is passed back empty.
void gxsXML::CleanTagName(const gxString &tag, gxString &tag_name)Public member function used to pass back the element name of a tag in the "tag_name" variable. For example, both <item id="1"/> and </item> give "item". Text before the "<", from the first space or ">" on, and leading or trailing "/" characters are removed.
void gxsXML::Clear()Public member function used to reset the object. Sets the read buffer length to gxsXML_DEFAULT_READ_LEN, sets the error string to "XML ERROR: None reported", resets doc_info to its defaults, sets parser_type to gxsXML_PARSER_DOM and empties pi_list, dtd_list, cdata_list, comment_list, node_tree and xs. Any pointers returned by the Add functions become invalid.
int gxsXML::CloseChild(gxsXMLNode_t *child)Public member function used to set the is_closed flag of an element node. The writer checks this flag for sub-elements: a closed sub-element is written with its end tag right away, and the next sub-element is not placed inside it. Elements added with AddSubChild() or AddSibling() carry their own nesting level, so this call is only needed for sub-elements with no depth set. Returns 1 if successful, or 0 and sets the error string if "child" is null.
int gxsXML::CloseSubChildAtSibling(gxsXMLSubElement *sub_child, gxsXMLSubElement *sibling)Public member function used to close a nested level before the end of the child element. The "sibling" element is marked as closed and "sub_child" is stored in its sub_child pointer. When the tree is written, the end tag of "sub_child" is written right after "sibling", so the elements that follow are written at the outer level. Since 10/14/2026 elements added with AddSubChild() and AddSibling() carry their nesting level, so the writer closes levels without this call. It is kept for code written for older versions and is used for sub-elements with no depth set. Returns 1 if successful, or 0 and sets the error string if either pointer is null.
void gxsXML::Copy(const gxsXML &ob)Public member function used to copy the read buffer length, error string, document information, parser type and all node lists from another object. Each list is cleared first and then filled with copies of the other object's nodes. The DiskFileB base class is not copied.
int gxsXML::DecodeString(const char *encoded_str, gxString &unencoded_str)Public member function used to replace the XML entity references &amp;, &lt;, &gt;, &apos; and &quot; with the characters they stand for. The result is passed back in "unencoded_str". Returns 1 if successful or 0 if "encoded_str" is null. NOTE: 10/14/2026: Fixed: "&amp;" is now replaced last, so each entity is decoded once and "&amp;lt;" becomes "&lt;". Before the fix it was decoded twice and became "<".
int gxsXML::DecodeString(const gxString &encoded_str, gxString &unencoded_str)Public member function that calls DecodeString() with the string's character buffer.
int gxsXML::EncodeString(const char *unencoded_str, gxString &encoded_str)Public member function used to replace the characters &, <, >, ' and " with the XML entity references &amp;, &lt;, &gt;, &apos; and &quot;. The result is passed back in "encoded_str". Returns 1 if successful or 0 if "unencoded_str" is null. The Add and Write functions do not encode data, so call this function on text that may contain these characters.
int gxsXML::EncodeString(const gxString &unencoded_str, gxString &encoded_str)Public member function that calls EncodeString() with the string's character buffer.
char *gxsXML::GetError()Public member function that returns the last error message. If the error string is empty it is set to "XML ERROR: None reported" first. The returned pointer belongs to the object and is valid until the error string changes.
int gxsXML::GetXMLAttribute(const gxString &tag_str, const gxString &attr_str, gxString &value)Public member function used to find the attribute named "attr_str" in "tag_str" and pass back its value, without the quotes, in the "value" variable. The value must be enclosed in double or single quotes. Returns 1 if the value was found. Returns 0 and sets the error string if the attribute, the "=" character or a closing quote is missing, or if the value is not quoted. Spaces are allowed around the "=" sign, and an empty value ("") passes back an empty string. NOTE: 10/14/2026: Fixed: only a whole attribute name outside quoted values is matched. Before the fix a plain text search could match part of a longer name (id in uid) or text inside an earlier value, and an empty value passed back the rest of the tag.
int gxsXML::HasAttributes(const gxString &tag)Public member function that returns 1 if the tag contains an "=" character, or 0 if it does not.
int gxsXML::LoadXMLFile(const char *fname)Public member function used to open, read and parse the XML file "fname". The document information is reset and then read from the "<?xml ... ?>" header, which must appear in the first read buffer. In DOM mode the nodes are added to pi_list, dtd_list, cdata_list, comment_list, xs and node_tree. The event handlers are called as each node is read. If a handler sets sax_end_parse to 1, the load stops and the function returns 1. Returns 1 if the file was loaded, or 0 if an error occurs. Call GetError() for the reason. CDATA sections are kept in the element data as written, including the "<![CDATA[" and "]]>" markers. NOTE: 10/14/2026: Fixed: the node lists (pi_list, dtd_list, cdata_list, comment_list, xs and node_tree) are now cleared before loading, so loading a second file replaces the first one instead of adding to it. The parser setting (parser_type) is kept. NOTE: 10/14/2026: Fixed: every element below a child is now stored, at any depth, with its nesting level. Before the fix, in <c><a>1</a><n><x>2</x></n><b>3</b></c> the <b> element was lost. Also fixed: an element that contains an element with the same name is no longer cut short at the inner end tag. A CDATA section now stays with the element it belongs to, and its text is no longer changed ("></" was turned into "> </"). An empty element such as <a></a> is now read as empty instead of holding a space.
char *gxsXML::SetError(const char *s)Public member function used to set the error string to "XML ERROR: " followed by "s". A null "s" leaves only the prefix. Returns a pointer to the error string, which belongs to the object.
int gxsXML::WriteTree(gxString &tree_string)Public member function used to write the node tree as XML text into "tree_string". The string is cleared first. No XML header, processing instructions, DTDs or comments are written. Returns 1 if successful, or 0 and sets the error string if the tree is empty.
int gxsXML::WriteXMLFile(const char *fname)Public member function used to create the file "fname" and write the complete document to it. If pi_list is empty an "<?xml ... ?>" header is built from doc_info (version, encoding, standalone="yes" if set, and the style sheet line). Otherwise the stored processing instructions are written. The comment, DTD and CDATA lists, the XML Schema list and the node tree follow. Element data and attributes are written as stored and are not encoded. Returns 1 if successful. Returns 0 and sets the error string if the tree is empty, "fname" is null or a write fails. NOTE: 10/14/2026: Fixed: the function now returns 0 when the file cannot be created (it returned 1). NOTE: 10/14/2026: Fixed: a tree loaded from a file is written with its original nesting at every depth. Before the fix, elements nested more than one level below a child were written flattened.
int gxsXML::WriteXS(gxString &xs_string)Public member function used to write the XML Schema list as XML text into "xs_string". The string is cleared first. Returns 1 if successful, or 0 and sets the error string if no schema is loaded.
SAX Event Handlers
The event handlers are virtual functions with empty bodies in the gxsXML class. A derived class overrides the handlers it needs. They are called while LoadXMLFile() reads the file, in both DOM and SAX mode. A handler that receives a gxsXMLNode can set its sax_end_parse member to 1 to stop the load. The node is stored in the DOM lists after the handler returns, so changes a handler makes to the node are kept.
gxsXML::Handle_CDATA_Event()
gxsXML::Handle_Child_Event()
gxsXML::Handle_Comment_Event()
gxsXML::Handle_DTD_Event()
gxsXML::Handle_Parent_Event()
gxsXML::Handle_PI_Event()
gxsXML::Handle_Root_Event()
gxsXML::Handle_Sibling_Event()
gxsXML::Handle_SubChild_Event()
gxsXML::Handle_XS_Event()
virtual void gxsXML::Handle_CDATA_Event(gxsXMLNode &n)Called for each CDATA section found before the root element. The full section text is in n.attributes.
virtual void gxsXML::Handle_Child_Event(gxsXMLNode &n)Called for a parent element that contains child elements, after its children have been parsed into n.child_nodes. It is called before Handle_Parent_Event() for the same node. NOTE: 10/14/2026: Fixed: n.node_type (gxsXML_PARENT_NODE) and n.attributes are now set before this handler is called.
virtual void gxsXML::Handle_Comment_Event(gxsXMLNode &n)Called for each comment found before the root element. The full comment text is in n.attributes.
virtual void gxsXML::Handle_DTD_Event(gxsXMLNode &n)Called for each document type declaration (DOCTYPE, ELEMENT, ENTITY, ATTLIST, NOTATION, INCLUDE or IGNORE) found before the root element. The full declaration text is in n.attributes.
virtual void gxsXML::Handle_Parent_Event(gxsXMLNode &n)Called for each element directly inside the root element after it has been completely parsed.
virtual void gxsXML::Handle_PI_Event(gxsXMLNode &n)Called for each processing instruction found before the root element, including the "<?xml ... ?>" header. The full tag text is in n.attributes.
virtual void gxsXML::Handle_Root_Event(gxsXMLNode &n)Called for the root element. It is called when the first parent element is reached (or at the root's end tag if there are no parents), so n.data already holds the root's own text.
virtual void gxsXML::Handle_Sibling_Event(gxsXMLNode_t &n)Called when a sub-element that contains further elements is added below a child. "n" is the child element that owns the sub-element list, not the new sub-element. Use n.GetLastSubElement() to reach the element just added. Its depth member gives its nesting level. The elements nested inside it follow, each reported by its own event. There is no way to stop the load from this handler.
virtual void gxsXML::Handle_SubChild_Event(gxsXMLNode_t &n)Called when a sub-element with no nested elements is added below a child. As with Handle_Sibling_Event(), "n" is the owning child element. There is no way to stop the load from this handler.
virtual void gxsXML::Handle_XS_Event()Called after the closing tag of an "xs:" schema root is read and its nodes have been moved to the xs list.
Internal Processing Functions
The following functions are protected. They are used by the load and write functions and are listed here for derived classes.
gxsXML::explode_nodes()
gxsXML::explode_sub_elements()
gxsXML::find_tag()
gxsXML::format_err_str()
gxsXML::has_sub_elements()
gxsXML::is_xml()
gxsXML::load_file()
gxsXML::strip_comments()
gxsXML::test_comments()
gxsXML::write_xml_tree()
int gxsXML::explode_nodes(gxString &node, gxList<gxsXMLNode_t> &node_list)Protected function used to split the text inside an element into a list of element nodes, one per top-level element in the text. The list is cleared first. Comments are removed from "node". The data of each node is the text between its start tag and its matching end tag, including any CDATA sections and nested elements. Nested elements with the same name are matched correctly. Returns the number of nodes found, or -1 if a comment or a closing tag is missing. NOTE: 10/14/2026: Rewritten. The old version ended an element at the first end tag with the same name, moved the first CDATA section in the text to the first element that ended, and wrote debug text to std::cout.
int gxsXML::explode_nodes(gxString &node, gxsXMLNode &n)Protected function used to fill n.child_nodes from the text of a parent element. For each child that contains elements, explode_sub_elements() adds them to the child's sub-element list. Returns the result of the first explode_nodes() call, or a negative value if an error occurs. NOTE: 10/14/2026: Fixed: elements after a nested element at the same level were lost, and deeper levels were not recorded.
int gxsXML::explode_sub_elements(gxsXMLNode_t &child, gxString &nodes, int depth)Protected function added 10/14/2026. Adds each element in "nodes" to the sub-element list of "child" with the given depth. Each element that contains other elements is followed by those elements, added by calling this function again with depth + 1. Handle_Sibling_Event() is called for an element that contains other elements and Handle_SubChild_Event() for any other element. Returns 0 if successful or -1 if an error occurs.
int gxsXML::find_tag(gxString &node, int offset, int &start, int &end)Protected function used to find the next tag in "node" starting at "offset". Passes back the position of the last "<" before the next ">" in "start" and the position just after the ">" in "end". CDATA sections are skipped. Returns 1 if a tag was found or 0 if not. If a ">" is found with no "<" before it, "start" is -1. NOTE: 10/14/2026: Fixed: the function no longer changes "node". It used to change "></" to "> </", which also changed the text of CDATA sections.
void gxsXML::format_err_str()Protected function used to set the error string to "XML ERROR: ".
int gxsXML::has_sub_elements(const gxString &node_data)Protected function that returns 1 if the text, after all CDATA sections are removed, contains both a "<" and a ">" character, or 0 if not. NOTE: 10/14/2026: Fixed: only the first CDATA section was removed, and the function wrote debug text to std::cout.
int gxsXML::has_sub_elements(const gxsXMLElement &element)Protected function that returns 1 if the data of any element in the list contains sub-elements, or 0 if not.
int gxsXML::is_xml(DiskFileB &xml_fp, unsigned read_length)Protected function used to rewind the open file, read the first "read_length" bytes and look for the "<?xml ... ?>" header. The version, encoding and standalone values found are stored in doc_info. Returns 1 if a header was found, or 0 and sets the error string if not or if a read error occurs.
int gxsXML::load_file(const char *fname)Protected function that does the work of LoadXMLFile(). The file is read in blocks of read_buf_len bytes. Returns 0 if successful or a non-zero value if an error occurs. Note that the return values are the opposite of LoadXMLFile(). NOTE: On 10/14/2026 a bounds test in this function was fixed. The test that checks whether a "<" is the last character of the current block compared against length()-1, which wrapped to a very large unsigned value when the block was empty. It now uses a signed compare against length(). NOTE: 10/14/2026: Fixed: the end of a parent element is now found at its matching end tag, skipping nested elements with the same name and CDATA sections. The text of CDATA sections is no longer changed, and an empty parent such as <p></p> is read as empty. Debug text is no longer written to std::cout.
int gxsXML::strip_comments(gxString &pd)Protected function used to remove all <!-- ... --> comments from "pd". Returns 1 if successful, or 0 and sets the error string if a comment has no closing "-->".
int gxsXML::test_comments(const gxString &pd)Protected function used to check that every comment in "pd" is closed. It works on a copy, so "pd" is not changed. Returns 1 if all comments are closed or 0 if not.
int gxsXML::write_xml_tree(gxList<gxsXMLNode> &xml_tree, DiskFileB *outfile, gxString *outstr)Protected function used to write a node list as XML text to an open file, a string, or both. A string is cleared first. The sub-elements of each child are nested by their depth member. Sub-elements with no depth set are nested by their has_siblings, is_closed and sub_child members, as in older versions. The tree is not changed while it is written. The root element's end tag is written last. Returns 1 if successful, or 0 and sets the error string if both outputs are null or a file write fails. NOTE: 10/14/2026: Fixed: levels opened with has_siblings stayed open to the end of the child, so loaded trees with more than one nested level were written with the wrong nesting. The old writer also changed the is_closed flags of the tree.
Data Structure Functions
gxsXMLDocInfo::Clear()
gxsXMLDocInfo::Copy()
gxsXMLNode_t::gxsXMLNode_t()
gxsXMLNode_t::~gxsXMLNode_t()
gxsXMLNode_t::add_sub_element()
gxsXMLNode_t::AddSubElement()
gxsXMLNode_t::Clear()
gxsXMLNode_t::Copy()
gxsXMLNode_t::delete_last_subelement()
gxsXMLNode_t::delete_subelement()
gxsXMLNode_t::destroy_subelements()
gxsXMLNode_t::detach_subelement()
gxsXMLNode_t::GetLastSubElement()
gxsXMLNode_t::GetSubElements()
gxsXMLNode_t::HasSiblings()
gxsXMLNode_t::HasSubElements()
gxsXMLNode_t::insert_after()
gxsXMLNode_t::insert_before()
gxsXMLSubElement::gxsXMLSubElement()
gxsXMLElement::Clear()
gxsXMLElement::Copy()
gxsXMLNode::Clear()
gxsXMLNode::Copy()
Each of the gxsXMLDocInfo, gxsXMLElement and gxsXMLNode structures has a default constructor that calls Clear(), a copy constructor that calls Copy(), and an assignment operator that calls Copy() unless the object is assigned to itself. The gxsXMLElement destructor calls Clear().
void gxsXMLDocInfo::Clear()Clears the file name and style sheet and sets the defaults: version "1.0", encoding "utf-8" and standalone "no".
void gxsXMLDocInfo::Copy(const gxsXMLDocInfo &ob)Copies all members from another object.
gxsXMLNode_t::gxsXMLNode_t()Default constructor. Sets the sub-element list and sub_child to null, and has_siblings, is_closed and depth to 0. NOTE: 10/14/2026: Fixed: is_closed was not set, so its value was undefined.
gxsXMLNode_t::gxsXMLNode_t(const gxsXMLNode_t &ob)Copy constructor. Starts with an empty sub-element list and calls Copy().
gxsXMLNode_t &gxsXMLNode_t::operator=(const gxsXMLNode_t &ob)Assignment operator. Calls Copy() unless the object is assigned to itself.
gxsXMLNode_t::~gxsXMLNode_t()Destructor. Deletes all sub-elements and the element nodes they point to.
gxsXMLSubElement *gxsXMLNode_t::add_sub_element(gxsXMLSubElement *n)Appends an existing sub-element link to the end of the list. The node takes ownership of "n" and of its element, so both must be allocated with new. Returns "n", or a null value if "n" is null.
gxsXMLSubElement *gxsXMLNode_t::AddSubElement(gxsXMLNode_t &element)Allocates a new sub-element link and a copy of "element" and appends them to the end of the list. The node owns the new memory. Returns a pointer to the new link, or a null value if the allocation fails.
void gxsXMLNode_t::Clear()Clears name, data and attributes, sets has_siblings, is_closed, depth and sub_child to zero, and deletes all sub-elements.
void gxsXMLNode_t::Copy(const gxsXMLNode_t &ob)Copies all members from another node. The sub-element list is deleted and rebuilt with copies of the other node's sub-elements. The sub_child pointers of the copied sub-elements are set to the matching sub-elements in this node's list. The node's own sub_child pointer is copied as is. NOTE: 10/14/2026: Fixed: the sub_child pointers of copied sub-elements still pointed into the other node's list.
void gxsXMLNode_t::delete_last_subelement()Removes the last sub-element from the list and deletes it and its element. The list must not be empty.
void gxsXMLNode_t::delete_subelement(gxsXMLSubElement *n)Removes the sub-element "n" from the list and deletes it and its element. "n" must not be null.
void gxsXMLNode_t::destroy_subelements()Deletes every sub-element and its element and leaves the list empty.
void gxsXMLNode_t::detach_subelement(gxsXMLSubElement *n)Unlinks the sub-element "n" from the list without deleting it. The caller then owns "n". The next and prev pointers of "n" are not reset.
gxsXMLSubElement *gxsXMLNode_t::GetLastSubElement()Returns a pointer to the last sub-element, or a null value if the list is empty.
gxsXMLSubElement *gxsXMLNode_t::GetSubElements()Returns a pointer to the first sub-element, or a null value if the list is empty. Follow the next pointers to walk the list.
int gxsXMLNode_t::HasSiblings()Returns the value of the has_siblings flag.
int gxsXMLNode_t::HasSubElements()Returns 1 if the node has any sub-elements, or 0 if not.
void gxsXMLNode_t::insert_after(gxsXMLSubElement *pos, gxsXMLSubElement *n)Links "n" into the list after "pos". If the list is empty, "n" becomes the only entry and "pos" is ignored. The node takes ownership of "n".
void gxsXMLNode_t::insert_before(gxsXMLSubElement *pos, gxsXMLSubElement *n)Links "n" into the list before "pos". If the list is empty, "n" becomes the only entry and "pos" is ignored. The node takes ownership of "n".
gxsXMLSubElement::gxsXMLSubElement()Constructor. Sets the next, prev and element pointers to null. The structure has no destructor, so it does not delete its element by itself.
void gxsXMLElement::Clear()Removes all elements from the list.
void gxsXMLElement::Copy(const gxsXMLElement &ob)Clears the list and fills it with copies of the other object's elements.
void gxsXMLNode::Clear()Sets node_type and element_type to their NOT_SET values, clears name, data, attributes and child_nodes, and sets sax_end_parse to 0.
void gxsXMLNode::Copy(const gxsXMLNode &ob)Copies all members, including the child nodes, from another node.
Example
The following example is based on examples_general/xml_parser/testprog.cpp. The first function loads a file and walks the root, parent and child nodes. The second function builds a small tree and writes it to a file.
#include <iostream>
using namespace std;
#include "gxsxml.h"
int ReadXML(const char *fname)
{
gxsXML xml;
if(!xml.LoadXMLFile(fname)) {
cout << "Error loading XML file" << "\n";
cout << xml.GetError() << "\n";
return 0;
}
cout << "Version: " << xml.doc_info.xml_version_str << "\n";
cout << "Encoding: " << xml.doc_info.xml_encoding << "\n";
gxListNode<gxsXMLNode> *lptr = xml.node_tree.GetHead();
while(lptr) {
if(lptr->data.node_type == gxsXML_ROOT_NODE) {
cout << "Root: " << lptr->data.name.c_str() << "\n";
}
if(lptr->data.node_type == gxsXML_PARENT_NODE) {
cout << " Parent: " << lptr->data.name.c_str()
<< " = " << lptr->data.data.c_str() << "\n";
gxListNode<gxsXMLNode_t> *cptr =
lptr->data.child_nodes.elements.GetHead();
while(cptr) {
cout << " Child: " << cptr->data.name.c_str()
<< " = " << cptr->data.data.c_str() << "\n";
gxsXMLSubElement *subptr = cptr->data.GetSubElements();
while(subptr) {
cout << " Sub-element: " << subptr->element->name.c_str()
<< " = " << subptr->element->data.c_str() << "\n";
subptr = subptr->next;
}
cptr = cptr->next;
}
}
lptr = lptr->next;
}
return 1;
}
int WriteTestTree(const char *outfile)
{
gxsXML xml;
xml.Clear(); // Clear the tree and headers before creating a new tree
gxsXMLNode *root_node = xml.AddRoot("xmlroot");
if(!root_node) {
cout << xml.GetError() << "\n";
return 0;
}
// Parent nodes with no data, with data, and with child nodes
gxsXMLNode *n = xml.AddParent("Author");
n = xml.AddParent("publisher", 0, "DataReel Software Development Group");
n = xml.AddParent("support1", "link=\"http://www.example.com\"");
xml.AddChild(n, "email", 0, "example@example.com");
xml.AddChild(n, "name", 0, "support");
// A child with nested sub-elements
n = xml.AddParent("subtree_depth_test");
gxsXMLNode_t *element = xml.AddChild(n, "email", "accountinfo=\"datareel\"");
xml.AddSubChild(element, "username", 0, "uname");
gxsXMLSubElement *sub_element = xml.AddSubChild(element, "server");
xml.AddSibling(element, sub_element, "hostname", 0, "pop3 host");
gxsXMLSubElement *sibling = xml.AddSibling(element, sub_element, "pop3");
xml.AddSibling(element, sibling, "port", 0, "default");
xml.AddSibling(element, sibling, "usessl", 0, "yes");
if(!xml.WriteXMLFile(outfile)) {
cout << "Error writing XML file" << "\n";
cout << xml.GetError() << "\n";
return 0;
}
return 1;
}
WriteTestTree() writes the following file:
<?xml version="1.0" encoding="utf-8"?>
<xmlroot>
<Author />
<publisher>DataReel Software Development Group</publisher>
<support1 link="http://www.example.com">
<email>example@example.com</email>
<name>support</name>
</support1>
<subtree_depth_test>
<email accountinfo="datareel">
<username>uname</username>
<server>
<hostname>pop3 host</hostname>
<pop3>
<port>default</port>
<usessl>yes</usessl>
</pop3>
</server>
</email>
</subtree_depth_test>
</xmlroot>