I am not sure if I'm really doing a mistake or if this is just not working properly:
<xsl:if test="position() = 1 or parent::position() = 1">
<!-- do something -->
</xsl:if>
If the current node is the first child, or it's parent is the first child, then do something special.
The problem is "parent::position() = 1" .. with .net's XmlCompiledTransform I get
Expected end of the expression, found '('. ...sition() = 1 or parent ::position -->(<-- = 1.
Now, apparently it doesn't like that position() function in the second part, but.. how do I get the parent's position? How do I, generally, combine XPath functions with XPaths in tests?
Good question, +1.
You need to define what is meant by position of the parent.
parent::node()[position() = 1]
is always true() when the parent node exists (is false() only if the context node is the document node / as this node is the top node in the tree and doesn't have a parent), because any node in a tree (well-formed XML document) by definition can have at most one parent node.
Most likely, you want to test that the parent element is the first in document order among its siblings. One way to test for this is:
not(parent::node()/preceding-sibling::*)
So, the complete code becomes:
<xsl:if test="position() = 1 or not(parent::node()/preceding-sibling::*)">
<!-- do something -->
</xsl:if>
Related
I have an xml file generated by Vector CANeds. This file contains information about CANopen Objects I want to read with my tool written in C#.
The (very basic) structure of the xml is as follows:
<ISO15745ProfileContainer xmlns="http://www.canopen.org/xml/1.0">
<ISO15745Profile>
<ProfileHeader></ProfileHeader>
<ProfileBody xsi:type="ProfileBody_Device_CANopen"</ProfileBody>
</ISO15745Profile>
<ISO15745Profile>
<ProfileHeader></ProfileHeader>
<ProfileBody xsi:type="ProfileBody_CommunicationNetwork_CANopen"</ProfileBody>
</ISO15745Profile>
</ISO15745ProfileContainer>
When I create an XmlNodeList with both ISO15745Profile nodes in it and loop through then i get a strange behaviour. By accessing the subnodes with explicit indexes, everything is as expected. When I am using xpath, allways the first node is used.
Code snippet:
const string filepath = "CANeds1.xdd";
const string s_ns = "//ns:";
var mDataXML = new XmlDocument();
mDataXML.Load(filepath);
var root = mDataXML.DocumentElement;
XmlNamespaceManager nsm = new XmlNamespaceManager(mDataXML.NameTable);
nsm.AddNamespace("ns", root.Attributes["xmlns"].Value);
foreach (XmlNode node in root.ChildNodes) {
Console.WriteLine(" " + node.ChildNodes[1].Attributes["xsi:type"].Value);
Console.WriteLine(" " + node.SelectSingleNode(s_ns + "ProfileBody", nsm).Attributes["xsi:type"].Value);
}
Console output:
ProfileBody_Device_CANopen
ProfileBody_Device_CANopen
ProfileBody_CommunicationNetwork_CANopen
ProfileBody_Device_CANopen
Since node references the 2nd node, the last output should be commNetwork to.
Does somebody see my mistake? I have already tried to rename one of the "ISO15745Profile" nodes but this did not change the outcome. I may have messed up something with the namespace...
Some more explanation to the answer given in the comments:
The important point is the // XPath expression. The definition from MSDN says:
Recursive descent; searches for the specified element at any depth. When this path operator appears at the start of the pattern, it indicates recursive descent from the root node.
This means an expression starting with // will always search for occurences the entire document, even if it's called from a specific child note. That's why SelectSingleNode will always return the first match in the entire document.
To search relative to the node that calls the selection method there is the . operator which indicates the current context.
Put together, an expression starting with .// will search for all occurrences of the following pattern, beginning at the current node.
In the specific case, this means changing //ns: to .//ns: to get the expected result.
Documentation says:
XContainer.Nodes Method ()
Returns a collection of the child nodes of this element or document, in document order.
Remarks
Note that the content does not include attributes. In LINQ to XML, attributes are not considered to be nodes of the tree. They are name/value pairs associated with an element.
XContainer.Elements Method ()
Returns a collection of the child elements of this element or document, in document order.
So it looks like Nodes() has a limitation, but then why does it exist? Are there any possible reasons or advantages of using Nodes()?
The reason is simple: XNode is a base (abstract) class for all xml "parts", and XElement is just one such part (so XElement is subclass of XNode). Consider this code:
XDocument doc = XDocument.Parse("<root><el1 />some text<!-- comment --></root>");
foreach (var node in doc.Root.Nodes()) {
Console.WriteLine(node);
}
foreach (var element in doc.Root.Elements()) {
Console.WriteLine(element);
}
Second loop (over Elements()) will only return one item: <el />
First loop however will return also text node (some text) and comment node (<!-- comment -->), so you see the difference.
You can see what other descendants of XNode there are in documentaiton of XNode class.
It's not the case that Nodes "have a limitation". Nodes are the fundamental building block on which most other things (including Elements) are built.
The XML document is represented as a hierarchy (tree), and the nodes are used to represent the fundamental structure of the hierarchy.
If we consider the following XML document:
<root>
<element>
<child>
Text
</child>
</element>
<!-- comment -->
<element>
<child>
Text
<child>
</element>
</root>
Clearly the whole document cannot be represented as elements, since the comment and the text within the "child" elements are not elements. Instead, it's represented as a hierarchy of nodes.
In this document, there are 5 elements (the root element, two "element" elements and two "child" elements). All of these are nodes, but there are also 3 other nodes: the text within "child" elements, and the comment.
It's misleading to say that nodes have a "limitation" because they don't have attributes. Only elements have attributes, and elements are nodes! But there are other nodes (e.g. the comment) that can't have attributes. So not all types of node have attributes.
In coding terms, Node is the base class on which higher-level types such as Element are built. If you want to enumerate the elements in the document, then using XContainer.Elements() is a nice shortcut to do that - but you could also use XContainer.Nodes() and get all the nodes, including both the elements and the other stuff. (You can check the type of the node to see whether you have an element node, a text node, or whatever; if it's an element, you can up-cast it).
Currently I have the following code to get the children of a given node with a specific local-name:
node.XPathSelectElements("//*[local-name()='param']");
But this gives me all the param nodes in the document and I just want the children of node.
What I need to change?
Add leading . to tell that the XPath is relative to current node element :
node.XPathSelectElements(".//*[local-name()='param']");
And replace double / with single if you really meant children instead of descendants :
node.XPathSelectElements("./*[local-name()='param']");
Using a XPath query how do you find if a node (tag) exists at all?
For example if I needed to make sure a website page has the correct basic structure like /html/body and /html/head/title.
<xsl:if test="xpath-expression">...</xsl:if>
so for example
<xsl:if test="/html/body">body node exists</xsl:if>
<xsl:if test="not(/html/body)">body node missing</xsl:if>
Try the following expression: boolean(path-to-node)
Patrick is correct, both in the use of the xsl:if, and in the syntax for checking for the existence of a node. However, as Patrick's response implies, there is no xsl equivalent to if-then-else, so if you are looking for something more like an if-then-else, you're normally better off using xsl:choose and xsl:otherwise. So, Patrick's example syntax will work, but this is an alternative:
<xsl:choose>
<xsl:when test="/html/body">body node exists</xsl:when>
<xsl:otherwise>body node missing</xsl:otherwise>
</xsl:choose>
Might be better to use a choice, don't have to type (or possibly mistype) your expressions more than once, and allows you to follow additional different behaviors.
I very often use count(/html/body) = 0, as the specific number of nodes is more interesting than the set. For example... when there is unexpectedly more than 1 node that matches your expression.
<xsl:choose>
<xsl:when test="/html/body">
<!-- Found the node(s) -->
</xsl:when>
<!-- more xsl:when here, if needed -->
<xsl:otherwise>
<!-- No node exists -->
</xsl:otherwise>
</xsl:choose>
I work in Ruby and using Nokogiri I fetch the element and look to see if the result is nil.
require 'nokogiri'
url = "http://somthing.com/resource"
resp = Nokogiri::XML(open(url))
first_name = resp.xpath("/movies/actors/actor[1]/first-name")
puts "first-name not found" if first_name.nil?
A variation when using xpath in Java using count():
int numberofbodies = Integer.parseInt((String) xPath.evaluate("count(/html/body)", doc));
if( numberofbodies==0) {
// body node missing
}
I came across an unusual problem. I have this XML:
<T durationMs="400">
<foo durationMs="407">
<foo-child durationMs="307" />
</foo>
<bar durationMs="208">
<bar-child durationMs="108" />
</bar>
</T>
I am using XPathExtentions to get an XElement out of this XML:
var xe = XElement.Parse(s);
var foo = xe.XPathSelectElement("/T/foo")
It returns nothing. However if I use:
var foo = xe.XPathSelectElement("./foo")
It gets an elements. So what's the difference between dot and slash in this case?
/ selects from the root node.
So with /T/foo it's trying to match T->T->foo which for sure won't match
. depicts the current node in which case it would be the root node
/foo would work
. selects the current node.
/ Selects from the root node.
// Selects nodes in the document from the current node that match the selection no matter where they are.
XPath Syntax gives you a brief idea of how selections are done
In your case ./foo denotes selection from root node i.e. T