XPath String that grabs an element with a specific id value - c#

I am trying to create an XPath query/string that grabs a specific element from a XML document. I am attempting to grab the element with the id=38 but my code always returns nothing for some reason.
If you look at my code & the organisation of my XML file can you tell me what XPath I need to grab the element with the id=38?
My code is:
XmlDocument xdoc = new XmlDocument();
xdoc.Load(getProductURL());
XmlNode node = xdoc.DocumentElement.SelectSingleNode("id('38')");
// node always is null for some reason?
The way the xml is organised is like so:
<courseg>
<group isempty="False" isbranch="true" id="1" name="abc">
<group isempty="False" isbranch="true" id="38" name="def"></group>
</group>
</courseg>

The XPath you need is
//*[#id='38']
Here is the example with XDocument:
XDocument xdoc = XDocument.Parse(#"
<courseg>
<group isempty=""False"" isbranch=""true"" id=""1"" name=""abc"">
<group isempty=""False"" isbranch=""true"" id=""38"" name=""def""></group>
</group>
</courseg>");
XElement node = xdoc.Root.XPathSelectElement("//*[#id='38']");
Console.WriteLine(node);

The function id('P38') would select an element with an ID value of P38. But this doesn't just mean "an attribute named 'id'". It means an attribute declared in the DTD or schema as being of type ID. You haven't shown a DTD or schema, and I suspect you don't have one. If you did, and if it declared the id attribute as being of type ID, then your document would be invalid, because an ID value cannot be all-numeric (for legacy SGML reasons, it has to take the form of a name).
In practice the id() function is probably best avoided unless you have severe performance requirements. It's too fragile - it only works when you are validating the source document against a schema or DTD. In XSLT, use key() instead. Alternatively, many processors now recognize the attribute name xml:id as a 'self declaring' ID value without reference to a schema or DTD: use that if your processor supports it.

Use this XPath query:
//*[#id = 38]
It selects every node with id attribute equals to 38. If you have to be more specific, i.e. select group with id attribute equals to 38, use this one:
//group[#id = 38]

When you mention
xdoc.DocumentElement.SelectSingleNode("id('38')"
you are asking xmldocument to search for a child node inside root node whose name is 'id'. But ideally 'id' is an attribute and not a xmlnode.
So you have to use //group[#id = '38'] to get all child node having name 'group' and attribute 'id' with a value of 38

Related

xpath to element with attribute and grandparent attribute

The relevant chunk of my xml is this:
[... lots of xml up here, including ancestor elements...]
<category id="MyCatID" ... >
<option ... >
<property id="MyPropID">The magic value I need</property>
[... lots of xml down here...]
My objective: Find the value of a <property> with id of MyPropID whose parent is <option> and whose grandparent (through <option>) is <category> containing the id of MyCatID.
Here is my attempted xpath:
//property[#id='MyPropID']/ancestor::category[#id='MyCatID']
In my .NET 4.7.2 that xpath query brings back all the xml inside the <category> element, which misses the mark. My hoped-for result is that it would bring back the value The magic value I need.
How is it done?
Why not reverse it, get the category with the ID you want and then navigate to the property with the ID you want? I'm not really sure how your XML looks, here's my pseudo attempt...
//category[#id='MyCatID']/option/property[#id='MyPropID']
And if for some reason you would really want to do it bottom-up way:
//property[#id='MyPropID']/../../../property[#id='MyPropID']
or
//property[#id='MyPropID']/ancestor::node()[3]/property[#id='MyPropID']

xpath to find node by element and attribute containing a child element with a certain id

In c# I'm trying to find an xpath expression that will get me the value of a <property> element with id of ROBEGIN whose parent is <option> and that parent contains a child <property> with id of CEProductID and value of 5832198a-7cec-ea11-a817-000d3a191efa. The expected value I want to get is 777. Here is an xml fragment from a large xml file:
...
<option id="Whatever">
<property id="CEProductID">5832198a-7cec-ea11-a817-000d3a191efa</property>
...
<property id="ROBEGIN">777</property>
</option>
...
Important: For the <option> to be a correct match it must contain BOTH the child elements shown above, with correct id attribute values and correct element value of CEProductID. If it has one or the other matching <property> but not both, it should be ignored.
I have tried the following (and other permutations of it) without success:
xmlNode.SelectNodes($"//property[#id='CEProductID']='5832198a-7cec-ea11-a817-000d3a191efa'");
Admittedly, the above line of c# code (even if it worked) would have only gotten me the CEProductID <property> element, with which I could go programmatically up to the parent, and back down into the properties to see if <ROBEGIN> exists, and if it does, grab the value. But that seems super inefficient and I think xpath has more power than that.
How is it done?
This should get you exactly what you need:
//property [#id = 'ROBEGIN' and
parent::option [property
[#id = 'CEProductID' and text() = '5832198a-7cec-ea11-a817-000d3a191efa' ]]
]/text()
Let's break it down:
//property descend to any node named property
[#id='ROBEGIN' which has this matching attribute id
and parent::option and has a parent node named option
which in turn has [property child node
which in turn has [#id='CEProductID' attribute
and that node's inner text matches text()='5832198a-7cec-ea11-a817-000d3a191efa'
]]]/text() going back to the original node, take the inner text
Result:
777
If I understood correctly, you want to match option tag with two properties as stated in your question and then go down that second ROBEGIN and extract the inner html.
//option[property[#id='CEProductID'] and property[#id='ROBEGIN']]/property[#id='ROBEGIN']/text()
# 777

Inserting XML nodes and inner nodes to an existing XML document in C#

Currently I have a working C# program that works as follows:
Accept .xls template with values (xls is manually created by user)
Save the values (matching fields) to the database
Convert and write .xls to XML. Please see below sample output:
Existing XML Structure
Now, what I want to do is:
Read the existing xml (the created xml)
Insert another set of nodes and subnodes (ReleaseLine and sub nodes). It must accept multiple ReleaseLine.
Save/create the new xml with appended nodes. Please see below output:
This is what I'm looking for:
My existing C# program is simple but the XML nodes and hierarchy is bloody deep. I just created the C# code using new XElement method and passing values for each nodes. Then I simply use xmlDocument.Save() method to write the xml.
[Existing XML Program][3]
To add nodes or append content in existing xml-data I´d use Linq to XML.
XElement xml = XElement.Load("file.xml");
xml.Add( new XElement("uberNode",
new XElement("childNode", content),
new XElement("anotherChildNode", content)));
xml.Save("file.xml");
Here are some other related solutions.
Add to specific node (with example):
Following exisiting XML-data:
`<Names>
<Name>
<prename>John</prename>
<lastname>Snow</lastname>
</Name>
<Name>
<prename>Harry</prename>
<lastname>Harry</lastname>
</Name>
</Names>`
Now I want to add an "age"-tag before the first "prename"-tag and a "family"-tag after the first "lastname"-tag.
XElement xml = XElement.Load("file.xml");
var childrens = xml.DescendantsAndSelf().ToArray();
var first_prename = childrens[2];
var first_lastname = childrens[3];
Console.WriteLine(childrens[0]); //prints out the whole content
first_prename.AddBeforeSelf(new XElement("age", 22));
first_lastname.AddAfterSelf(new XElement("family", new XElement("mother", "paula"), new XElement("father", "paul")));
xml.Save("file.xml");
Outcome:
`<Names>
<Name>
<age>22</age>
<prename>John</prename>
<lastname>Snow</lastname>
<family>
<mother>paula</mother>
<father>paul</father>
</family>
</Name>
<Name>
<prename>Harry</prename>
<lastname>Harry</lastname>
</Name>
</Names>`
I was facing the problem and Linq gave me the easiest way to accomplish that!
There are also other similar way e.g. here. But I tried a bit more and DescendantsAndSelf() made it easier for me to go through.
I found an answer to my question, here is the link http://www.xmlplease.com/add-xml-linq
Using XPathSelectElement method, I was able to find the right node and appended new block of XElement.

Using xPath in C# to get value of node attribute

If I have the following xml document:
<xml>
<data>
<dataset name="X"></dataset>
</data>
</xml>
How can I use Xpath in c# to retrieve the value of the name attribute (i.e. X)
How can I use Xpath in c# to retrieve the value of the name attribute
(i.e. X)
This XPath expression:
/xml/data/dataset/#name
selects the wanted attribute -- all atributes named name that belong to a dataset element that is a child of a data element that is a child of the top element of the XML document.
However, you want to get the value of the attribute -- not the node itself.
This XPath expression:
string(/xml/data/dataset/#name)
when evaluated, produces the wanted string value.
In C# use the XPathNavigator.Evaluate() method to evaluate the expression above.
Use this XPath:
xml/data/dataset/#name
use this XPath expression:
xml/data/dataset
this will retrieve the dataset node. after that you can use C# tools to retrieve the attribute name from the node.

Selecting multiple XML node attributes with LINQ.. why is everything after the first attribute null?

I have the following sample XML:
<?xml version="1.0" encoding="utf-8" ?>
<queryableData>
<table displayName="Shipments" dbName="Quotes">
<foreignKey column="CustomerId" references="CustomerRegistration"/>
<foreignKey column="QuoteStatusId" references="QuoteStatus"/>
<fields>
<field displayName="Quote Charge" dbColumn="QuoteCharge" type="Number"/>
<field displayName="Total Weight" dbColumn="TotalWeight" type="Number"/>
</fields>
</table>
</queryableData>
and I'm trying to create an anonymous object with the contents of the field node. Here is my LINQ code:
XElement root = XElement.Load("queryable.xml");
var elem = from el in root.Elements("table")
select new
{
DisplayName = el.Attribute("displayName").Value,
Column = el.Attribute("dbColumn").Value,
DataType = el.Attribute("type").Value
};
If I only specify the "DisplayName" attribute, it works fine. The other two are always null, and therefore trying to read the Value property is throwing a NullReferenceException.
What's the correct way to grab all of the three attributes that I need from the element? I think I am on the right track but missing something with the query (it seems to me that el isn't the entire element)
EDIT: Nevermind, I'm an idiot. I'm looking at one element and querying for the other!
In your sample document the sole table element has two attributes named displayName and dbName, I don't see any dbColumn or type attribute on the table element. If you want to access the field elements then use root.Descendants("field") instead of root.Element("table").

Categories