QXmlQuery change node found by XPath query - c++

I have a html tree and want to alter some attributes e.g. style of all nodes found by an QXmlQuery.
My code looks like this:
QXmlQuery query;
query.setFocus(data);
query.setQuery(xpathSelector);
QXmlResultItems result;
if (query.isValid()) {
query.evaluateTo(&result);
QXmlItem item(result.next());
while (!item.isNull()) {
if (item.isNode()) {
// alter the node here <--
}
item = result.next();
}
if (result.hasError()) {
/* Runtime error! */
}
}
How can I change attributes of a node in QXmlResultItems?

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);
}
}

Compare std::vector of pairs with pugi::xml_object_range attributes

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 "";
}
}

Issues loading data in from xml using TinyXml2

I am trying to make a function in my application that can load in an object through attributes in an xml file. I would like to use TinyXML2 as I hear it is pretty easy and quick for games.
Currently I have the following xml file:
<?xml version="1.0" encoding="UTF-8"?>
<Level>
<Pulsator starttime="0" type="0" higherradius="100" lowerradius="10" time="60" y="500" x="300" bpm="60"/>
</Level>
Each attribute of the Pulsator is a variable in my Pulsator class. I use the followign function to import my Pulsators and add them to an vector of objects.
void Game::LoadLevel(string filename)
{
tinyxml2::XMLDocument level;
level.LoadFile(filename.c_str());
tinyxml2::XMLNode* root = level.FirstChild();
tinyxml2::XMLNode* childNode = root->FirstChild();
while (childNode)
{
Pulsator* tempPulse = new Pulsator();
float bpm;
float type;
std::string::size_type sz;
tinyxml2::XMLElement* data = childNode->ToElement();
string inputdata = data->Attribute("bpm");
bpm = std::stof(inputdata, &sz);
if (type == 0)
{
tempPulse->type = Obstacle;
tempPulse->SetColor(D2D1::ColorF(D2D1::ColorF::Black));
}
if (type == 1)
{
tempPulse->type = Enemy;
tempPulse->SetColor(D2D1::ColorF(D2D1::ColorF::Red));
}
if (type == 2)
{
tempPulse->type = Score;
tempPulse->SetColor(D2D1::ColorF(D2D1::ColorF::Green));
}
else
{
tempPulse->type = No_Type;
}
objects.push_back(tempPulse);
}
}
Every time I get to the root node, it loads in incorrectly and the childnode becomes null.
Am I using this incorrectly or is there an issue with my XML file?
The code doesn't correctly specify the child it wants. You want the first XMLElement, not the first child. To do that, use this code when you get the childNode:
tinyxml2::XMLElement* childNode = root->FirstChildElement();
And that saves you the cast later. (You don't need, and shouldn't use, the ToElement()).

Replace instead of add a xml node

In this simple code after invoking second(), 1.xml has only one node "1". Why pugi replaces node and what should I do for correct modifying?
void first()
{
pugi::xml_document document;
pugi::xml_parse_result result = document.load_file("C:\\1.xml", parse_full);
pugi::xml_node node = document.append_child("0");
node.append_attribute("message") = "something";
document.save_file("C:\\1.xml");
}
void second()
{
pugi::xml_document document;
pugi::xml_parse_result result = document.load_file("C:\\1.xml", parse_full);
pugi::xml_node node = document.append_child("1");
node.append_attribute("message") = "something else";
document.save_file("C:\\1.xml");
}
void test()
{
first();
second();
}
You should check the result of load_file.
Here's what's going on here:
XML tag names can't start with digits. This is defined by XML standard.
pugixml performs this check while loading the document - so load_file() fails, producing an empty document
pugixml does not perform this check while appending nodes or saving document, so it is possible to save an invalid document

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.