RapidXML - how can I handle missing nodes/values - c++

I'd like to read from XML to C++ using RapidXML. However, if a node doen't exist or a value is missing the program crashes.
for (rapidxml::xml_node<> * xmlasset_node = root_node->first_node("Asset"); xmlasset_node; xmlasset_node = xmlasset_node->next_sibling())
{mystring += xmlasset_node->first_attribute("name")->value()};
However, this "name" attribute doesn't exist in all nodes and is to be filled with a default value, if its not in XML. Similar to this, I've got some sub-nodes not in all nodes. The reason is just to keep the XML as small and clear as possible for manual adjustments.
How can a check/test be implemented (C++), to prevent the program from crashing and just taking default values if a value/node doesn't exist?
Kind regards,
- Corak

Here is what I do, you can compare if the value of the node and its attribute matches your criteria then you accepts it:
// basically I am looking for "settings" node then "network" subnode, then "port" attribute
if( boost::iequals(doc.first_node()->next_sibling()->name(), "settings"))
{
for (xml_node<> *node = doc.first_node()->next_sibling()->first_node(); node; node = node->next_sibling())
{
// find network tag
if (boost::iequals(node->name(),"network"))
{
for (xml_attribute<> *attr = node->first_attribute(); attr; attr = attr->next_attribute())
{
if ( boost::iequals(attr->name(), "port"))
{
strcpy(attr->value(), portname);
}
}
}
}
}

Related

Reading a XML file in C++ with TinyXML2

I'm pretty new to using XML in C++ and i'm trying to parse a list of files to download.
THe XML file I'm using is generated via PHP and looks like this :
<?xml version="1.0"?>
<FileList>
<File Name="xxx" Path="xxx" MD5="xxx" SHA1="xxx"/>
</FileList>
The code I'm using in C++ is the following, which I came up using some online tutorials (it's included in some global function):
tinyxml2::XMLDocument doc;
doc.LoadFile("file_listing.xml");
tinyxml2::XMLNode* pRoot = doc.FirstChild();
tinyxml2::XMLElement* pElement = pRoot->FirstChildElement("FileList");
if (pRoot == nullptr)
{
QString text = QString::fromLocal8Bit("Error text in french");
//other stuff
}
else
{
tinyxml2::XMLElement* pListElement = pElement->FirstChildElement("File");
while (pListElement != nullptr)
{
QString pathAttr = QString::fromStdString(pListElement->Attribute("Path"));
QString md5Attr = QString:: fromStdString(pListElement->Attribute("MD5"));
QString sha1Attr = QString::fromStdString(pListElement->Attribute("SHA1"));
QString currentPath = pathAttr.remove("path");
QString currentMd5 = this->fileChecksum(currentPath, QCryptographicHash::Md5);
QString currentSha1 = this->fileChecksum(currentPath, QCryptographicHash::Sha1);
QFile currentFile(currentPath);
if (md5Attr != currentMd5 || sha1Attr != currentSha1 || !currentFile.exists())
{
QString url = "url" + currentPath;
this->downloadFile(url);
}
pListElement = pListElement->NextSiblingElement("File");
}
Problem is, I get an error like "Access violation, this was nullptr" on the following line :
tinyxml2::XMLElement* pListElement = pElement->FirstChildElement("File");
Since I'm far from a pro when it comes to coding and I already searched the internet up and down, I hope that someone here can provide me some pointers.
Have a good day, folks.
I don't know if you have C++17 available, but you can remove a lot of noise by using auto* and if-init-expressions (or rely on the fact that pointers can be implicitly converted to boolean values.)
The main issue with your code is you were not using XMLElement* but instead a XMLNode. The function tinyxml2::XMLDocument::RootElement() automatically gets the top-most element for you.
Because you have an xml declaration at the top, FirstChild returns that...which doesn't have any children, so the rest of the code fails.
By using RootElement tinyxml knows to skip any leading non-element nodes (comments, doctypes, etc.) and give you <FileList> instead.
tinyxml2::XMLDocument doc;
auto err = doc.LoadFile("file_listing.xml");
if(err != tinyxml2::XML_SUCCESS) {
//Could not load file. Handle appropriately.
} else {
if(auto* pRoot = doc.RootElement(); pRoot == nullptr) {
QString text = QString::fromLocal8Bit("Error text in french");
//other stuff
} else {
for(auto* pListElement = pRoot->FirstChildElement("File");
pListElement != nullptr;
pListElement = pListElement->NextSiblingElement("File"))
{
QString pathAttr = QString::fromStdString(pListElement->Attribute("Path"));
QString md5Attr = QString:: fromStdString(pListElement->Attribute("MD5"));
QString sha1Attr = QString::fromStdString(pListElement->Attribute("SHA1"));
QString currentPath = pathAttr.remove("path");
QString currentMd5 = this->fileChecksum(currentPath, QCryptographicHash::Md5);
QString currentSha1 = this->fileChecksum(currentPath, QCryptographicHash::Sha1);
QFile currentFile(currentPath);
if(md5Attr != currentMd5 || sha1Attr != currentSha1 || !currentFile.exists()) {
QString url = "url" + currentPath;
this->downloadFile(url);
}
}
}
}
According to the reference for tinyxml2::XMLNodeFirstChild():
Get the first child node, or null if none exists.
This line will therefore get the root node:
tinyxml2::XMLNode* pRoot = doc.FirstChild();
Meaning when you attempt to find a FileList node within the root node it returns null.
To avoid the access violation, check your pointers are valid before using them. There is an if check for pRoot but the line immediately before it tries to call a function on pRoot. There is no if check for pElement so this is why you get an access violation. As well as checking pointers are valid, consider adding else blocks with logging to say what went wrong (e.g. "could not find element X"). This will help you in the long run - XML parsing is a pain, even with a library like Tinyxml, there are always teething problems like this, so getting into the habit of checki g pointers and logging out helpful messages will definitely pay off.

Appending child's Json::Value after appending parent's Json::Value does not change parent data, any suggestion?

I am constructing json from tree data, but when i add node_level_3 from node_level_2 after adding node_level_2 from node_level_1, node_level_2 does not has information abour node_level_3.
Here is my code.
node_level_1 = new Json::Value();
(*node_level_1)["data"] = first_value;
if (some_other_string != "")
{
node_level_2 = new Json::Value();
(*node_level_2)["data"] = some_other_string ;
(*node_level_1)["child"].append(*node_level_2);
}
if (another_string!= "")
{
node_level_3 = new Json::Value();
(*node_level_3) ["data"] = another_string;
(*node_level_2) ["child"].append(*node_level_3 );
}
I guess the problem is that 'Json::Value.append() function' only copy its data, not pointer or reference. So If i change data of node_level_2, it does not affect previously added node_level_2.
How can i solve this problem??
Should i have to traverse all the bottom nodes(level #3) of tree, and construct parent tree node (level #2) and finally add all the parent to root node(level #1)? Is this only solution With JsonCpp ?

parsing comment in tinyXML2

I have problem with parsing XML comment. How can i properly access to comment?
Or is even possible to read comment with tinyXML2?
<xml>
<foo> Text <!-- COMMENT --> <foo2/></foo>
</xml>
I created
XMLElement *root = xmlDoc->FirstChildElement("foo");
XMLElement *child = root->FirstChildElement();
From child element i get foo2 element, What is propper way to read comment element from file.
Thanks
You can use XMLNode::FirstChild() and XMLNode::NextSibling() to loop through all child nodes. Use dynamic_cast to test if node is a comment.
if( const XMLElement *root = xmlDoc->FirstChildElement("foo") )
{
for( const XMLNode* node = root->FirstChild(); node; node = node->NextSibling() )
{
if( auto comment = dynamic_cast<const XMLComment*>( node ) )
{
const char* commentText = comment->Value();
}
}
}
I've made this up just from reading the documentation, so there might be mistakes in the code.
I just created a function on my project that navigates the entire document recursively and get rid of comments. You can use that to see how you can pick up any comment on the document... followed the example of the fellow above..
Code bellow:
// Recursively navigates the XML and get rid of comments.
void StripXMLInfo(tinyxml2::XMLNode* node)
{
// All XML nodes may have children and siblings. So for each valid node, first we
// iterate on it's (possible) children, and then we proceed to clear the node itself and jump
// to the next sibling
while (node)
{
if (node->FirstChild() != NULL)
StripXMLInfo(node->FirstChild());
//Check to see if current node is a comment
auto comment = dynamic_cast<tinyxml2::XMLComment*>(node);
if (comment)
{
// If it is, we ask the parent to delete this, but first move pointer to next member so we don't get lost in a NULL reference
node = node->NextSibling();
comment->Parent()->DeleteChild(comment);
}
else
node = node->NextSibling();
}
}

Parsing XML Elements using TinyXML

UPDATE: Still not working :( I have updated the code portion to reflect what I currently have.
This should be a pretty easy question for people who have used TinyXML. I'm attempting to use TinyXML to parse through an XML document and pull out some values. I figured out how to add in the library yesterday, and I have successfully loaded the document (hey, it's a start).
I've been reading through the manual and I can't quite figure out how to pull out individual attributes. After Googling around, I haven't found an example of my specific example, so perhaps someone here who has used TinyXML can help out. Below is a slice of the XML, and where I have started to parse it.
XML:
<EGCs xmlns="http://tempuri.org/XMLSchema.xsd">
<card type="EGC1">
<offsets>
[ ... ]
</offsets>
</card>
<card type="EGC2">
<offsets>
[ ... ]
</offsets>
</card>
</EGCs>
Loading/parsing code:
TiXmlDocument doc("EGC_Cards.xml");
if(doc.LoadFile())
{
TiXmlHandle hDoc(&doc);
TiXmlElement* pElem;
TiXmlHandle hRoot(0);
pElem = hDoc.FirstChildElement().Element();
if (!pElem) return false;
hRoot = TiXmlHandle(pElem);
//const char *attribval = hRoot.FirstChild("card").ToElement()->Attribute("card");
pElem = hDoc.FirstChild("EGCs").Child("card", 1).ToElement();
if(pElem)
{
const char* tmp = pElem->GetText();
CComboBox *combo = (CComboBox*)GetDlgItem(IDC_EGC_CARD_TYPE);
combo->AddString(tmp);
}
}
I want to pull out each card "type" and save it to a string to put into a combobox. How do I access this attribute member?
After a lot of playing around with the code, here is the solution! (With help from HERE)
TiXmlDocument doc("EGC_Cards.xml");
combo = (CComboBox*)GetDlgItem(IDC_EGC_CARD_TYPE);
if(doc.LoadFile())
{
TiXmlHandle hDoc(&doc);
TiXmlElement *pRoot, *pParm;
pRoot = doc.FirstChildElement("EGCs");
if(pRoot)
{
pParm = pRoot->FirstChildElement("card");
int i = 0; // for sorting the entries
while(pParm)
{
combo->InsertString(i, pParm->Attribute("type"));
pParm = pParm->NextSiblingElement("card");
i++;
}
}
}
else
{
AfxMessageBox("Could not load XML File.");
return false;
}
there should be a Attribute method that takes and attribut name as parameter see: http://www.grinninglizard.com/tinyxmldocs/classTiXmlElement.html
from the documentation I see the code would look like:
hRoot.FirstChildElement("card").ToElement()->Attibute("type");
However for the type of thing you are doing I would use XPATH if at all possible. I have never used it but the TinyXPath project may be helpful if you choose to go that route the link is: http://tinyxpath.sourceforge.net/
Hope this helps.
The documentation I am using to help you from is found at: http://www.grinninglizard.com/tinyxmldocs/hierarchy.html
What you need is to get the attribute type from the element card. So in your code it should be something like:
const char * attribval = hRoot.FirstChild("card").ToElement()->Attribute("card");

Parsin XML file using pugixml

Hi
I want to use XML file as a config file, from which I will read parameters for my application. I came across on PugiXML library, however I have problem with getting values of attributes.
My XML file looks like that
<?xml version="1.0"?>
<settings>
<deltaDistance> </deltaDistance>
<deltaConvergence>0.25 </deltaConvergence>
<deltaMerging>1.0 </deltaMerging>
<m> 2</m>
<multiplicativeFactor>0.7 </multiplicativeFactor>
<rhoGood> 0.7 </rhoGood>
<rhoMin>0.3 </rhoMin>
<rhoSelect>0.6 </rhoSelect>
<stuckProbability>0.2 </stuckProbability>
<zoneOfInfluenceMin>2.25 </zoneOfInfluenceMin>
</settings>
To pare XML file I use this code
void ReadConfig(char* file)
{
pugi::xml_document doc;
if (!doc.load_file(file)) return false;
pugi::xml_node tools = doc.child("settings");
//[code_traverse_iter
for (pugi::xml_node_iterator it = tools.begin(); it != tools.end(); ++it)
{
cout<<it->name() << " " << it->attribute(it->name()).as_double();
}
}
and I also was trying to use this
void ReadConfig(char* file)
{
pugi::xml_document doc;
if (!doc.load_file(file)) return false;
pugi::xml_node tools = doc.child("settings");
//[code_traverse_iter
for (pugi::xml_node_iterator it = tools.begin(); it != tools.end(); ++it)
{
cout<<it->name() << " " << it->value();
}
}
Attributes are loaded corectly , however all values are equals 0. Could somebody tell me what I do wrong ?
I think your problem is that you're expecting the value to be stored in the node itself, but it's really in a CHILD text node. A quick scan of the documentation showed that you might need
it->child_value()
instead of
it->value()
Are you trying to get all the attributes for a given node or do you want to get the attributes by name?
For the first case, you should be able to use this code:
unsigned int numAttributes = node.attributes();
for (unsigned int nAttribute = 0; nAttribute < numAtributes; ++nAttribute)
{
pug::xml_attribute attrib = node.attribute(nAttribute);
if (!attrib.empty())
{
// process here
}
}
For the second case:
LPCTSTR GetAttribute(pug::xml_node & node, LPCTSTR szAttribName)
{
if (szAttribName == NULL)
return NULL;
pug::xml_attribute attrib = node.attribute(szAttribName);
if (attrib.empty())
return NULL; // or empty string
return attrib.value();
}
If you want stock plain text data into the nodes like
<name> My Name</name>
You need to make it like
rootNode.append_child("name").append_child(node_pcdata).set_value("My name");
If you want to store datatypes, you need to set an attribute. I think what you want is to be able to read the value directly right?
When you are writing the node,
rootNode.append_child("version").append_attribute("value").set_value(0.11)
When you want to read it,
rootNode.child("version").attribute("version").as_double()
At least that's my way of doing it!