pugixml documentation |
pugixml is just another XML parser. This is a successor to pugxml (well, to be honest, the only part that is left as is is wildcard matching code, the rest was either heavily refactored or rewritten from scratch). The main features (call it USP) are:
Okay, you might ask - what's the catch? Everything is so cute - it's small, fast, robust, clean solution for parsing XML. What is missing? Ok, we are fair developers - so here is a misfeature list:
pugixml is a DOM-based parser. This means, that the XML document is converted to a tree. Each XML tag is converted to a node in DOM tree. If a tag is contained in some other tag, its node is a child to the outer tag's one. Comments, CDATA sections and PIs (Processing Instructions) also are transformed into tree nodes, as is the standalone text. Each node has its type.
Here is an example of an XML document:
<?xml version="1.0"?> <mesh name="mesh_root"> <!-- here is a mesh node --> some text <![CDATA[[someothertext]]> some more text <node attr1="value1" /> <node attr1="value2"> <?TARGET somedata?> <innernode/> </node> </mesh>It gets converted to the following tree (note, that with some parsing options comments, PIs and CDATA sections are not stored in the tree, and with some options there are also nodes with whitespaces and the contents of PCDATA sections is a bit different (with trailing/leading whitespaces). So generally the resulting DOM tree depends on the parsing options):
The parent-children relations are shown with lines. Some nodes have previous and next siblings (for example, the next sibling for node_comment node is node_pcdata with value "some text", and the previous sibling for node_element with name "mesh" is node_pi with target "xml" (target for PI nodes is stored in the node name)).
pugixml is a library for parsing XML files, which means that you give it XML data some way, and it gives you the DOM tree and the ways to traverse it and to get some useful information from it. The library source consist of two files, the header pugixml.hpp, and the source code pugixml.cpp. You can either compile cpp file in your project, or build a static library (or perhaps even a DLL), or make the whole code use inline linkage and make one big file (as it was done in pugxml). All library classes reside in namespace pugi, so you can either use fully qualified names (pugi::xml_node) or write a using declaration (using namespace pugi;, using pugi::xml_node) and use plain names. All classes have the xml_ prefix.
By default it's supposed that you compile the source file with your project (add it into your project, or add relevant entry in your Makefile, or do whatever you need to do with your compilation environment). The library is written in standard-conformant C++ and was tested on win32 platform (MSVC 7.1 (2003), MSVC 8.0 (2005)).
xml_parser class is the core of parsing process; you initiate parsing with it, you get DOM tree from it, the nodes and attributes are stored in it. You have two ways to load a file: either provide a string with XML-data (it has to be null-terminated, and it will be modified during parsing process, so it can not be a piece of read-only memory), or with an std::istream object (any input stream, like std::ifstream, std::istringstream, etc.) - in this case the parser will allocate the necessary amount of memory (equivalent to stream's size) and read everything from the stream.
The functions for parsing are:
void parse(std::istream& stream, unsigned int optmsk = parse_noset); |
stream
,
read the chunk of data from the stream and parse it with provided options (optmsk
).
The stream does not have to persist after the call to the function, the lifetime of internal buffer
with stream's data is managed by pugixml.
char* parse(char* xmlstr, unsigned int optmsk = parse_noset); |
xml_parser(std::istream& stream, unsigned int optmsk = parse_default); |
xml_parser(char* xmlstr, unsigned int optmsk = parse_default); |
If you want to provide XML data after the creation of the parser, use the default ctor. Otherwise you are free to use either parsing ctors or default ctor and later - parsing function.
After parsing an XML file, you'll get a DOM tree. To get access to it (or, more precisely, to its root), call either document() function or cast xml_parser object to xml_node by using the following functions:
operator xml_node() const; xml_node document() const; |
Ok, easy part behind - now let's dive into parsing options. There is a variety of them, and you must choose them wisely to get the needed results and the best speed/least memory overhead. At first, there are flags that determine which parts of the document will be put into DOM tree, and which will be just skipped:
Then there are flags that determine how the processing of the retrieved data is done. There are several reasons for these flags, mainly:
Finally, there are two more flags, that indicate closing tag parsing. When pugixml meets a close tags, there are three ways:
Did I say finally? Ok, so finally there are some helper flags, or better groups of flags. These are:
A couple of words on flag usage. The parsing options are just a set of bits, with each bit corresponding to one flag. You can turn the flag on by OR-ing the options value with this flag's constant:
parse_w3c | parse_wnorm_pcdataor turn the flag off by AND-ing the options value with the NEGation of this flag's constant:
parse_w3c & ~parse_commentsYou can access the current options of parser by options() method:
unsigned int options() const; unsigned int options(unsigned int optmsk); |
If xml_parser is a heart of constructing a DOM tree from file, xml_node is a heart of processing the tree. This is a simple wrapper, so it's small (4/8 bytes, depending on the size of pointer), you're free to copy it and it does not own anything. I'll continue with a list of methods with their description, with one note in advance. Some functions, that do something according to a string-like parameter, have a pair with a suffix _w. The _w suffix tells, that this function is doing a wildcard matching, instead of simple string comparison. You're free to use wildcards * (that is equal to any sequence of characters (possibly empty)), ? (that is equal to any character) and character sets ([Abc] means 'any symbol of A, b and c', [A-Z4] means 'any symbol from A to Z, or 4', [!0-9] means 'any symbol, that is not a digit'). So the wildcard ?ell_[0-9][0-9]_* will match strings like 'cell_23_xref', 'hell_00_', but will not match the strings like 'ell_23_xref', 'cell_0_x' or 'cell_0a_x'.
/// Access iterators for this node's collection of child nodes. iterator begin() const; iterator end() const; /// Access iterators for this node's collection of child nodes (same as begin/end). iterator children_begin() const; iterator children_end() const; /// Access iterators for this node's collection of attributes. attribute_iterator attributes_begin() const; attribute_iterator attributes_end() const; /// Access iterators for this node's collection of siblings. iterator siblings_begin() const; iterator siblings_end() const; |
Functions, returning the iterators to walk through children/siblings/attributes. More on that in Iterators section.
operator unspecified_bool_type() const; |
This is a safe bool-like conversion operator. You can check node's validity (if (xml_node), if (!xml_node), if (node1 && node2 && !node3 && cond1 && ...) - you get the idea) with it.
bool operator==(const xml_node& r) const; bool operator!=(const xml_node& r) const; bool operator<(const xml_node& r) const; bool operator>(const xml_node& r) const; bool operator<=(const xml_node& r) const; bool operator>=(const xml_node& r) const; |
Comparison operators
bool empty() const; |
if (node.empty())
is equivalent to if (!node)
xml_node_type type() const; const char* name() const; const char* value() const; |
Access node's properties (type, name and value). If there is no name/value, the corresponding functions return "" - they never return NULL.
xml_node child(const char* name) const; xml_node child_w(const char* name) const; |
Get a child node with specified name, or xml_node() (this is an invalid node) if nothing is found
xml_attribute attribute(const char* name) const; xml_attribute attribute_w(const char* name) const; |
Get an attribute with specified name, or xml_attribute() (this is an invalid attribute) if nothing is found
xml_node sibling(const char* name) const; xml_node sibling_w(const char* name) const; |
Get a node's sibling with specified name, or xml_node() if nothing is found.
node.sibling(name)
is equivalent to node.parent().child(name)
.
xml_node next_sibling(const char* name) const; xml_node next_sibling_w(const char* name) const; xml_node next_sibling() const; |
These functions get the next sibling, that is, one of the siblings of that node, that is to the
right. next_sibling()
just returns the right brother of the node (or xml_node()),
the two other functions are searching for the sibling with the given name
xml_node previous_sibling(const char* name) const; xml_node previous_sibling_w(const char* name) const; xml_node previous_sibling() const; |
These functions do exactly the same as next_sibling
ones, with the exception that they
search for the left siblings.
xml_node parent() const; |
Get a parent node. The parent node for the root one (the document) is considered to be the document itself.
const char* child_value() const; |
Look for the first node of type node_pcdata or node_cdata among the children of the current node and return its contents (or "" if nothing is found)
const char* child_value(const char* name) const; |
This is the convenient way of looking into child's child value - that is, node.child_value(name) is equivalent to node.child(name).child_value().
const char* child_value_w(const char* name) const; |
This is the convenient way of looking into child's child value - that is, node.child_value_w(name) is equivalent to node.child_w(name).child_value().
xml_attribute first_attribute() const; xml_attribute last_attribute() const; |
These functions get the first and last attributes of the node (or xml_attribute() if the node has no attributes).
xml_node first_child() const; xml_node last_child() const; |
These functions get the first and last children of the node (or xml_node() if the node has no children).
template <typename OutputIterator> void all_elements_by_name(const char* name, OutputIterator it) const; template <typename OutputIterator> void all_elements_by_name_w(const char* name, OutputIterator it) const; |
Get all elements with the specified name in the subtree (depth-first search) and return them with the help of output iterator (i.e. std::back_inserter)
template <typename Predicate> xml_attribute find_attribute(Predicate pred) const; template <typename Predicate> xml_node find_child(Predicate pred) const; template <typename Predicate> xml_node find_element(Predicate pred) const; |
Find attribute, child or a node in the subtree (find_element - depth-first search) with the help of the given predicate. Predicate should behave like a function which accepts a xml_node or xml_attribute (for find_attribute) parameter and returns bool. The first entity for which the predicate returned true is returned. If predicate returned false for all entities, xml_node() or xml_attribute() is returned.
xml_node first_element(const char* name) const; xml_node first_element_w(const char* name) const; xml_node first_element_by_value(const char* name, const char* value) const; xml_node first_element_by_value_w(const char* name, const char* value) const; xml_node first_element_by_attribute(const char* name, const char* attr_name, const char* attr_value) const; xml_node first_element_by_attribute_w(const char* name, const char* attr_name, const char* attr_value) const; xml_node first_element_by_attribute(const char* attr_name, const char* attr_value) const; xml_node first_element_by_attribute_w(const char* attr_name, const char* attr_value) const; |
Find the first node (depth-first search), which corresponds to the given criteria (i.e. either has a matching name, or a matching value, or has an attribute with given name/value, or has an attribute and has a matching name). Note that _w versions treat all parameters as wildcards.
xml_node first_node(xml_node_type type) const; |
Return a first node (depth-first search) with a given type, or xml_node().
std::string path(char delimiter = '/') const; |
Get a path of the node (i.e. the string of names of the nodes on the path from the DOM tree root to the node, separated with delimiter (/ by default).
xml_node first_element_by_path(const char* path, char delimiter = '/') const; |
Get the first element that has the following path. The path can be absolute (beginning with delimiter) or relative, '..' means 'up-level' (so if we are at the path mesh/fragment/geometry/stream, ../.. will lead us to mesh/fragment, and /mesh will lead us to mesh).
bool traverse(xml_tree_walker& walker) const; |
Traverse the subtree (beginning with current node) with the walker, return the result. See Miscellaneous section for details.
Like xml_node, xml_attribute is a simple wrapper of the node's attribute.
bool operator==(const xml_attribute& r) const; bool operator!=(const xml_attribute& r) const; bool operator<(const xml_attribute& r) const; bool operator>(const xml_attribute& r) const; bool operator<=(const xml_attribute& r) const; bool operator>=(const xml_attribute& r) const; |
Comparison operators.
operator unspecified_bool_type() const; |
Safe bool conversion - like in xml_node, use this to check for validity.
bool empty() const; |
Like with xml_node, if (attr.empty())
is equivalent to if (!attr)
.
xml_attribute next_attribute() const; xml_attribute previous_attribute() const; |
Get the next/previous attribute of the node, that owns the current attribute. Return xml_attribute() if no such attribute is found.
const char* name() const; const char* value() const; |
Get the name and value of the attribute. These methods never return NULL - they return "" instead.
int as_int() const; double as_double() const; float as_float() const; |
Convert the value of an attribute to the desired type. If the conversion is not successfull, return default value (0 for int, 0.0 for double, 0.0f for float). These functions rely on CRT functions ato*.
bool as_bool() const; |
Convert the value of an attribute to bool. This method returns true if the first character of the value is '1', 't', 'T', 'y' or 'Y'. Otherwise it returns false.
Sometimes you have to cycle through the children or the attributes of the node. You can do it either by using next_sibling, previous_sibling, next_attribute and previous_attribute (along with first_child, last_child, first_attribute and last_attribute), or you can use an iterator-like interface. There are two iterator types, xml_node_iterator and xml_attribute_iterator. They are bidirectional constant iterators, which means that you can either increment or decrement them, and use dereferencing and member access operators to get constant access to node/attribute (the constness of iterators may change with the introducing of mutable trees).
In order to get the iterators, use corresponding functions of xml_node. Note that _end() functions return past-the-end iterator, that is, in order to get the last attribute, you'll have to do something like:
if (node.attributes_begin() != node.attributes_end()) // we have at least one attribute { xml_attribute last_attrib = *(--node.attributes_end()); ... } |
If you want to traverse a subtree, you can use traverse function. There is a class xml_tree_walker, which has some functions that you can override in order to get custom traversing (the default one just does nothing).
virtual bool begin(const xml_node&); virtual bool end(const xml_node&); |
These functions are called when the processing of the node starts/ends. First begin() is called, then all children of the node are processed recursively, then end() is called. If any of these functions returns false, the traversing is stopped and the traverse() function returns false.
virtual void push(); virtual void pop(); |
These functions are called before and after the processing of node's children. If node has no children, none of these is called. The default behavior is to increment/decrement current node depth.
virtual int depth() const; |
Get the current depth. You can use this function to do your own indentation, for example.
Lets get to some minor notes. You can safely write something like:
bool value = node.child("stream").attribute("compress").as_bool(); |
As parsing is done in-situ, the XML data is to persist during the lifetime of xml_parser. If the parsing is called via a function of xml_parser, that accepts char*, you have to ensure yourself, that the string will outlive the xml_parser object.
The memory for nodes and attributes is allocated in blocks of data (the blocks form a linked list; the default size of the block is 32 kb, though you can change it via changing a memory_block_size constant in pugixml.hpp file. Remember that the first block is allocated on stack (it resides inside xml_parser object), and all subsequent blocks are allocated on heap, so expect a stack overflow when setting too large memory block size), so the xml_parser object (which contains the blocks) should outlive all xml_node and xml_attribute objects (as well as iterators), which belong to the parser's tree. Again, you should ensure it yourself.
Ok, so you are not much of documentation reader, are you? So am I. Let's assume that you're going to parse an xml file... something like this:
<?xml version="1.0" encoding="UTF-8"?> <mesh name="Cathedral"> <fragment name="Cathedral"> <geometry> <stream usage="main" source="StAnna.dmesh" compress="true" /> <stream usage="ao" source="StAnna.ao" /> </geometry> </fragment> <fragment name="Cathedral"> ... </fragment> ... </mesh>
<mesh> is a root node, it has 0 or more <fragment>s, each of them has a <geometry> node, and there are <stream> nodes with the shown attributes. We'd like to parse the file and... well, and do something with it's contents. There are several methods of doing that; I'll show 2 of them (the remaining one is using iterators).
Here we exploit the knowledge of the strict hierarchy of our XML document and read the nodes from DOM tree accordingly. When we have an xml_node object, we can get the desired information from it (name, value, attributes list, nearby nodes in a tree - siblings, parent and children).
#include <fstream> #include <vector> #include <algorithm> #include <iterator> #include "pugixml.hpp" using namespace pugi; int main() { std::ifstream in("mesh.xml"); in.unsetf(std::ios::skipws); std::vector<char> buf; std::copy(std::istream_iterator<char>(in), std::istream_iterator<char>(), std::back_inserter(buf)); buf.push_back(0); // zero-terminate xml_parser parser(&buf[0], pugi::parse_w3c); xml_node doc = parser.document(); if (xml_node mesh = doc.first_element("mesh")) { // store mesh.attribute("name").value() for (xml_node fragment = mesh.first_element("fragment"); fragment; fragment = fragment.next_sibling()) { // store fragment.attribute("name").value() if (xml_node geometry = fragment.first_element("geometry")) for (xml_node stream = geometry.first_element("stream"); stream; stream = stream.next_sibling()) { // store stream.attribute("usage").value() // store stream.attribute("source").value() if (stream.attribute("compress")) // store stream.attribute("compress").as_bool() } } } } |
We can also write a class that will traverse the DOM tree and store the information from nodes based on their names, depths, attributes, etc. This way is well known by the users of SAX parsers. To do that, we have to write an implementation of xml_tree_walker interface
#include <fstream> #include <vector> #include <algorithm> #include <iterator> #include "pugixml.hpp" using namespace pugi; struct mesh_parser: public xml_tree_walker { virtual bool begin(const xml_node& node) { if (strcmp(node.name(), "mesh") == 0) { // store node.attribute("name").value() } else if (strcmp(node.name(), "fragment") == 0) { // store node.attribute("name").value() } else if (strcmp(node.name(), "geometry") == 0) { // ... } else if (strcmp(node.name(), "stream") == 0) { // store node.attribute("usage").value() // store node.attribute("source").value() if (node.attribute("compress")) // store stream.attribute("compress").as_bool() } else return false; return true; } }; int main() { std::ifstream in("mesh.xml"); in.unsetf(std::ios::skipws); std::vector<char> buf; std::copy(std::istream_iterator<char>(in), std::istream_iterator<char>(), std::back_inserter(buf)); buf.push_back(0); // zero-terminate xml_parser parser(&buf[0], pugi::parse_w3c); mesh_parser mp; if (!parser.document().traverse(mp)) // generate an error } |
So, let's talk a bit about parsing process, and about the reason for providing XML data as a contiguous writeable block of memory. Parsing is done in-situ. This means, that the strings, representing the parts of DOM tree (node names, attribute names and values, CDATA content, etc.) are not separately allocated on heap, but instead are parts of the original data. This is the keypoint to parsing speed, because it helps achieve the minimal amount of memory allocations (more on that below) and minimal amount of copying data.
In-situ parsing can be done in two ways, with zero-segmenting the string (that is, set the past-the-end character for the part of XML string to 0, see this image for further details), and storing pointer + size of the string instead of pointer to the beginning of ASCIIZ string.
Originally, pugxml had only the first way, but then authors added the second method, 'non-segmenting' or non-destructive parsing. The advantages of this method are: you no longer need non-constant storage; you can even read data from memory-mapped files directly. Well, there are disadvantages. For one thing, you can not do any of the transformations in-situ. The transformations that are required by XML standard are:
In order to be able to modify the tree (change attribute/node names & values) with in-situ parsing, one needs to implement two ways of storing data (both in-situ and not). The DOM tree is now mutable, but it will change in the future releases (without introducing speed/memory overhead, except on clean- up stage).
The parsing process itself is more or less straightforward, when you see it - but the impression is fake, because the explicit jumps are made (i.e. we know, that if we come to a closing brace (>), we should expect CDATA after it (or a new tag), so let's just jump to the corresponding code), and, well, there can be bugs (see Bugs section).
And, to make things worse, memory allocation (which is done only for node and attribute structures) is done in pools. The pools are single-linked lists with predefined block size (32 kb by default), and well, it increases speed a lot (allocations are slow, and the memory gets fragmented when allocating a bunch of 16-byte (attribute) or 40-byte (node) structures)
pugixml is not a compliant XML parser. The main reason for that is that it does not reject most malformed XML files. The more or less complete list of incompatibilities follows (I will be talking of ones when using parse_w3c mode):
This table summarizes the comparison in terms of time and memory consumption between pugixml and other parsers. For DOM parsers (all, except Expat, irrXML and SAX parser of XercesC), the process is as follows:
For SAX parsers, the parse step is skipped (hence the N/A in relevant table cells), structure is filled during 'walk' step.
For all parsers, 'total time' column means total time spent on the whole process, 'total allocs' - total allocation count, 'total memory' - peak memory consumption for the whole process.
The tests were performed on a 1 Mb XML file with a small amount of text. They were compiled with Microsoft Visual C++ 8.0 (2005) compiler in Release mode, with checked iterators/secure STL turned off. The test system is AMD Sempron 2500+, 512 Mb RAM.
parser | parse time | parse allocs | parse memory | walk time | walk allocs | total time | total allocs | total memory |
---|---|---|---|---|---|---|---|---|
irrXML | N/A | N/A | N/A | 352 Mclocks | 697 245 | 356 Mclocks | 697 284 | 906 kb |
Expat | N/A | N/A | N/A | 97 Mclocks | 19 | 97 Mclocks | 23 | 1028 kb |
TinyXML | 168 Mclocks | 50 163 | 5447 kb | 37 Mclocks | 0 | 242 Mclocks | 50 163 | 5447 kb |
PugXML | 100 Mclocks | 106 597 | 2747 kb | 38 Mclocks | 0 | 206 Mclocks | 131 677 | 2855 kb |
XercesC SAX | N/A | N/A | N/A | 411 Mclocks | 70 380 | 411 Mclocks | 70 495 | 243 kb |
XercesC DOM | 300 Mclocks | 30 491 | 9251 kb | 65 Mclocks | 1 | 367 Mclocks | 30 492 | 9251 kb |
pugixml | 17 Mclocks | 40 | 2154 kb | 14 Mclocks | 0 | 32 Mclocks | 40 | 2154 kb |
pugixml (test of non-destructive parsing) | 12 Mclocks | 51 | 1632 kb | 21 Mclocks | 0 | 34 Mclocks | 51 | 1632 kb |
Note, that non-destructive parsing mode was just a test and is not yet in pugixml.
I'm always open for questions; feel free to write them to arseny.kapoulkine@gmail.com.
I'm always open for bug reports; feel free to write them to arseny.kapoulkine@gmail.com. Please provide as much information as possible - version of pugixml, compiling and OS environment (compiler and it's version, STL version, OS version, etc.), the description of the situation in which the bug arises, the code and data files that show the bug, etc. - the more, the better. Though, please, do not send executable files.
Note, that you can also submit bug reports/suggestions at project page.
Here are some improvements that will be done in future versions (they are sorted by priority, the upper ones will get there sooner).
The pugixml parser is distributed under the MIT license:
Copyright (c) 2006 Arseny Kapoulkine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Revised 7 November, 2006
© Copyright Arseny Kapoulkine 2006. All Rights Reserved.