I have a Linq to Xml query that needs to retrieve a value based on the attribute value of a particular node. I'm trying to retrieve a list of items and one of the nodes has an attribute that I can't seem to find a way to get the value.
Here's the XML:
<codelist-items>
<codelist-item>
<code>1</code>
<name>
<narrative>Planned start</narrative>
<narrative xml:lang="fr">Début prévu</narrative>
</name>
<description>
<narrative>
The date on which the activity is planned to start, for example the date of the first planned disbursement or when physical activity starts.
</narrative>
</description>
</codelist-item>
<codelist-item>
<code>2</code>
<name>
<narrative>Actual start</narrative>
<narrative xml:lang="fr">Début réel</narrative>
</name>
<description>
<narrative>
The actual date the activity starts, for example the date of the first disbursement or when physical activity starts.
</narrative>
</description>
</codelist-item>
</codelist-items>
I'm only displaying 2 items to keep it short. And here is my Linq query to try and retrieve the value from "name/narrative" where there is a "xml:lang='fr'" attribute:
XElement xelement = XElement.Load(xmlFile);
var elements = from adt in xelement.Elements("codelist-items").Elements("codelist-item")
select new ActivityDateType
{
Code = (string)adt.Element("code"),
NameEng = (string)adt.Element("name").Element("narrative"),
NameFra = (string)adt.Element("name").Element("narrative[#xml:lang='fr']"),
Description = (string)adt.Element("description")
};
return elements;
Anyone know how to get the value for NameFra?
Thanks
You can either use LINQ FirstOrDefault() with predicate that filters the element by its attribute value :
NameFra = (string)adt.Element("name")
.Elements("narrative")
.FirstOrDefault(o => (string)o.Attribute(XNamespace.Xml+"lang") == "fr"),
Or use XPathSelectElement() extension to execute your XPath expression which already contains attribute filtering logic :
NameFra = (string)adt.Element("name")
.XPathSelectElement("narrative[#xml:lang='fr']", nsmgr),
The latter can be simplified further to the following :
NameFra = (string)adt.XPathSelectElement("name/narrative[#xml:lang='fr']", nsmgr),
nsmgr assumed has previously been declared as follow :
var nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr was needed because the XPath contains prefix xml (XPathSelectElement() complained when I use the overload which accepts just XPath string argument without namespace manager).
Related
I cant get my Xpath query to work though on paper it should be right. I even tried to get a single node without the attribute and could not even get this ...
What am I doing wrong ?
var trxXml = new XmlDocument();
trxXml.Load(InputTrxFile);
XmlElement root = trxXml.DocumentElement;
var unitTestResult = trxXml.GetElementsByTagName("UnitTestResult");
foreach (XmlElement runinfo in unitTestResult)
{
// Find failed tests, works fine then...
string TestName = runinfo.GetAttribute("testName"); // works fine
// Want to find equivalent TestDefinitions/UnitTest
/* Tried
TestRun/TestDefinitions/UnitTest[#name='thetest']
/TestRun/TestDefinitions/UnitTest[#name='thetest']
TestDefinitions/UnitTest[#name='thetest']
/TestDefinitions/UnitTest[#name='thetest']
UnitTest[#name='thetest']
variations with no attribute test JUST to get a node
Example http://www.csharp-examples.net/xml-nodes-by-attribute-value/
*/
var xpath = string.Format(#"/TestRun/TestDefinitions/UnitTest[#name='{0}']", TestName);
XmlNode node = trxXml.SelectSingleNode(xpath);
XmlNode node2 = root.SelectSingleNode(xpath);
// These all return null
Any query that begins with a / represents an absolute path, i.e. it is from the root of the document. It seems your UnitTestResult element (at the very least) encloses your TestRun elements.
To have your query take into account the current context, you need to reference the current context. This can be selected using ..
Secondly, your XML elements all have a namespace, and this needs to make up part of your query. A prefix needs to be added to a namespace manager, used in your query and the manager passed into the query method.
So, taking these together, define the prefix:
var manager = new XmlNamespaceManager(new NameTable());
manager.AddNamespace("t", "http://microsoft.com/schemas/VisualStudio/TeamTest/2010");
Change your query:
./t:TestRun/t:TestDefinitions/t:UnitTest[#name='{0}']
And pass the namespace manager to the method:
trxXml.SelectSingleNode(xpath, manager);
XPath and XmlDocument are pain, though. This would all be a lot more straightforward in LINQ to XML:
var doc = XDocument.Load(InputTrxFile);
var unitTestQuery =
from result in doc.Descendants(ns + "UnitTestResult")
let name = (string)result.Attribute("testName")
from unitTest in result.Descendants(ns + "UnitTest")
where (string)unitTest.Attribute("name") == name
select unitTest;
var unitTest = unitTestQuery.Single();
I am trying to select values from elements using the XPathNodeIterator. But I am not able to get the expected value from my XML file.
My XML file
<?xml version="1.0" encoding="utf-8"?>
<ns0:Entity xmlns:ns0="http://schemas.mycompany.com/1.0">
<ns0:Data>
<Address_Id xmlns:ns0="http://schemas.mycompany.com/1.0">7</Address_Id>
<Customer_Id xmlns:ns0="http://schemas.mycompany.com/1.0">67</Customer_Id>
<CustomerName xmlns:ns0="http://schemas.mycompany.com/1.0">My Customer 1</CustomerName>
</ns0:Data>
</ns0:Entity>
My methods to get the values
I have created two methods which both doesn't return the values which I would like to have.
private static string GetValue_Attempt1(XPathDocument xPathDocument, string xpathExpression)
{
var xpathNavigator = xPathDocument.CreateNavigator();
var xpathNodeIterator = xpathNavigator.Select(xpathExpression);
xpathNodeIterator.MoveNext();
return xpathNodeIterator.Current.Value;
}
private static string GetValue_Attempt2(XPathDocument xPathDocument, string xpathExpression)
{
var xpathNavigator = xPathDocument.CreateNavigator();
var xpathNodeIterator = xpathNavigator.Select(xpathExpression);
xpathNodeIterator.MoveNext();
var nodesNavigator = xpathNodeIterator.Current;
var nodesText = nodesNavigator.SelectDescendants(XPathNodeType.Text, false);
nodesText.MoveNext();
return nodesText.Current.Value;
}
My XPath
Get Address_Id
XPath: //Data/Address_Id/text()
Attempt1 returns: 767My Customer 1. Why are all values concatenated ???
Attempt2 returns: 7. Looks OK
Get Customer_Id
XPath: //Data/Customer_Id/text()
Attempt1 returns: 767My Customer 1. Why are all values concatenated ???
Attempt2 returns: 7. Why do I get the Address_Id??
Questions
Probably I am using an incorrect XPath. But also I don't understand the results. In order to understand whats going on. I would like to know:
Is there a better way (method) to get the value of the elements?
What is the XPath string which I need to use?
Why does method GetValue_Attempt1 return a concatinated string?
Why does method GetValue_Attempt2 return the value of only the first element?
Your XPath returns no matches as you're not taking the namespaces into account.
Your 'Attempt 1' calls MoveNext() once, so Value returns all the concatenated text in Data, and in 'Attempt 2' you call MoveNext() twice which positions you inside the Address_Id element.
In addition, Select, even it the XPath was valid, doesn't actually move the cursor.
If you wanted to get the value, you'd do something like this - note I'm including the namespace in the XPath and using the result of SelectSingleNode:
var nsm = new XmlNamespaceManager(new NameTable());
nsm.AddNamespace("ns0", "http://schemas.mycompany.com/1.0");
var value = navigator.SelectSingleNode("//ns0:Data/Address_Id", nsm).Value;
However, do you have any reason to be using XPath and XPathDocument here? Using the much more expressive and (more) statically typed LINQ to XML would be far preferable, I'd think:
XNamespace ns = "http://schemas.mycompany.com/1.0";
var doc = XDocument.Parse(xml);
var addressId = (int)doc.Descendants(ns + "Data")
.Elements("Address_Id")
.Single();
var customerId = (int)doc.Descendants(ns + "Data")
.Elements("Customer_Id")
.Single();
I have XML where I remove tags using this code:
XElement xmlElement = XElement.Parse(xml);
xmlElement.XPathSelectElement("//"+ tagToRemove).Remove();
But now I have another problem. On the method above I remove a specific tag but now I want to remove the tag below. In the XML there is a lot of tag called param so I can't just remove that.
What I was thinking about was to remove the tag where the subtag
name = ERP_OUTPUT_400_20_DOCUMENTATIONLANGUAGE_field
<param type=\"read-only\" committed=\"no\" changed=\"no\" visible=\"yes\" no=\"1\">
<name>ERP_OUTPUT_400_20_DOCUMENTATIONLANGUAGE_field</name>
<desc>ERP OUTPUT 400 20 DOCUMENTATIONLANGUAGE</desc>
<val>SV;</val>
<val-desc>SV;</val-desc>
<domain name=\"function\" />
<aux>
<prop name=\"hidden\">no</prop>
</aux>
</param>
How can I do that?
To select element having child element <name> value equals specific name, you can use the following XPath query :
//element_name[name='specific_name']
Example based on your existing code :
var name = "ERP_OUTPUT_400_20_DOCUMENTATIONLANGUAGE_field";
var xpath = String.Format("//{0}[name='{1}']", tagToRemove, name);
xmlElement.XPathSelectElement(xpath).Remove();
This question already has answers here:
XPath doesn't work as desired in C#
(2 answers)
Closed 10 years ago.
I'm trying to use XmlDocument() to read an XML file node by node an output each element.
After much trial-and-error, I determined that having an xmlns attribute on my node causes no nodes to be returned from SelectNodes() call. Not sure why.
Since I can't change the output format and don't have access to the actual namespace, what are my options to get around this issue?
In addition, I have some elements that have subnodes. How do I access these while looping through the XML file? I.E., I need to decrypt the CipherValue elements, but not sure how to access this node anyway?
Sample below:
doc.Load(#"test.xml");
XmlNodeList logEntryNodeList = doc.SelectNodes("/Logs/LogEntry");
Console.WriteLine("Nodes = {0}", logEntryNodeList.Count);
foreach (XmlNode xn in logEntryNodeList)
{
string dateTime = xn["DateTime"].InnerText;
string sequence = xn["Sequence"].InnerText;
string appId = xn["AppID"].InnerText;
Console.WriteLine("{0} {1} {2}", dateTime, sequence, appId);
}
Sample XML looks like this:
<Logs>
<LogEntry Version="1.5" PackageVersion="10.10.0.10" xmlns="http://private.com">
<DateTime>2013-02-04T14:05:42.912349-06:00</DateTime>
<Sequence>5058</Sequence>
<AppID>TEST123</AppID>
<StatusDesc>
<EncryptedData>
<CipherData xmlns="http://www.w3.org/2001/04/xmlenc#">
<CipherValue>---ENCRYPTED DATA BASE64---</CipherValue>
</CipherData>
</EncryptedData>
</StatusDesc>
<Severity>Detail</Severity>
</LogEntry>
<LogEntry Version="1.5" PackageVersion="10.10.0.10" xmlns="http://private.com">
<DateTime>2013-02-04T14:05:42.912350-06:00</DateTime>
<Sequence>5059</Sequence>
<AppID>TEST123</AppID>
<StatusDesc>
<EncryptedData>
<CipherData xmlns="http://www.w3.org/2001/04/xmlenc#">
<CipherValue>---ENCRYPTED DATA BASE64---</CipherValue>
</CipherData>
</EncryptedData>
</StatusDesc>
<Severity>Detail</Severity>
</LogEntry>
</Logs>
After much trial-and-error, I determined that having an xmlns attribute on my node causes no nodes to be returned from SelectNodes() call. Not sure why.
The xmlns attribute effectively changes the default namespace within the element, including that element itself. So the namespace of your LogEntry element is "http://private.com". You'd need to include this appropriately in your XPath query, probably via an XmlNamespaceManager.
(If you can use LINQ to XML instead, it makes it much easier to work with namespaces.)
You can use Linq to Xml (as Jon stated). Here is code which parses your xml file with respect to namespaces. Result is a strongly typed sequence of anonymous objects (i.e. Date property has type of DateTime, Sequence is integer, and AppID is a string):
XDocument xdoc = XDocument.Load("test.xml");
XNamespace ns = "http://private.com";
var entries = xdoc.Descendants("Logs")
.Elements(ns + "LogEntry")
.Select(e => new {
Date = (DateTime)e.Element(ns + "DateTime"),
Sequence = (int)e.Element(ns + "Sequence"),
AppID = (string)e.Element(ns + "AppID")
}).ToList();
Console.WriteLine("Entries count = {0}", entries.Count);
foreach (var entry in entries)
{
Console.WriteLine("{0}\t{1} {2}",
entry.Date, entry.Sequence, entry.AppID);
}
I want to return the latitude node (for example) from the following XML string (from Yahoo geocoding API.)
<ResultSet version="1.0">
<Error>0</Error>
<ErrorMessage>No error</ErrorMessage>
<Locale>us_US</Locale>
<Quality>60</Quality>
<Found>1</Found>
<Result>
<quality>87</quality>
<latitude>37.68746446</latitude>
<longitude>-79.6469878</longitude>
<offsetlat>30.895931</offsetlat>
<offsetlon>-80.281192</offsetlon>
<radius>500</radius>
<name></name>
<line1>123 Main Street</line1>
<line2>Greenville, SC 29687</line2>
<line3></line3>
<line4>United States</line4>
<house>123</house>
<street>Main Street</street>
<xstreet></xstreet>
<unittype></unittype>
<unit></unit>
<postal>29687</postal>
<neighborhood></neighborhood>
<city>Greenville</city>
<county>Greenville County</county>
<state>South Carolina</state>
<country>United States</country>
<countrycode>US</countrycode>
<statecode>SC</statecode>
<countycode></countycode>
<uzip>29687</uzip>
<hash>asdfsdfas</hash>
<woeid>127757446454</woeid>
<woetype>11</woetype>
</Result>
</ResultSet>
I already have this XML successfully loaded into an XElement instance but I cannot seem to be able to find the way to load the latitude node (for example) into a string variable. If there is no node or the node is empty then I would like to get a Null or Nullstring. If there is more than one (there won't be but just in case) then return the first instance.
I thought this would be easy but I can't get it to work. All of the Linq queries I have tried are returning null.
While I am at it if you could explain it with enough detail so that I can also get the Error node. I only mention it because it is at a different level.
Thanks.
Seth
To get latitude's value:
var latitudeElement = resultXML.Descendants("latitude").FirstOrDefault();
string latitude = latitudeElement == null ? String.Empty : latitudeElement.Value;
And you could get the Error element with the following:
var errorElement = resultXML.Descendants("Error").First();
I'm using resultXML as the reference to the parsed XML.
Make sure you're using the System.Xml.XPath namespace, and try:
var doc = XDocument.Parse(<your xml here>);
var el = doc.XPathSelectElement("ResultSet/Result/latitude");
el should contain an XElement class or null if the node wasn't found.
See the MSDN docs for XPath 1.0 for more info on how to use it.