TinyXML2 doesn't seem to load my file correctly? - c++

I am just starting to use TinyXML2 so I am probably doing something wrong. Anyway:
tinyxml2::XMLDocument txDoc;
tinyxml2::XMLElement *rootnode;
XMLError err = txDoc.LoadFile(xmlFile.c_str()); // err says no error.
rootnode = txDoc.FirstChildElement("common");
rootnode is still set to a null pointer after the final line. I assume this is because it cannot find "common".
Here is my XML (shortened):
<?xml version="1.0"?>
<font>
<info outline="0" spacing="1,1" padding="0,0,0,0" aa="1" smooth="1" stretchH="100" unicode="1" charset="" italic="0" bold="0" size="16" face="Arial"/>
<common blueChnl="0" greenChnl="0" redChnl="0" alphaChnl="1" packed="0" pages="1" scaleH="128" scaleW="256" base="13" lineHeight="16"/>
<pages>
<page file="Font_Arial_16_0.png" id="0"/>
</pages>
<chars count="191">
... (removed additional <char>'s)
<char id="32" chnl="15" page="0" xadvance="4" yoffset="0" xoffset="0" height="16" width="1" y="85" x="121"/>
... (removed additional <char>'s)
</chars>
<kernings count="70">
... (removed additional <kerning>'s)
<kerning amount="-1" second="65" first="32"/>
... (removed additional <kerning>'s)
</kernings>
</font>
However, in the XMLDocument txDoc, the charBuffer only contains:
<?xml version="1.0"?>
<font
And apparently nothing more. So I assume it says there is no error because it finds and opens the file, but doesn't seem to get everything that's inside it.
Does anyone have any ideas? I am using TinyXML2, not 1.
I get the impression I am navigating the file incorrectly.

For the XMLDocument, FirstChildElement() is equivalent to RootElement() and your root element here is font. You want to call FirstChildElement() of the root element.

Related

Why does TinyXml2 put XMLDeclaration at the end?

I'm using TinyXml2 v8.0.0 to create an XML buffer to send to an API. The example includes a declaration. I'm implementing this with:
XMLDocument doc;
doc.InsertEndChild(doc.NewDeclaration());
XMLElement* pRoot = doc.NewElement("Stuff");
doc.InsertFirstChild(pRoot);
The documentation for NewDeclaration states:
If the text param is null, the standard declaration is used.:
<?xml version="1.0" encoding="UTF-8"?>
You can see this as a test in https://github.com/leethomason/tinyxml2/blob/master/xmltest.cpp#L1637
But when I print the buffer out the declaration has been placed at the end of the buffer after a newline:
<Stuff>
</Stuff>
<?xml version="1.0" encoding="UTF-8"?>
Does anyone know why this is happening? I'd expect it to be at the start of the buffer with no newline.
Presumably, that's because you told it to put the declaration as the EndChild and the Stuff element as the FirstChild.

rapidxml weird parsing for one specific node

I've been looking for hours a solution to my (simple ?) problem but I cannot find anyone who encountered this. I'm using latest version of rapidxml(1.13).
I'm currently trying to create a tile-based engine and I need to read tmx file.
I'm been using rapidxml for a while and so far everything was great. It was able to read every node perfectly and with an expected behavior. But I came across one node it has a problem with.
This is my tmx file :
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.5" tiledversion="1.6.0" orientation="orthogonal" renderorder="right-down" width="100" height="100" tilewidth="32" tileheight="32" infinite="0" nextlayerid="12" nextobjectid="1">
<tileset firstgid="1" source="../../../Tile_engine/Tile_engine/sprite/[Base]BaseChip_pipo.tsx"/>
<tileset firstgid="1065" source="../../../Tile_engine/Tile_engine/sprite/collision.tsx"/>
<layer id="4" name="background" width="100" height="100">
<properties>
<property name="bg" type="bool" value="false"/>
</properties>
<data encoding="csv">
//I've removed the data for clearer view
</data>
</layer>
<layer id="6" name="object" width="100" height="100">
<properties>
<property name="isSolid" type="bool" value="true"/>
</properties>
<data encoding="csv">
//I've removed the data for clearer view
</data>
</layer>
<layer id="9" name="front" width="100" height="100">
<properties>
<property name="isSolid" type="bool" value="false"/>
</properties>
<data encoding="csv">
//I've removed the data for clearer view
</data>
</layer>
<layer id="11" name="collision" width="100" height="100">
<data encoding="csv">
//I've removed the data for clearer view
</data>
</layer>
</map>
In order to debug, I'm using a basic read with rapidxml :
xml_document<> doc;
xml_node<> * root_node;
// Read the xml file into a vector
ifstream theFile ("sprites/map_wtf.tmx");
vector<char> buffer((istreambuf_iterator<char>(theFile)), istreambuf_iterator<char>());
buffer.push_back('\0');
// Parse the buffer using the xml file parsing library into doc
doc.parse<0>(&buffer[0]);
// Find our root node
root_node = doc.first_node("map");
When I try to read (and count) the layer node for example :
int count_node(0);
for(xml_node<> * child = root_node->first_node("layer"); child != nullptr; child = child->next_sibling())
count_node++;
cout << count_node;
The output is correct and gives me 4.
But when I try to read the tileset node, the output gives me 6.
So I've assumed the behavior is link to the /> at the end of the tileset node (<tileset firstgid="1" source="../../../Tile_engine/Tile_engine/sprite/[Base]BaseChip_pipo.tsx"/>).
Since the nested property node has the same pattern (<property name="bg" type="bool" value="false"/>), I've tried this code :
int count_node(0);
for(xml_node<> * child = root_node->first_node("layer")->first_node("properties")->first_node("property"); child != nullptr; child = child->next_sibling())
count_node++;
cout << count_node;
who gives me the correct output aka : 1.
I've tried different parsing options for the line doc.parse<0>(&buffer[0]) but nothing works. I've also read the content of the buffer during theses tests and it was correct.
I must have the wrong way to read the file but I cannot understand why this script reads layer node and property node fine, but not the tileset ones.
Can anyone help me ?
Thanks !
The question is based on a misunderstanding about the result of
xml_node<> * child = root_node->first_node("layer");
child = child->next_sibling();
The child = child->next_sibling() returns the next node on the same level irregardless of the node name or any other constraint that was applied during the root_node->first_node("layer") selection.
However, the next_sibling signature is basically the same as the first_node signature, so it can be used to apply criteria to the next sibling node to be found.
So the (untested) approach would be like
int count_node(0);
for(xml_node<> * child = root_node->first_node("tileset"); child != nullptr; child = child->next_sibling("tileset"))
count_node++;
cout << count_node;

Pass a Request.QueryString into a xslt doc

I have a URL that looks like this:
www.example.com/careers/job.aspx?jobTitle=analytics-developer
And I would like to pass the 'jobTitle' into an xslt file using <%=Request.QueryString["jobTitle"]%>
The code I am using in job.aspx to pass information to the jobTitle.xslt file is this:
<asp:Xml ID="Xml1" runat="server" DocumentSource="~/_/xml/Jobs.xml" TransformSource="~/_/xslt/jobTitle.xslt"></asp:Xml>
Where Jobs.xml is the xml file with all the job info.
Jobs.xml looks sth like this:
<?xml version="1.0" encoding="utf-8" ?>
<jobs>
<devjobs>
<job>
<title>Analytics Developer</title>
<group>Mobile Analytics Group</group>
<url>analytics-developer</url>
<shortdesc>We are looking for talented developers.</shortdesc>
</job>
</devjobs>
</jobs>
Basically I want to be able to write in the xslt file that "if the url element from the Jobs.xml file is equal to the 'jobTitle' given in the url then we show that job's details".
Is this possible?
The asp XML control has a "TransformArgumentList" property which can be used to pass paramters to your XSLT. To be honest, I don't know if you can it directly on the asp:xml tag itself, but you can always set in your code behind, in the page_load event, for example
protected void Page_Load(Object Sender, EventArgs e)
{
string param = Request.QueryString["jobTitle"];
XsltArgumentList args = new XsltArgumentList();
args.AddParam("jobtitle", "", param);
Xml1.TransformArgumentList = ags;
}
Then, in your XSLT file itself, you would need to have a xsl:param element accordingly:
<xsl:param name="jobtitle" />
You can then access this just like any variable (by refering to it as $jobtitle).

XMLNS attribute removed by Slow Cheetah transformation

I'm trying to use Slow Cheetah to transform a Windows scheduled task config file. I'm simply trying to add "repetition" node information, like so:
ORIGINAL:
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2013-01-02T09:32:12.2196371</Date>
<Author>xxx</Author>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<StartBoundary>2013-01-10T01:00:00</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
.....
</Task>
REQUIRED, ADDITIONAL XML
<CalendarTrigger>
<Repetition>
<Interval>PT300S</Interval>
</Repetition>
</CalendarTrigger>
To do this, I have the following transformation file:
<?xml version="1.0" encoding="utf-16" ?>
<Task version="1.2">
<Triggers>
<CalendarTrigger xdt:Transform="Insert">
<Repetition>
<Interval>PT300S</Interval>
</Repetition>
</CalendarTrigger>
</Triggers>
</Task>
The problem I'm having is that all attributes outside of the CalendarTrigger node are removed (and therefore making the resultant transformation config an invalid scheduled task format).
I have tried adding
xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform" xdt:Transform="SetAttributes" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"
to the Task node, but the attribute is then generated at CalendarTrigger level (and I cannot put this attribute on the original, because I then get "No element in the source document matches '/Task/Triggers' ").
Any pointers?
UPDATE:
The problem seems to be isolated to the xmlns attribute; if I try to include this in the 'Task' node of the original, I get "No element in the source document matches '/Task/Triggers'" - BUT changing this attribute to 'xmlns2' works fine and produces exactly what I need (albeit with an 'xmlns2' attribute!). Is this a known limitation of Slow Cheetah? Anyone know of a potential work-around?
That's because your xdt:Transform="Insert" is one level to high.
This should work:
<?xml version="1.0" encoding="utf-16" ?>
<Task xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Triggers>
<CalendarTrigger>
<Repetition xdt:Transform="Insert">
<Interval>PT300S</Interval>
</Repetition>
</CalendarTrigger>
</Triggers>
</Task>

not able to remove tags that "xsi:nil" in them via xslt

I have following xml which contains several xml tags with xsi:nil="true". These are tags that are basically null. I am not able to use/find any sxlt transformer to remove these tags from the xml and obtain the rest of the xml.
<?xml version="1.0" encoding="utf-8"?>
<p849:retrieveAllValues xmlns:p849="http://package.de.bc.a">
<retrieveAllValues>
<messages xsi:nil="true" />
<existingValues>
<Values>
<value1> 10.00</value1>
<value2>123456</value2>
<value3>1234</value3>
<value4 xsi:nil="true" />
<value5 />
</Values>
</existingValues>
<otherValues xsi:nil="true" />
<recValues xsi:nil="true" />
</retrieveAllValues>
</p849:retrieveAllValues>
The reason of error you get
[Fatal Error] file2.xml:5:30: The prefix "xsi" for attribute "xsi:nil" associated with an element type "messages" is not bound.
is absence of prefix named "xsi" declared, you should specify it in root element such as:
<p849:retrieveAllValues xmlns:p849="http://package.de.bc.a"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<retrieveAllValues>
<messages xsi:nil="true" />
// other code...
update
If you could not change xml document you're receiving from webservice, you could try next approach(if this approach is acceptable for you):
Change your xslt document to process xml documents without specifying element prefixes
Set property namespaceAware of DocumentBuilderFactory to false
After this yout transformer shouldn't complain
It doesn't look like this is going to be possible in XSLT - because of the missing namespace declarations you have to parse the XML file with a non-namespace-aware parser, but all the XSLT processors I've tried don't get on well with such documents, they must rely on some information that is only present when parsing with namespace awareness enabled, even if the document in question doesn't actually contain any namespaced nodes.
So you'll have to approach it a different way, for example by traversing the DOM tree yourself. Since you say you're working in Java, here's an example using Java DOM APIs (the example runs as-is in the Groovy console, or wrap it up in a proper class definition and add whatever exception handling is required to run it as Java)
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.ls.*;
public void stripNils(Node n) {
if(n instanceof Element &&
"true".equals(((Element)n).getAttribute("xsi:nil"))) {
// element is xsi:nil - strip it out
n.getParentNode().removeChild(n);
} else {
// we're keeping this node, process its children (if any) recursively
NodeList children = n.getChildNodes();
for(int i = 0; i < children.getLength(); i++) {
stripNils(children.item(i));
}
}
}
// load the document (NB DBF is non-namespace-aware by default)
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document xmlDoc = db.parse(new File("input.xml"));
stripNils(xmlDoc);
// write out the modified document, in this example to stdout
LSSerializer ser =
((DOMImplementationLS)xmlDoc.getImplementation()).createLSSerializer();
LSOutput out =
((DOMImplementationLS)xmlDoc.getImplementation()).createLSOutput();
out.setByteStream(System.out);
ser.write(xmlDoc, out);
On your original example XML this produces the correct result:
<?xml version="1.0" encoding="UTF-8"?>
<p849:retrieveAllValues xmlns:p849="http://package.de.bc.a">
<retrieveAllValues>
<existingValues>
<Values>
<value1> 10.00</value1>
<value2>123456</value2>
<value3>1234</value3>
<value5/>
</Values>
</existingValues>
</retrieveAllValues>
</p849:retrieveAllValues>
The empty lines are not actually empty, they contain the whitespace text nodes either side of the removed elements, as only the elements themselves are being removed here.