XPath for attribute value that ends with a string? - c#

I'm having trouble selecting an element with an attribute that ends with a certain value.
XML looks like
<root>
<object name="1_2"><attribute name="show" value="example"></object>
<object name="1_1"><attribute name="show" value="example"></object>
<object name="2_1"><attribute name="show" value="example"></object>
</root>
So I need to extract all values from attributes in objects ends with _1, how can I do that?
I did this code
XmlNodeList childnodes = xRoot.SelectNodes("//Object[#Name='1_1']");
foreach (XmlNode n in childnodes)
Console.WriteLine(n.SelectSingleNode("Attribute[#Name='show']").OuterXml);
but I can't find how to search for the part of attributes name and how to get the exact value of target parameter.

First note that XML and XPath are case sensitive, so Object is different than object, and Name is different than name.
XPath 2.0
This XPath 2.0 expression,
//object[ends-with(#name,'_1')]
will select all object elements whose name attribute value ends with _1.
XPath 1.0
XPath 1.0 lacks the ends-with() function but can achieve the same result with a bit more work:
ends-with($s, $e) ≡ (substring($s, string-length($s) - string-length($e) +1) = $e)
Applied to your case where $s is #name and $e is '_1', the above simplifies to this expression:
//object[substring(#name, string-length(#name) - 1) = '_1']

If C# supports XPath 2.0 you should be able to use:
XmlNodeList childnodes = xRoot.SelectNodes("//object[ends-with(#name, '_1')]");
if not then a slightly longer version should work:
XmlNodeList childnodes = xRoot.SelectNodes("//object[substring(#name, string-length(#name) - 1) = '_1']");
Also your xml is not valid as you need to close the attribute elements:
<root>
<object name="1_2"><attribute name="show" value="example"/></object>
<object name="1_1"><attribute name="show" value="example"/></object>
<object name="2_1"><attribute name="show" value="example"/></object>
</root>

Related

property .count returns all xml nodes in xpath

I am trying to filter out one xml document using xpath and count number of nodes which contain given string.
here is my code
string ElementValue = Action
nodeList = root.SelectNodes(#"/moviedb/movie[contains(genres, "+ ElementValue +")]");
return nodeList.Count;
and part of XML
<moviedb>
<movie>
<imdbid>tt2226321</imdbid>
<genres>Thriller</genres>
<languages>English</languages>
<country>USA</country>
<rating>8</rating>
<runtime>155</runtime>
<title>The Dark Knight</title>
<year>2014</year>
</movie>
<movie>
<imdbid>tt1959490</imdbid>
<genres>Action,Adventure,Drama</genres>
<languages>English</languages>
<country>USA</country>
<rating>6.5</rating>
<runtime>138</runtime>
<title>Noah</title>
<year>2014</year>
</movie>
</moviedb>
I guess that it is something with xpath contains expression. While i was using full match syntax, everything was working correctly.
thanks again
Use single-quotes in your XPath whenever possible to make your life easier :
nodeList = root.SelectNodes(#"/moviedb/movie[contains(genres, '"+ ElementValue +"')]");
Or you can use String.Format() method to concatenate values from variables into single string result in a more intuitive way (especially when you have many variables to be "injected" to the string) :
var xpath = String.Format("/moviedb/movie[contains(genres, '{0}')]", ElementValue);
nodeList = root.SelectNodes(xpath);

Locating a value in XML

I have an xml file loaded into an XDocument that I need to extract a value from, and I'm not sure of the best way to do it. Most of the things I'm coming up with seem to be overkill or don't make good use of xml rules. I have the following snippet of xml:
<entry>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.6.2.12" />
<code code="121070" codeSystem="1.2.840.10008.2.16.4" codeSystemName="DCM" displayName="Findings">
</code>
<value xsi:type="ED">
<reference value="#121071">
</reference>
</value>
</observation>
</entry>
There can be any number of <entry> nodes, and they will all follow a similar pattern. The value under the root attribute on the templateId element contains a known UID that identifies this entry as the one I want. I need to get the reference value.
My thought is to find the correct templateID node, back out to the observation node, find <valuexsi:type="ED"> and then get the reference value. This seems overly complex, and I am wondering if there is another way to do this?
EDIT
The xml I receive can sometimes have xml nested under the same node name. In other words, <observation> may be located under another node named <observation>.
You have problems, because your document uses Namespaces, and your query is missing them.
First of all, you have to find xsi namespace declaration somewhere in your XML (probably in the most top element).
It will look like that:
xmlns:xsi="http://test.namespace"
The, take the namespace Uri and create XNamespace instance according to it's value:
var xsi = XNamespace.Get("http://test.namespace");
And use that xsi variable within your query:
var query = from o in xdoc.Root.Element("entries").Elements("entry").Elements("observation")
let tId = o.Element("templateId")
where tId != null && (string)tId.Attribute("root") == "2.16.840.1.113883.10.20.6.2.12"
let v = o.Element("value")
where v != null && (string)v.Attribute(xsi + "type") != null
let r = v.Element("reference")
where r != null
select (string)r.Attribute("value");
var result = query.FirstOrDefault();
I have tested it for following XML structure:
<root xmlns:xsi="http://test.namespace">
<entries>
<entry>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.6.2.12" />
<code code="121070" codeSystem="1.2.840.10008.2.16.4" codeSystemName="DCM" displayName="Findings">
</code>
<value xsi:type="ED">
<reference value="#121071">
</reference>
</value>
</observation>
</entry>
</entries>
</root>
The query returns #121071 for it.
For your input XML you will probably have to change first line of query:
from o in xdoc.Root.Element("entries").Elements("entry").Elements("observation")
to match <observation> elements from your XML structure.
Would something along the lines of the following help?
XDocument xdoc = GetYourDocumentHere();
var obsvlookfor =
xdoc.Root.Descendants("observation")
.SingleOrDefault(el =>
el.Element("templateId")
.Attribute("root").Value == "root value to look for");
if (obsvlookfor != null)
{
var reference = obsvlookfor
.Element("value")
.Element("reference").Attribute("value").Value;
}
My thought is as follows:
Pull out all the observation elements in the document
Find the only one (or null) where the observation's templateId element has a root attribute you're looking for
If you find that observation element, pull out the value attribute against the reference element which is under the value element.
You might have to include the Namespace in your LINQ. To retrieve that you would do something like this:
XNamespace ns = xdoc.Root.GetDefaultNamespace();
Then in your linq:
var obsvlookfor = xdoc.Root.Descendants(ns + "observation")
I know I had some issues retrieving data once without this. Not saying its the issue just something to keep in mind particularly if your XML file is very in depth.

XDocument.XPathSelectElements : I can't seem get the xpath syntax right

I'm trying to work out why my xpath won't select the nodes I specificy
My xpath expression is //DefaultValue, so I expect all elements of name DefaultValue to be selected
My test file (cut down) is :
<?xml version="1.0" encoding="utf-8"?>
<SharedDataSet xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner" xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/01/shareddatasetdefinition">
<Description />
<DataSet Name="ddd">
<Query>
<DataSourceReference>xxx</DataSourceReference>
<DataSetParameters>
<DataSetParameter Name="p1">
<DefaultValue>baaaah</DefaultValue> <!-- this node should be selected eh? -->
</DataSetParameter>
<DataSetParameter Name="p2">
<DefaultValue>fooo</DefaultValue> <!-- this node should be selected too eh? -->
</DataSetParameter>
</DataSetParameters>
</Query>
</SharedDataSet>
Code is :
XmlNamespaceManager xn = new XmlNamespaceManager(new NameTable());
xn.AddNamespace("ns", "http://schemas.microsoft.com/sqlserver/reporting/2010/01/shareddatasetdefinition");
xn.AddNamespace("rd", "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner");
xn.AddNamespace("cl", "http://schemas.microsoft.com/sqlserver/reporting/2010/01/componentdefinition");
XDocument document = XDocument.Parse(reportBuffer, LoadOptions.PreserveWhitespace);
IEnumerable<XElement> elements = document.XPathSelectElements(xpath, xn);
at this point elements.Count() equals 0
Can anyone see what foolishness I present?
I can't seem to make the xml demons happy... :-(
The <DefaultValue> element is actually bound to the namespace http://schemas.microsoft.com/sqlserver/reporting/2010/01/shareddatasetdefinition.
The <SharedDataSet> document element has it declared without a prefix, so it is easy to miss. Since <DefaultValue> is a descendant, it inherits the namespace.
If you want to select the <DefaultValue> element you need to adjust your XPath:
//ns:DefaultValue

looking up a value in xml C#

given this xml (just a part..)
<?xml version="1.0" encoding="utf-8"?>
<translations>
<key name="BillOfMaterials">
<translation culture="en-GB"><![CDATA[Bill of materials]]>
</translation>
<translation culture="da-DK"><![CDATA[Materiale liste]]>
</translation>
</key>
<key name="TechnicalDetails">
<translation culture="en-GB">
<![CDATA[Technical details
]]>
</translation>
</key>
..
..
...i'm looking for the simplest solution to look up for instance:
so
string thisTranslation = GetTranslation("BillOfMaterials","en-GB"); //value gets to be "Bill of materials"
I have tried the linq way, but it gets rather messy with too many itherations... especially when a simple xpath is enough in xslt... But I can't seem to just do that
Thanks in advance
Edit:
- xml is physical file
- function may not find anything.....should then just return the original key name
/translations/key[#name="BillOfMaterials"]/translation[#culture="en-GB"]
is the xpath that elsewhere is usable..
I would still use LINQ to XML - there's absolutely no need for it to get messy:
XDocument doc = XDocument.Load("test.xml");
string key = "BillOfMaterials";
var element = doc.Root.Elements("key")
.Where(key => key.Attribute("name").Value == key)
.Elements("translation")
.Where(tr => tr.Attribute("culture").Value == "en-GB")
.FirstOrDefault();
string result = (string) element ?? key;
Personally I find that cleaner than using XPath (even though the XPath is undeniably shorter). It separates each section of the query more distinctly, and also if you need any namespace handling, that would be significantly simpler with LINQ to XML than messing around with namespace managers.
You can use XPathSelectElement extension method set on XElement with your XPath selector:
Load your XML into an XDocument:
var doc = XDocument.Load("path\to\file");
and then search it with XPath:
var translation = (string)doc.XPathSelectElement(string.format("/translations/key[#name=\"{0}\"]/translation[#culture=\"{1}\"]", key, culture));
if(string.IsNullOrEmpty(translation))
translation = key;
return translation;
Get you Xml document in a XmlDocument type. You can do this like:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("<xml>"); // Can be a XML in a string, or filename, etc.
Then you can use XPath with the SelectNodes() and SelectNode() methods.
Like:
XmlNodeList xmlNodes = xmlDoc.SelectNodes("/translations/key[#name=\"BillOfMaterials\"]/translation[#culture=\"en-GB\"]");

XmlNode.SelectSingleNode returns element outside current?

my problem is like this. Let's say i have xml like this
<root>
<child Name = "child1">
<element1>Value1</element1>
<element2>Value2</element2>
</child>
<child Name = "child2">
<element1>Value1</element1>
<element2>Value2</element2>
<element3>Value3</element3>
</child>
</root>
I have a method that gets as parameter XmlNode "node". Lets say "node" has value "child1" Then i try like this:
node.SelectSingleNode( "//element3" );
The problem is this code returns element3 from "child2". What i want is if there is no child "element3" of "node" to return null so i add it by hand.
Best Regards,
Iordand
The XPath expression you have isn't what you want.
Replace it with this:
node.SelectSingleNode( "element3" );
And you'll get the result you're looking for.
The following work perfect when i want to run xpath on the specified node.
XmlNodeList nodes = xmlDoc.SelectNodes(".//Child");
The "//" is a global look up.
What you'll need to do is get a list of all children
XmlNodeList nodes = xmlDoc.SelectNodes("//Child");
loop through that list and do a
XmlNode node = nodes.SelectSingleNode("element3");
This will return null if it's not there, and will step through every child looking.
the problem here is the XPath expression you are using, try it without the '//'. Like that:
node.SelectSingleNode( "element3" );
Read more here .

Categories

Resources