Add classes xml_node and xml_element
authorArjen Baart <arjen@andromeda.nl>
Tue, 24 Mar 2020 06:21:13 +0000 (07:21 +0100)
committerArjen Baart <arjen@andromeda.nl>
Tue, 24 Mar 2020 06:21:13 +0000 (07:21 +0100)
14 files changed:
doc/Makefile.am
doc/manual.xml
doc/xml.xml [new file with mode: 0644]
src/xml.cpp
src/xml.h
test/Makefile.am
test/xml_elem.cpp [new file with mode: 0644]
test/xml_elem.exp [new file with mode: 0644]
test/xml_file.cpp
test/xml_find.cpp
test/xml_find.exp
test/xml_node.cpp [new file with mode: 0644]
test/xml_test01.xml
test/xml_test02.xml [new file with mode: 0644]

index e863ea0..cb8ac88 100644 (file)
@@ -7,7 +7,7 @@ SUFFIXES = .obj .eps .png
 .obj.eps:
        tgif -print -eps -color $<
 
-XMLS = manual.xml string.xml date.xml hour.xml utc.xml Integer.xml
+XMLS = manual.xml string.xml date.xml hour.xml utc.xml Integer.xml xml.xml
 IMAGES=
 
 PICTURES=
index 79bb2b9..32e8d08 100644 (file)
@@ -15,5 +15,7 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"/>
     <xi:include href="Integer.xml"
          xmlns:xi="http://www.w3.org/2001/XInclude"/>
+    <xi:include href="xml.xml"
+         xmlns:xi="http://www.w3.org/2001/XInclude"/>
   </book>
 </doc>
diff --git a/doc/xml.xml b/doc/xml.xml
new file mode 100644 (file)
index 0000000..11d9e34
--- /dev/null
@@ -0,0 +1,100 @@
+<chapter>
+<heading>xml - XML document classes</heading>
+
+<pre>
+#include &lt;xml.h&gt;
+
+xml document;
+xml_node node;
+xml_element element;
+</pre>
+
+<para>
+The collection of xml classes provide an easy way to process XML documents
+and to traverse the tree of XML data.
+Built upon the xml2 library on xmlsoft.org, the classes encapsulate concepts
+of XML data.
+After parsing an XML document with an xml object, the xml_node class
+can access the data of the XML document tree.
+A node in the tree can have any number of children which are also nodes.
+The child nodes are accessed by using the subscript operator with a numerical index.
+Elements of the XML data can be selected by name when a String argument is used
+with the subscript operator.
+Using this operator will return a vector of xml_element objects that hold
+all child elements having this name.
+<pre>
+
+xml data(filename);
+xml_node root(data);
+xml_node third = root[2];
+
+String tagname("chapter");
+std::vector &lt;xml_element&gt; all_chapters;
+
+all_chapters = root[tagname];
+
+</pre>
+</para>
+<section>
+<heading>Class xml</heading>
+<para>
+An object of class xml holds an entire XML document.
+It supports reading and writing XML documents from and to files
+as well conversion of XML objects to and from String objects.
+
+<description>
+<item tag="xml()">
+The default constructor creates an empty xml document.
+</item>
+<item tag="xml(String s)">
+Parse the xml document from s.
+</item>
+</description>
+</para>
+
+<description>
+<item tag="operator=(String &amp;s)">
+Parse the xml document from String s.
+</item>
+</description>
+
+</section>
+
+<section>
+<heading>class xml_node</heading>
+
+<para>
+A node is the basic building block that makes up the tree of an XML document.
+There are several kinds of nodes, such as ekements, attributes and text nodes.
+If a node is an ekement node it can have any number if child nodes.
+These child nodes can be accessed by using the subscript operator with
+a numerical index.
+</para>
+
+<description>
+<item tag="xml_node()">
+The default constructor creates an empty xml node.
+</item>
+<item tag="xml_node(xml doc)">
+Construct a node from the XML document. The xml_node object will be the root node
+of the XML document.
+</item>
+<item tag="bool is_a_node(void)">
+Return true if the object actually is an XML node.
+</item>
+<item tag="xml_node operator[](int n)">
+Return the child node with index n.
+</item>
+</description>
+</section>
+
+<section>
+<heading>class xml_element : xml_node</heading>
+<para>
+The class xml_element is derived from the class xml_node and adds properties
+soecific to XML ekements.
+</para>
+
+</section>
+
+</chapter>
index 2ec74c7..3e9267a 100644 (file)
@@ -1,13 +1,23 @@
 
 #include "xml.h"
 
+void xml::ParseFile(const char * filename)
+{
+   ctxt     = xmlNewParserCtxt();
+   //document = xmlParseFile(filename);
+   document = xmlCtxtReadFile(ctxt, filename, NULL, XML_PARSE_NOWARNING|XML_PARSE_NOERROR);
+}
+
 String xml::RootElementName(void)
 {
    String       root_name;
    xmlNodePtr   root;
 
-   root = xmlDocGetRootElement(document);
-   root_name = String((const char *)root->name);
+   if (document != NULL)
+   {
+      root = xmlDocGetRootElement(document);
+      root_name = String((const char *)root->name);
+   }
 
    return root_name;
 }
@@ -21,7 +31,25 @@ xml_node xml::RootNode(void)
    return xml_node(root);
 }
 
+//xml_node::xml_node(xml doc)
+//{
+//   *this = doc.RootNode();
+//}
+
 //  Find the nth child element by name (non-recursive)
+xml_node xml_node::operator[](int i)
+{
+   xmlNodePtr np = n->children;
+
+   while (np != NULL && i != 0)
+   {
+      i--;
+      np = np->next;
+   }
+
+   return xml_node(np);
+}
+
 xml_node xml_node::FindChildElement(String name, int nth)
 {
    xml_node element;
@@ -42,3 +70,22 @@ xml_node xml_node::FindChildElement(String name, int nth)
 
    return element;
 }
+
+
+std::vector<xml_element> xml_element::operator[](String tagname)
+{
+   std::vector<xml_element> elements_found;
+
+   xmlNodePtr np = n->children;
+   while (np != NULL)
+   {
+      if (np->type == XML_ELEMENT_NODE && tagname == (const char *)np->name)
+      {
+         xml_element e(np);
+         elements_found.push_back(e);
+      }
+      np = np->next;
+   }
+
+   return elements_found;
+}
index 63f1e5a..4579a17 100644 (file)
--- a/src/xml.h
+++ b/src/xml.h
@@ -1,9 +1,47 @@
 #include <libxml/parser.h>   // usually in /usr/include/libxml2, see xml2-config
+#include <vector>
 
 #include "String.h"
 
+class xml_node;
+
+class xml
+{
+   xmlParserCtxtPtr ctxt;
+   xmlDocPtr        document;
+
+public:
+
+   xml()
+   {
+      ctxt     = NULL;
+      document = NULL;
+   }
+
+   ~xml()
+   {
+      if (document != NULL)
+      {
+         xmlFreeDoc(document);
+         document = NULL;
+      }
+   }
+
+   void ParseFile(const char * filename);
+
+   void SaveFile(const char * filename)
+   {
+      xmlSaveFile(filename, document);
+   }
+
+   String RootElementName(void);
+   xml_node RootNode(void);
+  
+};
+
 class xml_node
 {
+protected:
    xmlNodePtr     n;
 
 public:
@@ -18,11 +56,21 @@ public:
       n = np;
    }
 
+   xml_node(xml &doc)
+   {
+      xml_node node;
+      node = doc.RootNode();
+
+      n = node.n;
+   }
+
    bool is_a_node(void)
    {
       return n != NULL;
    }
 
+   xml_node operator[](int n);
+
    String name(void)
    {
       String node_name;
@@ -35,6 +83,19 @@ public:
       return node_name;
    }
 
+   xmlElementType type(void)
+   {
+      xmlElementType nodetype;
+
+      nodetype = xmlElementType(0);
+      if (n != NULL)
+      {
+         nodetype = n->type;
+      }
+
+      return nodetype;
+   }
+
    // Assumes one childnode of type TEXT
    // TODO collect content from multiple TEXT nodes (recursively ?)
    String content(void)
@@ -53,38 +114,22 @@ public:
    xml_node FindChildElement(String name, int nth = 0);
 };
 
-class xml
+class xml_element : public xml_node
 {
-   xmlDocPtr      document;
-
 public:
-
-   xml()
+   xml_element() : xml_node()
    {
-      document = NULL;
    }
 
-   ~xml()
+   xml_element(xmlNodePtr np) : xml_node(np)
    {
-      if (document != NULL)
-      {
-         xmlFreeDoc(document);
-         document = NULL;
-      }
    }
 
-   void ParseFile(const char * filename)
+   xml_element(xml &doc) : xml_node(doc)
    {
-      document = xmlParseFile(filename);
    }
 
-   void SaveFile(const char * filename)
-   {
-      xmlSaveFile(filename, document);
-   }
+   std::vector<xml_element> operator[](String tagname);
 
-   String RootElementName(void);
-   xml_node RootNode(void);
-  
 };
 
index 740bac0..f593b49 100644 (file)
@@ -7,7 +7,7 @@ check_PROGRAMS = string_assign string_basics string_compare string_cat string_su
                  hour_assign hour_parse hour_compare hour_arithmetic hour_check_now \
                  utc_assign utc_compare utc_arithmetic \
                  integer_assign integer_factorial integer_fibonacci integer_pow64 integer_modulo \
-                 xml_file xml_find \
+                 xml_file xml_find xml_node xml_elem \
                  configuration_read configuration_find
 
 string_assign_SOURCES      = string_assign.cpp
@@ -46,6 +46,8 @@ integer_modulo_SOURCES     = integer_modulo.cpp tInteger.cpp
 
 xml_file_SOURCES           = xml_file.cpp
 xml_find_SOURCES           = xml_find.cpp
+xml_node_SOURCES           = xml_node.cpp
+xml_elem_SOURCES           = xml_elem.cpp
 
 configuration_read_SOURCES = configuration_read.cpp
 configuration_find_SOURCES = configuration_find.cpp
diff --git a/test/xml_elem.cpp b/test/xml_elem.cpp
new file mode 100644 (file)
index 0000000..02a9b0d
--- /dev/null
@@ -0,0 +1,59 @@
+/*******************************************************
+ *  Unit test for the xml_element class
+ *
+ * test xml element access
+ ******************************************************
+ *
+ */
+
+#include "xml.h"
+#include <assert.h>
+
+int main()
+{
+   xml    doc;
+
+   const char xml_file[] = "xml_test01.xml";
+
+   std::cout << "Reading XML file " << xml_file << "\n";
+
+   doc.ParseFile("xml_test01.xml");
+
+   xml_element root_node(doc);
+
+   std::cout << "Root element is " << root_node.name() << "\n";
+   std::cout << "Root node type is " << root_node.type() << "\n";
+   assert(root_node.name() == "doc");
+
+   std::vector<xml_element> book, chapters;
+
+   book = root_node["book"];
+   assert(book.size() == 1);
+
+   chapters = book[0]["chapter"];
+
+   std::cout << "Found " << chapters.size() << " chapter elements.\n";
+
+   std::vector<xml_element> report;
+
+   report = root_node["report"];
+   assert(report.size() == 0);
+
+   // Find an element three levels deep
+   std::vector<xml_element> children;
+   xml_element              found_node;
+
+   children = book[0]["titlepage"];
+   found_node = children[0];
+
+   assert(found_node.is_a_node() == true);
+   children = found_node["title"];
+   found_node = children[0];
+   assert(found_node.is_a_node() == true);
+   std::cout << "Name of title node = " << found_node.name() << "\n";
+   std::cout << "Content of title node = " << found_node.content() << "\n";
+
+   return 0;
+}
+
diff --git a/test/xml_elem.exp b/test/xml_elem.exp
new file mode 100644 (file)
index 0000000..0517b13
--- /dev/null
@@ -0,0 +1,7 @@
+Reading XML file xml_test01.xml
+Root element is doc
+Root node type is 1
+Found 3 chapter elements.
+Name of title node = title
+Content of title node = Andromeda Class Library
+PASS xml_elem (exit status: 0)
index ea4722e..6e9adcd 100644 (file)
@@ -22,8 +22,19 @@ int main()
    assert (root == "doc");
 
    root_node = doc.RootNode();
+   assert(root_node.is_a_node() == true);
    assert(root_node.name() == "doc");
 
+   doc.ParseFile("notexist.xml");
+   root_node = doc.RootNode();
+   assert(root_node.is_a_node() == false);
+   assert(root_node.name() == "");
+
+   doc.ParseFile("xml_test02.xml");
+   root_node = doc.RootNode();
+   assert(root_node.is_a_node() == false);
+   assert(root_node.name() == "");
+
    return 0;
 }
 
index b5c3cd6..8bebaa9 100644 (file)
@@ -25,9 +25,6 @@ int main()
 
    root_node = doc.RootNode();
 
-   std::cout << "Root element is " << root_node.name() << "\n";
-   assert(root_node.name() == "doc");
-
    child_name = "book";
    found_node = root_node.FindChildElement(child_name);
    std::cout << "Child element " << child_name;
index f62e36b..db39d6d 100644 (file)
@@ -1,5 +1,4 @@
 Reading XML file xml_test01.xml
-Root element is doc
 Child element book found.
 Child element report not found.
 Name of title node = title
diff --git a/test/xml_node.cpp b/test/xml_node.cpp
new file mode 100644 (file)
index 0000000..e45b480
--- /dev/null
@@ -0,0 +1,42 @@
+/*******************************************************
+ *  Unit test for the xml_node class
+ *
+ * test xml node access
+ ******************************************************
+ *
+ */
+
+#include "xml.h"
+#include <assert.h>
+
+int main()
+{
+   xml    doc;
+
+   const char xml_file[] = "xml_test01.xml";
+
+   std::cout << "Reading XML file " << xml_file << "\n";
+
+   doc.ParseFile("xml_test01.xml");
+
+   xml_node root_node(doc);
+
+   std::cout << "Root element is " << root_node.name() << "\n";
+   std::cout << "Root node type is " << root_node.type() << "\n";
+   assert(root_node.name() == "doc");
+
+   int i;
+   xml_node child;
+
+   root_node = root_node[1]; // The book element
+   child = root_node[0];
+   for (i = 0; child.is_a_node(); i++)
+   {
+      child = root_node[i];
+      std::cout << "Node " << i << " type is " << child.type() << "\n";
+      std::cout << "Node " << i << " name is " << child.name() << "\n";
+   }
+
+   return 0;
+}
+
index a6dd12d..dcfd8d3 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
 <doc style="main.css">
-  <book>
+  <book attr="value">
     <titlepage>
       <title>Andromeda Class Library</title>
     </titlepage>
diff --git a/test/xml_test02.xml b/test/xml_test02.xml
new file mode 100644 (file)
index 0000000..1a0069a
--- /dev/null
@@ -0,0 +1,3 @@
+<?xml 
+Input for testing xml classes.
+this is not really xml.