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']");
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.
I have large xml. The xml's nodes have attribute id with valus like this: "1_32434", "2_45656". With this code:
var node = myXml.XPathSelectElement(string.Format("//*[starts-with(#id,\"{0}_\"))", someValue));
I am trying to find all nodes that have attribute id that start with "someValue_", but I get error that there is an invalid token.
There is an mismatch between opened and closed brackets, try to replace the last ')' by ']'
string.Format("//*[starts-with(#id,\"{0}_\")]", someValue)
I am not proud of this xpath. But it should give you all the nodes irregardless of starting id. If you only need for one id at a time. you should just add an ending bracket to your current xpath.
"//*[number(substring-before(#id,"_"))<10 and number(substring-after(#id,"_"))]"
some example XML would be greatly appreciated.
I have been struggling to resolve this problem I am having over the past couple of days. Say, I want to get all the text() from a HTML document, however I only want to know of and retrieve of the XPath of the node that contains text data. Example:
foreach (var textNode in node.SelectNodes(".//text()"))
//do stuff here
However, when it comes to retrieving the XPath of the textNode using textNode.XPath, I get the full XPath including the #text node:
/html[1]/body[1]/div[1]/a[1]/#text
Yet I only want the containing node of the text, for example:
/html[1]/body[1]/div[1]/a[1]
Could anyone point me toward a better XPath solution to retrieve all nodes that contains text but only retrieve the XPath up until the containing node?
Instead of:
.//text()
use:
.//*[normalize-space(text())]
This selects all "leaf-elements"-descendants of the context (current) node that have at least one non-whitespace-only text node child.
Why don't you
string[] elements = getXPath(textNode).Split(new char[1] { '/' });
return String.Join("/", elements, 0, elements.Length-2);
I would like to search an HTML file for a certain string and then extract the tags. Given:
<div_outer><div_inner>Happy birthday<div><div>
I would like to search the HTML for "Happy birthday" then have a function return some sort of tag structure: this is the innermost tag, this is the tag outside that one, etc. So, <div_inner></div> then <div_outer></div>.
Any ideas? I am thinking HTMLAgilityPack but I haven't been able to figure out how to do it.
Thanks as always, guys.
The HAP is a good place indeed for this.
You can use the OuterHtml and Parent properties of a Node to get the enclosing elements and markup.
You could use xpath for this. I use //*[text()='Happy birthday'][1]/ancestor-or-self::* expression which finds a first (for simplicity) node which text content is Happy birthday, and then returns all the ancestors (parent, grandparent, etc.) of this node and the node itself:
var doc = new HtmlDocument();
doc.LoadHtml("<div_outer><div_inner>Happy birthday<div><div>");
var ancestors = doc.DocumentNode
.SelectNodes("//*[text()='Happy birthday'][1]/ancestor-or-self::*")
.Reverse()
.ToList();
It seems that the order of the nodes returned is the order the nodes found in the document, so I used Enumerable.Reverse method to reverse it.
This will return 2 nodes: div_inner and div_outer.
I am trying to select all nodes with attribute equal to something, I got the error in title.
My Xpath string looks like //#[id=****], anyone know what's wrong?
Your XPath expression probably should be:
//*[#id='something']
Which means match all elements whose id attributes are equal to something, anywhere in the document.
EDIT: If you want the id attribute nodes themselves and not their parent elements, you can use:
//*[#id='something']/#id
Or even better, as #Dimitre Novatchev suggested:
//#id[. = 'something']
I am trying to select all nodes with
attribute equal to something, I got
the error in title.
My Xpath string looks like
//#[id=****], anyone know what's
wrong?
A lot of issues with this expression:
.1. //#[some-condition] The predicate can only be applied to selected nodes, but //# doesn't select any node. # is an abbreviation for attribute:: and this is an unfinished node-test. It is missing the node-type or node-name here.
What would be correct is: //#*[some-condition] or //#attrName[some-condition]
.2. id=**** is syntactically invalid, unless ** is a valid XPath expression itself. My guess is that you want to get all attributes with value equal to some known, literal value. In any such case the syntax to use is id='someLiteral -- do note the single quotes (they can also be double quotes) surrounding the literal value.
Solution:
//*[#id='something']
This selects all elements in the XML document that have attribute id with value 'something'.
//#id[. = 'something']
This selects all attributes named id in the XML document, whose value is 'something'.
//#*[. = 'something']
This selects all attributes in the XML document (regardless of their name), whose value is 'something'.