Weird XPath behavior in libxml2 - c

I have this XML tree that looks like this (I've changed the tag names but if you're really clever you may figure out what I'm actually doing.)
<ListOfThings>
<Thing foo:action="add">
<Bar>doStuff --slowly</Bar>
<Index>1</Index>
</Thing>
<Thing foo:action="add">
<Bar>ping yourMother.net</Bar>
<Index>2</Index>
</Thing>
</ListOfThings>
With libxml2, I want to programmatically insert a new Thing tag into the ListOfThings with the Index being the highest current index, plus one. I do it like this (sanity checking removed for brevity):
xpath = "//urn:myformat[#foo='bar']/"
"urn:mysection[#name='baz']/"
"urn:ListOfThings/urn:Thing/urn:Index";
xpathObj = xmlXPathEvalExpression(xpath, xpathCtx);
nodes = xpathObj->nodesetval;
/* Find last value and snarf the value of the tag */
highest = atoi(nodes->nodeTab[nodes->nodeNr - 1]->children->content);
snprintf(order, sizeof(order), "%d", highest + 1); /* highest index plus one */
/* now move up two levels.. */
cmdRoot = nodes->nodeTab[nodes->nodeNr - 1];
ASSERT(cmdRoot->parent && cmdRoot->parent->parent);
cmdRoot = cmdRoot->parent->parent;
/* build the child tag */
newTag = xmlNewNode(NULL, "Thing");
xmlSetProp(newTag, "foo:action", "add");
/* set new node values */
xmlNewTextChild(newTag, NULL, "Bar", command);
xmlNewChild(newTag, NULL, "Index", order);
/* append this to cmdRoot */
xmlAddChild(cmdRoot, newTag);
But if I call this function twice (to add two Things), the XPath expression doesn't catch the new entry I made. Is there a function I need to call to kick XPath in the shins and get it to make sure it really looks over the whole xmlDocPtr again? It clearly does get added to the document, because when I save it, I get the new tags I added.
To be clear, the output looks like this:
<ListOfThings>
<Thing foo:action="add">
<Bar>doStuff --slowly</Bar>
<Index>1</Index>
</Thing>
<Thing foo:action="add">
<Bar>ping yourMother.net</Bar>
<Index>2</Index>
</Thing>
<Thing foo:action="add">
<Bar>newCommand1</Bar>
<Index>3</Index>
</Thing>
<Thing foo:action="add">
<Bar>newCommand2</Bar>
<Index>3</Index> <!-- this is WRONG! -->
</Thing>
</ListOfThings>
I used a debugger to check what happened after xmlXPathEvalExpression got called and I saw that nodes->nodeNr was the same each time.
Help me, lazyweb, you're my only hope!

Based on your XPath it looks like that is just a snippet of a namespaced document (using default namespace at that). I bet you made a prior call to register "urn" as the prefix for a namespace using xmlXPathRegisterNs. When you add the new nodes, you aren't creating them in namespaces so the XPath is correctly only picking the namespaced "Index" elements.

Related

Checking if XML values are in an AS3 array

I have some XML in the below format.
<user uid="0001">
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<ImagePath>images/0001.jpg</ImagePath>
<flightno>GS1234</flightno>
</user>
<user uid="0002">
<FirstName>Luke</FirstName>
<LastName>Dixon</LastName>
<ImagePath>images/0002.jpg</ImagePath>
<flightno>TD1234</flightno>
</user>
<user uid="0003">
<FirstName>Paul</FirstName>
<LastName>Kerr</LastName>
<ImagePath>images/0003.jpg</ImagePath>
<flightno>GS1234</flightno>
</user>
This is a small sample, there are a couple 100 of these.
I have used E4x filtering to filter down another set of XML data that produces an as3 array. The array contains some flight numbers (eg : [GS1234,PB7367,TD1234].
I'm wondering how I can filter my XML (as shown above) to only show the users whose 'flightno' EXISTS in the AS3 array.
I'm guessing its some sort of E4X query but I can't seem to get it right!
Thanks!
// Don't mind me using this trick with inline XML, it's not the point.
// It's here just to make it possible to copy and paste code
// with multiline XML sample.The actual solution is a one-liner below.
var usersXML:XML = new XML(<x><![CDATA[
<data>
<user uid="0001">
<FirstName>John</FirstName>
<LastName>Smith</LastName>
<ImagePath>images/0001.jpg</ImagePath>
<flightno>GS1234567</flightno>
</user>
<user uid="0002">
<FirstName>Luke</FirstName>
<LastName>Dixon</LastName>
<ImagePath>images/0002.jpg</ImagePath>
<flightno>TD1234</flightno>
</user>
<user uid="0003">
<FirstName>Paul</FirstName>
<LastName>Kerr</LastName>
<ImagePath>images/0003.jpg</ImagePath>
<flightno>GS1234</flightno>
</user>
</data>
]]></x>.toString());
// once again, the way I create sample departingXML
// is not important, it's just for copying and pasting into IDE.
var departingXML:XML = new XML(<x><![CDATA[
<flights>
<flight>
<number>GS1234</number>
<date>10/11/2015</date>
<time>1440</time>
</flight>
<flight>
<number>TD1234</number>
<date>10/11/2015</date>
<time>1450</time>
</flight>
</flights>
]]></x>.toString());
// 1. create filter array
var flightNoArray:Array = [];
departingXML.flight.number.(flightNoArray.push(toString()));
trace(flightNoArray); // GS1234,TD1234
trace(typeof(flightNoArray[0])); // string
// 2. filter users:
var list:XMLList = usersXML.user.(flightNoArray.indexOf(flightno.toString()) >= 0);
trace(list); // traces users 0002 and 0003
I wouldn't call it efficient or at least readable though.
// Note: this line is somewhat queer and I don't like it,
departingXML.flight.number.(flightNoArray.push(toString()));
// but this is the only way I can now think of to get and array
// of strings from an XMLList nodes without a loop.
// I would advise to use a readable and simple loop instead.
usersXML.user -- this gets you an XMLList with all nodes named "user"
usersXML.user.(some condition) -- this filters the XMLList of user nodes given a condition
flightNoArray.indexOf(flightno.toString()) >= 0 -- and this is a filter condition
flightno.toString() -- gets you a string inside flightno child
REFERENCE: Traversing XML structures.
Explanation of the search trick in the note above.
UPDATE: it turned out in comments that it was also the way the filter array was populated that was causing trouble. The code below demonstrates some more E4X.
This is how the filter list was created and what was actually happening:
// once again, the way I create sample departingXML
// is just for the sake of copying and pasting, it's not related to solution.
var departingXML:XML = new XML(<x><![CDATA[
<flights>
<flight>
<number>GS1234</number>
<date>10/11/2015</date>
<time>1440</time>
</flight>
<flight>
<number>TD1234</number>
<date>10/11/2015</date>
<time>1450</time>
</flight>
</flights>
]]></x>.toString());
// the way it was done before
var flightNoArray: Array = [];
for each(var num: XML in departingXML.flight) {
flightNoArray.push(num.number);
// WARNING! num.number is an XMLList! It s NOT a string.
// Addressing nodes by name ALWAYS gets you an XMLList,
// even if there's only one node with that name
// Hence, `flightNoArray.indexOf("string")` could not possibly work,
// as there are lists inside of the array, not strings.
// We can check if this is a fact:
trace(flash.utils.getQualifiedClassName(flightNoArray[flightNoArray.length-1]));
// (traces XMLList)
// Or this way (note toXMLString() method)
trace(flightNoArray[flightNoArray.length-1].toXMLString());
// (traces <number>GS1234</number>)
}
trace(flightNoArray);
trace(flightNoArray); traces GS1234,TD1234 because this is the way toString() method works for xml nodes -- it gets you text, that is inside. This is why there is a special method toXMLString(), that gets you a string representation of a node.

SSIS Xpath query expression error

I'm trying to call a SalesForce web service via SSIS, and I am trying to retrieve the value of the sessionID node.
Here is the XML:
<?xml version="1.0" encoding="utf-16"?>
<LoginResult xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<metadataServerUrl xmlns="urn:partner.soap.sforce.com">https://xxxxxx/services/Soap/m/31.0/xxxxxx</metadataServerUrl>
<passwordExpired xmlns="urn:partner.soap.sforce.com">false</passwordExpired>
<sandbox xmlns="urn:partner.soap.sforce.com">true</sandbox>
<serverUrl xmlns="urn:partner.soap.sforce.com">https://xxx/services/Soap/u/31.0/xxx</serverUrl>
<sessionId xmlns="urn:partner.soap.sforce.com">xxxxxxxxxx</sessionId>
<userId xmlns="urn:partner.soap.sforce.com">xxx</userId>
<userInfo xmlns="urn:partner.soap.sforce.com">
<accessibilityMode>false</accessibilityMode>
<currencySymbol>$</currencySymbol>
<orgAttachmentFileSizeLimit>5242880</orgAttachmentFileSizeLimit>
<orgDefaultCurrencyIsoCode>USD</orgDefaultCurrencyIsoCode>
<orgDisallowHtmlAttachments>false</orgDisallowHtmlAttachments>
<orgHasPersonAccounts>false</orgHasPersonAccounts>
<organizationId>xxxxxxxx</organizationId>
<organizationMultiCurrency>false</organizationMultiCurrency>
<organizationName>xxxxx</organizationName>
<profileId>xxxxx</profileId>
<roleId xsi:nil="true" />
<sessionSecondsValid>7200</sessionSecondsValid>
<userDefaultCurrencyIsoCode xsi:nil="true" />
<userEmail>xxxxxxx</userEmail>
<userFullName>xxxxx</userFullName>
<userId>xxxxxxx</userId>
<userLanguage>en_US</userLanguage>
<userLocale>en_US</userLocale>
<userName>xxxxxxx</userName>
<userTimeZone>America/New_York</userTimeZone>
<userType>Standard</userType>
<userUiSkin>Theme3</userUiSkin>
</userInfo>
</LoginResult>
I successfully tested this expression via http://www.xpathtester.com/xpath.
Edit:
Trying a script task now, and still I'm not finding the exact right combination to select this information. There are multiple namespaces in the XML, and the below code returns 0 nodes. Quite frustrating!
public void Main()
{
string loginResult;
string sessionID;
loginResult = Dts.Variables["User::loginResult"].Value.ToString();
XmlDocument doc = new XmlDocument();
doc.LoadXml(loginResult);
var xmlnsManager = new System.Xml.XmlNamespaceManager(doc.NameTable);
//xmlnsManager.AddNamespace("t1", "http://www.w3.org/2001/XMLSchema-instance");
xmlnsManager.AddNamespace("ns", "urn:partner.soap.sforce.com");
XmlNodeList list = doc.SelectNodes("/LoginResult/ns:sessionID", xmlnsManager);
for (int i = 0; i < list.Count; i++)
{
sessionID = list[i].Value;
}
Dts.TaskResult = (int)ScriptResults.Success;
}
}
A correct expression to find the sessionId element is:
//*[local-name() = 'sessionId']/text()
In your input XML, the element you'd like to find:
<sessionId xmlns="urn:partner.soap.sforce.com">xxxxxxxxxx</sessionId>
does not have a prefix - it is in a default namespace that does not require elements to be prefixed.
Therefore, a possible explanation is that an expression like
//*:sessionId
only finds elements that are prefixed in the input XML. That should not be a problem but as far as I can see, SSIS is known for problems with namespaced XML (see e.g. here or here).
As far as the XPath specification is concerned, an expression like //*:root should be able to find an element like
<root xmlns="www.example.com"/>
EDIT: Apparently, you have changed the question alltogether - now there is another problem: The outermost element LoginResult is in no namespace at all, not in the xsi: namespace:
<LoginResult xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
The schema instance namespace just happens to be declared on this element, but it is not used there. So, change your code to:
var xmlnsManager = new System.Xml.XmlNamespaceManager(doc.NameTable);
xmlnsManager.AddNamespace("ns", "urn:partner.soap.sforce.com");
XmlNodeList list = doc.SelectNodes("/LoginResult/ns:sessionId", xmlnsManager);

How can I add strings to array in javascript

I wanna create an array in javascript which looks like this:
[0,0,0,0,0,1,1,0,0,1,1,1,1,0,0],[0,0,0,0,1,1,1,1,0,1,0,1,1,1,0],[0,0,0,0,1,1,1,1,0,1,0,0,1,1,0],[0,0,0,0,0,1,1,0,1,0,1,0,1,0,0]
My problem is that I don't know how to add the opening and closing square brackets to the start and the end of the output string.
here's my code:
game = new Array();
for(row=0;row<matrix.length;++row){
game[row]=matrix[row].join(',');
}
document.getElementById('jsvalue').value=game.join('],[');
document.getElementById('name2').value = name;
I tried a few things, but they didn't seem to work and all I got were errors or this output:
0,0,0,0,0,1,1,0,0,1,1,1,1,0,0],[0,0,0,0,1,1,1,1,0,1,0,1,1,1,0],[0,0,0,0,1,1,1,1,0,1,0,0,1,1,0],[0,0,0,0,0,1,1,0,1,0,1,0,1,0,0
How could I add them? Is there a simple array method that I missed and would solve my problem?
Thanks in advance!
It looks like you are trying to set the value of an HTML element to the format you described in your question. However, you are not setting the value of that HTML element to an Array - you are setting it to a string. the .join function outputs a string. If indeed you want the value to be set to a string formatted in the way you described, then you could take advantage of .join, but have to do a little bit in addition to what you are doing:
game = new Array();
for(row=0;row<matrix.length;++row){
game[row]= "[" + matrix[row].join(',') + "]";
}
document.getElementById('jsvalue').value=game.join(',');
document.getElementById('name2').value = name;
If you are using join to create the string, then why not just manually add the brackets?
For example:
document.getElementById('jsvalue').value= '[' + game.join('],[') + ']';

NullReferenceException while saving XML File with Linq

I keep getting a NullReferenceException at this line UserRoot.Element("User_ID").Value = User.User_ID.ToString();
What exactly am I doing wrong?
Here's the majority of the code for that method
if (File.Exists(Path2UserDB + User.User_ID.ToString() + ".db") == false)
{
File.Create(Path2UserDB + User.User_ID.ToString() + ".db");
}
XElement UserRoot = new XElement("User");
UserRoot.Element("User_ID").Value = User.User_ID.ToString();
UserRoot.Element("Full_Name").Value = User.Full_Name;
UserRoot.Element("Gender").Value = User.Gender;
UserRoot.Element("BirthDate").Value = User.BirthDate.ToString();
UserRoot.Element("PersonType").Value = User.PersonType.ToString();
UserRoot.Element("Username").Value = User.Username;
UserRoot.Element("Password").Value = User.Password;
UserRoot.Element("Email_adddress").Value = User.Email_Address;
XDocument UserDoc = new XDocument();
UserDoc.Save(Path2UserDB + User.User_ID.ToString() + ".db");
Thanks
I know that saving Usernames and Passwords in plain text is incredibly unsafe, but this is only going to be accessed by one process that I will eventually implement strong security
The Element("User_ID") method returns an existing element named <User_ID>, if any.
Since your XML element is empty, it returns null.
You should create your XML like this:
var userDoc = new XDocument(
new XElement("User",
new XElement("User_ID", User.User_ID),
new XElement("Full_Name", User.Full_Name),
new XElement("Gender", User.Gender),
...
)
);
Alternatively, you can call the Add method to add a node to an existing element.
You are getting this error, because there is no XML element called User_ID under UserRoot to set its value. If you comment it out, you will get the same error on the next line and so on for every other Element, since you haven't added Elements with thos names. To create the tree that you want, try this:
XElement UserRoot =
new XElement("User",
new XElement("User_ID", User.User_ID.ToString()),
new XElement("Full_Name", User.Full_Name),
new XElement("Gender", User.Gender),
new XElement("BirthDate", User.BirthDate.ToString()),
new XElement("PersonType", User.PersonType.ToString()),
new XElement("Username", User.Username),
new XElement("Password", User.Password),
new XElement("Email_adddress", User.Email_Address)
);
The following MSDN link on XML Tree Creation with XElement will be of help.
You want to check if the value is null or empty before running methods on it.
if(!String.IsnullorEmpty(User.User_ID))
UserRoot.Element("User_ID").Value = User.User_ID.ToString();

How to add values to combobox in C++ Builder?

I want to add values to combobox in C++ builder 6.
I know I can add string to combobox by string list editor.
For example, I have added this list to combobox:
car
ball
apple
bird
I want behind each text, it has their own value, so I can get the value rahter than the text when user selected a text. Just like HTML select.
But when I try to add value to each text:
ComboBox1->Items->Values[0] = "mycar";
ComboBox1->Items->Values[1] = "aball";
etc...
it will add more text to the list, like
car
ball
apple
bird
0=mycar
1=aball
This is not what I want. I don't want the extra text to add to the list.
So, how can I add values to each text properly, and get the value?
If you want to store the values in the ComboBox itself, then you need to use the Objects[] property instead of the Values[] property, for example:
ComboBox1->Items->Objects[0] = (TObject*) new String("mycar");
ComboBox1->Items->Objects[1] = (TObject*) new String("aball");
...
String value = * (String*) ComboBox1->Items->Objects[ComboBox1->ItemIndex];
...
delete (String*) ComboBox1->Items->Objects[0];
delete (String*) ComboBox1->Items->Objects[1];
As you can see, this requires managing dynamically allocated String objects. A better option would be to store the values in a separate list, such as a TStringList or std::vector, like PoweRoy suggested. As long as that list has the same number of items as the ComboBox, you can use ComboBox indexes to access the values, for example:
TStringList *MyValues = new TStringList;
...
MyValues->Add("mycar");
MyValues->Add("aball");
...
String value = MyValues->Strings[ComboBox1->ItemIndex];
...
delete MyValues;
Or:
#include <vector>
std::vector<String> MyValues;
...
MyValues.push_back("mycar");
MyValues.push_back("aball");
...
String value = MyValues[ComboBox1->ItemIndex];
...
hold a list(vector/array whatever you want) containing the name and value pairs. When selecting a name look the value up in the list.

Resources