Volomike
Volomike

Reputation: 24916

How To Iterate XML Nodes in C++ with the TinyXML2 Library

How does one iterate nodes in TinyXML2? I tried following the documentation but am not able to grasp this.

http://www.grinninglizard.com/tinyxml2docs/index.html

My XML is already loaded into std::string. Thus, the following compiles:

#include "tinyxml2.hpp"
// assume I have code here which reads my XML into std::string sXML
tinyxml2::XMLDocument doc;
doc.Parse( sXML.c_str() );

Now what do I do with doc to iterate the item list so that I can pull out the title and author fields inside into std::string variables?

Here's my XML sample:

<?xml version=“1.0” encoding=“utf-8”?>
<books>
    <item>
        <title>Letters to Gerhardt</title>
        <author>Von Strudel, Jamath</author>
    </item>
    <item>
        <title>Swiss Systemic Cleanliness Principles, The</title>
        <author>Jöhansen, Jahnnes</author>
    </item>
</books>

Was hoping for something simple like a C++ vector of item and then perhaps a C++ map inside where I can address it by "title" and "author" or .title or .author.

Upvotes: 0

Views: 6181

Answers (4)

Martin Wilde
Martin Wilde

Reputation: 1

Maybe this could be of help - quick implementation of missing GetElementsByTagName (...) that I used a lot in other XML DOM APIs:

bool ScanNodeForElementsByTagName (std::vector<XMLNode*>& vecNodeList, const std::string strTagName, XMLNode* pclNode)
{
    bool bRetValue = true;
    
    // check, if node is != null
    if (pclNode == NULL)
    {
        bRetValue = false;
    }
    else
    {
        // check, if node itself has the target tag name
        if (pclNode->ToElement()->Name() == strTagName)
        {
            vecNodeList.push_back (pclNode);
        }

        XMLNode* e = pclNode->FirstChild();
        while (e != NULL)
        {
            if (e->ToElement() != NULL)
            {
                ScanNodeForElementsByTagName (vecNodeList, strTagName, e);            
            }
            
            e = e->NextSibling();
        } 
    }    
    
    return (bRetValue);
}



// MWI: added GetElementsByTagName function
std::vector<XMLNode*> XMLNode::GetElementsByTagName (const std::string strTagName, XMLNode* pclNode)
{
    std::vector<XMLNode*> vecNodeList;
    
    vecNodeList.clear ();
    
    ScanNodeForElementsByTagName (vecNodeList, strTagName, pclNode);    
    
    return (vecNodeList);
}

Upvotes: 0

CXJ
CXJ

Reputation: 4467

Probably the best way to do this is to implement an XMLVisitor class and use the XMLNode::Accept() method. Then in your callback, you can grab the strings you want.

Upvotes: 1

stanthomas
stanthomas

Reputation: 1181

Here's some work in progress you might find useful: tinyxml2 extension . Documentation is incomplete so you'll need to deduce from the test example until it's finished.

You can read data from the xml with something like this:

#include <iostream>
#include <tixml2ex.h>

auto doc = tinyxml2::load_document (sXML);
for (auto item : selection (*doc, "books/item"))
{
    std::cout << "title  : " << text (find_element (item, "title")) << std::endl;
    std::cout << "author : " << text (find_element (item, "author")) << std::endl << std::endl;
}

N.B. should really be wrapped in a try/catch block.

If you want to store element names and attributes as some combination of vector and map you'll have to copy over as required.

Upvotes: 1

Volomike
Volomike

Reputation: 24916

// PARSE BOOKS

#pragma once
#include <string>
#include <stdio.h>
#include <vector>
#include "tinyxml2.hpp"

struct myRec {
  std::string title;
  std::string author;
};

std::vector<myRec> recs;

tinyxml2::XMLDocument doc;
doc.Parse( sXML.c_str() );
tinyxml2::XMLElement* parent = doc.FirstChildElement("books");

tinyxml2::XMLElement *row = parent->FirstChildElement();
while (row != NULL) {
  tinyxml2::XMLElement *col = row->FirstChildElement();
  myRec rec;
  while (col != NULL) {
    std::string sKey;
    std::string sVal;
    char *sTemp1 = (char *)col->Value();
    if (sTemp1 != NULL) {
      sKey = static_cast<std::string>(sTemp1);
    } else {
      sKey = "";
    }
    char *sTemp2 = (char *)col->GetText();
    if (sTemp2 != NULL) {
      sVal = static_cast<std::string>(sTemp2);
    } else {
      sVal = "";
    }
    if (sKey == "title") {
      rec.title = sVal;
    }
    if (sKey == "author") {
      rec.author = sVal;
    }
    col = col->NextSiblingElement();
  } // end while col
  recs.push_back(rec);
  row = row->NextSiblingElement();
} // end while row
signed long nLen = recs.size();
if (nLen > 0) {
  --nLen;
  nLen = (nLen < 0) ? 0 : nLen;
  for (int i = 0; i <= nLen; i++) {
    std::string sTitle = recs[i].title;
    std::string sAuthor = recs[i].author;
    std::cout << sTitle << "\n" << sAuthor << "\n";
  }
} else {
  std::cout << "Empty rowset of books.\n";
}

Note, I'm fairly new to C++. If you know of a way to optimize this in less lines, I'd be thrilled to see it.

Upvotes: 1

Related Questions