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();
Related
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);
I have been trying to deserialize the InnerXML into a class and for some reason the XML keeps changing shape and however many times I try to get the class right it seems to change shape again.
So I have given up and decided to try another method.
Is it possible to retrieve the value of a parameter within the InnerXML manually using c#?
Say for example, my XML innerXML looked like this:
<Timestamp>2014-08-22T21:45:00Z</Timestamp>
<Subscriber>https://www.dogdoza.co.uk</Subscriber>
<Order>
<OrderID>111867</OrderID>
<InvoiceNumber>DOZA-9725410</InvoiceNumber>
<CustomerID>4542</CustomerID>
Is it possible to pull out say the value of Subscriber
If this is possible I can just pull out the values I want manually. Not ideal, but there are only about 10...
I have looked around but not managed to find any code I can get working..
Can anyone please give me any guidance?
Thanks
You can do achieve what you want using LINQ to XML:
XElement myXml = XElement.Load(#"XmlLocationHere");
XElement subscriber = myXml.Descendants("Subscriber").FirstOrDefault();
XElement.Descendants returns a collection of the descendant elements for this document or element, in document order. This method will return an IEnumerable<XElement>, since there might be more than one "Subscriber" element, but in your case, we choose FirstOrDefault, which returns the first occurrence.
Try loading your XML into an XDocument. Then try to use XPathSelectElement to find the specific value you want.
It could be that you need to wrap your inner xml into a root element, because it doesn't accept multiple roots.
Pseudo example:
// set up your xml document
string xml = "<rootelement>" + myInnerXml + "</rootelement>";
XDocument doc = new XDocument();
doc.Parse(xml);
XElement subscriber = doc.XPathSelectElement("/rootelement/Subscriber");
string value = subscriber.Value;
To autogenerate some documentation (and learn xpath) I am tring to get a list of all operations from a WSDL file.
What I have tried so far is:
doc = new XmlDocument();
doc.Load(#"C:\temp\tempuri.org.wsdl");
var list = doc.SelectNodes("wsdl:definitions/wsdl:portType/wsdl:operation");
This gives me the error:
Namespace Manager or XsltContext needed. This query has a prefix,
variable, or user-defined function.
Can anyone explain why I am getting this error and how to fix it?
I'd recommend taking a look at this answer: C# XPath help - Expression not working
You need to register the namespace wsdl before you start querying it.
e.g.:
XPathDocument xDoc = new XPathDocument(#"C:\temp\tempuri.org.wsdl");
XPathNavigator xNav = xDoc.CreateNavigator();
XmlNamespaceManager mngr = new XmlNamespaceManager(xNav.NameTable);
mngr.AddNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/"); // this namespace may need to be different - I don't know what your wsdl file looks like
XPathNodeIterator xIter = xNav.Select("wsdl:definitions/wsdl:portType/wsdl:operation",mngr);
Alternatively you can use LINQ to XML - see this answer from Jon Skeet: Namespace Manager or XsltContext needed
But you said you wanted to learn xPath so I guess it's irrelevant.
I'm writing one of my first C# programs. Here's what I'm trying to do:
Open an XML document
Navigate to a part of the XML tree and select all child elements of type <myType>
For each <myType> element, change an attribute (so <myType id="oldValue"> would become <myType id="newValue">
Write this modified XML document to a file.
I found the XmlDocument.SelectNodes method, which takes an XPath expression as its argument. However, it returns an XmlNodeList. I read a little bit about the difference between an XML node and an XML element, and this seems to explain why there is no XmlNode.SetAttribute method. But is there a way I can use my XPath expression to retrieve a list of XmlElement objects, so that I can loop through this list and set the id attributes for each?
(If there's some other easier way, please do let me know.)
Simply - it doesn't know if you are reading an element or attribute. Quite possibly, all you need is a cast here:
foreach(XmlElement el in doc.SelectNodes(...)) {
el.SetAttribute(...);
}
The SelectNodes returns an XmlNodeList, but the above treats each as an XmlElement.
I am a big fan of System.Xml.Linq.XDocument and the features it provides.
XDocument xDoc = XDocument.Load("FILENAME.xml");
// assuming you types is the parent and mytype is a bunch of nodes underneath
IEnumerable<XElement> elements = xdoc.Element("types").Elements("myType");
foreach (XElement type in elements)
{
// option 1
type.Attribute("id").Value = NEWVALUE;
// option 2
type.SetAttributeValue("id", NEWVALUE);
}
Option 1 or 2 works but I prefer 2 because if the attribute doesn't exist this'll create it.
I'm sitting at my Mac so no .NET for me...
However, I think that you can cast an XmlNode to an XmlElement via an explicit cast.
You should be able to cast the XmlElement to an XmlNode then and get it's children Nodes using something like XmlNode.ChildNodes.
I've got a XmlNodeList which I need to have it in a format that I can then re-use within a XSLT stylesheet by calling it from a C# extension method.
Can anyone help? I have read that it might have something to do with using a XPathNavigator but I'm still a bit stuck.
I had to solve this issue myself a couple of years ago. The only way I managed it was to create an XML fragment containing the nodes in the node list and then passing in the children of the fragment.
XsltArgumentList arguments = new XsltArgumentList();
XmlNodeList nodelist;
XmlDocument nodesFrament = new XmlDocument();
XmlNode root = nodesFragment.CreateElement("root");
foreach (XmlNode node in nodeList)
{
root.AppendChild(node);
}
nodesFragment.AppendChild(root);
arguments.AddParam("argumentname", string.Empty, nodesFragment.CreateNavigator().SelectChildren(XPathNodeType.All));
Then you need to make sure you have the corresponding argument in your XSLT, of course.
Note that you probably don't need the additional XmlDocument. You could just call CreateNavigator() on the root XmlNode instance and use your existing XmlDocument for creating the element (I wrote this code some time ago and I've learned more since then - but I know the code above works, I haven't tried any alternatives).
The note at the end was the most useful, I had infact transformed the XmlNodeList into a XmlDocument already so could just use the Navigator on there and create it as a XPathNodeIterator.
Thanks for you help!
Didn't succeed with the answer provided before.
Using AppendChild() to add previously selected nodes of XmlNodeList to a new document resulted in exception on trying to append nodes originating from a different document context. Several trials to fix this included adding my custom default namespace selected nodes in list are bound to. Finally I dropped the approach completely and switched the way I select nodes into XmlNodeList instances.
Instead of
myXmlNode.SelectNodes( xpath, nsmgr )
I'm using
myXmlNode.CreateNavigator().Select( xpath, nsmgr )
to get an XPathNodeIterator instead of XmlNodeList. The resulting XPathNodeIterator of second code is now properly added as parameter value to XsltArgumentsList.