Parsers
Prev |  |

## Parsers

Like the underlying libxml library, libxml++ allows the use of 3 parsers, depending on your needs - the DOM, SAX, and TextReader parsers. The relative advantages and behaviour of these parsers will be explained here.

All of the parsers may parse XML documents directly from disk, a string, or a C++ std::istream. Although the libxml++ API uses only Glib::ustring, and therefore the UTF-8 encoding, libxml++ can parse documents in any encoding, converting to UTF-8 automatically. This conversion will not lose any information because UTF-8 can represent any locale.

Remember that white space is usually significant in XML documents, so the parsers might provide unexpected text nodes that contain only spaces and new lines. The parser does not know whether you care about these text nodes, but your application may choose to ignore them.

### DOM Parser

The DOM parser parses the whole document at once and stores the structure in memory, available via `Parser::get_document()`. With methods such as `Document::get_root_node()` and `Node::get_children()`, you may then navigate into the heirarchy of XML nodes without restriction, jumping forwards or backwards in the document based on the information that you encounter. Therefore the DOM parser uses a relatively large amount of memory.

You should use C++ RTTI (via `dynamic_cast<>`) to identify the specific node type and to perform actions which are not possible with all node types. For instance, only `Element`s have attributes. Here is the inheritance hierarchy of node types:

- xmlpp::Node:
  - xmlpp::Attribute
  - xmlpp::ContentNode
    - xmlpp::CdataNode
    - xmlpp::CommentNode
    - xmlpp::ProcessingInstructionNode
    - xmlpp::TextNode
  - xmlpp::Element
  - xmlpp::EntityReference

Although you may obtain pointers to the `Node`s, these `Node`s are always owned by their parent Nodes. In most cases that means that the Node will exist, and your pointer will be valid, as long as the `Document` instance exists.

There are also several methods which can create new child `Node`s. By using these, and one of the `Document::write_*()` methods, you can use libxml++ to build a new XML document.

#### Example

This example looks in the document for expected elements and then examines them. All these examples are included in the libxml++ source distribution.

Source Code

File: main.cc

    #ifdef HAVE_CONFIG_H
    #include <config.h>
    #endif

    #include <libxml++/libxml++.h>

    #include <iostream>

    void print_indentation(unsigned int indentation)
    {
      for(unsigned int i = 0; i < indentation; ++i)
        std::cout << " ";
    }

    void print_node(const xmlpp::Node* node, unsigned int indentation = 0)
    {
      std::cout << std::endl; //Separate nodes by an empty line.

      const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
      const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
      const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);

      if(nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
        return;

      const Glib::ustring nodename = node->get_name();

      if(!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
      {
        print_indentation(indentation);

        const Glib::ustring namespace_prefix = node->get_namespace_prefix();
        if(namespace_prefix.empty())
          std::cout << "Node name = " << nodename << std::endl;
        else
          std::cout << "Node name = " << namespace_prefix << ":" << nodename << std::endl;
      }
      else if(nodeText) //Let's say when it's text. - e.g. let's say what that white space is.
      {
        print_indentation(indentation);
        std::cout << "Text Node" << std::endl;
      }

      //Treat the various node types differently:
      if(nodeText)
      {
        print_indentation(indentation);
        std::cout << "text = \"" << nodeText->get_content() << "\"" << std::endl;
      }
      else if(nodeComment)
      {
        print_indentation(indentation);
        std::cout << "comment = " << nodeComment->get_content() << std::endl;
      }
      else if(nodeContent)
      {
        print_indentation(indentation);
        std::cout << "content = " << nodeContent->get_content() << std::endl;
      }
      else if(const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node))
      {
        //A normal Element node:

        //line() works only for ElementNodes.
        print_indentation(indentation);
        std::cout << "     line = " << node->get_line() << std::endl;

        //Print attributes:
        const xmlpp::Element::AttributeList& attributes = nodeElement->get_attributes();
        for(xmlpp::Element::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
        {
          const xmlpp::Attribute* attribute = *iter;
          print_indentation(indentation);

          const Glib::ustring namespace_prefix = attribute->get_namespace_prefix();
          if(namespace_prefix.empty())
            std::cout << "  Attribute " << attribute->get_name() << " = " << attribute->get_value() << std::endl;
          else
            std::cout << "  Attribute " << namespace_prefix  << ":" << attribute->get_name() << " = " << attribute->get_value() << std::endl;
        }

        const xmlpp::Attribute* attribute = nodeElement->get_attribute("title");
        if(attribute)
        {
          std::cout << "title found: =" << attribute->get_value() << std::endl;
        }
      }

      if(!nodeContent)
      {
        //Recurse through child nodes:
        xmlpp::Node::NodeList list = node->get_children();
        for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
        {
          print_node(*iter, indentation + 2); //recursive
        }
      }
    }

    int main(int argc, char* argv[])
    {
      std::string filepath;
      if(argc > 1 )
        filepath = argv[1]; //Allow the user to specify a different XML file to parse.
      else
        filepath = "example.xml";

      #ifdef LIBXMLCPP_EXCEPTIONS_ENABLED
      try
      {
      #endif //LIBXMLCPP_EXCEPTIONS_ENABLED
        xmlpp::DomParser parser;
        //parser.set_validate();
        parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
        parser.parse_file(filepath);
        if(parser)
        {
          //Walk the tree:
          const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
          print_node(pNode);
        }
      #ifdef LIBXMLCPP_EXCEPTIONS_ENABLED
      }
      catch(const std::exception& ex)
      {
        std::cout << "Exception caught: " << ex.what() << std::endl;
      }
      #endif //LIBXMLCPP_EXCEPTIONS_ENABLED

      return 0;
    }

### SAX Parser

The SAX parser presents each node of the XML document in sequence. So when you process one node, you must have already stored information about any relevant previous nodes, and you have no information at that time about subsequent nodes. The SAX parser uses less memory than the DOM parser and it is a suitable abstraction for documents that can be processed sequentially rather than as a whole.

By using the `parse_chunk()` method instead of `parse()`, you can even parse parts of the XML document before you have received the whole document.

As shown in the example, you should derive your own class from SaxParser and override some of the virtual methods. These "handler" methods will be called while the document is parsed.

#### Example

This example shows how the handler methods are called during parsing.

Source Code

File: myparser.h

    #ifndef __LIBXMLPP_EXAMPLES_MYPARSER_H
    #define __LIBXMLPP_EXAMPLES_MYPARSER_H

    #include <libxml++/libxml++.h>

    class MySaxParser : public xmlpp::SaxParser
    {
    public:
      MySaxParser();
      virtual ~MySaxParser();

    protected:
      //overrides:
      virtual void on_start_document();
      virtual void on_end_document();
      virtual void on_start_element(const Glib::ustring& name,
                                    const AttributeList& properties);
      virtual void on_end_element(const Glib::ustring& name);
      virtual void on_characters(const Glib::ustring& characters);
      virtual void on_comment(const Glib::ustring& text);
      virtual void on_warning(const Glib::ustring& text);
      virtual void on_error(const Glib::ustring& text);
      virtual void on_fatal_error(const Glib::ustring& text);
    };

    #endif //__LIBXMLPP_EXAMPLES_MYPARSER_H

File: main.cc

    #ifdef HAVE_CONFIG_H
    #include <config.h>
    #endif

    #include <fstream>
    #include <iostream>

    #include "myparser.h"

    int
    main(int argc, char* argv[])
    {
      std::string filepath;
      if(argc > 1 )
        filepath = argv[1]; //Allow the user to specify a different XML file to parse.
      else
        filepath = "example.xml";

      // Parse the entire document in one go:
      #ifdef LIBXMLCPP_EXCEPTIONS_ENABLED
      try
      {
      #endif //LIBXMLCPP_EXCEPTIONS_ENABLED
        MySaxParser parser;
        parser.set_substitute_entities(true); //
        parser.parse_file(filepath);
      #ifdef LIBXMLCPP_EXCEPTIONS_ENABLED
      }
      catch(const xmlpp::exception& ex)
      {
        std::cout << "libxml++ exception: " << ex.what() << std::endl;
      }
      #endif //LIBXMLCPP_EXCEPTIONS_ENABLED

      // Demonstrate incremental parsing, sometimes useful for network connections:
      {
        //std::cout << "Incremental SAX Parser:" << std:endl;

        std::ifstream is(filepath.c_str());
        char buffer[64];
        const size_t buffer_size = sizeof(buffer) / sizeof(char);

        //Parse the file:
        MySaxParser parser;
        parser.parse_file(filepath);

        //Or parse chunks (though this seems to have problems):
    /*
        do
        {
          memset(buffer, 0, buffer_size);
          is.read(buffer, buffer_size-1);
          if(is && is.gcount())
          {
            Glib::ustring input(buffer, is.gcount());
            parser.parse_chunk(input);
          }
        }
        while(is);

        parser.finish_chunk_parsing();
    */
      }

      return 0;
    }

File: myparser.cc

    #include "myparser.h"
    #include <glibmm/convert.h> //For Glib::ConvertError

    #include <iostream>

    MySaxParser::MySaxParser()
      : xmlpp::SaxParser()
    {
    }

    MySaxParser::~MySaxParser()
    {
    }

    void MySaxParser::on_start_document()
    {
      std::cout << "on_start_document()" << std::endl;
    }

    void MySaxParser::on_end_document()
    {
      std::cout << "on_end_document()" << std::endl;
    }

    void MySaxParser::on_start_element(const Glib::ustring& name,
                                       const AttributeList& attributes)
    {
      std::cout << "node name=" << name << std::endl;

      // Print attributes:
      for(xmlpp::SaxParser::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
      {
        try
        {
          std::cout << "  Attribute name=" << iter->name << std::endl;
        }
        catch(const Glib::ConvertError& ex)
        {
          std::cerr << "MySaxParser::on_start_element(): Exception caught while converting name for std::cout: " << ex.what() << std::endl;
        }

        try
        {
          std::cout << "    , value= " << iter->value << std::endl;
        }
        catch(const Glib::ConvertError& ex)
        {
          std::cerr << "MySaxParser::on_start_element(): Exception caught while converting value for std::cout: " << ex.what() << std::endl;
        }
      }
    }

    void MySaxParser::on_end_element(const Glib::ustring& name)
    {
      std::cout << "on_end_element()" << std::endl;
    }

    void MySaxParser::on_characters(const Glib::ustring& text)
    {
      try
      {
        std::cout << "on_characters(): " << text << std::endl;
      }
      catch(const Glib::ConvertError& ex)
      {
        std::cerr << "MySaxParser::on_characters(): Exception caught while converting text for std::cout: " << ex.what() << std::endl;
      }
    }

    void MySaxParser::on_comment(const Glib::ustring& text)
    {
      try
      {
        std::cout << "on_comment(): " << text << std::endl;
      }
      catch(const Glib::ConvertError& ex)
      {
        std::cerr << "MySaxParser::on_comment(): Exception caught while converting text for std::cout: " << ex.what() << std::endl;
      }
    }

    void MySaxParser::on_warning(const Glib::ustring& text)
    {
      try
      {
        std::cout << "on_warning(): " << text << std::endl;
      }
      catch(const Glib::ConvertError& ex)
      {
        std::cerr << "MySaxParser::on_warning(): Exception caught while converting text for std::cout: " << ex.what() << std::endl;
      }
    }

    void MySaxParser::on_error(const Glib::ustring& text)
    {
      try
      {
        std::cout << "on_error(): " << text << std::endl;
      }
      catch(const Glib::ConvertError& ex)
      {
        std::cerr << "MySaxParser::on_error(): Exception caught while converting text for std::cout: " << ex.what() << std::endl;
      }
    }

    void MySaxParser::on_fatal_error(const Glib::ustring& text)
    {
      try
      {
        std::cout << "on_fatal_error(): " << text << std::endl;
      }
      catch(const Glib::ConvertError& ex)
      {
        std::cerr << "MySaxParser::on_characters(): Exception caught while converting value for std::cout: " << ex.what() << std::endl;
      }
    }

### TextReader Parser

Like the SAX parser, the TextReader parser is suitable for sequential parsing, but instead of implementing handlers for specific parts of the document, it allows you to detect the current node type, process the node accordingly, and skip forward in the document as much as necessary. Unlike the DOM parser, you may not move backwards in the XML document. And unlike the SAX parser, you must not waste time processing nodes that do not interest you.

All methods are on the single parser instance, but their result depends on the current context. For instance, use `read()` to move to the next node, and `move_to_element()` to navigate to child nodes. These methods will return false when no more nodes are available. Then use methods such as `get_name()` and `get_value()` to examine the elements and their attributes.

#### Example

This example examines each node in turn, then moves to the next node.

Source Code

File: main.cc

    #ifdef HAVE_CONFIG_H
    #include <config.h>
    #endif

    #include <libxml++/libxml++.h>
    #include <libxml++/parsers/textreader.h>

    #include <iostream>

    struct indent {
      int depth_;
      indent(int depth): depth_(depth) {};
    };

    std::ostream & operator<<(std::ostream & o, indent const & in)
    {
      for(int i = 0; i != in.depth_; ++i)
      {
        o << "  ";
      }
      return o;
    }

     int
    main(int argc, char* argv[])
    {
      #ifdef LIBXMLCPP_EXCEPTIONS_ENABLED
      try
      {
      #endif //LIBXMLCPP_EXCEPTIONS_ENABLED
        xmlpp::TextReader reader("example.xml");

        while(reader.read())
        {
          int depth = reader.get_depth();
          std::cout << indent(depth) << "--- node ---" << std::endl;
          std::cout << indent(depth) << "name: " << reader.get_name() << std::endl;
          std::cout << indent(depth) << "depth: " << reader.get_depth() << std::endl;

          if(reader.has_attributes())
          {
            std::cout << indent(depth) << "attributes: " << std::endl;
            reader.move_to_first_attribute();
            do
            {
              std::cout << indent(depth) << "  " << reader.get_name() << ": " << reader.get_value() << std::endl;
            } while(reader.move_to_next_attribute());
            reader.move_to_element();
          }
          else
          {
            std::cout << indent(depth) << "no attributes" << std::endl;
          }

          if(reader.has_value())
            std::cout << indent(depth) << "value: '" << reader.get_value() << "'" << std::endl;
          else
            std::cout << indent(depth) << "novalue" << std::endl;

        }
      #ifdef LIBXMLCPP_EXCEPTIONS_ENABLED
      }
      catch(const std::exception& e)
      {
        std::cout << "Exception caught: " << e.what() << std::endl;
      }
      #endif //LIBXMLCPP_EXCEPTIONS_ENABLED
    }

Prev |  |
libxml++ - An XML Parser for C++ | Home |