Compare std::vector of pairs with pugi::xml_object_range attributes - c++

I'm writing some convenience functions for my XML parser based on pugixml, and now I have the problem that I want to retrieve only XML nodes with a specific attribute name and value!
XML example:
<Readers>
<Reader measurement="..." type="Mighty">
<IP reader="1">192.168.1.10</IP>
<IP reader="2">192.168.1.25</IP>
<IP reader="3">192.168.1.30</IP>
<IP reader="4">192.168.1.50</IP>
</Reader>
<Reader measurement="..." type="Standard">
...
</Reader>
</Readers>
My try:
std::string GetNodeValue(std::string node, std::vector<std::pair<std::string,std::string>> &attributes)
{
pugi::xml_node xmlNode = m_xmlDoc.select_single_node(("//" + node).c_str()).node();
// get all attributes
pugi::xml_object_range<pugi::xml_attribute_iterator> nodeAttributes(xmlNode.attributes());
// logic to compare given attribute name:value pairs with parsed ones
// ...
}
Can someone help me or give me a hint?! (maybe with Lambda Expressions)

To solve this we have to use XPath.
/*!
Create XPath from node name and attributes
#param XML node name
#param vector of attribute name:value pairs
#return XPath string
*/
std::string XmlParser::CreateXPath(std::string node, std::vector<std::pair<std::string,std::string>> &attributes)
{
try
{
// create XPath
std::string xpath = node + "[";
for(auto it=attributes.begin(); it!=attributes.end(); it++)
xpath += "#" + it->first + "='" + it->second + "' and ";
xpath.replace(xpath.length()-5, 5, "]");
return xpath;
}
catch(std::exception exp)
{
return "";
}
}
CreateXPath constructs a valid XML XPath statement from given node name and attribute list.
/*!
Return requested XmlNode value
#param name of the XmlNode
#param filter by given attribute(s) -> name:value
#return value of XmlNode or empty string in case of error
*/
std::string XmlParser::GetNodeValue(std::string node, std::vector<std::pair<std::string,std::string>> &attributes /* = std::vector<std::pair<std::string,std::string>>() */)
{
try
{
std::string nodeValue = "";
if(attributes.size() != 0) nodeValue = m_xmlDoc.select_node(CreateXPath(node, attributes).c_str()).node().child_value();
else nodeValue = m_xmlDoc.select_node(("//" + node).c_str()).node().child_value();
return nodeValue;
}
catch(std::exception exp)
{
return "";
}
}

Related

recursively get complete key path to all values in a boost property tree

I'm reading an XML file into a boost::property_tree and trying to get the complete key path for every value.
Does boost have a built in way to do this
Were is the error in my recursion?
example input - my_file.xml
<foo>
<bar>abc</bar>
<baz>
<buz>def</buz>
</baz>
</foo>
desired result
"foo.bar"
"foo.baz.buz"
actual result (wrong)
"foo.bar"
"foo.bar.baz.buz"
code that doesn't quite work
void walk_ptree(boost::property_tree::ptree tree, std::unordered_set<std::string>& key_set, std::string parent_key)
{
if (tree.empty())
{
key_set.insert(parent_key);
return;
}
for (auto& it : tree)
{
// add a separator between key segments
if (!parent_key.empty())
{
parent_key += ".";
}
parent_key += it.first;
walk_ptree(it.second, key_set, parent_key);
}
}
boost::property_tree::ptree prop_tree;
boost::property_tree::read_xml("my_file.xml", prop_tree);
std::unordered_set<std::string> key_set{};
walk_ptree(prop_tree, key_set, "");
Every iteration of your for loop adds to the parentKey value so it will have all the children's names in it by the end of the loop. Use a separate variable to hold the key name for each node:
void walk_ptree(const boost::property_tree::ptree& tree, std::unordered_set<std::string>& key_set, const std::string& parent_key)
{
if (tree.empty())
{
key_set.insert(parent_key);
return;
}
for (auto& it : tree)
{
std::string key = parent_key;
// add a separator between key segments
if (!key.empty())
{
key += ".";
}
key+= it.first;
walk_ptree(it.second, key_set, key);
}
}

Boost JSON parser and ip address

I want to get the child nodes of node which is having ip address.
Below is the reference JSON format and code i am using.
{
"nodes":{
"192.168.1.1": {
"type":"type1",
"info":"info1",
"error":"error1"
},
"192.168.1.2":{
"type":"type2",
"info":"info2",
"error":"error2"
},
"test":{
"type":"type2",
"info":"info2",
"error":"error2"
}
}
}
Below is the reference code to read above json data.
using boost::property_tree::ptree;
ptree pt;
std::string ttr("test.json");
read_json(ttr, pt);
BOOST_FOREACH(ptree::value_type &v, pt.get_child("nodes"))
{
std::string key_ = v.first.data();
std::string val_ = v.second.data();
boost::optional< ptree& > child = pt.get_child_optional( "nodes.192.168.1.1" );
if( !child )
{
std::cout << "Child Node Missing.............." << std::endl; //Always shows Node Missing. How to deal with "." as key ?
}
else
std::cout << "Child Node Not Missing.............." << std::endl;
}
Can you please suggest how to read the child if node contains "." ( ip address ) ? Here "nodes.test" will work but "nodes.192.168.1.1" will not work because it contains "." as string ? How to get it working ?
Thanks in Advance.
From the docs:
To use a separator character other than default '.', you need to construct a path object explicitly. The path type for a ptree is a string_path instantiation, so the easiest way to refer to it is ptree::path_type. This way you can use trees that have dots in their keys[.]
In your case:
boost::optional< ptree& > child = pt.get_child_optional(ptree::path_type("nodes/192.168.1.1", '/'));

Property tree: complete path and name

I'd like to create a class that can be constructed from a property tree, as in this example:
<?xml version="1.0" encoding="utf-8"?>
<config>
<name>testing</name>
<!-- Test property tree -->
<lambda min="200000" max="200">100</lambda>
...
That is easy with a property tree, but then I need to access two properties of a subtree, as in this class:
parameter::parameter(boost::property_tree::ptree t)
{
// Set the value
value = t.get_value<double>();
// ?????
auto nodename = t.something();
// ?????
std::string nodepath = t.somethingelse();
// Get the attributes (or empty)
auto p = t.get_child("<xmlattr>", boost::property_tree::ptree());
// If we have attributes, read them
if (p != boost::property_tree::ptree())
{
min = t.get<double>("<xmlattr>.min");
max = t.get<double>("<xmlattr>.max");
if (min > max)
throw std::runtime_error("Min and max values invalid for the parameter " + nodename + ", path: " + nodepath);
}
else
{
min = +1.0;
max = -1.0;
}
}
// ... Someplace else
lambda = parameter(config.get_child("config.lambda"));
In the XML the mim/max attributes for lambda are invalid, and I need to throw an exception that could be read as
Min and max values invalid for the parameter lambda, path: config.lambda
Of course I could just pass the string, but it would defat the purpose. I've tried messing around t's iterator and data, but got nothing.
Can I obtain those values from a ptree?
Thanks!
I'd slightly shuffle the interface around so you don't drop the information you need prematurely:
Live On Coliru
#include <boost/property_tree/xml_parser.hpp>
using boost::property_tree::ptree;
struct parameter {
parameter(ptree const& tree, ptree::path_type const& nodepath)
{
ptree const& t = tree.get_child(nodepath);
// Set the value
value = t.get_value<double>();
auto nodename = [nodepath] {
auto copy = nodepath;
while (!copy.single()) copy.reduce();
return copy.reduce();
}();
// Get the attributes (or empty)
auto p = t.get_child("<xmlattr>", boost::property_tree::ptree());
// If we have attributes, read them
if (p != boost::property_tree::ptree())
{
auto min = t.get<double>("<xmlattr>.min");
auto max = t.get<double>("<xmlattr>.max");
if (min > max)
throw std::runtime_error("Min and max values invalid for the parameter " + nodename + ", path: " + nodepath.dump());
}
else
{
min = +1.0;
max = -1.0;
}
}
private:
double min, max;
double value;
};
int main() {
ptree config;
std::ifstream xml("input.txt");
read_xml(xml, config);
auto lambda = parameter(config, "config.lambda");
}
Prints
terminate called after throwing an instance of 'std::runtime_error'
what(): Min and max values invalid for the parameter lambda, path: config.lambda

libxml2 xpath parsing, doesn't work as expected

I decided to use libxml2 parser for my qt application and im stuck on xpath expressions. I found an example class and methods, and modified this a bit for my needs. The code
QStringList* LibXml2Reader::XPathParsing(QXmlInputSource input)
{
xmlInitParser();
xmlDocPtr doc;
xmlXPathContextPtr xpathCtx;
xmlXPathObjectPtr xpathObj;
QStringList *valList =NULL;
QByteArray arr = input.data().toUtf8(); //convert input data to utf8
int length = arr.length();
const char* data = arr.data();
doc = xmlRecoverMemory(data,length); // build a tree, ignoring the errors
if(doc == NULL) { return NULL;}
xpathCtx = xmlXPathNewContext(doc);
if(xpathCtx == NULL)
{
xmlFreeDoc(doc);
xmlCleanupParser();
return NULL;
}
xpathObj = xmlXPathEvalExpression(BAD_CAST "//[#class='b-domik__nojs']", xpathCtx); //heres the parsing fails
if(xpathObj == NULL)
{
xmlXPathFreeContext(xpathCtx);
xmlFreeDoc(doc);
xmlCleanupParser();
return NULL;
}
xmlNodeSetPtr nodes = xpathObj->nodesetval;
int size = (nodes) ? nodes->nodeNr : 0;
if(size==0)
{
xmlXPathFreeContext(xpathCtx);
xmlFreeDoc(doc);
xmlCleanupParser();
return NULL;
}
valList = new QStringList();
for (int i = 0; i < size; i++)
{
xmlNodePtr current = nodes->nodeTab[i];
const char* str = (const char*)current->content;
qDebug() << "name: " << QString::fromLocal8Bit((const char*)current->name);
qDebug() << "content: " << QString::fromLocal8Bit((const char*)current->content) << "\r\n";
valList->append(QString::fromLocal8Bit(str));
}
xmlXPathFreeObject(xpathObj);
xmlXPathFreeContext(xpathCtx);
xmlFreeDoc(doc);
xmlCleanupParser();
return valList;
}
As an example im making a request to http://yandex.ru/ and trying to get the node with class b-domik__nojs which is basically one div.
xpathObj = xmlXPathEvalExpression(BAD_CAST "//[#class='b-domik__nojs']", xpathCtx); //heres the parsing fails
the problem is the expression //[#class='b-domik__nojs'] doesn't work at all. I checked it in firefox xpath ext., and in opera developer tools xpath ext. in there this expression works perfectly.
I also tried to get other nodes with attributes but for some reason xpath for ANY attribute fails. Is there something wrong in my method? Also when i load a tree using xmlRecover, it gives me a lot of parser errors in debug output.
Ok i played a bit with my libxml2 function more and used "//*" expression to get all elements in the document, but! It returns me only the elements in the first children node of the body tag. This is the yandex.ru dom tree
so basically it gets ALL the elements in the first div "div class="b-line b-line_bar", but doesnt look for the other elements in other child nodes of the <body> for some reason.
Why can that happen? Maybe xmlParseMemory doesnt build a full tree for some reason? Is there any possible solution to fix this.
It is really strange that the expression works anywhere, because it is not a valid XPath expression. After the axis specification (//), there should be a nodetest (element name or *) before the predicate (the condition in square brackets).
//*[#class='bdomik__nojs']
Allright it works now, if my mistake was to use xml functions to make html documents into a tree. I used htmlReadMemory and the tree is fully built now. Some code again
xmlInitParser();
xmlDocPtr doc;
xmlXPathContextPtr xpathCtx;
xmlXPathObjectPtr xpathObj;
QByteArray arr = input.data().toUtf8();
int length = arr.length();
const char* data = arr.data();
doc = htmlReadMemory(data,length,"",NULL,HTML_PARSE_RECOVER);
if(doc == NULL) { return NULL;}
xpathCtx = xmlXPathNewContext(doc);
if(xpathCtx == NULL)
{
xmlFreeDoc(doc);
xmlCleanupParser();
return NULL;
}
xpathObj = xmlXPathEvalExpression(BAD_CAST "//*[#class='b-domik__nojs']", xpathCtx);
etc.

How to read Boost property_map with peer values with identical tag names

I am using the property_map from Boost c++ library v1.53, and its working great for me except I can't figure out how to parse data nodes with the same name that are peers of each other. As in the following XML:
<RECORDSET>
<C>
<CY>
<CZ>
<I>1</I>
<CT>10</CT>
</CZ>
<CZ>
<I>2</I>
<CT>12</CT>
</CZ>
</CY>
<CS>
<I>1</I>
<I>2</I>
</CS>
</C>
</RECORDSET>
I can parse everything above except the "I" data node elements under the "CS" tag at the bottom. I am trying to use the code:
// (works no problem)
BOOST_FOREACH(const ptree::value_type & vpairC, proptreeCs.get_child(string("C")))
{
if (vpairC.first.data() != std::string("C"))
continue;
// grab the "C" ptree sub-tree for readability.
ptree proptreeC = vpairC.second;
// (this works no problem to iterate through the multiple CZ nodes under CY)
// RM_CZs
short nCZCount = 0;
sTagName = ;
BOOST_FOREACH(const ptree::value_type & vpairCZ, proptreeC.get_child("C"))
{
// get a local ptree for readability.
ptree ptreeCZ = vpairCZ.second;
// get the I and CT ids.
sTagName = "I";
long lId = ptreeCZ.get<long>(sTagName));
sTagName = "CT";
long lCT = ptreeCZ.get<long>(sTagName));
// do something with id and ct...
// increment the count.
nCZCount++;
}
// nCZCount ends up set to 2 based on input XML above
// (this loop does NOT work)
sTagName = "CS";
const ptree proptreeCS = proptreeC.get_child(sTagName);
// (this does NOT work to iterate through <I> values under the <CS> node)
sTagName = "I";
BOOST_FOREACH(const ptree::value_type & vpairCS,
proptreeCS.get_child(sTagName))
{
// check to see if this is a "I" value; if not skip it.
if (vpairCS.first.data() != sTagName)
continue;
long lId = atol(vpairCS.second.data().c_str());
// do something with id...
}
// the above loop does NOT execute one time.
}
So how can I iterate through the "I" value peers under the "CS" node?
In the code in my question, I was asking for children too low in the tree. Here is the loop that will retrieve the "I" values from the "CS" node (replaces the last BOOST_FOREACH in the code in my question):
BOOST_FOREACH(const ptree::value_type & vpairI, proptreeC.get_child(std::str("CS")))
{
// check to see if this is an "I" value; if not skip it.
if (vpairCapability.first.data() != std::string("I"))
continue;
long lId = atol(vpairI.second.data().c_str());
// do something with lId...
}