RapidXML weird output - c++

I'm currently trying to use RapidXML to write a file out, this is the code I've got for it
xml_document<> doc;
xml_node<>* decl = doc.allocate_node(node_declaration);
decl->append_attribute(doc.allocate_attribute("version", "1.0"));
decl->append_attribute(doc.allocate_attribute("encoding", "utf-8"));
doc.append_node(decl);
xml_node<> *root = doc.allocate_node(node_element, "config");
for(int i = 0; i < params.size(); i++) {
bool LineEdit = false;
std::pair<QLabel*, QLineEdit*> LabelAndLine;
LabelAndLine.first = ui->centralwidget->findChild<QLabel*>(params.at(i).first);
if (LabelAndLine.first == nullptr) {
LineEdit = true;
LabelAndLine.second = ui->centralwidget->findChild<QLineEdit*>(params.at(i).first);
}
QString nodename = params.at(i).second;
qInfo() << "Setting: "+nodename+" from "+params.at(i).first;
xml_node<> *param = doc.allocate_node(node_element, QStringToConstCharPoint(nodename));
xml_attribute<char> *value;
if (LineEdit) {
value = doc.allocate_attribute("value", QStringToConstCharPoint(LabelAndLine.second->text()));
}
else {
value = doc.allocate_attribute("value", QStringToConstCharPoint(LabelAndLine.first->text()));
}
param->append_attribute(value);
root->append_node(param);
}
doc.append_node(root);
std::string xmlName = std::to_string(time(NULL))+".xml";
std::ofstream fileStored(xmlName);
fileStored << doc;
fileStored.close();
doc.clear();
It reads a value from Qt (either a QLabel or a QLineEdit) then compares it to a vector of pairs (const QString, const QString) defined here:
std::vector<std::pair<const QString,const QString>> params = {{"PopulationInput","population"},
{"XInput","AreaX"},
{"YInput","AreaY"},
{"AlreadyInfectedInput","AlreadyInfected"},
{"InfectionProbabilityIndicator","infectProbability"},
{"RadOfInfectionInput","infectionRadius"},
{"CPUThreadsDisplay","threads"},
};
but when I look at the output file it just shows this:
<?xml version="1.0" encoding="utf-8"?>
<config>
<ding="utf-8"?>
<config>
<ding="utf-8"?>
<co value='ding="utf-8"'/>
<ding="utf-8" value='ding="utf-8"'/>
<ding="utf-8" value='ding="utf-8"'/>
<ding="utf-8"?>
<config>
<ding="utf-8"?>
<co value='ding="utf-8"'/>
<ding="utf-8"?>
<config>
<ding="utf-8"?>
<co value='ding="utf-8"'/>
<ding="utf-8"?>
<config>
<ding="utf-8"?>
<co value='ding="utf-8"'/>
<ding="utf-8"?>
<config>
<ding="utf-8"?>
<co value='ding="utf-8"'/>
</config>
The QStringToConstCharPoint function is defined as this:
const char* QStringToConstCharPoint(QString input) {
QByteArray ba = input.toLocal8Bit();
const char *result = ba.data();
return result;
}
Why is my output looking like that? Have I done something wrong?

Turns out the QStringToConstCharPoint function was the issue, it was messing something up with RapidXML and I don't know why, but I found a better way to do it.

Related

Using pugixml to read an entire xml file

I know there's already a way to loop through a file with pugi::xml_node::traverse, but I'm very interested in how things work, so I want to reimplement it using a recursive function.
Currently, I can only parse the first depth of the function because I don't know how to detect whether the current item has children (next_siblings returns an invalid value).
// TODO: use std::ostringstream instead of std::string
void MyClass::recursive(const pugi::xml_node& start, std::string& output)
{
// Check for invalid node
if (!start.first_child() || (!start.next_sibling() && start.parent() != start.parent())) {
return;
}
// Process the current node
for (auto node : start.children()) {
output += node.name();
output += "\n";
for (auto attribute : node.attributes()) {
output += "Attribute Name : ";
output += attribute.name();
output += ", Attribute Value = ";
output += attribute.value();
output += " ";
}
output += "\n";
const char* PCDATA = node.child_value();
output += PCDATA == "" ? "[no pcdata]"
: PCDATA;
if (node.first_child()) {
recursive(node, output);
}
else {
recursive(node.next_sibling(), output);
}
}
}
Sample XML file
<?xml version="1.0" encoding="UTF-8"?>
<root>
<child1>
<sub name="attr1">value</sub>
<sub name="attr2">value</sub>
<sub name="attr3">value</sub>
</child1>
<child2>
<sub name="attr1">value</sub>
<sub name="attr2">value</sub>
<sub name="attr3">value</sub>
</child2>
<child3>
<sub name="attr1">value</sub>
<sub_with_children>
<child1 name="[]">value</sub>
<child2 name="[]">value</sub>
<child3 name="[]">value</sub>
</sub_with_children>
</child3>
<child4>
<sub name="attr1">value</sub>
<sub name="attr2">value</sub>
</child4>
</root>
Edit: the code above is now working

Issue in updating XML attribute value for every node with the same name using boost library

I am trying to update the value of totalresult attribute in every test_list node found. The issue is, it will only update the first test_list node found.
The testListCount will increment every time a test_list node is added. Once done adding test_list node, each totalresult value will then be updated in every test_list node.
Here is my code:
BOOST_FOREACH(ptree::value_type const & subTree, mainTree.get_child("my_report"))
{
auto &nodeTestList = mainTree.get_child("my_report.test_list");
BOOST_FOREACH(ptree::value_type const & subval, nodeTestList)
{
ptree subvalTree = subval.second;
BOOST_FOREACH(ptree::value_type const & paramNode, subvalTree)
{
std::string name = paramNode.first;
if (name == TestListAttrib[TestListParam::TOTALRESULT])
{
wxMessageBox("firing!");
nodeTestList.put("<xmlattr>." + name, testListCount);
}
}
}
}
Below is the actual result:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="report.xsl"?>
<my_report>
<test_list overall_status="FAILED" result="1" totalresult="3">
<test_list overall_status="FAILED" result="2" totalresult=""/>
<test_list overall_status="FAILED" result="3" totalresult=""/>
</my_report>
Below is the expected result:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="report.xsl"?>
<my_report>
<test_list overall_status="FAILED" result="1" totalresult="3">
<test_list overall_status="FAILED" result="2" totalresult="3"/>
<test_list overall_status="FAILED" result="3" totalresult="3"/>
</my_report>
Like others said, Property Tree is not an XML library (see What XML parser should I use in C++?).
That said, it looks like your error is here:
for (auto const &subTree : mainTree.get_child("my_report")) {
auto &nodeTestList = mainTree.get_child("my_report.test_list");
The second line doesn't use subTree at all, instead it just matches the first "my_report.test_list" node from mainTree.
Use Modern C++ And Compiler Warnings
I made the code self-contained c++11:
#include <boost/property_tree/xml_parser.hpp>
using boost::property_tree::ptree;
enum TestListParam { OVERALLSTATUS, TOTALRESULT };
std::array<std::string, 2> TestListAttrib{ "overall_status", "totalresult" };
int main() {
ptree mainTree;
{
std::ifstream ifs("input.xml");
read_xml(ifs, mainTree);
}
auto const testListCount = 3;
for (auto const& subTree : mainTree.get_child("my_report")) {
auto& nodeTestList = mainTree.get_child("my_report.test_list");
for (auto& subval : nodeTestList) {
ptree subvalTree = subval.second;
for (auto& paramNode : subvalTree) {
std::string name = paramNode.first;
if (name == TestListAttrib[TestListParam::TOTALRESULT]) {
nodeTestList.put("<xmlattr>." + name, testListCount);
}
}
}
}
}
If you enable compiler warnings, you will see your error:
Live On Wandbox
prog.cc:16:22: warning: unused variable 'subTree' [-Wunused-variable]
for (auto const& subTree : mainTree.get_child("my_report")) {
^
1 warning generated.
More Modern C++
Using the niceties of C++17 things become cleaner and easier fixed. Here's a first shot, also adding output printing:
Live On Wandbox
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
auto const pretty = boost::property_tree::xml_writer_make_settings<std::string>(' ', 4);
enum TestListParam { OVERALLSTATUS, TOTALRESULT };
std::array<std::string, 2> TestListAttrib{ "overall_status", "totalresult" };
int main() {
ptree mainTree;
{
std::ifstream ifs("input.xml");
read_xml(ifs, mainTree);
}
auto const testListCount = 3;
for (auto& [key, subTree] : mainTree.get_child("my_report"))
for (auto& [name, node] : subTree.get_child("<xmlattr>")) {
if (name == TestListAttrib[TestListParam::TOTALRESULT]) {
node.put_value(testListCount);
}
}
write_xml(std::cout, mainTree, pretty);
}
Prints: (whitespace reduced)
<?xml version="1.0" encoding="utf-8"?>
<my_report>
<test_list overall_status="FAILED" result="1" totalresult="3"/>
<test_list overall_status="FAILED" result="2" totalresult="3"/>
<test_list overall_status="FAILED" result="3" totalresult="3"/>
</my_report>
Caveats
Note how because of the way we write the loops the code
will fail if <xmlattr> or my_report are not found
Conversely, it will erroneously descend all child nodes of my_report even if they have different names than test_list
the XSL processing instruction is lost. Once again, this is inherent because Boost Property Tree doesn't know about XML. It uses a subset of XML to implement serialization for property trees.
To fix the first two bullets, I'd suggest making a helper to query nodes from your XML (from Iterating on xml file with boost):
enumerate_nodes(mainTree,
"my_report.test_list.<xmlattr>.totalresult",
back_inserter(nodes));
This doesn't suffer from any of the problems mentioned, and you can elegantly assing all matching nodes:
for (ptree& node : nodes)
node.put_value(3);
If you really didn't /want/ to require the test_list node name, use a wildcard:
enumerate_nodes(mainTree,
"my_report.*.<xmlattr>.totalresult",
back_inserter(nodes));
Live Demo
Live On Wandbox
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
auto const pretty = boost::property_tree::xml_writer_make_settings<std::string>(' ', 4);
enum TestListParam { OVERALLSTATUS, TOTALRESULT };
std::array<std::string, 2> TestListAttrib{ "overall_status", "totalresult" };
template <typename Ptree, typename Out>
Out enumerate_nodes(Ptree& pt, ptree::path_type path, Out out) {
if (path.empty())
return out;
if (path.single()) {
auto name = path.reduce();
for (auto& child : pt) {
if (child.first == name)
*out++ = child.second;
}
} else {
auto head = path.reduce();
for (auto& child : pt) {
if (head == "*" || child.first == head) {
out = enumerate_nodes(child.second, path, out);
}
}
}
return out;
}
int main() {
ptree mainTree;
{
std::ifstream ifs("input.xml");
read_xml(ifs, mainTree);
}
std::vector<std::reference_wrapper<ptree> > nodes;
enumerate_nodes(mainTree,
"my_report.test_list.<xmlattr>.totalresult",
back_inserter(nodes));
for (ptree& node : nodes)
node.put_value(3);
write_xml(std::cout, mainTree, pretty);
}
Prints
<?xml version="1.0" encoding="utf-8"?>
<my_report>
<test_list overall_status="FAILED" result="1" totalresult="3"/>
<test_list overall_status="FAILED" result="2" totalresult="3"/>
<test_list overall_status="FAILED" result="3" totalresult="3"/>
</my_report>

Lastchild in xml file using QDomDocument Class

i have this xml:
<VCAAnalysis>
<VCAStream>
<VCAFrame width="768" height="432" rtptime="" utctime="102157000" utctimeHigh="0" configID="0" />
<VCAFrame width="768" height="432" rtptime="" utctime="102157160" utctimeHigh="0" configID="0">
<Object objectID="138.96.200.59_20160126_102157160_1" minX="276" minY="0" maxX="320" maxY="123" width="44" height="123" ObjPropTag="PERSON">
</Object>
</VCAFrame>
<VCAFrame width="768" height="432" rtptime="" utctime="102157320" utctimeHigh="0" configID="0" />
<VCAFrame width="768" height="432" rtptime="" utctime="102157480" utctimeHigh="0" configID="0">
<Object objectID="138.96.200.59_20160126_102157480_2" minX="224" minY="264" maxX="287" maxY="343" width="63" height="79" ObjPropTag="PERSON">
</Object>
</VCAFrame>
<VCAFrame width="768" height="432" rtptime="" utctime="102157640" utctimeHigh="0" configID="0">
<Object objectID="138.96.200.59_20160126_102157480_3" minX="204" minY="266" maxX="331" maxY="400" width="127" height="134" ObjPropTag="PERSON">
</Object>
</VCAFrame>
<VCAFrame width="768" height="432" rtptime="" utctime="102157000" utctimeHigh="0" configID="0" />
</VCAStream>
</VCAAnalysis>
I want to get the last objectID(138.96.200.59_20160126_102157480_3) in the last VCAFrame which have an object.
i tried this code but it doesn't work.
QDomNodeList a = VCAStream.elementsByTagName("VCAFrame");
if(a.size()!=0) {
QDomElement lastobj = VCAStream.lastChild().toElement();
QDomElement last = lastobj.firstChild().toElement();
QString lastid = last.attribute("objectID");
cout << qPrintable("laaaaaaaast "+lastid) << endl;
}
This worked for me:
QDomNodeList vcaStreams = VCAStream.elementsByTagName("VCAStream");
QDomNodeList vcaFrames = vcaStreams.at(0).childNodes(); //Gives 6 VCAFrame tags
QDomNodeList vcaObjects = vcaFrames.at(4).childNodes(); //Gives 1 Object tag
qDebug() << vcaObjects.at(0).toElement().attribute("objectID");
lastobj in your code refers to the last VCAFrame, which does not have an objectID.
EDIT: If you need to iterate over an entire xml file. I'm assuming that you want the last vcaFrame that has an objectID in each VCAStream.
QDomNodeList vcaStreams = VCAStream.elementsByTagName("VCAStream");
for (int i = 0; i < vcaStreams.count(); ++i) {
QDomNodeList vcaFrames = vcaStreams.at(i).childNodes(); //Gives us all VCAFrameTags
//Find last tag with objectID
QDomElement last;
for (int j = vcaFrames.count() - 1; j >= 0; --j) {
//Assumes there is at most one <object> tag in each VCAFrame
if (vcaFrames.at(j).hasChildNodes()) {
QDomElement tmp = vcaFrames.at(j).firstChild().toElement();
if (tmp.hasAttribute("objectID")) {
last = tmp;
break;
}
}
}
//last now holds the last VCAFrame with an object tag or is Null
if (last.isNull())
qDebug() << "No objectID found";
else
qDebug() << last.attribute("objectID");
}
I tested this on your XML file and it gave me the correct result, but I did not try adding more than one VCAStream tag.

Better XML formatting using Boost? [duplicate]

This question already has answers here:
boost::property_tree XML pretty printing
(4 answers)
Closed 8 years ago.
I'm using Boost Property Trees to export my class-instants as XML nodes.
It works but it just puts everything in 1 line. I would like it to have indents, like:
<?xml version="1.0" encoding="utf-8"?>
<root>
<sensorconfigurations>
<configuration>
<name>SensorConfiguration1</name>
<sensorid>1</sensorid>
<signalindex>1</signalindex>
<mappingscheme>mappingscheme1</mappingscheme>
<soundpack>test1.wav</soundpack>
</configuration>
<configuration>
<name>SensorConfiguration2</name>
<sensorid>2</sensorid>
<signalindex>2</signalindex>
<mappingscheme>mappingscheme1</mappingscheme>
<soundpack>test2.wav</soundpack>
</configuration>
<configuration>
<name>SensorConfiguration3</name>
<sensorid>3</sensorid>
<signalindex>3</signalindex>
<mappingscheme>mappingscheme2</mappingscheme>
<soundpack>test3.wav</soundpack>
</configuration>
</sensorconfigurations>
</root>
Is this possible somehow? Am I missing a parameter in the write_xml method?
Here's my code:
void SensorConfigurationBank::save()
{
using boost::property_tree::ptree;
ptree pt;
for(map<string, SensorConfiguration>:: iterator it = sensorConfigurations_.begin(); it != sensorConfigurations_.end(); ++it)
{
ptree myTree;
myTree.put("name", it->second.getName());
myTree.put("sensorid", it->second.getSensorID());
myTree.put("signalindex", it->second.getsignalIndex());
MappingScheme myScheme = it->second.getMScheme();
myTree.put("mappingscheme", myScheme.getId());
SoundPack mySound = it->second.getSound();
myTree.put("soundpack", mySound.filepath_);
pt.add_child("root.sensorconfigurations.configuration", myTree);
}
write_xml("SensorConfigurationBank2.xml", pt);
}
These days, xml_writer_settings apparently takes a string type as template argument, so:
boost::property_tree::xml_writer_settings<std::string> settings('\t', 1);
write_xml(std::cout, pt, settings);
will do the trick. Full sample:
Live On Coliru
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <map>
#include <iostream>
struct SoundPack {
std::string filepath_ = "soundpack.wav";
};
struct MappingScheme {
std::string getId() const { return "Id"; }
};
struct SensorConfiguration {
std::string getName() const { return "Name"; }
std::string getSensorID() const { return "SensorID"; }
std::string getsignalIndex() const { return "signalIndex"; }
SoundPack getSound() const { return {}; }
MappingScheme getMScheme() const { return {}; }
};
void save(std::map<std::string, SensorConfiguration> sensorConfigurations_)
{
using boost::property_tree::ptree;
ptree pt;
for(std::map<std::string, SensorConfiguration>:: iterator it = sensorConfigurations_.begin(); it != sensorConfigurations_.end(); ++it)
{
ptree myTree;
MappingScheme myScheme = it->second.getMScheme();
SoundPack mySound = it->second.getSound();
myTree.put("name", it->second.getName());
myTree.put("sensorid", it->second.getSensorID());
myTree.put("signalindex", it->second.getsignalIndex());
myTree.put("mappingscheme", myScheme.getId());
myTree.put("soundpack", mySound.filepath_);
pt.add_child("root.sensorconfigurations.configuration", myTree);
}
boost::property_tree::xml_writer_settings<std::string> settings('\t', 1);
write_xml(std::cout, pt, settings);
}
int main() {
save({
{ "first", SensorConfiguration {} },
{ "second", SensorConfiguration {} },
{ "third", SensorConfiguration {} },
{ "fourth", SensorConfiguration {} }
});
}
Output:
<?xml version="1.0" encoding="utf-8"?>
<root>
<sensorconfigurations>
<configuration>
<name>Name</name>
<sensorid>SensorID</sensorid>
<signalindex>signalIndex</signalindex>
<mappingscheme>Id</mappingscheme>
<soundpack>soundpack.wav</soundpack>
</configuration>
<configuration>
<name>Name</name>
<sensorid>SensorID</sensorid>
<signalindex>signalIndex</signalindex>
<mappingscheme>Id</mappingscheme>
<soundpack>soundpack.wav</soundpack>
</configuration>
<configuration>
<name>Name</name>
<sensorid>SensorID</sensorid>
<signalindex>signalIndex</signalindex>
<mappingscheme>Id</mappingscheme>
<soundpack>soundpack.wav</soundpack>
</configuration>
<configuration>
<name>Name</name>
<sensorid>SensorID</sensorid>
<signalindex>signalIndex</signalindex>
<mappingscheme>Id</mappingscheme>
<soundpack>soundpack.wav</soundpack>
</configuration>
</sensorconfigurations>
</root>

Parse XML with QXmlStreamReader

I created this xml file with QXmlStreamWriter:
<?xml version="1.0" encoding="UTF-8"?>
<Draw>
<Input>
<Column title="A"/>
<Column title="B"/>
<Column title="C"/>
<Column title="D">
<item id="0">Bayer Leverkusen</item>
<item id="1">Benfica</item>
<item id="2">Villareal</item>
<item id="3">Montpellier</item>
</Column>
</Input>
</Draw>
I would like to create a Vector of String containing all the items inside the tag Column title="D": Now, I know how to create a QVector and how they fit elements on the inside, I just have to figure out how I can do this by extrapolating information from an xml file.
Can you help me?
You can use the QXmlStreamReader to iterate through the XML elements and find the <Column title="D"> element. Once you found it, the readNextStartElement() in combination of skipCurrentElement() can be used to read its all child elements.
Let's assume that the XML document you shown in your examle can be read from the xmlDocument object. To extract all <item> elements from <Column title="D"> element with appropriate error checking, you can do the following:
QXmlStreamReader xmlIterator(xmlDocument);
QVector<QString> output;
for(; !xmlIterator.atEnd(); xmlIterator.readNext()) {
if(isStartElementOfColumnD(xmlIterator)) {
while(xmlIterator.readNextStartElement()) {
if(isItemElement(xmlIterator))
output.append(xmlIterator.readElementText());
else
xmlIterator.skipCurrentElement();
}
}
}
if(xmlIterator.hasError())
qCritical() << "Error has occurred:" << xmlIterator.errorString();
else
qDebug() << output;
In the example above I used two predicates to hide the long and hardly readable validation of xmlIterator. These are the following:
inline bool isStartElementOfColumnD(const QXmlStreamReader& xmlIterator) {
return xmlIterator.isStartElement() && xmlIterator.name() == "Column" &&
xmlIterator.attributes().value("title") == "D";
}
inline bool isItemElement(const QXmlStreamReader& xmlIterator) {
return xmlIterator.name() == "item" &&
xmlIterator.attributes().hasAttribute("id");
}
Sample result:
QVector("Bayer Leverkusen", "Benfica", "Villareal", "Montpellier")
I would write it in the following way:
QVector<QString> store;
[..]
if (reader.readNextStartElement() && reader.name() == "Draw") {
while (reader.readNextStartElement() && reader.name() == "Input") {
while (reader.readNextStartElement()) {
QXmlStreamAttributes attr = reader.attributes();
if (reader.name() == "Column" && attr.value("title").toString() == "D") {
while(!(reader.isEndElement() && reader.name() == "Column")) {
if (reader.isStartElement() && reader.name() == "item") {
QString text = reader.readElementText();
store.append(text);
}
reader.readNext();
if (reader.hasError()) {
// Handle error.
QString msg = reader.errorString();
break;
}
}
} else {
reader.readNext();
}
}
}
} else {
reader.raiseError("Expected <Draw> element");
}
[..]