A Closer Look at BizTalk Xpath – What is a Predicate?

Have you ever been confused by the long XPaths found in BizTalk? XPath is a great syntax used to select or query XML and return XML nodes or specific element/attribute values. XPath is good to know for BizTalk, .NET programming, and XSLT.

Example 1

For example, look at the schema below.

Demo BizTalk Schema
Demo BizTalk Schema

If you click on the “Name” element, then go to the properties window, you will see the following Xpath in the value of the field called “Property XPath”.

/*[local-name()='SchemaDemo2' and namespace-uri()='http://BizTalkSampleProject.SchemaDemo2']/*[local-name()='ShippingAddress' and namespace-uri()='']/*[local-name()='Name' and namespace-uri()='']

What does the above actually mean? W3Schools has a good tutorial on XPath, but it doesn’t cover dealing with namespaces. On this xpath syntax page, it does however define a predicate which is the XPath code in between the square brackes [].

Predicates are used to find a specific node or a node that contains a specific value.

Predicates are always embedded in square brackets.

In the table below we have listed some path expressions with predicates and the result of the expressions:

 

And here are the example they give, but no examples with namespaces!

Xpath_Predicate_Examples

So think of a predicate as a “Where Clause”. Even in SQL terminology, a predicate is something that results in a TRUE or FALSE.

So when you see the typical Microsoft generated XPath, it can be explained as follows. The / is the normal separator, and starting point from the current node. The “*” is the abbreviation for the “child” axis (explained more here: https://www.stylusstudio.com/docs/v2007/d_xpath95.html, http://www.informit.com/articles/article.aspx?p=102644, http://www.w3schools.com/xsl/xpath_axes.asp). It selects all element children of the context node.

But then we don’t really want all the nodes, so we apply the predicate or “where clause” – only give me the nodes where the local-name()=’SchemaDemo2′ and namespace-uri()=’http://BizTalkSampleProject.SchemaDemo2′ (and that must be enclosed in brackets). local-name() and namespace-uri() are functions (or internal variables?) that return the current element name and the current namespace). If you have the same namespace all the way through your schema, then you can leave off the namespace-uri() predicate.

In other words, the longer full syntax is often shortened to this:

/*[local-name()='SchemaDemo2']/*[local-name()='ShippingAddress']/*[local-name()='Name']

The only time this shortening would get you in trouble is if you had the same element under more than one namespace.

Example 2

Consider this example, where I created an Address schema, then I imported it into the Labels Schema, then made one Billing Address and one Shipping Address.

Nested_Schema

/*[local-name()='Labels' and namespace-uri()='http://BizTalkSampleProject.Labels']/*[local-name()='BillingAddress' and namespace-uri()='']/*[local-name()='Address' and namespace-uri()='http://BizTalkSampleProject.AddressSchema']/*[local-name()='Name' and namespace-uri()='']

It turns out that even in this case, you could shorten the XPath to the following:
/*[local-name()='Labels']/*[local-name()='BillingAddress']/*[local-name()='Address']/*[local-name()='Name']

So one case where you couldn’t shorten it would be when you had two Address nodes with different namespaces.

Testing XPath Online

Now, I want to go into some reasons why I wrote this blog. I was using C# to loop through XML of a bigger document.
Inside the loop, I have this chunk of XML in an XmlNode.

I’ve been testing XPath Online with http://www.xpathtester.com/xpath.
If I start my XPath with two slashes, it seems to be going back to the top of the entire XML document, rather than just searching the current node.

I originally had this xpath:

//*[local-name()='Property'][@Name='Name']

So basically, I my aim is to pick out the orchestration variable name, which is the example below is “strYNTraceOnFromSSO”;
It works fine in Xpath Tester because I have just the above text, but in C# it is returning some node outside of that text. (I guess I could reload the XML in a new XML document, but using this as an opportunity to get a better graso my XPath).

This was my XML, which actually came out of an .odx file.


Here is what the query looks like in XPath Tester: Xpath Tester So if I start my Xpath with /*, I would need to specify the parent node, right? This example returns the following error:
    /*[local-name()='Element']/[local-name()='Property'][@Name='Name']

returns error: ERROR – Failed to evaluate XPath expression: javax.xml.transform.TransformerException: A location step was expected following the ‘/’ or ‘//’ token.

Looking back, it’s an obvious error, but it wasn’t at the time. In easy XPath, you can just put a slash between the elements. But when using the predicates, you have to put /* to indicate “all elements where the name=’Property'”.

Why is there no slash or /* before the attribute? Because it’s a predicate on the predicate. Only return for me Property elements that have an attribute called “Name” that also has a value of “Name”.

Here is the correct result in XPath Tester, returning the selected Property element:
XPathTester_Example2

C# Code

Above example found the desired Property element only. In the sample code below I go down and pick off the value of the attribute.  This code basically loops through all the variables in an orchestration.

            string xpathTopLevelVariables = @"//*[local-name()='Element'][@Type='VariableDeclaration']";
            XmlNodeList xmlNodeList = xmlNodeServiceDeclaration.SelectNodes(xpathTopLevelVariables);

            foreach (XmlNode xmlNode in xmlNodeList)
            {
                XmlDocument xmlDoc2 = new XmlDocument();
                xmlDoc2.LoadXml(xmlNode.OuterXml);   // this seems unnecessary to me, but it works 
                string xpathProperty = "/*[local-name()='Element']/*[local-name()='Property'][@Name='Name']/@Value";
                string variableName = xmlDoc2.SelectSingleNode(xpathProperty).Value;
                // other unrelated code omitted 
           }

 


Note: For some reason, I had to reload the chunk of XML into a new xml document, otherwise I was getting all data from the top rather from the current node downward. I wanted to put the SelectSingleNode method directly on the xmlNode variable, but I didn’t solve that challenge.

Uncategorized  

Leave a Reply