Well here I am again working on this open source game, anyway I made this parser for an xml file however I am unsure how to parse the vocation's children nodes, here is the code and the xml for the function, I want to be able to loop through its children, I assuming I need to create another for loop, however I am not quite sure how I should would reference vocation's child nodes, I am not concerned with their attributes atm.
bool Game::loadCreatureAdditionalAttributes()
{
pugi::xml_document data;
pugi::xml_parse_result result = data.load_file("data/XML/attributes.xml");
if (!result) {
printXMLError("Error - Game::loadCreatureAdditionalAttributes", "data/XML/attributes.xml", result);
return false;
}
bool attributesEnabled = false;
for (auto attributeNode : data.child("attributes").children()) {
if (strcasecmp(attributeNode.name(), "additionalAttributes") == 0) {
if (attributeNode.attribute("enabled").as_bool()) {
if (strcasecmp(attributeNode.name(), "vocation") == 0) {
pugi::xml_attribute vocation = attributeNode.attribute("id");
uint32_t aNode;
pugi::xml_attribute attr = attributeNode.attribute(attributeNode.name());
if (attr) {
aNode = pugi::cast<uint32_t>(attr.value());
}
else {
aNode = 0;
}
CreatureAttributes[pugi::cast<uint32_t>(vocation.value())][attributeNode.name()] = aNode;
}
}
}
}
return true;
}
And now the xml
<?xml version="1.0" encoding="UTF-8"?>
<attributes>
<additionalAttributes enabled = "0" />
<vocation id = "1">
<attribute stamina = "1" />
<attribute strength = "1" />
<attribute intelligence = "1" />
<attribute dexterity = "1" />
<attribute wisdom = "1" />
<attribute luck = "1" />
<attribute critical = "1" />
<attribute block = "1" />
<attribute experienceGain = "1" />
<attribute power = "1" />
<attribute precision = "1" />
</vocation>
<vocation id = "2">
<attribute stamina = "1" />
<attribute strength = "1" />
<attribute intelligence = "1" />
<attribute dexterity = "1" />
<attribute wisdom = "1" />
<attribute luck = "1" />
<attribute critical = "1" />
<attribute block = "1" />
<attribute experienceGain = "1" />
<attribute power = "1" />
<attribute precision = "1" />
</vocation>
</attributes>
Edit 1:
Haven't tested this just yet, but I thought I'd show an update based somewhat on the answer given.
bool Game::loadCreatureAdditionalAttributes()
{
pugi::xml_document data;
pugi::xml_parse_result result = data.load_file("data/XML/attributes.xml");
if (!result) {
printXMLError("Error - Game::loadCreatureAdditionalAttributes", "data/XML/attributes.xml", result);
return false;
}
auto attr = data.child("attributes").child("additionalAttributes");
bool attributesEnabled = attr.attribute("enabled").as_bool();
if (attributesEnabled) {
for (auto vocation : data.child("attributes").children("vocation")) {
uint32_t id = pugi::cast<uint32_t>(vocation.attribute("id").value());
for (auto attribute : vocation.children("attribute")) {
for (auto& a : attribute.attributes()) {
CreatureAttributes[id][a.name()] = pugi::cast<uint32_t>(a.value());
}
}
}
}
return true;
}
Not exactly sure if child to child is legal... but just throwing it out there lol
Edit 2:
Still haven't tested it yet, just updating it in case it doesn't work as expected.
bool Game::loadCreatureAdditionalAttributes()
{
pugi::xml_document data;
pugi::xml_parse_result result = data.load_file("data/XML/attributes.xml");
if (!result) {
printXMLError("Error - Game::loadCreatureAdditionalAttributes", "data/XML/attributes.xml", result);
return false;
}
auto attr = data.child("attributes").child("additionalAttributes");
if (attr) {
bool attributesEnabled = attr.attribute("enabled").as_bool();
if (attributesEnabled) {
for (auto vocation : data.child("attributes").children("vocation")) {
uint32_t id = pugi::cast<uint32_t>(vocation.attribute("id").value());
for (auto attribute : vocation.children("attribute")) {
for (auto& a : attribute.attributes()) {
CreatureAttributes[id][a.name()] = pugi::cast<uint32_t>(a.value());
}
}
}
return true;
}
}
return false;
}
Just wanted to give an update for those having similar issues, the code works great!
vocation id = 1 attributes = stamina value = 1
vocation id = 1 attributes = strength value = 45
vocation id = 1 attributes = intelligence value = 1
vocation id = 1 attributes = dexterity value = 1
vocation id = 1 attributes = wisdom value = 63
vocation id = 1 attributes = luck value = 1
vocation id = 1 attributes = critical value = 1
vocation id = 1 attributes = block value = 1
vocation id = 1 attributes = experienceGain value = 1
vocation id = 1 attributes = power value = 81
vocation id = 1 attributes = precision value = 1
vocation id = 2 attributes = stamina value = 100
vocation id = 2 attributes = strength value = 1
vocation id = 2 attributes = intelligence value = 20
vocation id = 2 attributes = farfenugen value = 1
vocation id = 2 attributes = stackoverflow value = 1000
Of course I changed the code a bit to make it work with other code I am working on, but the example code in Edit 2 will work fine with an xml file similar in structure.
I like pugixml a lot it makes parsing very easy. However your XML format is kind of tricky, I would consider storing your <attributes> differently.
Currently you can iterate through the vocations and the attributes like this:
#include <iostream>
#define PUGIXML_HEADER_ONLY
#include "pugixml.hpp"
auto xml = R"(
<?xml version="1.0" encoding="UTF-8"?>
<attributes>
<additionalAttributes enabled = "0" />
<vocation id = "1">
<attribute stamina = "1" />
<attribute strength = "1" />
<attribute intelligence = "1" />
</vocation>
<vocation id = "2">
<attribute stamina = "1" />
<attribute strength = "1" />
<attribute intelligence = "1" />
</vocation>
</attributes>
)";
int main()
{
pugi::xml_document doc;
doc.load(xml);
for(auto vocation: doc.child("attributes").children("vocation"))
{
std::cout << "vocation id:" << vocation.attribute("id").as_string() << '\n';
for(auto attribute: vocation.children("attribute"))
{
for(auto& a: attribute.attributes())
{
std::cout << " attribute: " << a.name() << '\n';
std::cout << " : " << a.value() << '\n';
}
}
}
}
I would probably organize my XML more like this:
#include <iostream>
#define PUGIXML_HEADER_ONLY
#include "pugixml.hpp"
auto xml = R"(
<?xml version="1.0" encoding="UTF-8"?>
<attributes>
<additionalAttributes enabled = "0" />
<vocation id = "1">
<stamina>1</stamina>
<strength>1</strength>
<intelligence>1</intelligence>
</vocation>
<vocation id = "2">
<stamina>1</stamina>
<strength>1</strength>
<intelligence>1</intelligence>
</vocation>
</attributes>
)";
int main()
{
pugi::xml_document doc;
doc.load(xml);
for(auto vocation: doc.child("attributes").children("vocation"))
{
std::cout << "vocation id:" << vocation.attribute("id").as_string() << '\n';
std::cout << " : stamina = " << vocation.child("stamina").text().as_string() << '\n';
std::cout << " : strength = " << vocation.child("strength").text().as_string() << '\n';
std::cout << " : intelligence = " << vocation.child("intelligence").text().as_string() << '\n';
std::cout << '\n';
}
}
Related
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
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.
I have a piece of code that iterates over a boost property tree (XML).
I need a ptree of the current node, not the children of the node.
UPDATE
xml tree
<node id="A.html">
<subnode> child A1 </subnode>
<subnode> child A2 </subnode>
</node>
<node id="B.html">
<subnode> child B1 </subnode>
<subnode> child B2 </subnode>
</node>
itteration code
void parse_tree(ptree& pt, std::string key)
{
string nkey;
if (!key.empty())
nkey = key + ".";
ptree::const_iterator end = pt.end();
for(ptree::iterator it = pt.begin(); it != end; ++it){
//if the node's id is a .html filname, save the node to file
string id = it->second.get("<xmlattr>.id","");
if(id.find("B.html") != std::string::npos){ //Let's just test for "B.html"
write_xml("test.html", pt); //saves entire tree
write_xml("test.html", it->second); //saves only children of the node
}
parse_tree(it->second, nkey + it->first); //recursion
}
}
Results using write_xml("test.html", pt)
(We get the entire tree, we only want the node)
<node id="A.html">
<subnode> child A1 </subnode>
<subnode> child A2 </subnode>
</node>
<node id="B.html">
<subnode> child B1 </subnode>
<subnode> child B2 </subnode>
</node>
Results using write_xml("test.html", it->second)
(We have no parent node.. only child nodes)
<subnode> child B1 </subnode>
<subnode> child B2 </subnode>
Desired result
(We want the node, and it's children,.. like so)
<node id="B.html">
<subnode> child B1 </subnode>
<subnode> child B2 </subnode>
</node>
UPDATE 2
Rewritten in response to the comment/updated question.
There are two ways.
You can use the undocumented function write_xml_element to write the single element (using the key as element name):
// write the single element: (undocumented API)
boost::property_tree::xml_parser::write_xml_element(
std::cout, it->first, it->second,
0, settings
);
or you can create a new ptree object with the single child
ptree tmp;
tmp.add_child(it->first, it->second);
write_xml(std::cout, tmp, settings);
Live On Coliru
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <fstream>
#include <iostream>
using namespace boost::property_tree;
void parse_tree(ptree& pt, std::string key)
{
std::string nkey;
auto settings = xml_parser::xml_writer_make_settings<std::string>('\t', 1);
if (!key.empty()) {
nkey = key + ".";
}
ptree::const_iterator end = pt.end();
for(ptree::iterator it = pt.begin(); it != end; ++it)
{
//if the node's id an .html filname, save the node to file
std::string id = it->second.get("<xmlattr>.id","");
if (id.find(key) != std::string::npos) {
// write the single element: (undocumented API)
boost::property_tree::xml_parser::write_xml_element(
std::cout, it->first, it->second,
0, settings
);
// or: create a new pt with the single child
std::cout << "\n==========================\n\n";
ptree tmp;
tmp.add_child(it->first, it->second);
write_xml(std::cout, tmp, settings);
}
parse_tree(it->second, nkey + it->first); //recursion
}
}
int main() {
ptree pt;
read_xml("input.txt", pt);
parse_tree(pt, "B");
}
Output:
<node id="B.html">
<subnode> child B1 </subnode>
<subnode> child B2 </subnode>
</node>
==========================
<?xml version="1.0" encoding="utf-8"?>
<node id="B.html">
<subnode> child B1 </subnode>
<subnode> child B2 </subnode>
</node>
I have an xml file built in this way:
<?xml version="1.0" encoding="UTF-8"?>
<Draw>
<Input>
<Cells>
<width>100</width>
<height>28</height>
</Cells>
<Column>custom</Column>
<Custom>
<header id="0">one</header>
<header id="1">two</header>
<header id="2">three</header>
<header id="3">four</header>
<header id="4">five</header>
</Custom>
</Input>
<Output>
<Cells>
<width>82</width>
<height>20</height>
</Cells>
<Column>upper</Column>
<Custom>
<header id="0">alfa</header>
<header id="1">beta</header>
<header id="2">gamma</header>
<header id="3">delta</header>
<header id="4">epsilon</header>
</Custom>
</Output>
</Draw>
And I’m trying to extrapolate the values of the header tag: since we have two sets of header tags (Input and Output) the only working code that I managed to work for now is this:
void MainWindow::readXmlFile() {
QString target;
QFile* file = new QFile("System/Settings.xml");
/* If we can't open it, let's show an error message. */
if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) return;
QXmlStreamReader xmlReader(file);
/* We'll parse the XML until we reach end of it.*/
while(!xmlReader.atEnd() && !xmlReader.hasError()) {
QXmlStreamReader::TokenType token = xmlReader.readNext();
if(token == QXmlStreamReader::StartDocument) {
continue;
}
/* If token is StartElement, we'll see if we can read it.*/
if (token == 4) {
if (xmlReader.name() == "Input" || xmlReader.name() == "Output") {
target = xmlReader.name().toString();
while (!(xmlReader.tokenType() == QXmlStreamReader::EndElement && xmlReader.name() == target)) {
if (xmlReader.tokenType() == QXmlStreamReader::StartElement) {
qDebug() << xmlReader.name().toString();
if (xmlReader.name() == "width") {
QString num = xmlReader.readElementText();
//input->horizontalHeader()->setDefaultSectionSize(num.toInt());
}
if (xmlReader.name() == "height") {
QString num = xmlReader.readElementText();
//input->verticalHeader()->setDefaultSectionSize(num.toInt());
}
if (xmlReader.name() == "header") {
//headers->append(xmlReader.readElementText());
//qDebug() << xmlReader.readElementText();
}
//input->setHorizontalHeaderLabels(header);
}
xmlReader.readNext();
}
}
}
}
/* Error handling. */
if(xmlReader.hasError()) {
QMessageBox::critical(this,
"QXSRExample::parseXML",
xmlReader.errorString(),
QMessageBox::Ok);
}
xmlReader.clear();
}
Since this code seems very repetitive, especially from line 15 to 18, could you help me to make it a little cleaner? The examples in the web are not very explanatory.
Thanks in advance
I usually use lambda expressions. You will need to add CONFIG += c++11 to your .pro file.
Then define utilities for most repetitive patterns: for instance
/* If token is StartElement, we'll see if we can read it.*/
if (token == 4) {
auto name = [&]() { return xmlReader.name().toString(); };
auto type = [&]() { return xmlReader.tokenType(); };
auto num = [&]() { return xmlReader.readElementText().toInt(); };
if (name() == "Input" || name() == "Output") {
target = name();
while (!(type() == QXmlStreamReader::EndElement && name() == target)) {
if (type() == QXmlStreamReader::StartElement) {
qDebug() << name();
if (name() == "width") {
input->horizontalHeader()->setDefaultSectionSize(num());
}
else if (name() == "height") {
input->verticalHeader()->setDefaultSectionSize(num());
}
else if (name() == "header") {
//headers->append(xmlReader.readElementText());
//qDebug() << xmlReader.readElementText();
}
//input->setHorizontalHeaderLabels(header);
}
xmlReader.readNext();
}
}
unrelated warning: your code is leaking memory, instead of allocating with new, consider the simpler stack allocation:
QFile file("System/Settings.xml");
/* If we can't open it, let's show an error message. */
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return;
QXmlStreamReader xmlReader(&file);
I encountered the same problem few weeks ago, I had to read and write some xml file (arround 20/50 lines).
I started with QXmlStreamReader/Writer and finally give up and use QDomDocument.
The main difference (in terms of performance) between these two objects is QDomDocument loads all the xml file in memory. The syntax is also quite easier with QDomDoc !
See the doc for some written examples: http://qt-project.org/doc/qt-4.8/qdomdocument.html
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");
}
[..]