Replace instead of add a xml node - c++

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

Related

QXmlQuery change node found by XPath query

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?

Why can't I copy the contents of an XMLDocument to another XMLDocument using TinyXml2?

I do not understand why the below code doesn't work as intended to copy the one element from doc1 to doc2:
void test_xml(){
using namespace tinyxml2;
XMLDocument doc1, doc2;
XMLPrinter printer;
doc1.LoadFile("./res/xml/basic.xml");
if(doc1.Error())
std::cout << doc1.ErrorName();
doc1.Print(&printer);
std::cout << printer.CStr(); // prints "</atag>" without quotes
printer.ClearBuffer();
doc2.InsertFirstChild(doc1.RootElement());
if(doc2.Error())
std::cout << doc2.ErrorName(); // doesn't run, there's no error
doc2.Print(&printer);
std::cout << printer.CStr(); // prints nothing, no child got inserted to doc2
std::cout << doc2.NoChildren(); //prints "1" meaning we didn't insert anything
}
Can someone point out how this can be improved?
From the TinyXml2 documentation:
InsertFirstChild ... Returns the addThis argument or 0 if the node does not belong to the same document.
Basically you can only add a node to a document if the node was manufactured by that document (with NewElement, NewText etc).
You have to walk through doc1 creating corresponding nodes as you go (with ShallowClone, and adding them to doc2. It appears there is no DeepClone to do it all for you.
At http://sourceforge.net/p/tinyxml/discussion/42748/thread/820b0377/, "practisevoodoo" suggests:
XMLNode *deepCopy( XMLNode *src, XMLDocument *destDoc )
{
XMLNode *current = src->ShallowClone( destDoc );
for( XMLNode *child=src->FirstChild(); child; child=child->NextSibling() )
{
current->InsertEndChild( deepCopy( child, destDoc ) );
}
return current;
}

Get child count from rapidxml:xmlnode to get random child node

I can turn my XML document file into rapidxml object:
if(exists("generators.xml")) { //http://stackoverflow.com/a/12774387/607407
rapidxml::file<> xmlFile("generators.xml"); // Open file, default template is char
xml_document<> doc; // character type defaults to char
doc.parse<0>(xmlFile.data());; // 0 means default parse flags
xml_node<> *main = doc.first_node(); //Get the main node that contains everything
cout << "Name of my first node is: " << doc.first_node()->name() << "\n";
if(main!=NULL) {
//Get random child node?
}
}
I'd like to pick one random child node from the main object. My XML looks like this (version with comments):
<?xml version="1.0" encoding="windows-1250"?>
<Stripes>
<Generator>
<stripe h="0,360" s="255,255" l="50,80" width="10,20" />
</Generator>
<Generator>
<loop>
<stripe h="0,360" s="255,255" l="50,80" width="10,20" />
<stripe h="0,360" s="255,255" l="0,0" width="10,20" />
</loop>
</Generator>
</Stripes>
I want to pick random <Generator> entry. I think getting the child count would be a way to do it:
//Fictional code - **all** methods are fictional!
unsigned int count = node->child_count();
//In real code, `rand` is not a good way to get anything random
xmlnode<> *child = node->childAt(rand(0, count));
How can I get child count and child at offset from rapidxml node?
RapidXML stores the DOM tree using linked lists, which as you'll know are not directly indexable.
So you'd basically need to implement those two methods yourself, by traversing the nodes children. Something like this (untested) code.
int getChildCount(xmlnode<> *n)
{
int c = 0;
for (xmlnode<> *child = n->first_node(); child != NULL; child = child->next_sibling())
{
c++;
}
return c;
}
getChildAt is obviously similar.

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()).

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.