SSIS Xpath query expression error - sql-server

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);

Related

Regex to match the last value in given data

I get data from a URL, and am working on the data to check for a few conditions. The data from the URL look like this:
1528190345":100,"1528190346":100,"1528190368":100,"1528190414":100,"1528190439":99,"1528190440":99,"1528190463":100,"1528190485":100,"1528190508":100,"1528190550":100,"1528190575":100,"1528190576":100,"1528190599":100,"1528190600":100,"1528190622":100,"1528190667":100,"1528190688":100,"1528190689":100,"1528190712":100,"1528190736":100,"1528190762":100,"1528190785":100,"1528190786":100,"1528190807":100,"1528190828":100,"1528190853":100,"1528190877":100,"1528190901":100,"1528190925":100,"1528190948":100,"1528190968":100,"1528190991":100}}]
====
I have converted that too JSON
{"metric"=>"Insta_real-unique_value", "tags"=>{"host"=>"letme.quickly.com", "tier"=>"2", "device"=>"tester1", "dc"=>"xxx"}, "aggregateTags"=>["device_name", "device_ip"], "dps"=>{"1526972408"=>100, "1526972424"=>100, "1526972440"=>100, "1526972456"=>100, "1526972472"=>100, "1526972488"=>100, "1526972504"=>100, "1526972520"=>100, "1526972536"=>100, "1526972552"=>100, "1526972568"=>100, "1526972569"=>100, "1526972584"=>100, "1526972585"=>100, "1526972601"=>100, "1526972617"=>100, "1526972633"=>100, "1526972649"=>100, "1526972665"=>100, "1526972681"=>100}}
I want to extract the value that corresponds to 100. When I do this:
url = "#{URL}"
uri = URI(url)
response = Net::HTTP.get(uri)
value = response[-6..-4]
puts value
I get the last value, but when the last value changes to 99/9/0, it prints :99 or ":9.
Is there a way to get the exact value as is?
When dealing with JSON data, it's almost always better to parse the data properly rather than using regex against the string.
In this case, we can do:
JSON.parse(response)['dps'].values.last #=> 100
If the response is a json response, you must use a json parser else if is not a json response, you can use a regex expression with a Regex Object.
In case of a json response, assuming that the object is something like is declared into the variable response of the next code, you can parse it into a JObject. (using Newtonsoft.Json available from nuget repository).
See the next example :
string response = "[{\"response\":{\"1528190345\":100,\"1528190346\":100,\"1528190368\":100,\"1528190414\":100,\"1528190439\":99,\"1528190440\":99,\"1528190463\":100,\"1528190485\":100,\"1528190508\":100,\"1528190550\":100,\"1528190575\":100,\"1528190576\":100,\"1528190599\":100,\"1528190600\":100,\"1528190622\":100,\"1528190667\":100,\"1528190688\":100,\"1528190689\":100,\"1528190712\":100,\"1528190736\":100,\"1528190762\":100,\"1528190785\":100,\"1528190786\":100,\"1528190807\":100,\"1528190828\":100,\"1528190853\":100,\"1528190877\":100,\"1528190901\":100,\"1528190925\":100,\"1528190948\":100,\"1528190968\":100,\"1528190991\":100}}]";
List<Dictionary<string, Dictionary<string, int>>> values = JsonConvert.DeserializeObject<List<Dictionary<string, Dictionary<string, int>>>>(response);
Dictionary<string, Dictionary<string, int>> firstLevel = values[0]; // Access to the first object of the list closed with ']'
Dictionary<string, int> secondLevel = firstLevel["response"]; // Access to the first object response and get's it's object context of first '}' starting from the end of response
/** This is an option, if you ever knows the name of the element (1528190991) */
int thirdLevel = secondLevel["1528190991"]; // Access to the last element of the object by it's name, context of second '}' starting from the end of response.
Console.WriteLine(thirdLevel);
/** This is another option if you doesn't know the name of the element and wants ever the last element. */
List<int> listOfValues = secondLevel.Values.ToList();
Console.WriteLine(listOfValues[listOfValues.Count-1]);
Note that i've chenged a little bit your response adding [{\"response\":{\" at the start to become a json response.
If is not a json response you can use this pattern with regular expression :
:(.{2,6})}}\]$
Hope will help!

Passing a full-text search parameter using Dapper.net

Consider this simple query which use full text searching on the Keywords field:
DECLARE #searchTerm VARCHAR(500) = 'painted'
SELECT * FROM StockCatalogueItems
WHERE (CONTAINS(KeyWords, #searchTerm))
This works as expected, but I need to do the same using a Dapper.net parameterised query. When using stored procedures, I create the full text parameter like this: "\"painted*\""
But using the same approach this doesn't work using dapper. No results are returned. This is the line in the query where I use the parameter:
AND (CONTAINS(KeyWords, #search))
and it's passed to the query like so:
return _context.Database.Connection.Query<StockProfileMatrix>(basequery, new
{
search = searchTerm
}
I can only assume that dapper is sanitising the string somehow, removing quotes perhaps?
Any ideas?
This works for me. However the tech stack am working on is .net core RTM and "Dapper": "1.50.0-rc3",
_dbConnection.QueryAsync<Guid>(#"select br.Id from Brand br where CONTAINS(br.Text,#Name)",new {Name = $"\"*{name}*\""}))
For completeness, I'll answer the question. The query syntax is correct, but the way in which the full-text parameter is created was obviously not. I created an extension method that formats the parameter:
public static string ToFullText(this string str)
{
string searchTerm = null;
if (!string.IsNullOrEmpty(str))
{
string[] keywords = str.Trim().Split(null);
foreach (var keyword in keywords)
{
searchTerm += string.Format("\"{0}*\" AND ", keyword);
}
if (searchTerm != null)
searchTerm = searchTerm.Substring(0, searchTerm.LastIndexOf(" AND "));
}
return searchTerm;
}
Then I call the method when I pass the parameter in to the dapper query:
_context.Database.Connection.Query<dynamic>(query, new
{
search = filter.SearchTerm.ToFullText()
});

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.

get the record value from XML Store in EXT js

I am ExtJS for GUI development. I am using XML Store for retrieving the data from server.
My XML looks like this.
<meta>
<entry>x</entry>
<entry>Y</entry>
</meta>
<data>
<value>100</value>
<value>500</value>
</data>
Where X=100 and Y=500
How do i retrieve the data and value from this using XMLStore?
Since the XML structure is not really suitable for what the XML Store/Reader expect, I suggest you parse the XML yourself into a more standard format and then load the data to a JsonStore for example.
Parsing code: (wrote it off the top of my head, so might need some adjustments ...)
var data = [];
var q = Ext.DomQuery;
var entries = q.select('meta > entry', theXML);
var values = q.select('data > value', theXML);
for (var i = 0; i < entries.length; ++i) {
var recordData = {
entry: entries[i].firstChild.nodeValue,
value: values[i].firstChild.nodeValue
}
data.push(recordData);
}
Hope it will be useful to you ...

Weird XPath behavior in libxml2

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.

Resources