I want to select nodes of a XML document using XPath. But it does not work when the XML document contains xml-namespaces.
How can I search for nodes with XPath considering the namespaces?
This is my XML Document (simplified):
<ComponentSettings xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Company.Product.Components.Model">
<Created xmlns="http://schemas.datacontract.org/2004/07/Company.Configuration">2016-12-14T10:29:28.5614696+01:00</Created>
<LastLoaded i:nil="true" xmlns="http://schemas.datacontract.org/2004/07/Company.Configuration" />
<LastSaved xmlns="http://schemas.datacontract.org/2004/07/Company.Configuration">2016-12-14T16:31:37.876987+01:00</LastSaved>
<RemoteTracer>
<TraceListener>
<Key>f987d7bb-9dea-49b4-a689-88c4452d98e3</Key>
<Url>http://192.168.56.1:9343/</Url>
</TraceListener>
</RemoteTracer>
</ComponentSettings>
I want to get all Url tags of a TraceListener tag of a RemoteTracer tag.
This is how i get them, but this only work if the XML document don't use namespaces:
componentConfigXmlDocument = new XmlDocument();
componentConfigXmlDocument.LoadXml(myXmlDocumentCode);
var remoteTracers = componentConfigXmlDocument.SelectNodes("//RemoteTracer/TraceListener/Url");
Currently, my workaround is to remove all namespaces from the XML raw string using regular expression, before loading the XML. Then my SelectNodes() works fine. But that is no proper solution.
You have two namespaces here. First is
http://schemas.datacontract.org/2004/07/Company.Product.Components.Model
Root element (ComponentSettings), RemoteTracer and everything below it belong to this namespace. Second namespace is
http://schemas.datacontract.org/2004/07/Company.Configuration
Created, LastLoaded and Saved belong to it.
To get the node you need, you have to prefix all elements in your xpath query with their respective namespace prefixes. Mapping of those prefixes to actual namespaces you can do like this:
var componentConfigXmlDocument = new XmlDocument();
componentConfigXmlDocument.LoadXml(File.ReadAllText(#"G:\tmp\xml.txt"));
var ns = new XmlNamespaceManager(componentConfigXmlDocument.NameTable);
ns.AddNamespace("model", "http://schemas.datacontract.org/2004/07/Company.Product.Components.Model");
ns.AddNamespace("config", "http://schemas.datacontract.org/2004/07/Company.Configuration");
And then query like this:
var remoteTracers = componentConfigXmlDocument.SelectNodes("//model:RemoteTracer/model:TraceListener/model:Url", ns);
Related
I need to parse XML files where I can't predict the structure. I need to fill a string array with the inner text from every instance of the below tag no matter where they occur in the tree.
<SpecialCode>ABCD1234</SpecialCode>
Is there a simple way to accomplish this using c#?
Solution
If your XML is a string:
XDocument doc = XDocument.Parse("<SpecialCode>ABCD1234</SpecialCode>");
string[] specialCodes = doc.Descendants("SpecialCode").Select(n => n.Value).ToArray();
If your XML is a file:
XDocument doc = XDocument.Load("specialCodes.xml");
string[] specialCodes = doc.Descendants("SpecialCode").Select(n => n.Value).ToArray();
Explanation
XDocument is a handy class that allows for easy XML parsing. You'll need to add a reference to the System.Xml.Linq assembly to your project in order to use it.
The Descendents method will get all children of the XML document, which takes care of your unknown XML structure.
The Select method is a LINQ method and allows us to select a property from each node--in this case, Value.
ToArray converts the IEnumerable result from Select() to your desired result.
XmlDocument doc = new XmlDocument();
doc.Load(FILENAME);
// IN CASE OF STRING, USE FOLLOWING
//doc.LoadXml(XAML_STRING);
XmlNodeList list = doc.DocumentElement.SelectNodes("//SpecialCode");
// prefic will fetch all the SpecialCode tags from xml.
I have some very basic XML:
<ReconnectResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://platform.intuit.com/api/v1">
<ErrorMessage/>
<ErrorCode>0</ErrorCode>
<ServerTime>2012-01-04T19:21:21.0782072Z</ServerTime>
<OAuthToken>redacted</OAuthToken>
<OAuthTokenSecret>redacted</OAuthTokenSecret>
</ReconnectResponse>
Simple, right?
So when I want to get the value for ErrorCode, my experience with XPath tells me to try /ReconnectResponse/ErrorCode/text(). This works in Notepad++ equipped with the XML Tools plugin, so lets try it in C#:
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlString);
var namespaceMan = new XmlNamespaceManager(xmlDoc.NameTable);
Console.WriteLine(xmlDoc.SelectSingleNode(#"/ReconnectResponse/ErrorCode", namespaceMan).InnerText);
I get an exception:
Object reference not set to an instance of an object.
Which smells like a problem finding the specified node. Given how simple the XML is though, I'm struggling to work out what's going wrong.
On a whim, I plopped the XML into XMLQuire. This gives XSD schema errors for each element type, like this:
Could not find schema information for the element 'http://platform.intuit.com/api/v1:ReconnectResponse'.
So, my question is whether or not the schema errors could be causing SelectSingleNode() to miss my nodes? Secondary question: how can I fix it?
You've ignored the namespace of your elements, which in this case is http://platform.intuit.com/api/v1. This is defined by the xmlns=".." attribute in the root element, and all child elements inherit this.
You need to add this namespace to the namespace manager with a prefix:
namespaceMan.AddNamespace("api", "http://platform.intuit.com/api/v1");
And use this prefix in your query:
xmlDoc.SelectSingleNode(#"/api:ReconnectResponse/api:ErrorCode", namespaceMan).InnerText;
As an aside, LINQ to XML is a far cleaner API than XmlDocument and offers much nicer query language than XPath. This code will get you the error code as an integer:
var doc = XDocument.Parse(xmlString);
XNamespace api = "http://platform.intuit.com/api/v1";
var errorCode = (int) doc.Descendants(api + "ErrorCode").Single();
I have problem to select second node from root element in following example xml code:
<?xml version="1.0"?>
<config>
<FirstNode>
<ShowBlahBlah>
</ShowBlahBlah>
</FirstNode>
<SecondNode>
<ShowBlahBlah>
</ShowBlahBlah>
</SecondNode>
</config>
and using this C# code to select SecondNode:
XmlDocument doc = new XmlDocument();
doc.LoadXml(sReadXML);
XmlNode sChangesLog = doc.SelectSingleNode("config").SelectSingleNode("//SecondNode").SelectSingleNode("//ShowBlahBlah")
XmlNodeList sChildNodes = sChangesLog.ChildNodes;
but it selecting first node and return its value!
how can I fix this problem?
You're using // at the start of each of your selections - which means "find descendant nodes starting at the root" (so the context is irrelevant). You could either do things in one step as per Jeffrey's answer, or use relative paths:
doc.SelectSingleNode("config")
.SelectSingleNode("SecondNode")
.SelectSingleNode("ShowBlahBlah")
Personally I'd use LINQ to XML instead, if at all possible:
var doc = XDocument.Parse(sReadXml);
var changes = doc.Root.Element("SecondNode").Element("ShowBlahBlah");
LINQ to XML is generally a much cleaner API than XmlDocument et al.
i need to read the value of a particular value in an xml tag, of he solutions i tried i could only find that to get a value of a tag element ,i need to traverse from root element to the child element.is there an optiion where i can directly select a tag and get its value.
In the below xml exa,i need to get 123456 value from the xml using c#.
Ex:-
<ForwardActionRequest xmlns:auth="test" xmlns="http://www.test">
<auth:Authentication>
<auth:AuthenticationData>
<auth:AuthenticationKey>test</auth:AuthenticationKey>
<auth:Username>test</auth:Username>
<auth:Password>test</auth:Password>
</auth:AuthenticationData>
</auth:Authentication>
<SearchOrderReference>
<Reference>123456</Reference>
<AllocatedBy>test</AllocatedBy>
<Description>test</Description>
</SearchOrderReference>
You can use LINQ to XML:
XDocument doc = XDocument.Load(yourXMLText);
string value = doc.Element("SearchOrderReference").Element("Reference").Value;
Please note that I haven't tested this code.
I also encourage you to read more about LINQ to XML here.
Use XmlDocument.SelectSingleNode() to pass in an XPath that will describe the node you want and then extract the value. Use this prototype as you are using namespaces:
http://msdn.microsoft.com/en-us/library/h0hw012b(v=vs.110).aspx
Read about how to instantiate an XmlNamespaceManager() and initialize it with the relevant prefix (it needs not be what you have in the xml itself), then issue the following request:
XmlNode node = doc.SelectSingleNode("/t:ForwardActionRequest/t:SearchOrderReference/t:Reference");
Given that you associate "t" with "http://www.test".
You can deserialize the xml content into a class and directly get the value of the element or you can use LINQ to XML to retrieve the element value,
XDocument doc=XDocument.Load(XMLContent or XMLPath);
string=doc.Element("SearchOrderReference").Element("Reference").Value;
From your post using VS 2005 you can try the XML Reader which reads the XML from string. Here is an example.
using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
{
reader.ReadToFollowing("SearchOrderReference");
reader.ReadToFollowing("Reference");
string r = reader.ReadInnerXml();
}
Try following code:
XDocument doc = XDocument.Load(yourXMLText);
var node = xmlDoc.SelectSingleNode("//SearchOrderReference[#Reference='123456']");
Then extract node's attribute to get the value of reference tag.
<Document>
<Heading1>
<text>Heading Title</text>
<para>para1</para>
<para>para2</para>
<para>para3</para>
</Heading1>
<Heading1>
<text>2nd Heading Title</text>
<para>para4</para>
<para>para5</para>
<para>para6</para>
<Heading2>
<text>3rd Heading Title</text>
<para>para4</para>
<para>para5</para>
</Heading2>
</Heading1>
</Document>
This is XML Document. Now, i want to parse this XML file using C# (4.0). Here, I want to get all the Heading1 elements without using that element name in my program. For example, don't use document.GetElementsByTagName("Heading1");. How i get it. Guide me get out of this issue.
Thanks & Regards.
Using LINQ to XML, you can do:
var headings = yourXDocument.Root.Elements();
Using Nodes() instead of Elements() will also return text nodes and comments, which is apparently not what you want.
You can access the child elements of the document or element through the Elements() method if using LINQ to XML.
XDocument doc = ...;
var query = doc.Root.Elements();
If you're using XmlDocument, this works:
var elements = doc.SelectNodes("/*/*");
That finds all child elements of the top-level element irrespective of any of their names. It's usually safer to specify the names if you know them, so that elements with unexpected names don't get returned in your list - use /Document/Heading1 to do this.