From 69cc3fcb3a28d4b7f69dfa5f4dcc025eb53332d8 Mon Sep 17 00:00:00 2001 From: "arseny.kapoulkine" Date: Mon, 6 Nov 2006 18:31:28 +0000 Subject: pugixml 0.1: sources and documentation git-svn-id: http://pugixml.googlecode.com/svn/trunk@2 99668b35-9821-0410-8761-19e4c4f06640 --- docs/index.html | 993 +++++++++++++++++++++++++++++ docs/tree.png | Bin 0 -> 16103 bytes src/pugixml.cpp | 1868 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/pugixml.hpp | 619 ++++++++++++++++++ 4 files changed, 3480 insertions(+) create mode 100644 docs/index.html create mode 100644 docs/tree.png create mode 100644 src/pugixml.cpp create mode 100644 src/pugixml.hpp diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..f56af74 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,993 @@ + + + +pugixml documentation + + + + + + +
+

pugixml documentation

+
+
+

Contents

+
+
Introduction
+
Document Object Model
+
Documentation +
Introduction
+
xml_parser class
+
xml_node class
+
xml_attribute class
+
Iterators
+
Miscellaneous
+
Lifetime issues and memory management
+ +
Parsing process
+
W3C compliance
+
FAQ
+
Bugs
+
Future work
+
Changelog
+
Acknowledgements
+
License
+
+ +
+ + +

Introduction

+

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:

+ + + +
+ +1 The tests were done on a 1 mb XML file with a 4 levels deep tree +with a small amount of text. The times are that of building DOM tree.
+
2 Obviously, you can't estimate time of building DOM tree for a +SAX parser, so the times of reading the data into storage that closely represented the structure of +an XML file were measured. + +
+ +
+

Document Object Model

+ +

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)).

+
+ +
+

Documentation

+ +
+

Introduction

+ +

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

+ +

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);
_Winnie C++ Colorizer
+
This function will create a buffer with the size equal to that of provided 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);
+
_Winnie C++ Colorizer
+
This function parses the provided string with provided options, and returns the position where the +parsing stopped (do not expect, that parsing will stop on every error, or on most of them - as I've +said, pugixml is error ignorant). The input string is modified. The string must persist for the +lifetime of the parser. + +
 
+
+

+        xml_parser(std::istream& stream, unsigned int optmsk = parse_default);
_Winnie C++ Colorizer
+
Just a convenience ctor, that calls the corresponding parse() function.
+ +
 
+
+

+        xml_parser(char* xmlstr, unsigned int optmsk = parse_default);
_Winnie C++ Colorizer
+
Just a convenience ctor, that calls the corresponding parse() function.
+ +
+ +

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;
+
_Winnie C++ Colorizer
+ +

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: +

+So, these are the processing flags: +

+ + + +

Finally, there are two more flags, that indicate closing tag parsing. When pugixml meets a +close tags, there are three ways: +

+Note, that these 2 flags are mutually exclusive. +

+ +

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_pcdata
+
+or turn the flag off by AND-ing the options value with the NEGation of this flag's constant: +
+	parse_w3c & ~parse_comments
+
+You can access the current options of parser by options() method: +

+        unsigned int options() const;
+        unsigned int options(unsigned int optmsk);
+
_Winnie C++ Colorizer
+(the latter one returns previous options). These options are used when parse_noset flag set is +passed to parse() functions (which is the default value of corresponding parameter). +

+ +
+

xml_node class

+ +

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;
+
_Winnie C++ Colorizer
+ +

Functions, returning the iterators to walk through children/siblings/attributes. More on that in +Iterators section.

+ +

+        operator unspecified_bool_type() const;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

Comparison operators

+ +

+        bool empty() const;
+
_Winnie C++ Colorizer
+ +

if (node.empty()) is equivalent to if (!node)

+ +

+        xml_node_type type() const;
+        const char* name() const;
+        const char* value() const;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

These functions do exactly the same as next_sibling ones, with the exception that they +search for the left siblings.

+ +

+        xml_node parent() const;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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)

+ +

+        xml_attribute first_attribute() const;
+        xml_attribute last_attribute() const;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

Return a first node (depth-first search) with a given type, or xml_node().

+ +

+        std::string path(char delimiter = '/') const;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

Traverse the subtree (beginning with current node) with the walker, return the result. See +Miscellaneous section for details.

+ + +

xml_attribute class

+ +

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;
+
_Winnie C++ Colorizer
+ +

Comparison operators.

+ +

+        operator unspecified_bool_type() const;
+
_Winnie C++ Colorizer
+ +

Safe bool conversion - like in xml_node, use this to check for validity.

+ +

+        bool empty() const;
+
_Winnie C++ Colorizer
+ +

Like with xml_node, if (attr.empty()) is equivalent to if (!attr). +

+ +

+        xml_attribute next_attribute() const;
+        xml_attribute previous_attribute() const;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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.

+ +
+

Iterators

+ +

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());
+        ...
+    }
+
_Winnie C++ Colorizer
+

+ +
+

Miscellaneous

+ +

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&);
+
_Winnie C++ Colorizer
+ +

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();
+
_Winnie C++ Colorizer
+ +

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;
+
_Winnie C++ Colorizer
+ +

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();
+
_Winnie C++ Colorizer
+ +If node has a child with the name 'geometry', and this child has an attribute 'compress', than everything +is ok. If node has a child with the name 'geometry' with no attribute 'compress', then attribute("compress") +will return xml_attribute(), and the corresponding call to as_bool() will return default value (false). +If there is no child node 'geometry', the child(...) call will return xml_node(), the subsequent call +to attribute(...) will return xml_attribute() (because there are no attributes belonging to invalid +node), and as_bool() will again return false, so this call sequence is perfectly safe.

+ +
+

Lifetime issues and memory management

+ +

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.

+ +
+ +
+

Example

+ +

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()
+    
+                }
+        }
+    }
+}
+
_Winnie C++ Colorizer
+ +

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
+}
+
_Winnie C++ Colorizer
+ +
+ +
+

Parsing process

+ +

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: +

+ +None of these can be done in-situ. pugxml did neither character nor entity reference expansion, +and allocated new memory when normalizing white spaces when in non-destructive mode. I chose complete +in-situ parsing (the good thing about it is that any transformation, except entity reference, can be +done in-situ because it does not increase the amount of characters - even converting a character +reference to UTF-8). There is no entity reference expansion because of this and because I do not want +to parse DOCTYPE and, moreover, use DOCTYPE in following parsing (performing selective whitespace +normalization in attributes and CDATA sections and so on).

+ +

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)

+ +
+ + +

W3C compliance

+ +

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): + +

+ +In short, it accepts most malformed XML files and does not do anything that is related to DOCTYPE. +This is because the main goal was developing fast, easy-to-use and error ignorant (so you can always +get something even from a malformed document) parser, there are some good validating and conformant +parsers already.

+ +
+ +
+

FAQ

+ +

I'm always open for questions; feel free to write them to zeux@mathcentre.com. +

+ +
+ + +

Bugs

+ +

I'm always open for bug reports; feel free to write them to zeux@mathcentre.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.

+ +
+ + +

Future work

+ +

Here are some improvements that will be done in future versions (they are sorted by priority, the +upper ones will get there sooner).

+ + + +
+ +
+

Changelog

+ +
+
15.07.2006 - v0.1 +
First private release for testing purposes + +
+ +
+ +
+

Acknowledgements

+ +
+ +
+ + +

License

+ +

The pugixml parser is released into the public domain (though this may change).

+ +
+ +

Revised 15 July, 2006

+

© Copyright Zeux 2006. All Rights Reserved.

+ + diff --git a/docs/tree.png b/docs/tree.png new file mode 100644 index 0000000..14d48d6 Binary files /dev/null and b/docs/tree.png differ diff --git a/src/pugixml.cpp b/src/pugixml.cpp new file mode 100644 index 0000000..1018bda --- /dev/null +++ b/src/pugixml.cpp @@ -0,0 +1,1868 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Pug Improved XML Parser - Version 0.1 +// -------------------------------------------------------- +// Copyright (C) 2006, by Zeux (zeux@mathcentre.com) +// This work is based on the pugxml parser, which is: +// Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) +// Released into the Public Domain. Use at your own risk. +// See pugxml.xml for further information, history, etc. +// Contributions by Neville Franks (readonly@getsoft.com). +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "pugixml.hpp" + +namespace pugi +{ + /// A 'name=value' XML attribute structure. + struct xml_attribute_struct + { + /// Default ctor + xml_attribute_struct(); + + char* name; ///< Pointer to attribute name. + char* value; ///< Pointer to attribute value. + + xml_attribute_struct* prev_attribute; ///< Previous attribute + xml_attribute_struct* next_attribute; ///< Next attribute + }; + + /// An XML document tree node. + struct xml_node_struct + { + /// Default ctor + /// \param type - node type + xml_node_struct(xml_node_type type = node_element); + + xml_node_type type; ///< Node type; see xml_node_type. + xml_node_struct* parent; ///< Pointer to parent + + char* name; ///< Pointer to element name. + char* value; ///< Pointer to any associated string data. + + xml_node_struct* first_child; ///< First child + xml_node_struct* last_child; ///< Last child + + xml_node_struct* prev_sibling; ///< Left brother + xml_node_struct* next_sibling; ///< Right brother + + xml_attribute_struct* first_attribute; ///< First attribute + xml_attribute_struct* last_attribute; ///< Last attribute + }; + + class xml_allocator + { + public: + xml_allocator(xml_memory_block* root): _root(root), _count(0) + { + } + + template T* allocate() + { + void* buf = memalloc(sizeof(T)); + return new (buf) T(); + } + + template T* allocate(U val) + { + void* buf = memalloc(sizeof(T)); + return new (buf) T(val); + } + + private: + xml_memory_block* _root; + unsigned int _count; + + void* memalloc(size_t size) + { + if (_root->size + size <= memory_block_size) + { + void* buf = _root->data + _root->size; + _root->size += size; + return buf; + } + else + { + _root->next = new xml_memory_block(); + _root = _root->next; + + _root->size = size; + + ++_count; + + return _root->data; + } + } + }; +} + +namespace +{ + using namespace pugi; + + namespace utf8 + { + const unsigned char BYTE_MASK = 0xBF; + const unsigned char BYTE_MARK = 0x80; + const unsigned char BYTE_MASK_READ = 0x3F; + const unsigned char FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + } + + // Get the size that is needed for strutf16_utf8 applied to all s characters + size_t strutf16_utf8_size(const wchar_t* s) + { + size_t length = 0; + + for (; *s; ++s) + { + if (*s < 0x80) length += 1; + else if (*s < 0x800) length += 2; + else if (*s < 0x10000) length += 3; + else if (*s < 0x200000) length += 4; + } + + return length; + } + + // Write utf16 char to stream, return position after the last written char + // \param s - pointer to string + // \param ch - char + // \return position after the last char + // \rem yes, this is trom TinyXML. How would you write it the other way, without switch trick?.. + char* strutf16_utf8(char* s, unsigned int ch) + { + unsigned int length; + + if (ch < 0x80) length = 1; + else if (ch < 0x800) length = 2; + else if (ch < 0x10000) length = 3; + else if (ch < 0x200000) length = 4; + else return s; + + s += length; + + // Scary scary fall throughs. + switch (length) + { + case 4: + *--s = (char)((ch | utf8::BYTE_MARK) & utf8::BYTE_MASK); + ch >>= 6; + case 3: + *--s = (char)((ch | utf8::BYTE_MARK) & utf8::BYTE_MASK); + ch >>= 6; + case 2: + *--s = (char)((ch | utf8::BYTE_MARK) & utf8::BYTE_MASK); + ch >>= 6; + case 1: + *--s = (char)(ch | utf8::FIRST_BYTE_MARK[length]); + } + + return s + length; + } + + // Get the size that is needed for strutf8_utf16 applied to all s characters + size_t strutf8_utf16_size(const char* s) + { + size_t length = 0; + + for (; *s; ++s) + { + if (*s < 0x80 || (*s >= 0xC0 && *s < 0xFC)) ++length; + } + + return length; + } + + // Read utf16 char from utf8 stream, return position after the last read char + // \param s - pointer to string + // \param ch - char + // \return position after the last char + const char* strutf8_utf16(const char* s, unsigned int& ch) + { + unsigned int length; + + const unsigned char* str = reinterpret_cast(s); + + if (*str < utf8::BYTE_MARK) + { + ch = *str; + return s + 1; + } + else if (*str < 0xC0) + { + ch = ' '; + return s + 1; + } + else if (*str < 0xE0) length = 2; + else if (*str < 0xF0) length = 3; + else if (*str < 0xF8) length = 4; + else if (*str < 0xFC) length = 5; + else + { + ch = ' '; + return s + 1; + } + + ch = (*str++ & ~utf8::FIRST_BYTE_MARK[length]); + + // Scary scary fall throughs. + switch (length) + { + case 5: + ch <<= 6; + ch += (*str++ & utf8::BYTE_MASK_READ); + case 4: + ch <<= 6; + ch += (*str++ & utf8::BYTE_MASK_READ); + case 3: + ch <<= 6; + ch += (*str++ & utf8::BYTE_MASK_READ); + case 2: + ch <<= 6; + ch += (*str++ & utf8::BYTE_MASK_READ); + } + + return reinterpret_cast(str); + } + + struct xml_parser_impl + { + xml_allocator& alloc; + bool chartype_symbol_table[256]; + + bool chartype_symbol(char c) { return chartype_symbol_table[(unsigned char)c]; } + + static bool chartype_space(char c) { return c < '!' && c > 0; } + static bool chartype_enter(char c) { return c == '<'; } + static bool chartype_leave(char c) { return c == '>'; } + static bool chartype_close(char c) { return c == '/'; } + static bool chartype_equals(char c) { return c == '='; } + static bool chartype_special(char c) { return c == '!'; } + static bool chartype_pi(char c) { return c == '?'; } + static bool chartype_dash(char c) { return c == '-'; } + static bool chartype_quote(char c) { return c == '"' || c == '\''; } + static bool chartype_lbracket(char c) { return c == '['; } + static bool chartype_rbracket(char c) { return c == ']'; } + + template static void strconv_t(char** s) + { + if (!s || !*s) return; + + // Trim whitespaces + if (opt_trim) while (chartype_space(**s)) ++(*s); + + char* str = *s; + char* lastpos = str; + + if (!*str) return; + + while (*str) + { + if (*str == '&' && opt_escape) // & + { + char* stre = str + 1; + + switch (*stre) + { + case '#': // &#... + { + unsigned int ucsc = 0; + + ++stre; + + if (*stre == 'x') // &#x... (hex code) + { + ++stre; + + while (*stre) + { + if (*stre >= '0' && *stre <= '9') + ucsc = 16 * ucsc + (*stre++ - '0'); + else if (*stre >= 'A' && *stre <= 'F') + ucsc = 16 * ucsc + (*stre++ - 'A' + 10); + else if (*stre >= 'a' && *stre <= 'f') + ucsc = 16 * ucsc + (*stre++ - 'a' + 10); + else break; + } + + if (*stre == ';') ++stre; + } + else // &#... (dec code) + { + while (*stre >= '0' && *stre <= '9') + ucsc = 10 * ucsc + (*stre++ - '0'); + + if (*stre == ';') ++stre; + } + + str = stre; + lastpos = strutf16_utf8(lastpos, ucsc); + continue; + } + case 'a': // &a + { + ++stre; + + if (*stre == 'm') // &am + { + if (*++stre == 'p' && *++stre == ';') // & + { + *lastpos++ = '&'; + str = ++stre; + continue; + } + } + else if (*stre == 'p') // &ap + { + if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' + { + *lastpos++ = '\''; + str = ++stre; + continue; + } + } + break; + } + case 'g': // &g + { + if (*++stre == 't' && *++stre == ';') // > + { + *lastpos++ = '>'; + str = ++stre; + continue; + } + break; + } + case 'l': // &l + { + if (*++stre == 't' && *++stre == ';') // < + { + *lastpos++ = '<'; + str = ++stre; + continue; + } + break; + } + case 'q': // &q + { + if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " + { + *lastpos++ = '"'; + str = ++stre; + continue; + } + break; + } + } + } + else if (chartype_space(*str) && opt_wconv) + { + if (*str == 0x0d && *(str + 1) == 0x0a) ++str; + + ++str; + *lastpos++ = ' '; + + continue; + } + else if (*str == 0x0d && !opt_wnorm) + { + if (*(str + 1) == 0x0a) ++str; + ++str; + *lastpos++ = 0x0a; + + continue; + } + else if (chartype_space(*str) && opt_wnorm) + { + *lastpos++ = ' '; + + while (chartype_space(*str)) ++str; + + continue; + } + + *lastpos++ = *str++; + } + + if (opt_trim) + { + do *lastpos-- = 0; + while (chartype_space(*lastpos)); + } + else *lastpos = 0; + } + + static void strconv_setup(void (*&func)(char**), unsigned int opt_trim, unsigned int opt_escape, unsigned int opt_wnorm, unsigned int opt_wconv) + { + if (opt_wconv) + { + if (opt_trim) + { + if (opt_escape) + { + if (opt_wnorm) func = &strconv_t; + else func = &strconv_t; + } + else + { + if (opt_wnorm) func = &strconv_t; + else func = &strconv_t; + } + } + else + { + if (opt_escape) + { + if (opt_wnorm) func = &strconv_t; + else func = &strconv_t; + } + else + { + if (opt_wnorm) func = &strconv_t; + else func = &strconv_t; + } + } + } + else + { + if (opt_trim) + { + if (opt_escape) + { + if (opt_wnorm) func = &strconv_t; + else func = &strconv_t; + } + else + { + if (opt_wnorm) func = &strconv_t; + else func = &strconv_t; + } + } + else + { + if (opt_escape) + { + if (opt_wnorm) func = &strconv_t; + else func = &strconv_t; + } + else + { + if (opt_wnorm) func = &strconv_t; + else func = &strconv_t; + } + } + } + } + // Allocate & append a new xml_node_struct onto the given parent. + // \param parent - pointer to parent node. + // \param type - desired node type. + // \return pointer to the new node + xml_node_struct* append_node(xml_node_struct* parent, xml_node_type type = node_element) + { + if(!parent) return NULL; // Must have a parent. + + xml_node_struct* child = alloc.allocate(type); // Allocate a new child. + child->parent = parent; // Set it's parent pointer. + if (parent->last_child) + { + parent->last_child->next_sibling = child; + child->prev_sibling = parent->last_child; + parent->last_child = child; + } + else parent->first_child = parent->last_child = child; + return child; + } + + // Allocate & append a new attribute to the given xml_node_struct. + // \param node - pointer to parent node. + // \return pointer to appended xml_attribute_struct. + xml_attribute_struct* append_attribute(xml_node_struct* node) + { + if(!node) return NULL; + xml_attribute_struct* a = alloc.allocate(); + + if (node->last_attribute) + { + node->last_attribute->next_attribute = a; + a->prev_attribute = node->last_attribute; + node->last_attribute = a; + } + else node->first_attribute = node->last_attribute = a; + + return a; + } + + // Parser utilities. + #define SKIPWS() { while(chartype_space(*s)) ++s; if(*s==0) return s; } + #define OPTSET(OPT) ( optmsk & OPT ) + #define PUSHNODE(TYPE) { cursor = append_node(cursor,TYPE); } + #define POPNODE() { cursor = cursor->parent; } + #define SCANFOR(X) { while(*s!=0 && !(X)) ++s; if(*s==0) return s; } + #define SCANWHILE(X) { while((X)) ++s; if(*s==0) return s; } + #define ENDSEG() { ch = *s; *s = 0; ++s; if(*s==0) return s; } + + xml_parser_impl(xml_allocator& alloc): alloc(alloc) + { + for (unsigned int c = 0; c < 256; ++c) + { + chartype_symbol_table[c] = c > 127 || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == ':' || c == '-' || c == '.'; + } + } + + // Static single-pass in-situ parse the given xml string. + // \param s - pointer to XML-formatted string. + // \param xmldoc - pointer to root. + // \param optmsk - parse options mask. + // \return last string position or null. + char* parse(register char* s,xml_node_struct* xmldoc,unsigned int optmsk = parse_default) + { + if(!s || !xmldoc) return s; + + void (*strconv_pcdata)(char**); + void (*strconv_attribute)(char**); + + strconv_setup(strconv_attribute, OPTSET(parse_trim_attribute), OPTSET(parse_escapes_attribute), OPTSET(parse_wnorm_attribute), OPTSET(parse_wconv_attribute)); + strconv_setup(strconv_pcdata, OPTSET(parse_trim_pcdata), OPTSET(parse_escapes_pcdata), OPTSET(parse_wnorm_pcdata), false); + + char ch = 0; // Current char, in cases where we must null-terminate before we test. + xml_node_struct* cursor = xmldoc; // Tree node cursor. + char* mark = s; // Marked string position for temporary look-ahead. + while(*s!=0) + { + LOC_SEARCH: // Obliviously search for next element. + SCANFOR(chartype_enter(*s)); // Find the next '<'. + if(chartype_enter(*s)) + { + ++s; + LOC_CLASSIFY: // What kind of element? + if(chartype_pi(*s)) // 'name = mark; + + if (chartype_space(ch)) + { + SKIPWS(); + + mark = s; + } + else mark = 0; + + SCANFOR(chartype_pi(*s) && chartype_leave(*(s+1))); // Look for '?>'. + ENDSEG(); + + cursor->value = mark; + + POPNODE(); + + goto LOC_LEAVE; + } + else // Bad PI or parse_pi not set. + { + SCANFOR(chartype_pi(*s) && chartype_leave(*(s+1))); // Look for '?>'. + ++s; + goto LOC_LEAVE; + } + } + else if(chartype_special(*s)) // 'value = s; // Save the offset. + } + + // Scan for terminating '-->'. + SCANFOR(chartype_dash(*s) && chartype_dash(*(s+1)) && chartype_leave(*(s+2))); + + if (OPTSET(parse_comments)) + { + *s = 0; // Zero-terminate this segment at the first terminating '-'. + POPNODE(); // Pop since this is a standalone. + } + + s += 2; // Step over the '\0-'. + goto LOC_LEAVE; // Look for any following PCDATA. + } + } + else if(chartype_lbracket(*s)) + { + // 'value = s; // Save the offset. + // Scan for terminating ']]>'. + SCANFOR(chartype_rbracket(*s) && chartype_rbracket(*(s+1)) && chartype_leave(*(s+2))); + ENDSEG(); // Zero-terminate this segment. + + if (OPTSET(parse_eol_cdata)) + { + strconv_t(&cursor->value); + } + + POPNODE(); // Pop since this is a standalone. + } + else // Flagged for discard, but we still have to scan for the terminator. + { + // Scan for terminating ']]>'. + SCANFOR(chartype_rbracket(*s) && chartype_rbracket(*(s+1)) && chartype_leave(*(s+2))); + ++s; + } + ++s; // Step over the last ']'. + goto LOC_LEAVE; // Look for any following PCDATA. + } + continue; // Probably a corrupted CDATA section, so just eat it. + } + else if(*s=='D' && *++s=='O' && *++s=='C' && *++s=='T' && *++s=='Y' && *++s=='P' && *++s=='E') + { + ++s; + SKIPWS(); // Eat any whitespace. + LOC_DOCTYPE: + SCANWHILE(chartype_quote(*s) || chartype_lbracket(*s) || chartype_leave(*s)); + if(chartype_quote(*s)) // '...SYSTEM "..." + { + ch = *s++; + SCANFOR(*s == ch); + ++s; + goto LOC_DOCTYPE; + } + if(chartype_lbracket(*s)) // '...[...' + { + ++s; + unsigned int bd = 1; // Bracket depth counter. + while(*s!=0) // Loop till we're out of all brackets. + { + if(chartype_rbracket(*s)) --bd; + else if(chartype_lbracket(*s)) ++bd; + if(bd == 0) break; + ++s; + } + // Note: 's' now points to end of DTD, i.e.: ']'. + SCANFOR(chartype_leave(*s)); + continue; + } + // Fall-through + continue; + } + } + else if(chartype_symbol(*s)) // '<#...' + { + cursor = append_node(cursor); // Append a new node to the tree. + + cursor->name = s; + SCANWHILE(chartype_symbol(*s)); // Scan for a terminator. + ENDSEG(); // Save char in 'ch', terminate & step over. + if (*s!=0 && chartype_close(ch)) // '' + POPNODE(); // Pop. + goto LOC_LEAVE; + } + else if(*s!=0 && !chartype_space(ch)) + { + if (!chartype_leave(ch)) SCANWHILE(!chartype_leave(*s)); + if (!*s) return s; + goto LOC_PCDATA; // No attributes, so scan for PCDATA. + } + else if(*s!=0 && chartype_space(ch)) + { + SKIPWS(); // Eat any whitespace. + LOC_ATTRIBUTE: + if(chartype_symbol(*s)) // <... #... + { + xml_attribute_struct* a = append_attribute(cursor); // Make space for this attribute. + a->name = s; // Save the offset. + SCANWHILE(chartype_symbol(*s)); // Scan for a terminator. + ENDSEG(); // Save char in 'ch', terminate & step over. + if(*s!=0 && chartype_space(ch)) SKIPWS(); // Eat any whitespace. + if(*s!=0 && (chartype_equals(ch) || chartype_equals(*s))) // '<... #=...' + { + if(chartype_equals(*s)) ++s; + SKIPWS(); // Eat any whitespace. + if(chartype_quote(*s)) // '<... #="...' + { + ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. + ++s; // Step over the quote. + a->value = s; // Save the offset. + SCANFOR(*s == ch); // Scan for the terminating quote + ENDSEG(); // Save char in 'ch', terminate & step over. + + strconv_attribute(&a->value); + + if(chartype_leave(*s)) + { + ++s; + goto LOC_PCDATA; + } + else if(chartype_close(*s)) + { + ++s; + POPNODE(); + SKIPWS(); // Eat any whitespace. + goto LOC_LEAVE; + } + else if(chartype_space(*s)) // This may indicate a following attribute. + { + SKIPWS(); // Eat any whitespace. + goto LOC_ATTRIBUTE; // Go scan for additional attributes. + } + } + } + goto LOC_ATTRIBUTE; + } + + SCANWHILE(!chartype_leave(*s) && !chartype_close(*s)); + } + LOC_LEAVE: + if(chartype_leave(*s)) // '...>' + { + ++s; // Step over the '>'. + LOC_PCDATA: // '>...<' + mark = s; // Save this offset while searching for a terminator. + SKIPWS(); // Eat whitespace if no genuine PCDATA here. + // We hit a '<...', with only whitespace, so don't bother storing anything. + if((mark == s || !OPTSET(parse_ws_pcdata)) && chartype_enter(*s)) + { + if(chartype_close(*(s+1))) // 'type != node_document; + + if (preserve) + { + PUSHNODE(node_pcdata); // Append a new node on the tree. + cursor->value = s; // Save the offset. + } + + while (*s && !chartype_enter(*s)) ++s; // '...<' + + if (preserve) + { + if (*s) ENDSEG(); // Save char in 'ch', terminate & step over. + + strconv_pcdata(&cursor->value); + + POPNODE(); // Pop since this is a standalone. + } + + if (!*s) return s; + + if(chartype_enter(ch)) // Did we hit a '<...'? + { + if(chartype_close(*s)) goto LOC_CLOSE; + else if(chartype_special(*s)) goto LOC_CLASSIFY; // We hit a '' + { + POPNODE(); // Pop. + goto LOC_LEAVE; + } + } + } + // Fall-through B. + else if(chartype_close(*s)) // '.../' + { + LOC_CLOSE: + ++s; + + if (OPTSET(parse_match_end_tags)) + { + while (cursor && cursor->type != node_document) + { + char* tagname = s; + + // is it the matching tag? + char* name = cursor->name; + + if (name) + { + while (*tagname && chartype_symbol(*tagname)) + { + if (*tagname++ != *name++) goto TAG_NEXTMATCH; + } + + if (!*name) + { + // we've found matching tag + POPNODE(); + s = tagname; + break; + } + } + + TAG_NEXTMATCH: + POPNODE(); + } + } + else if (OPTSET(parse_check_end_tags)) + { + char* name = cursor->name; + if (!name) return s; + + while (*s && chartype_symbol(*s)) + { + if (*s++ != *name++) return s; + } + if (*name) return s; + + POPNODE(); // Pop. + } + else + { + SCANFOR(chartype_leave(*s)); // '...>' + POPNODE(); // Pop. + } + + SKIPWS(); + + goto LOC_LEAVE; + } + } + } + return s; + } + }; + + // Compare lhs with [rhs_begin, rhs_end) + int strcmprange(const char* lhs, const char* rhs_begin, const char* rhs_end) + { + while (*lhs && rhs_begin != rhs_end && *lhs == *rhs_begin) + { + ++lhs; + ++rhs_begin; + } + + if (rhs_begin == rhs_end && *lhs == 0) return 0; + else return 1; + } + + // Character set pattern match. + int strcmpwild_cset(const char** src, const char** dst) + { + int find = 0, excl = 0, star = 0; + + if (**src == '!') + { + excl = 1; + ++(*src); + } + + while (**src != ']' || star == 1) + { + if (find == 0) + { + if (**src == '-' && *(*src-1) < *(*src+1) && *(*src+1) != ']' && star == 0) + { + if (**dst >= *(*src-1) && **dst <= *(*src+1)) + { + find = 1; + ++(*src); + } + } + else if (**src == **dst) find = 1; + } + ++(*src); + star = 0; + } + + if (excl == 1) find = (1 - find); + if (find == 1) ++(*dst); + + return find; + } +} + +namespace pugi +{ + namespace impl + { + int strcmpwild(const char* src, const char* dst); + } +} + +namespace +{ + using namespace pugi; + + // Wildcard pattern match. + int strcmpwild_astr(const char** src, const char** dst) + { + int find = 1; + ++(*src); + while ((**dst != 0 && **src == '?') || **src == '*') + { + if(**src == '?') ++(*dst); + ++(*src); + } + while (**src == '*') ++(*src); + if (**dst == 0 && **src != 0) return 0; + if (**dst == 0 && **src == 0) return 1; + else + { + if (impl::strcmpwild(*src,*dst)) + { + do + { + ++(*dst); + while(**src != **dst && **src != '[' && **dst != 0) + ++(*dst); + } + while ((**dst != 0) ? impl::strcmpwild(*src,*dst) : 0 != (find=0)); + } + if (**dst == 0 && **src == 0) find = 1; + return find; + } + } +} + +namespace pugi +{ + namespace impl + { + // Compare two strings, with globbing, and character sets. + int strcmpwild(const char* src, const char* dst) + { + int find = 1; + for(; *src != 0 && find == 1 && *dst != 0; ++src) + { + switch (*src) + { + case '?': ++dst; break; + case '[': ++src; find = strcmpwild_cset(&src,&dst); break; + case '*': find = strcmpwild_astr(&src,&dst); --src; break; + default : find = (int) (*src == *dst); ++dst; + } + } + while (*src == '*' && find == 1) ++src; + return (find == 1 && *dst == 0 && *src == 0) ? 0 : 1; + } + } + + extern "C" + { + int strcmpwildimpl(const char* src, const char* dst) + { + return impl::strcmpwild(src, dst); + } + + typedef int (*strcmpfunc)(const char*, const char*); + } + + xml_attribute_struct::xml_attribute_struct(): name(0), value(0), prev_attribute(0), next_attribute(0) + { + } + + xml_node_struct::xml_node_struct(xml_node_type type): name(0), value(0), prev_sibling(0), next_sibling(0), first_child(0), last_child(0), first_attribute(0), last_attribute(0), type(type) + { + } + + xml_tree_walker::xml_tree_walker() : _deep(0) + { + } + + xml_tree_walker::~xml_tree_walker() + { + } + + void xml_tree_walker::push() + { + ++_deep; + } + + void xml_tree_walker::pop() + { + --_deep; + } + + int xml_tree_walker::depth() const + { + return (_deep > 0) ? _deep : 0; + } + + bool xml_tree_walker::begin(const xml_node&) + { + return true; + } + + bool xml_tree_walker::end(const xml_node&) + { + return true; + } + + xml_attribute::xml_attribute(): _attr(NULL) + { + } + + xml_attribute::xml_attribute(const xml_attribute_struct* attr): _attr(attr) + { + } + + xml_attribute::operator xml_attribute::unspecified_bool_type() const + { + return empty() ? 0 : &xml_attribute::_attr; + } + + bool xml_attribute::operator==(const xml_attribute& r) const + { + return (_attr == r._attr); + } + + bool xml_attribute::operator!=(const xml_attribute& r) const + { + return (_attr != r._attr); + } + + bool xml_attribute::operator<(const xml_attribute& r) const + { + return (_attr < r._attr); + } + + bool xml_attribute::operator>(const xml_attribute& r) const + { + return (_attr > r._attr); + } + + bool xml_attribute::operator<=(const xml_attribute& r) const + { + return (_attr <= r._attr); + } + + bool xml_attribute::operator>=(const xml_attribute& r) const + { + return (_attr >= r._attr); + } + + xml_attribute xml_attribute::next_attribute() const + { + return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute(); + } + + xml_attribute xml_attribute::previous_attribute() const + { + return _attr ? xml_attribute(_attr->prev_attribute) : xml_attribute(); + } + + int xml_attribute::as_int() const + { + if(empty() || !_attr->value) return 0; + return atoi(_attr->value); + } + + double xml_attribute::as_double() const + { + if(empty() || !_attr->value) return 0.0; + return atof(_attr->value); + } + + float xml_attribute::as_float() const + { + if(empty() || !_attr->value) return 0.0f; + return (float)atof(_attr->value); + } + + bool xml_attribute::as_bool() const + { + if(empty() || !_attr->value) return false; + if(*(_attr->value)) + { + return // Only look at first char: + ( + *(_attr->value) == '1' || // 1* + *(_attr->value) == 't' || // t* (true) + *(_attr->value) == 'T' || // T* (true|true) + *(_attr->value) == 'y' || // y* (yes) + *(_attr->value) == 'Y' // Y* (Yes|YES) + ) + ? true : false; // Return true if matches above, else false. + } + else return false; + } + + bool xml_attribute::empty() const + { + return (_attr == NULL); + } + + const char* xml_attribute::name() const + { + return (!empty() && _attr->name) ? _attr->name : ""; + } + + const char* xml_attribute::value() const + { + return (!empty() && _attr->value) ? _attr->value : ""; + } + + xml_node::xml_node(): _root(0) + { + } + + xml_node::xml_node(const xml_node_struct* p): _root(p) + { + } + + xml_node::operator xml_node::unspecified_bool_type() const + { + return empty() ? 0 : &xml_node::_root; + } + + xml_node::iterator xml_node::begin() const + { + return iterator(_root->first_child); + } + + xml_node::iterator xml_node::end() const + { + return iterator(0, _root->last_child); + } + + xml_node::iterator xml_node::children_begin() const + { + return iterator(_root->first_child); + } + + xml_node::iterator xml_node::children_end() const + { + return iterator(0, _root->last_child); + } + + xml_node::attribute_iterator xml_node::attributes_begin() const + { + return attribute_iterator(_root->first_attribute); + } + + xml_node::attribute_iterator xml_node::attributes_end() const + { + return attribute_iterator(0, _root->last_attribute); + } + + xml_node::iterator xml_node::siblings_begin() const + { + return parent().children_begin(); + } + + xml_node::iterator xml_node::siblings_end() const + { + return parent().children_end(); + } + + bool xml_node::operator==(const xml_node& r) const + { + return (_root == r._root); + } + + bool xml_node::operator!=(const xml_node& r) const + { + return (_root != r._root); + } + + bool xml_node::operator<(const xml_node& r) const + { + return (_root < r._root); + } + + bool xml_node::operator>(const xml_node& r) const + { + return (_root > r._root); + } + + bool xml_node::operator<=(const xml_node& r) const + { + return (_root <= r._root); + } + + bool xml_node::operator>=(const xml_node& r) const + { + return (_root >= r._root); + } + + bool xml_node::empty() const + { + return (_root == 0 || _root->type == node_null); + } + + bool xml_node::type_document() const + { + return (_root && _root == _root->parent && _root->type == node_document); + } + + const char* xml_node::name() const + { + return (!empty() && _root->name) ? _root->name : ""; + } + + xml_node_type xml_node::type() const + { + return (_root) ? (xml_node_type)_root->type : node_null; + } + + const char* xml_node::value() const + { + return (!empty() && _root->value) ? _root->value : ""; + } + + xml_node xml_node::child(const char* name) const + { + if (!empty()) + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && !strcmp(name, i->name)) return xml_node(i); + + return xml_node(); + } + + xml_node xml_node::child_w(const char* name) const + { + if (!empty()) + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && !impl::strcmpwild(name, i->name)) return xml_node(i); + + return xml_node(); + } + + xml_attribute xml_node::attribute(const char* name) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) + if (i->name && !strcmp(name, i->name)) + return xml_attribute(i); + + return xml_attribute(); + } + + xml_attribute xml_node::attribute_w(const char* name) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) + if (i->name && !impl::strcmpwild(name, i->name)) + return xml_attribute(i); + + return xml_attribute(); + } + + xml_node xml_node::sibling(const char* name) const + { + if (!empty() && !type_document()) return parent().child(name); + else return xml_node(); + } + + xml_node xml_node::sibling_w(const char* name) const + { + if (!empty() && !type_document()) return parent().child_w(name); + else return xml_node(); + } + + xml_node xml_node::next_sibling(const char* name) const + { + if(empty()) return xml_node(); + + for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) + if (i->name && !strcmp(name, i->name)) return xml_node(i); + + return xml_node(); + } + + xml_node xml_node::next_sibling_w(const char* name) const + { + if(empty()) return xml_node(); + + for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) + if (i->name && !impl::strcmpwild(name, i->name)) return xml_node(i); + + return xml_node(); + } + + xml_node xml_node::next_sibling() const + { + if(empty()) return xml_node(); + + if (_root->next_sibling) return xml_node(_root->next_sibling); + else return xml_node(); + } + + xml_node xml_node::previous_sibling(const char* name) const + { + if (empty()) return xml_node(); + + for (xml_node_struct* i = _root->prev_sibling; i; i = i->prev_sibling) + if (i->name && !strcmp(name, i->name)) return xml_node(i); + + return xml_node(); + } + + xml_node xml_node::previous_sibling_w(const char* name) const + { + if (empty()) return xml_node(); + + for (xml_node_struct* i = _root->prev_sibling; i; i = i->prev_sibling) + if (i->name && !impl::strcmpwild(name, i->name)) return xml_node(i); + + return xml_node(); + } + + xml_node xml_node::previous_sibling() const + { + if(empty()) return xml_node(); + + if (_root->prev_sibling) return xml_node(_root->prev_sibling); + else return xml_node(); + } + + xml_node xml_node::parent() const + { + return (!type_document()) ? xml_node(_root->parent) : xml_node(); + } + + const char* xml_node::child_value() const + { + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if ((i->type == node_pcdata || i->type == node_cdata) && i->value) + return i->value; + return ""; + } + + xml_attribute xml_node::first_attribute() const + { + return _root ? xml_attribute(_root->first_attribute) : xml_attribute(); + } + + xml_attribute xml_node::last_attribute() const + { + return _root ? xml_attribute(_root->last_attribute) : xml_attribute(); + } + + xml_node xml_node::first_child() const + { + if (_root) return xml_node(_root->first_child); + else return xml_node(); + } + + xml_node xml_node::last_child() const + { + if (_root) return xml_node(_root->last_child); + else return xml_node(); + } + + namespace impl + { + xml_node first_element(const xml_node_struct* node, const char* name, strcmpfunc pred) + { + for (xml_node_struct* i = node->first_child; i; i = i->next_sibling) + { + if (i->name && !pred(name, i->name)) return xml_node(i); + else if (i->first_child) + { + xml_node found = first_element(i, name, pred); + if (found) return found; // Found. + } + } + return xml_node(); // Not found. + } + } + + xml_node xml_node::first_element(const char* name) const + { + if (empty()) return xml_node(); + + return impl::first_element(_root, name, &strcmp); + } + + xml_node xml_node::first_element_w(const char* name) const + { + if (empty()) return xml_node(); + + return impl::first_element(_root, name, &strcmpwildimpl); + } + + namespace impl + { + xml_node first_element_by_value(const xml_node_struct* node, const char* name, const char* value, strcmpfunc pred) + { + for (xml_node_struct* i = node->first_child; i; i = i->next_sibling) + { + if (i->name && !pred(name,i->name)) + { + for (xml_node_struct* j = i->first_child; j; j = j->next_sibling) + if (j->type == node_pcdata && j->value && !pred(value, j->value)) + return xml_node(i); + } + else if (i->first_child) + { + xml_node found = first_element_by_value(i, name, value, pred); + if(!found.empty()) return found; // Found. + } + } + return xml_node(); // Not found. + } + } + + xml_node xml_node::first_element_by_value(const char* name,const char* value) const + { + if (empty()) return xml_node(); + + return impl::first_element_by_value(_root, name, value, &strcmp); + } + + xml_node xml_node::first_element_by_value_w(const char* name,const char* value) const + { + if (empty()) return xml_node(); + + return impl::first_element_by_value(_root, name, value, &strcmpwildimpl); + } + + namespace impl + { + xml_node first_element_by_attribute(const xml_node_struct* node, const char* name, const char* attr_name, const char* attr_value, strcmpfunc pred) + { + for (xml_node_struct* i = node->first_child; i; i = i->next_sibling) + { + if (i->name && !pred(name, i->name)) + { + for (xml_attribute_struct* j = i->first_attribute; j; j = j->next_attribute) + { + if (j->name && j->value && !pred(attr_name, j->name) && !pred(attr_value, j->value)) + return xml_node(i); // Wrap it up and return. + } + } + else if (i->first_child) + { + xml_node found = first_element_by_attribute(i, name, attr_name, attr_value, pred); + if(!found.empty()) return found; // Found. + } + } + return xml_node(); // Not found. + } + } + + xml_node xml_node::first_element_by_attribute(const char* name,const char* attr_name,const char* attr_value) const + { + if (empty()) return xml_node(); + + return impl::first_element_by_attribute(_root, name, attr_name, attr_value, &strcmp); + } + + xml_node xml_node::first_element_by_attribute_w(const char* name,const char* attr_name,const char* attr_value) const + { + if (empty()) return xml_node(); + + return impl::first_element_by_attribute(_root, name, attr_name, attr_value, &strcmpwildimpl); + } + + namespace impl + { + xml_node first_element_by_attribute(const xml_node_struct* node, const char* attr_name,const char* attr_value, strcmpfunc pred) + { + for (xml_node_struct* i = node->first_child; i; i = i->next_sibling) + { + for (xml_attribute_struct* j = i->first_attribute; j; j = j->next_attribute) + { + if (j->name && j->value && !pred(attr_name, j->name) && !pred(attr_value, j->value)) + return xml_node(i); // Wrap it up and return. + } + + if (i->first_child) + { + xml_node found = first_element_by_attribute(i->first_child, attr_name, attr_value, pred); + if (!found.empty()) return found; // Found. + } + } + return xml_node(); // Not found. + } + } + + xml_node xml_node::first_element_by_attribute(const char* attr_name,const char* attr_value) const + { + if (empty()) return xml_node(); + + return impl::first_element_by_attribute(_root, attr_name, attr_value, &strcmp); + } + + xml_node xml_node::first_element_by_attribute_w(const char* attr_name,const char* attr_value) const + { + if (empty()) return xml_node(); + + return impl::first_element_by_attribute(_root, attr_name, attr_value, &strcmpwildimpl); + } + + xml_node xml_node::first_node(xml_node_type type) const + { + if(!_root) return xml_node(); + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + { + if (i->type == type) return xml_node(i); + else if (i->first_child) + { + xml_node subsearch(i); + xml_node found = subsearch.first_node(type); + if(!found.empty()) return found; // Found. + } + } + return xml_node(); // Not found. + } + + std::string xml_node::path(char delimiter) const + { + std::string path; + + xml_node cursor = *this; // Make a copy. + + path = cursor.name(); + + while (cursor.parent() && !cursor.type_document()) // Loop to parent (stopping on actual root because it has no name). + { + cursor = cursor.parent(); + + std::string temp = cursor.name(); + temp += delimiter; + temp += path; + path.swap(temp); + } + + return path; + } + + xml_node xml_node::first_element_by_path(const char* path, char delimiter) const + { + xml_node found = *this; // Current search context. + + if (empty() || !path || !path[0]) return found; + + if (path[0] == delimiter) + { + // Absolute path; e.g. '/foo/bar' + while (found.parent()) found = found.parent(); + ++path; + } + + const char* path_segment = path; + + while (*path_segment == delimiter) ++path_segment; + + const char* path_segment_end = path_segment; + + while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end; + + if (path_segment == path_segment_end) return found; + + const char* next_segment = path_segment_end; + + while (*next_segment == delimiter) ++next_segment; + + if (*path_segment == '.' && path_segment + 1 == path_segment_end) + return found.first_element_by_path(next_segment, delimiter); + else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end) + return found.parent().first_element_by_path(next_segment, delimiter); + else + { + for (xml_node_struct* j = found._root->first_child; j; j = j->next_sibling) + { + if (j->name && !strcmprange(j->name, path_segment, path_segment_end)) + { + xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); + + if (subsearch) return subsearch; + } + } + + return xml_node(); + } + } + + bool xml_node::traverse(xml_tree_walker& walker) const + { + if (!walker.begin(*this)) return false; // Send the callback for begin traverse if depth is zero. + if(!empty()) // Don't traverse if this is a null node. + { + walker.push(); // Increment the walker depth counter. + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + { + xml_node subsearch(i); + if (!subsearch.traverse(walker)) return false; + } + walker.pop(); // Decrement the walker depth counter. + } + if (!walker.end(*this)) return false; // Send the callback for end traverse if depth is zero. + + return true; + } + + xml_node_iterator::xml_node_iterator() + { + } + + xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node) + { + } + + xml_node_iterator::xml_node_iterator(const xml_node_struct* ref): _wrap(ref) + { + } + + xml_node_iterator::xml_node_iterator(const xml_node_struct* ref, const xml_node_struct* prev): _wrap(ref), _prev(prev) + { + } + + bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const + { + return (_wrap == rhs._wrap); + } + + bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const + { + return (_wrap != rhs._wrap); + } + + const xml_node& xml_node_iterator::operator*() const + { + return _wrap; + } + + const xml_node* xml_node_iterator::operator->() const + { + return &_wrap; + } + + const xml_node_iterator& xml_node_iterator::operator++() + { + _prev = _wrap; + _wrap = xml_node(_wrap._root->next_sibling); + return *this; + } + + xml_node_iterator xml_node_iterator::operator++(int) + { + xml_node_iterator temp = *this; + ++*this; + return temp; + } + + const xml_node_iterator& xml_node_iterator::operator--() + { + if (_wrap._root) _wrap = xml_node(_wrap._root->prev_sibling); + else _wrap = _prev; + return *this; + } + + xml_node_iterator xml_node_iterator::operator--(int) + { + xml_node_iterator temp = *this; + --*this; + return temp; + } + + xml_attribute_iterator::xml_attribute_iterator() + { + } + + xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr): _wrap(attr) + { + } + + xml_attribute_iterator::xml_attribute_iterator(const xml_attribute_struct* ref): _wrap(ref) + { + } + + xml_attribute_iterator::xml_attribute_iterator(const xml_attribute_struct* ref, const xml_attribute_struct* prev): _wrap(ref), _prev(prev) + { + } + + bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const + { + return (_wrap == rhs._wrap); + } + + bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const + { + return (_wrap != rhs._wrap); + } + + const xml_attribute& xml_attribute_iterator::operator*() const + { + return _wrap; + } + + const xml_attribute* xml_attribute_iterator::operator->() const + { + return &_wrap; + } + + const xml_attribute_iterator& xml_attribute_iterator::operator++() + { + _prev = _wrap; + _wrap = xml_attribute(_wrap._attr->next_attribute); + return *this; + } + + xml_attribute_iterator xml_attribute_iterator::operator++(int) + { + xml_attribute_iterator temp = *this; + ++*this; + return temp; + } + + const xml_attribute_iterator& xml_attribute_iterator::operator--() + { + if (_wrap._attr) _wrap = xml_attribute(_wrap._attr->prev_attribute); + else _wrap = _prev; + return *this; + } + + xml_attribute_iterator xml_attribute_iterator::operator--(int) + { + xml_attribute_iterator temp = *this; + --*this; + return temp; + } + + xml_memory_block::xml_memory_block(): next(0), size(0) + { + } + + xml_parser::xml_parser(unsigned int optmsk): _xmldoc(0), _optmsk(optmsk) + { + } + + xml_parser::xml_parser(std::istream& stream,unsigned int optmsk): _xmldoc(0), _optmsk(optmsk) + { + parse(stream, _optmsk); + } + + xml_parser::xml_parser(char* xmlstr,unsigned int optmsk): _xmldoc(0), _optmsk(optmsk) + { + parse(xmlstr, _optmsk); + } + + xml_parser::~xml_parser() + { + xml_memory_block* current = _memory.next; + + while (current) + { + xml_memory_block* next = current->next; + delete current; + current = next; + } + } + + xml_parser::operator xml_node() const + { + return xml_node(_xmldoc); + } + + xml_node xml_parser::document() const + { + return xml_node(_xmldoc); + } + + unsigned int xml_parser::options() const + { + return _optmsk; + } + + unsigned int xml_parser::options(unsigned int optmsk) + { + unsigned int prev = _optmsk; + _optmsk = optmsk; + return prev; + } + + void xml_parser::parse(std::istream& stream,unsigned int optmsk) + { + int length = 0, pos = stream.tellg(); + stream.seekg(0, std::ios_base::end); + length = stream.tellg(); + stream.seekg(pos, std::ios_base::beg); + + _buffer.resize(length + 1); + stream.read(&_buffer[0], length); + _buffer[length] = 0; + + parse(&_buffer[0],optmsk); // Parse the input string. + } + + char* xml_parser::parse(char* xmlstr,unsigned int optmsk) + { + if(!xmlstr) return NULL; + + xml_allocator alloc(&_memory); + + _xmldoc = alloc.allocate(node_document); // Allocate a new root. + _xmldoc->parent = _xmldoc; // Point to self. + if(optmsk != parse_noset) _optmsk = optmsk; + + xml_parser_impl parser(alloc); + + return parser.parse(xmlstr,_xmldoc,_optmsk); // Parse the input string. + } + + std::string utf8(const wchar_t* str) + { + std::string result; + result.reserve(strutf16_utf8_size(str)); + + for (; *str; ++str) + { + char buffer[6]; + + result.append(buffer, strutf16_utf8(buffer, *str)); + } + + return result; + } + + std::wstring utf16(const char* str) + { + std::wstring result; + result.reserve(strutf8_utf16_size(str)); + + for (; *str;) + { + unsigned int ch; + str = strutf8_utf16(str, ch); + result += (wchar_t)ch; + } + + return result; + } +} diff --git a/src/pugixml.hpp b/src/pugixml.hpp new file mode 100644 index 0000000..026e6ba --- /dev/null +++ b/src/pugixml.hpp @@ -0,0 +1,619 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Pug Improved XML Parser - Version 0.1 +// -------------------------------------------------------- +// Copyright (C) 2006, by Zeux (zeux@mathcentre.com) +// This work is based on the pugxml parser, which is: +// Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) +// Released into the Public Domain. Use at your own risk. +// See pugxml.xml for further information, history, etc. +// Contributions by Neville Franks (readonly@getsoft.com). +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef HEADER_PUGIXML_HPP +#define HEADER_PUGIXML_HPP + +/// The PugiXML Parser namespace. +namespace pugi +{ + /// Tree node classification. + /// See 'xml_node_struct::type' + enum xml_node_type + { + node_null, ///< An undifferentiated entity. + node_document, ///< A document tree's absolute root. + node_element, ///< E.g. '<...>' + node_pcdata, ///< E.g. '<...>' + node_cdata, ///< E.g. '' + node_comment, ///< E.g. '' + node_pi ///< E.g. '' + }; + + /// Parser Options + const size_t memory_block_size = 32768; ///< Memory block size, 32 kb + + const unsigned int parse_minimal = 0x00000000; ///< Unset the following flags. + const unsigned int parse_pi = 0x00000001; ///< Parse '' + const unsigned int parse_comments = 0x00000002; ///< Parse '' + const unsigned int parse_cdata = 0x00000004; ///< Parse '' + const unsigned int parse_ws_pcdata = 0x00000008; ///< Skip PCDATA that consists only of whitespaces + const unsigned int parse_ext_pcdata = 0x00000010; ///< Skip PCDATA that is outside all tags (i.e. root) + const unsigned int parse_trim_pcdata = 0x00000020; ///< Trim '>...<' + const unsigned int parse_trim_attribute = 0x00000040; ///< Trim 'foo="..."'. + const unsigned int parse_escapes_pcdata = 0x00000080; ///< Parse <, >, &, ", ', &#.. sequences + const unsigned int parse_escapes_attribute = 0x00000100; ///< Parse <, >, &, ", ', &#.. sequences + const unsigned int parse_wnorm_pcdata = 0x00000200; ///< Normalize spaces in pcdata + const unsigned int parse_wnorm_attribute = 0x00000400; ///< Normalize spaces in attributes + const unsigned int parse_wconv_attribute = 0x00000800; ///< Convert space-like characters to spaces in attributes (only if wnorm is not set) + const unsigned int parse_eol_cdata = 0x00001000; ///< Perform EOL handling in CDATA sections + const unsigned int parse_check_end_tags = 0x00002000; ///< Check start and end tag names and return error if names mismatch + const unsigned int parse_match_end_tags = 0x00004000; ///< Try to find corresponding start tag for an end tag + const unsigned int parse_default = 0x0000FFFF & ~parse_ws_pcdata; ///< Set all flags, except parse_ws_pcdata + const unsigned int parse_noset = 0x80000000; ///< Parse with flags in xml_parser + + const unsigned int parse_w3c = parse_pi | parse_comments | parse_cdata | + parse_escapes_pcdata | parse_escapes_attribute | + parse_wconv_attribute | parse_check_end_tags | + parse_ws_pcdata | parse_eol_cdata; + + /// Forward declarations + struct xml_attribute_struct; + struct xml_node_struct; + + class xml_node_iterator; + class xml_attribute_iterator; + + class xml_tree_walker; + + /// Provides a light-weight wrapper for manipulating xml_attribute_struct structures. + /// Note: xml_attribute does not create any memory for the attribute it wraps; + /// it only wraps a pointer to an existing xml_attribute_struct. + class xml_attribute + { + friend class xml_attribute_iterator; + friend class xml_node; + + private: + const xml_attribute_struct* _attr; ///< The internal attribute pointer. + + /// Safe bool type + typedef const xml_attribute_struct* xml_attribute::*unspecified_bool_type; + + /// Initializing ctor + explicit xml_attribute(const xml_attribute_struct* attr); + + public: + /// Default ctor + xml_attribute(); + + public: + /// Comparison operators + 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; + + /// Safe bool conversion + operator unspecified_bool_type() const; + + /// Get next attribute if any, else xml_attribute() + xml_attribute next_attribute() const; + + /// Get previous attribute if any, else xml_attribute() + xml_attribute previous_attribute() const; + + /// Cast attribute value as int. If not found, return 0. + /// \return Attribute value as int, or 0. + int as_int() const; + + /// Cast attribute value as double. If not found, return 0.0. + /// \return Attribute value as double, or 0.0. + double as_double() const; + + /// Cast attribute value as float. If not found, return 0.0. + /// \return Attribute value as float, or 0.0. + float as_float() const; + + /// Cast attribute value as bool. If not found, return false. + /// \return Attribute value as bool, or false. + bool as_bool() const; + + public: + /// True if internal pointer is valid + bool empty() const; + + public: + /// Access the attribute name. + const char* name() const; + + /// Access the attribute value. + const char* value() const; + }; + + /// Provides a light-weight wrapper for manipulating xml_node_struct structures. + class xml_node + { + friend class xml_node_iterator; + friend class xml_parser; + + private: + const xml_node_struct* _root; ///< Pointer to node root. + + /// Safe bool type + typedef const xml_node_struct* xml_node::*unspecified_bool_type; + + private: + /// Node is tree root. + bool type_document() const; + + public: + /// Default constructor. + /// Node root points to a dummy 'xml_node_struct' structure. Test for this + /// with 'empty'. + xml_node(); + + /// Construct, wrapping the given 'xml_node_struct' pointer. + explicit xml_node(const xml_node_struct* p); + + public: + /// Base iterator type (for child nodes). Same as 'child_iterator'. + typedef xml_node_iterator iterator; + + /// Attribute iterator type. + typedef xml_attribute_iterator attribute_iterator; + + /// Access the begin iterator for this node's collection of child nodes. + /// Same as 'children_begin'. + iterator begin() const; + + /// Access the end iterator for this node's collection of child nodes. + /// Same as 'children_end'. + iterator end() const; + + /// Access the begin iterator for this node's collection of child nodes. + /// Same as 'begin'. + iterator children_begin() const; + + /// Access the end iterator for this node's collection of child nodes. + /// Same as 'end'. + iterator children_end() const; + + /// Access the begin iterator for this node's collection of attributes. + attribute_iterator attributes_begin() const; + + /// Access the end iterator for this node's collection of attributes. + attribute_iterator attributes_end() const; + + /// Access the begin iterator for this node's collection of siblings. + iterator siblings_begin() const; + + /// Access the end iterator for this node's collection of siblings. + iterator siblings_end() const; + + public: + /// Safe bool conversion + operator unspecified_bool_type() const; + + /// Comparison operators + 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; + + public: + /// Node pointer is null, or type is node_null. Same as type_null. + bool empty() const; + + public: + /// Access node entity type. + xml_node_type type() const; + + /// Access pointer to node name if any, else empty string. + const char* name() const; + + /// Access pointer to data if any, else empty string. + const char* value() const; + + /// Access child node at name as xml_node or xml_node(NULL) if bad name. + xml_node child(const char* name) const; + + /// Access child node at name as xml_node or xml_node(NULL) if bad name. + /// Enable wildcard matching. + xml_node child_w(const char* name) const; + + /// Access the attribute having 'name'. + xml_attribute attribute(const char* name) const; + + /// Access the attribute having 'name'. + /// Enable wildcard matching. + xml_attribute attribute_w(const char* name) const; + + /// Access sibling node at name as xml_node or xml_node(NULL) if bad name. + xml_node sibling(const char* name) const; + + /// Access sibling node at name as xml_node or xml_node(NULL) if bad name. + /// Enable wildcard matching. + xml_node sibling_w(const char* name) const; + + /// Access current node's next sibling by position and name. + xml_node next_sibling(const char* name) const; + + /// Access current node's next sibling by position and name. + /// Enable wildcard matching. + xml_node next_sibling_w(const char* name) const; + + /// Access current node's next sibling by position. + xml_node next_sibling() const; + + /// Access current node's previous sibling by position and name. + xml_node previous_sibling(const char* name) const; + + /// Access current node's previous sibling by position and name. + /// Enable wildcard matching. + xml_node previous_sibling_w(const char* name) const; + + /// Access current node's previous sibling by position. + xml_node previous_sibling() const; + + /// Access node's parent if any, else xml_node(NULL) + xml_node parent() const; + + /// Return PCDATA/CDATA that is child of current node. If none, return empty string. + const char* child_value() const; + + public: + /// Access node's first attribute if any, else xml_attribute() + xml_attribute first_attribute() const; + + /// Access node's last attribute if any, else xml_attribute() + xml_attribute last_attribute() const; + + /// Find all elements having the given name. + template void all_elements_by_name(const char* name, OutputIterator it) const; + + /// Find all elements having the given name. + /// Enable wildcard matching. + template void all_elements_by_name_w(const char* name, OutputIterator it) const; + + /// Access node's first child if any, else xml_node() + xml_node first_child() const; + + /// Access node's last child if any, else xml_node() + xml_node last_child() const; + + /// Find attribute using the predicate + /// Predicate should take xml_attribute and return bool. + template xml_attribute find_attribute(Predicate pred) const; + + /// Find child using the predicate + /// Predicate should take xml_node and return bool. + template xml_node find_child(Predicate pred) const; + + /// Recursively-implemented depth-first find element using the predicate + /// Predicate should take xml_node and return bool. + template xml_node find_element(Predicate pred) const; + + /// Recursively-implemented depth-first find the first matching element. + /// Use for shallow drill-downs. + xml_node first_element(const char* name) const; + + /// Recursively-implemented depth-first find the first matching element. + /// Use for shallow drill-downs. + /// Enable wildcard matching. + xml_node first_element_w(const char* name) const; + + /// Recursively-implemented depth-first find the first matching element + /// also having matching PCDATA. + xml_node first_element_by_value(const char* name, const char* value) const; + + /// Recursively-implemented depth-first find the first matching element + /// also having matching PCDATA. + /// Enable wildcard matching. + xml_node first_element_by_value_w(const char* name, const char* value) const; + + /// Recursively-implemented depth-first find the first matching element + /// also having matching attribute. + xml_node first_element_by_attribute(const char* name, const char* attr_name, const char* attr_value) const; + + /// Recursively-implemented depth-first find the first matching element + /// also having matching attribute. + /// Enable wildcard matching. + xml_node first_element_by_attribute_w(const char* name, const char* attr_name, const char* attr_value) const; + + /// Recursively-implemented depth-first find the first element + /// having matching attribute. + xml_node first_element_by_attribute(const char* attr_name, const char* attr_value) const; + + /// Recursively-implemented depth-first find the first element + /// having matching attribute. + /// Enable wildcard matching. + xml_node first_element_by_attribute_w(const char* attr_name, const char* attr_value) const; + + /// Recursively-implemented depth-first find the first matching entity. + /// Use for shallow drill-downs. + xml_node first_node(xml_node_type type) const; + + /// Compile the absolute node path from root as a text string. + /// \param delimiter - Delimiter character to insert between element names. + /// \return path string (e.g. with '/' as delimiter, '/document/.../this'. + std::string path(char delimiter = '/') const; + + /// Search for a node by path. + /// \param path - Path string; e.g. './foo/bar' (relative to node), '/foo/bar' (relative + /// to root), '../foo/bar' (pop relative position). + /// \param delimiter - Delimiter character to use in tokenizing path. + /// \return Matching node, or xml_node() if not found. + xml_node first_element_by_path(const char* path, char delimiter = '/') const; + + /// Recursively traverse the tree. + bool traverse(xml_tree_walker& walker) const; + }; + + /// Child node iterator. + class xml_node_iterator: public std::iterator + { + friend class xml_node; + + private: + xml_node _prev; + xml_node _wrap; + + /// Initializing ctor + explicit xml_node_iterator(const xml_node_struct* ref); + public: + /// Default ctor + xml_node_iterator(); + + /// Initializing ctor + xml_node_iterator(const xml_node& node); + + /// Initializing ctor (for past-the-end) + xml_node_iterator(const xml_node_struct* ref, const xml_node_struct* prev); + + bool operator==(const xml_node_iterator& rhs) const; + bool operator!=(const xml_node_iterator& rhs) const; + + const xml_node& operator*() const; + const xml_node* operator->() const; + + const xml_node_iterator& operator++(); + xml_node_iterator operator++(int); + + const xml_node_iterator& operator--(); + xml_node_iterator operator--(int); + }; + + /// Attribute iterator. + class xml_attribute_iterator: public std::iterator + { + friend class xml_node; + + private: + xml_attribute _prev; + xml_attribute _wrap; + + /// Initializing ctor + explicit xml_attribute_iterator(const xml_attribute_struct* ref); + public: + /// Default ctor + xml_attribute_iterator(); + + /// Initializing ctor + xml_attribute_iterator(const xml_attribute& attr); + + /// Initializing ctor (for past-the-end) + xml_attribute_iterator(const xml_attribute_struct* ref, const xml_attribute_struct* prev); + + bool operator==(const xml_attribute_iterator& rhs) const; + bool operator!=(const xml_attribute_iterator& rhs) const; + + const xml_attribute& operator*() const; + const xml_attribute* operator->() const; + + const xml_attribute_iterator& operator++(); + xml_attribute_iterator operator++(int); + + const xml_attribute_iterator& operator--(); + xml_attribute_iterator operator--(int); + }; + + /// Abstract tree walker class for xml_node::traverse(). + class xml_tree_walker + { + private: + int _deep; ///< Current node depth. + public: + /// Default ctor + xml_tree_walker(); + + /// Virtual dtor + virtual ~xml_tree_walker(); + + public: + /// Increment node depth. + virtual void push(); + + /// Decrement node depth + virtual void pop(); + + /// Access node depth + virtual int depth() const; + + public: + /// Callback when traverse on a node begins. + /// \return returning false will abort the traversal. + virtual bool begin(const xml_node&); + + /// Callback when traverse on a node ends. + /// \return Returning false will abort the traversal. + virtual bool end(const xml_node&); + }; + + /// Memory block (internal) + struct xml_memory_block + { + xml_memory_block(); + + xml_memory_block* next; + size_t size; + + char data[memory_block_size]; + }; + + /// Provides a high-level interface to the XML parser. + class xml_parser + { + private: + std::vector _buffer; ///< character buffer + + xml_memory_block _memory; ///< Memory block + + xml_node_struct* _xmldoc; ///< Pointer to current XML document tree root. + unsigned int _optmsk; ///< Parser options. + + xml_parser(const xml_parser&); + const xml_parser& operator=(const xml_parser&); + + public: + /// Constructor. + /// \param optmsk - Options mask. + xml_parser(unsigned int optmsk = parse_default); + + /// Parse constructor. + /// \param xmlstr - readwrite string with xml data + /// \param optmsk - Options mask. + /// \see parse + xml_parser(char* xmlstr, unsigned int optmsk = parse_default); + + /// Parse constructor. + /// \param stream - stream with xml data + /// \param optmsk - Options mask. + /// \see parse + xml_parser(std::istream& stream, unsigned int optmsk = parse_default); + + /// Dtor + ~xml_parser(); + + public: + /// Cast as xml_node (same as document). + operator xml_node() const; + + /// Returns the root wrapped by an xml_node. + xml_node document() const; + + public: + /// Get parser options mask. + unsigned int options() const; + + /// Set parser options mask. + unsigned int options(unsigned int optmsk); + + public: + /// Parse the given XML stream + /// \param stream - stream with xml data + /// \param optmsk - Options mask. + void parse(std::istream& stream, unsigned int optmsk = parse_noset); + + /// Parse the given XML string in-situ. + /// \param xmlstr - readwrite string with xml data + /// \param optmsk - Options mask. + /// \return last position or NULL + /// \rem input string is zero-segmented + char* parse(char* xmlstr, unsigned int optmsk = parse_noset); + }; + + /// Utility functions for xml + + /// Convert utf16 to utf8 + std::string utf8(const wchar_t* str); + + /// Convert utf8 to utf16 + std::wstring utf16(const char* str); +} + +/// Inline implementation + +namespace pugi +{ + namespace impl + { + int strcmpwild(const char*, const char*); + } + + template void xml_node::all_elements_by_name(const char* name, OutputIterator it) const + { + if (empty()) return; + + for (xml_node node = first_child(); node; node = node.next_sibling()) + { + if (!strcmp(name, node.name())) + { + *it = node; + ++it; + } + + if (node.first_child()) node.all_elements_by_name(name, it); + } + } + + template void xml_node::all_elements_by_name_w(const char* name, OutputIterator it) const + { + if (empty()) return; + + for (xml_node node = first_child(); node; node = node.next_sibling()) + { + if (!impl::strcmpwild(name, node.name())) + { + *it = node; + ++it; + } + + if (node.first_child()) node.all_elements_by_name_w(name, it); + } + } + + template inline xml_attribute xml_node::find_attribute(Predicate pred) const + { + if (!empty()) + for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) + if (pred(attrib)) + return attrib; + + return xml_attribute(); + } + + template inline xml_node xml_node::find_child(Predicate pred) const + { + if (!empty()) + for (xml_node node = first_child(); node; node = node.next_sibling()) + if (pred(node)) + return node; + + return xml_node(); + } + + template inline xml_node xml_node::find_element(Predicate pred) const + { + if (!empty()) + for (xml_node node = first_child(); node; node = node.next_sibling()) + { + if (pred(node)) + return node; + + if (node.first_child()) + { + xml_node found = node.find_element(pred); + if (found) return found; + } + } + + return xml_node(); + } +} + +#endif -- cgit v1.2.3