Problem-
I have a windows application which loads XML file and as other input takes XPath to fetch data which user wants from given XML (obviously that's not a problem).
Code snippet I use to load element from given XPath is like-
XPathDocument docNav;
XPathNavigator nav;
XPathNodeIterator NodeIter;
XmlNamespaceManager manager;
docNav = new XPathDocument(fileName);
nav = docNav.CreateNavigator();
nav.MoveToFollowing(XPathNodeType.Element);
IDictionary<string, string> names = nav.GetNamespacesInScope(XmlNamespaceScope.All);
manager = new XmlNamespaceManager(nav.NameTable);
foreach (KeyValuePair<string, string> item in names)
{
manager.AddNamespace(item.Key, item.Value);
}
NodeIter = nav.Select(path, manager);
I have one sample XML as below-
<FpML xmlns:ns="http://www.fpml.org/2005/FpML-4-2">
<header>
<messageId>ts4-XYZ</messageId>
<sentBy>XYZ</sentBy>
<creationTimestamp>2017-07-08T08:05:53.929Z</creationTimestamp>
</header>
<trade>
<tradeHeader>
<partyTradeIdentifier>
<partyReference href="OUR_PARTY"/>
<tradeId tradeIdScheme="uniqueId">AAAAAA</tradeId>
</partyTradeIdentifier>
<partyTradeInformation>
<partyReference href="OUR_PARTY"/>
<trader>dummy trader name</trader>
</partyTradeInformation>
<tradeDate>2017-07-08</tradeDate>
<tsfpml:completedDateTime>2017-07-08T08:05:53.656Z</tsfpml:completedDateTime>
</tradeHeader>
</trade>
</FpML>
Now, I want to retrieve the 'trader' element value. It works if I use XPath query as
//*[local-name()="trader" and namespace-uri()='http://www.fpml.org/2005/FpML-4-2']
But it fails to load any element if I give /FpML/trade/tradeHeader/partyTradeInformation/trader
What is change in my XPath helper application required so as to select nodes with default namespace in general.
Thank you in advance!
You can remove namespaces using :
public static string RemoveAllNamespaces(string xmlDocument)
{
XElement xmlDocumentWithoutNs = RemoveAllNamespaces(XElement.Parse(xmlDocument));
return xmlDocumentWithoutNs.ToString();
}
private static XElement RemoveAllNamespaces(XElement xmlDocument)
{
if (!xmlDocument.HasElements)
{
XElement xElement = new XElement(xmlDocument.Name.LocalName);
xElement.Value = xmlDocument.Value;
foreach (XAttribute attribute in xmlDocument.Attributes())
xElement.Add(attribute);
return xElement;
}
return new XElement(xmlDocument.Name.LocalName, xmlDocument.Elements().Select(el => RemoveAllNamespaces(el)));
}
And called by :
var doc = new XmlDocument();
doc.Load(fileName);
doc.InnerText = RemoveAllNamespaces(doc.InnerText);
var nav = doc.CreateNavigator();
Then, you haven't many choice, use XmlNamespaceManager:
var docNav = new XPathDocument("");
var nav = docNav.CreateNavigator();
XmlNamespaceManager nsmanager = new XmlNamespaceManager(docNav.NameTable);
nsmanager.AddNamespace("x", "ns:http://www.fpml.org/2005/FpML-4-2");
var nodes = nav.Select(#"/x:FpML/trade/tradeHeader/partyTradeInformation/trader", nsmanager);
It should work.
I can't test myself, but if it doesn't work, test changing Namespace uri without "ns:", or add others "x:" in your XPath and edit my answer with the correct answer ;)
Related
I've got an XML document with a default namespace. I'm using a XPathNavigator to select a set of nodes using Xpath as follows:
XmlElement myXML = ...;
XPathNavigator navigator = myXML.CreateNavigator();
XPathNodeIterator result = navigator.Select("/outerelement/innerelement");
I am not getting any results back: I'm assuming this is because I am not specifying the namespace. How can I include the namespace in my select?
First - you don't need a navigator; SelectNodes / SelectSingleNode should suffice.
You may, however, need a namespace-manager - for example:
XmlElement el = ...; //TODO
XmlNamespaceManager nsmgr = new XmlNamespaceManager(
el.OwnerDocument.NameTable);
nsmgr.AddNamespace("x", el.OwnerDocument.DocumentElement.NamespaceURI);
var nodes = el.SelectNodes(#"/x:outerelement/x:innerelement", nsmgr);
You might want to try an XPath Visualizer tool to help you through.
XPathVisualizer is free, easy to use.
IMPORTANT: If you are using Windows 7/8 and don't see File, Edit and Help Menu items, please press ALT key.
For anyone looking for a quick hack solution, especially in those cases where you know the XML and don't need to worry about namespaces and all that, you can get around this annoying little "feature" by simply reading the file to a string and replacing the offensive attribute:
XmlDocument doc = new XmlDocument();
string fileData = File.ReadAllText(fileName);
fileData = fileData.Replace(" xmlns=\"", " whocares=\"");
using (StringReader sr = new StringReader(fileData))
{
doc.Load(sr);
}
XmlNodeList nodeList = doc.SelectNodes("project/property");
I find this easier than all the other non-sense requiring a prefix for a default namespace when I'm dealing with a single file. Hope this helps.
When using XPath in .NET (via a navigator or SelectNodes/SelectSingleNode) on XML with namespaces you need to:
provide your own XmlNamespaceManager
and explicitly prefix all elements in XPath expression, which are in namespace.
The latter is (paraphrased from MS source linked below): because XPath 1.0 ignores default namespace specifications (xmlns="some_namespace"). So when you use element name without prefix it assumes null namespace.
That's why .NET implementation of XPath ignores namespace with prefix String.Empty in XmlNamespaceManager and allways uses null namespace.
See XmlNamespaceManager and UndefinedXsltContext don't handle default namespace for more information.
I find this "feature" very inconvenient because you cannot make old XPath namespace-aware by simply adding default namespace declaration, but that's how it works.
You can use XPath statement without using XmlNamespaceManager like this:
...
navigator.Select("//*[ local-name() = 'innerelement' and namespace-uri() = '' ]")
...
That is a simple way of selecting element within XML with default namespace definied.
The point is to use:
namespace-uri() = ''
which will found element with default namespace without using prefixes.
My answer extends the previous answer by Brandon. I used his example to create an extension method as follows:
static public class XmlDocumentExt
{
static public XmlNamespaceManager GetPopulatedNamespaceMgr(this System.Xml.XmlDocument xd)
{
XmlNamespaceManager nmsp = new XmlNamespaceManager(xd.NameTable);
XPathNavigator nav = xd.DocumentElement.CreateNavigator();
foreach (KeyValuePair<string,string> kvp in nav.GetNamespacesInScope(XmlNamespaceScope.All))
{
string sKey = kvp.Key;
if (sKey == "")
{
sKey = "default";
}
nmsp.AddNamespace(sKey, kvp.Value);
}
return nmsp;
}
}
Then in my XML parsing code, I just add a single line:
XmlDocument xdCandidate = new XmlDocument();
xdCandidate.Load(sCandidateFile);
XmlNamespaceManager nmsp = xdCandidate.GetPopulatedNamespaceMgr(); // 1-line addition
XmlElement xeScoreData = (XmlElement)xdCandidate.SelectSingleNode("default:ScoreData", nmsp);
I really like this method because it is completely dynamic in terms of loading the namespaces from the source XML file, and it doesn't completely disregard the concept of XML namespaces so this can be used with XML that requires multiple namespaces for deconfliction.
I encountered a similar problem with a blank default namespace. In this example XML, I have a mix of elements with namespace prefixes, and a single element (DataBlock) without:
<src:SRCExample xmlns="urn:some:stuff:here" xmlns:src="www.test.com/src" xmlns:a="www.test.com/a" xmlns:b="www.test.com/b">
<DataBlock>
<a:DocID>
<a:IdID>7</a:IdID>
</a:DocID>
<b:Supplimental>
<b:Data1>Value</b:Data1>
<b:Data2/>
<b:Extra1>
<b:More1>Value</b:More1>
</b:Extra1>
</b:Supplimental>
</DataBlock>
</src:SRCExample>
I attempted to use an XPath that worked in XPath Visualizer, but did not work in my code:
XmlDocument doc = new XmlDocument();
doc.Load( textBox1.Text );
XPathNavigator nav = doc.DocumentElement.CreateNavigator();
XmlNamespaceManager nsman = new XmlNamespaceManager( nav.NameTable );
foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
nsman.AddNamespace( nskvp.Key, nskvp.Value );
}
XPathNodeIterator nodes;
XPathExpression failingexpr = XPathExpression.Compile( "/src:SRCExample/DataBlock/a:DocID/a:IdID" );
failingexpr.SetContext( nsman );
nodes = nav.Select( failingexpr );
while ( nodes.MoveNext() ) {
string testvalue = nodes.Current.Value;
}
I narrowed it down to the "DataBlock" element of the XPath, but couldn't make it work except by simply wildcarding the DataBlock element:
XPathExpression workingexpr = XPathExpression.Compile( "/src:SRCExample/*/a:DocID/a:IdID" );
failingexpr.SetContext( nsman );
nodes = nav.Select( failingexpr );
while ( nodes.MoveNext() ) {
string testvalue = nodes.Current.Value;
}
After much headscratching and googling (which landed me here) I decided to tackle the default namespace directly in my XmlNamespaceManager loader by changing it to:
foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
nsman.AddNamespace( nskvp.Key, nskvp.Value );
if ( nskvp.Key == "" ) {
nsman.AddNamespace( "default", nskvp.Value );
}
}
So now "default" and "" point to the same namespace. Once I did this, the XPath "/src:SRCExample/default:DataBlock/a:DocID/a:IdID" returned my results just like I wanted. Hopefully this helps to clarify the issue for others.
In case the namespaces differ for outerelement and innerelement
XmlNamespaceManager manager = new XmlNamespaceManager(myXmlDocument.NameTable);
manager.AddNamespace("o", "namespaceforOuterElement");
manager.AddNamespace("i", "namespaceforInnerElement");
string xpath = #"/o:outerelement/i:innerelement"
// For single node value selection
XPathExpression xPathExpression = navigator.Compile(xpath );
string reportID = myXmlDocument.SelectSingleNode(xPathExpression.Expression, manager).InnerText;
// For multiple node selection
XmlNodeList myNodeList= myXmlDocument.SelectNodes(xpath, manager);
In my case adding a prefix wasn't practical. Too much of the xml or xpath were determined at runtime. Eventually I extended the methds on XmlNode. This hasn't been optimised for performance and it probably doesn't handle every case but it's working for me so far.
public static class XmlExtenders
{
public static XmlNode SelectFirstNode(this XmlNode node, string xPath)
{
const string prefix = "pfx";
XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
string prefixedPath = GetPrefixedPath(xPath, prefix);
return node.SelectSingleNode(prefixedPath, nsmgr);
}
public static XmlNodeList SelectAllNodes(this XmlNode node, string xPath)
{
const string prefix = "pfx";
XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
string prefixedPath = GetPrefixedPath(xPath, prefix);
return node.SelectNodes(prefixedPath, nsmgr);
}
public static XmlNamespaceManager GetNsmgr(XmlNode node, string prefix)
{
string namespaceUri;
XmlNameTable nameTable;
if (node is XmlDocument)
{
nameTable = ((XmlDocument) node).NameTable;
namespaceUri = ((XmlDocument) node).DocumentElement.NamespaceURI;
}
else
{
nameTable = node.OwnerDocument.NameTable;
namespaceUri = node.NamespaceURI;
}
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nameTable);
nsmgr.AddNamespace(prefix, namespaceUri);
return nsmgr;
}
public static string GetPrefixedPath(string xPath, string prefix)
{
char[] validLeadCharacters = "#/".ToCharArray();
char[] quoteChars = "\'\"".ToCharArray();
List<string> pathParts = xPath.Split("/".ToCharArray()).ToList();
string result = string.Join("/",
pathParts.Select(
x =>
(string.IsNullOrEmpty(x) ||
x.IndexOfAny(validLeadCharacters) == 0 ||
(x.IndexOf(':') > 0 &&
(x.IndexOfAny(quoteChars) < 0 || x.IndexOfAny(quoteChars) > x.IndexOf(':'))))
? x
: prefix + ":" + x).ToArray());
return result;
}
}
Then in your code just use something like
XmlDocument document = new XmlDocument();
document.Load(pathToFile);
XmlNode node = document.SelectFirstNode("/rootTag/subTag");
Hope this helps
I used the hacky-but-useful approach described by SpikeDog above. It worked very well until I threw an xpath expression at it that used pipes to combine multiple paths.
So I rewrote it using regular expressions, and thought I'd share:
public string HackXPath(string xpath_, string prefix_)
{
return System.Text.RegularExpressions.Regex.Replace(xpath_, #"(^(?![A-Za-z0-9\-\.]+::)|[A-Za-z0-9\-\.]+::|[#|/|\[])(?'Expression'[A-Za-z][A-Za-z0-9\-\.]*)", x =>
{
int expressionIndex = x.Groups["Expression"].Index - x.Index;
string before = x.Value.Substring(0, expressionIndex);
string after = x.Value.Substring(expressionIndex, x.Value.Length - expressionIndex);
return String.Format("{0}{1}:{2}", before, prefix_, after);
});
}
Or, if anyone should be using an XPathDocument, like me:
XPathDocument xdoc = new XPathDocument(file);
XPathNavigator nav = xdoc.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable);
nsmgr.AddNamespace("y", "http://schemas.microsoft.com/developer/msbuild/2003");
XPathNodeIterator nodeIter = nav.Select("//y:PropertyGroup", nsmgr);
1] If you have a XML file without any prefix in the namespace:
<bookstore xmlns="http://www.contoso.com/books">
…
</bookstore>
you have this workaround:
XmlTextReader reader = new XmlTextReader(#"C:\Temp\books.xml");
// ignore the namespace as there is a single default namespace:
reader.Namespaces = false;
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");
2] If you have a XML file with a prefix in the namespace:
<bookstore xmlns:ns="http://www.contoso.com/books">
…
</bookstore>
Use this:
XmlTextReader reader = new XmlTextReader(#"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");
Of course, you can use a namespace manage if needed:
XmlTextReader reader = new XmlTextReader(#"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(reader.NameTable);
nsmgr.AddNamespace("ns", "http://www.contoso.com/book");
XPathNodeIterator nodes = navigator.Select("//book", nsmgr);
I think that it's the easiest way to make the code working in the most cases.
I hope this help to solve this Microsoft issue…
This one still keeps bugging me. I've done some testing now, so hopefully I can help you with this.
This is the source from Microsoft, which is the key to the problem
The important paragraph is here:
XPath treats the empty prefix as the null namespace. In other words, only prefixes mapped to namespaces can be used in XPath queries. This means that if you want to query against a namespace in an XML document, even if it is the default namespace, you need to define a prefix for it.
In essence, you have to remember the XPath parser uses the Namespace URI - with the design that the prefix is interchangeable. This is so, when programming, you can assign whatever prefix we want - as long as the URI matches.
For clarity with examples:
Example A:
<data xmlns:nsa="http://example.com/ns"><nsa:a>World</nsa:a></data>
This has a NULL default URI (xmlns= is not defined). Because of this /data/nsa:a returns "World".
Example B:
<data xmlns:nsa="http://example.com/ns" xmlns="https://standardns/"><nsa:a>World</nsa:a></data>
This document has a named default prefix https://standardns/. XPathNavigator.Execute with /data/nsa:a therefore returns no results. MS considers that the XML namespace uri for data should be NULL, and the namespace URI for data is actually "https://standardns/". Essentially XPath is looking for /NULL:data/nsa:a - although this won't work, as you can't refer to the NULL URI as "NULL" as a prefix. NULL prefix is the default in all XPath - hence the issue.
How do we solve this?
XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");
result.AddNamespace("nsa", "http://example.com/ns");
In this way, we can now refer to a as /DEFAULT:data/nsa:a
Example C:
<data><a xmlns="https://standardns/">World</a></data>
In this example data is in the NULL namespace. a is in the default namespace "https://standardns/". /data/a should not work, according to Microsoft, because a is in the NS https://standardns/ and data is in the namespace NULL. <a> is therefore hidden (except by doing weird "ignore the namespace" hacks) and cannot be selected upon as-is. This is essentially the root cause - you should not be able to select "a" and "data" with no prefixes for both, as this would assume that they were in the same namespace, and they aren't!
How do we solve this?
XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");
In this way, we can now refer to a as /data/DEFAULT:a as data is selected from the NULL namespace, and a is selected from the new prefix "DEFAULT". The important thing in this example is that the namespace prefix does not need to remain the same. It's perfectly acceptable to refer to a URI namespace with a different prefix in your code, as to what is written in the document you are processing.
Hope this helps some people!
In this case, it is probably namespace resolution which is the cause of the problem, but it is also possible that your XPath expression is not correct in itself. You may want to evaluate it first.
Here is the code using an XPathNavigator.
//xNav is the created XPathNavigator.
XmlNamespaceManager mgr = New XmlNamespaceManager(xNav.NameTable);
mgr.AddNamespace("prefix", "http://tempuri.org/");
XPathNodeIterator result = xNav.Select("/prefix:outerelement/prefix:innerelement", mgr);
I've got an XML document with a default namespace. I'm using a XPathNavigator to select a set of nodes using Xpath as follows:
XmlElement myXML = ...;
XPathNavigator navigator = myXML.CreateNavigator();
XPathNodeIterator result = navigator.Select("/outerelement/innerelement");
I am not getting any results back: I'm assuming this is because I am not specifying the namespace. How can I include the namespace in my select?
First - you don't need a navigator; SelectNodes / SelectSingleNode should suffice.
You may, however, need a namespace-manager - for example:
XmlElement el = ...; //TODO
XmlNamespaceManager nsmgr = new XmlNamespaceManager(
el.OwnerDocument.NameTable);
nsmgr.AddNamespace("x", el.OwnerDocument.DocumentElement.NamespaceURI);
var nodes = el.SelectNodes(#"/x:outerelement/x:innerelement", nsmgr);
You might want to try an XPath Visualizer tool to help you through.
XPathVisualizer is free, easy to use.
IMPORTANT: If you are using Windows 7/8 and don't see File, Edit and Help Menu items, please press ALT key.
For anyone looking for a quick hack solution, especially in those cases where you know the XML and don't need to worry about namespaces and all that, you can get around this annoying little "feature" by simply reading the file to a string and replacing the offensive attribute:
XmlDocument doc = new XmlDocument();
string fileData = File.ReadAllText(fileName);
fileData = fileData.Replace(" xmlns=\"", " whocares=\"");
using (StringReader sr = new StringReader(fileData))
{
doc.Load(sr);
}
XmlNodeList nodeList = doc.SelectNodes("project/property");
I find this easier than all the other non-sense requiring a prefix for a default namespace when I'm dealing with a single file. Hope this helps.
When using XPath in .NET (via a navigator or SelectNodes/SelectSingleNode) on XML with namespaces you need to:
provide your own XmlNamespaceManager
and explicitly prefix all elements in XPath expression, which are in namespace.
The latter is (paraphrased from MS source linked below): because XPath 1.0 ignores default namespace specifications (xmlns="some_namespace"). So when you use element name without prefix it assumes null namespace.
That's why .NET implementation of XPath ignores namespace with prefix String.Empty in XmlNamespaceManager and allways uses null namespace.
See XmlNamespaceManager and UndefinedXsltContext don't handle default namespace for more information.
I find this "feature" very inconvenient because you cannot make old XPath namespace-aware by simply adding default namespace declaration, but that's how it works.
You can use XPath statement without using XmlNamespaceManager like this:
...
navigator.Select("//*[ local-name() = 'innerelement' and namespace-uri() = '' ]")
...
That is a simple way of selecting element within XML with default namespace definied.
The point is to use:
namespace-uri() = ''
which will found element with default namespace without using prefixes.
My answer extends the previous answer by Brandon. I used his example to create an extension method as follows:
static public class XmlDocumentExt
{
static public XmlNamespaceManager GetPopulatedNamespaceMgr(this System.Xml.XmlDocument xd)
{
XmlNamespaceManager nmsp = new XmlNamespaceManager(xd.NameTable);
XPathNavigator nav = xd.DocumentElement.CreateNavigator();
foreach (KeyValuePair<string,string> kvp in nav.GetNamespacesInScope(XmlNamespaceScope.All))
{
string sKey = kvp.Key;
if (sKey == "")
{
sKey = "default";
}
nmsp.AddNamespace(sKey, kvp.Value);
}
return nmsp;
}
}
Then in my XML parsing code, I just add a single line:
XmlDocument xdCandidate = new XmlDocument();
xdCandidate.Load(sCandidateFile);
XmlNamespaceManager nmsp = xdCandidate.GetPopulatedNamespaceMgr(); // 1-line addition
XmlElement xeScoreData = (XmlElement)xdCandidate.SelectSingleNode("default:ScoreData", nmsp);
I really like this method because it is completely dynamic in terms of loading the namespaces from the source XML file, and it doesn't completely disregard the concept of XML namespaces so this can be used with XML that requires multiple namespaces for deconfliction.
I encountered a similar problem with a blank default namespace. In this example XML, I have a mix of elements with namespace prefixes, and a single element (DataBlock) without:
<src:SRCExample xmlns="urn:some:stuff:here" xmlns:src="www.test.com/src" xmlns:a="www.test.com/a" xmlns:b="www.test.com/b">
<DataBlock>
<a:DocID>
<a:IdID>7</a:IdID>
</a:DocID>
<b:Supplimental>
<b:Data1>Value</b:Data1>
<b:Data2/>
<b:Extra1>
<b:More1>Value</b:More1>
</b:Extra1>
</b:Supplimental>
</DataBlock>
</src:SRCExample>
I attempted to use an XPath that worked in XPath Visualizer, but did not work in my code:
XmlDocument doc = new XmlDocument();
doc.Load( textBox1.Text );
XPathNavigator nav = doc.DocumentElement.CreateNavigator();
XmlNamespaceManager nsman = new XmlNamespaceManager( nav.NameTable );
foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
nsman.AddNamespace( nskvp.Key, nskvp.Value );
}
XPathNodeIterator nodes;
XPathExpression failingexpr = XPathExpression.Compile( "/src:SRCExample/DataBlock/a:DocID/a:IdID" );
failingexpr.SetContext( nsman );
nodes = nav.Select( failingexpr );
while ( nodes.MoveNext() ) {
string testvalue = nodes.Current.Value;
}
I narrowed it down to the "DataBlock" element of the XPath, but couldn't make it work except by simply wildcarding the DataBlock element:
XPathExpression workingexpr = XPathExpression.Compile( "/src:SRCExample/*/a:DocID/a:IdID" );
failingexpr.SetContext( nsman );
nodes = nav.Select( failingexpr );
while ( nodes.MoveNext() ) {
string testvalue = nodes.Current.Value;
}
After much headscratching and googling (which landed me here) I decided to tackle the default namespace directly in my XmlNamespaceManager loader by changing it to:
foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
nsman.AddNamespace( nskvp.Key, nskvp.Value );
if ( nskvp.Key == "" ) {
nsman.AddNamespace( "default", nskvp.Value );
}
}
So now "default" and "" point to the same namespace. Once I did this, the XPath "/src:SRCExample/default:DataBlock/a:DocID/a:IdID" returned my results just like I wanted. Hopefully this helps to clarify the issue for others.
In case the namespaces differ for outerelement and innerelement
XmlNamespaceManager manager = new XmlNamespaceManager(myXmlDocument.NameTable);
manager.AddNamespace("o", "namespaceforOuterElement");
manager.AddNamespace("i", "namespaceforInnerElement");
string xpath = #"/o:outerelement/i:innerelement"
// For single node value selection
XPathExpression xPathExpression = navigator.Compile(xpath );
string reportID = myXmlDocument.SelectSingleNode(xPathExpression.Expression, manager).InnerText;
// For multiple node selection
XmlNodeList myNodeList= myXmlDocument.SelectNodes(xpath, manager);
In my case adding a prefix wasn't practical. Too much of the xml or xpath were determined at runtime. Eventually I extended the methds on XmlNode. This hasn't been optimised for performance and it probably doesn't handle every case but it's working for me so far.
public static class XmlExtenders
{
public static XmlNode SelectFirstNode(this XmlNode node, string xPath)
{
const string prefix = "pfx";
XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
string prefixedPath = GetPrefixedPath(xPath, prefix);
return node.SelectSingleNode(prefixedPath, nsmgr);
}
public static XmlNodeList SelectAllNodes(this XmlNode node, string xPath)
{
const string prefix = "pfx";
XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
string prefixedPath = GetPrefixedPath(xPath, prefix);
return node.SelectNodes(prefixedPath, nsmgr);
}
public static XmlNamespaceManager GetNsmgr(XmlNode node, string prefix)
{
string namespaceUri;
XmlNameTable nameTable;
if (node is XmlDocument)
{
nameTable = ((XmlDocument) node).NameTable;
namespaceUri = ((XmlDocument) node).DocumentElement.NamespaceURI;
}
else
{
nameTable = node.OwnerDocument.NameTable;
namespaceUri = node.NamespaceURI;
}
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nameTable);
nsmgr.AddNamespace(prefix, namespaceUri);
return nsmgr;
}
public static string GetPrefixedPath(string xPath, string prefix)
{
char[] validLeadCharacters = "#/".ToCharArray();
char[] quoteChars = "\'\"".ToCharArray();
List<string> pathParts = xPath.Split("/".ToCharArray()).ToList();
string result = string.Join("/",
pathParts.Select(
x =>
(string.IsNullOrEmpty(x) ||
x.IndexOfAny(validLeadCharacters) == 0 ||
(x.IndexOf(':') > 0 &&
(x.IndexOfAny(quoteChars) < 0 || x.IndexOfAny(quoteChars) > x.IndexOf(':'))))
? x
: prefix + ":" + x).ToArray());
return result;
}
}
Then in your code just use something like
XmlDocument document = new XmlDocument();
document.Load(pathToFile);
XmlNode node = document.SelectFirstNode("/rootTag/subTag");
Hope this helps
I used the hacky-but-useful approach described by SpikeDog above. It worked very well until I threw an xpath expression at it that used pipes to combine multiple paths.
So I rewrote it using regular expressions, and thought I'd share:
public string HackXPath(string xpath_, string prefix_)
{
return System.Text.RegularExpressions.Regex.Replace(xpath_, #"(^(?![A-Za-z0-9\-\.]+::)|[A-Za-z0-9\-\.]+::|[#|/|\[])(?'Expression'[A-Za-z][A-Za-z0-9\-\.]*)", x =>
{
int expressionIndex = x.Groups["Expression"].Index - x.Index;
string before = x.Value.Substring(0, expressionIndex);
string after = x.Value.Substring(expressionIndex, x.Value.Length - expressionIndex);
return String.Format("{0}{1}:{2}", before, prefix_, after);
});
}
Or, if anyone should be using an XPathDocument, like me:
XPathDocument xdoc = new XPathDocument(file);
XPathNavigator nav = xdoc.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable);
nsmgr.AddNamespace("y", "http://schemas.microsoft.com/developer/msbuild/2003");
XPathNodeIterator nodeIter = nav.Select("//y:PropertyGroup", nsmgr);
1] If you have a XML file without any prefix in the namespace:
<bookstore xmlns="http://www.contoso.com/books">
…
</bookstore>
you have this workaround:
XmlTextReader reader = new XmlTextReader(#"C:\Temp\books.xml");
// ignore the namespace as there is a single default namespace:
reader.Namespaces = false;
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");
2] If you have a XML file with a prefix in the namespace:
<bookstore xmlns:ns="http://www.contoso.com/books">
…
</bookstore>
Use this:
XmlTextReader reader = new XmlTextReader(#"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");
Of course, you can use a namespace manage if needed:
XmlTextReader reader = new XmlTextReader(#"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(reader.NameTable);
nsmgr.AddNamespace("ns", "http://www.contoso.com/book");
XPathNodeIterator nodes = navigator.Select("//book", nsmgr);
I think that it's the easiest way to make the code working in the most cases.
I hope this help to solve this Microsoft issue…
This one still keeps bugging me. I've done some testing now, so hopefully I can help you with this.
This is the source from Microsoft, which is the key to the problem
The important paragraph is here:
XPath treats the empty prefix as the null namespace. In other words, only prefixes mapped to namespaces can be used in XPath queries. This means that if you want to query against a namespace in an XML document, even if it is the default namespace, you need to define a prefix for it.
In essence, you have to remember the XPath parser uses the Namespace URI - with the design that the prefix is interchangeable. This is so, when programming, you can assign whatever prefix we want - as long as the URI matches.
For clarity with examples:
Example A:
<data xmlns:nsa="http://example.com/ns"><nsa:a>World</nsa:a></data>
This has a NULL default URI (xmlns= is not defined). Because of this /data/nsa:a returns "World".
Example B:
<data xmlns:nsa="http://example.com/ns" xmlns="https://standardns/"><nsa:a>World</nsa:a></data>
This document has a named default prefix https://standardns/. XPathNavigator.Execute with /data/nsa:a therefore returns no results. MS considers that the XML namespace uri for data should be NULL, and the namespace URI for data is actually "https://standardns/". Essentially XPath is looking for /NULL:data/nsa:a - although this won't work, as you can't refer to the NULL URI as "NULL" as a prefix. NULL prefix is the default in all XPath - hence the issue.
How do we solve this?
XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");
result.AddNamespace("nsa", "http://example.com/ns");
In this way, we can now refer to a as /DEFAULT:data/nsa:a
Example C:
<data><a xmlns="https://standardns/">World</a></data>
In this example data is in the NULL namespace. a is in the default namespace "https://standardns/". /data/a should not work, according to Microsoft, because a is in the NS https://standardns/ and data is in the namespace NULL. <a> is therefore hidden (except by doing weird "ignore the namespace" hacks) and cannot be selected upon as-is. This is essentially the root cause - you should not be able to select "a" and "data" with no prefixes for both, as this would assume that they were in the same namespace, and they aren't!
How do we solve this?
XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");
In this way, we can now refer to a as /data/DEFAULT:a as data is selected from the NULL namespace, and a is selected from the new prefix "DEFAULT". The important thing in this example is that the namespace prefix does not need to remain the same. It's perfectly acceptable to refer to a URI namespace with a different prefix in your code, as to what is written in the document you are processing.
Hope this helps some people!
In this case, it is probably namespace resolution which is the cause of the problem, but it is also possible that your XPath expression is not correct in itself. You may want to evaluate it first.
Here is the code using an XPathNavigator.
//xNav is the created XPathNavigator.
XmlNamespaceManager mgr = New XmlNamespaceManager(xNav.NameTable);
mgr.AddNamespace("prefix", "http://tempuri.org/");
XPathNodeIterator result = xNav.Select("/prefix:outerelement/prefix:innerelement", mgr);
I have the following XML File. I want to copy a new "Test" and change the ID of the Test. How is it possible?
I already can copy the nodes, unfortunately not on the correct position (see images) and I also can´t change the ID.
Anyone have a solution for me?
Before:
After:
XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(Before.xml");
XmlNode Set = xmldoc.DocumentElement;
string strXmlQuery = "/Toolings/Testing/Test1";
XmlNode NodeToCopy = Set.SelectSingleNode(strXmlQuery);
XmlNode NewNode = NodeToCopy.CloneNode(true);
NodeToCopy.AppendChild(NewNode);
Set.InsertAfter(NewNode, Set.LastChild);
XPathNavigator navigator = xmldoc.CreateNavigator();
navigator.MoveToRoot();
navigator.MoveToFirstChild();
navigator.MoveToFirstChild();
navigator.MoveToFirstChild();
navigator.MoveToFirstChild();
navigator.SetValue("5678");
xmldoc.Save(After.xml");
Here is an example using System.Xml.Linq.XDocument which is a much easier API than XmlDocument:
//You can also use Load(), this is just so I didn't have to make a file
XDocument doc = XDocument.Parse("<Toolings><Testing><Test><ID>1234</ID></Test></Testing></Toolings>");
//Grab the first Test node (change the predicate if you have other search criteria)
var elTest = doc.Descendants().First(d => d.Name == "Test");
//Copy the node, only necessary if you don't know the structure at design time
XElement el = new XElement(elTest);
el.Element("ID").Value = "5678";
//inject new node
elTest.AddAfterSelf(el);
doc.Save("After.xml");
I am trying to read an XML feed from http://itunes.apple.com/us/rss/topsongs/limit=10/genre=2/xml.
I want to access the fields like this:
<im:price amount="1.29000" currency="USD">$1.29</im:price>
<im:releaseDate label="December 31, 1960">1960-12-31T16:00:00-07:00</im:releaseDate>
Here is what I have done so far:
var xml = "http://itunes.apple.com/us/rss/topsongs/limit=10/genre=2/xml";
XmlDocument doc = new XmlDocument();
doc.Load(xml);
XmlNodeList items = doc.SelectNodes("//entry");
foreach (var item in items) {
// Do something with item.
}
No luck, though. items is null. Why? What am I doing wrong?
You need to create a namespace manager to map the RSS and also the iTunes custom tags namespace URIs to short prefixes (itunes and im in the example below):
var xml = "http://itunes.apple.com/us/rss/topsongs/limit=10/genre=2/xml";
XmlDocument doc = new XmlDocument();
doc.Load(xml);
var namespaceManager = new XmlNamespaceManager(doc.NameTable);
namespaceManager.AddNamespace("itunes", "http://www.w3.org/2005/Atom");
namespaceManager.AddNamespace("im", "http://itunes.apple.com/rss");
XmlNodeList items = doc.SelectNodes("//itunes:entry", namespaceManager);
foreach (XmlNode item in items)
{
var price = item.SelectSingleNode("im:price", namespaceManager);
var releaseDate = item.SelectSingleNode("im:releaseDate", namespaceManager);
if (price != null)
{
Console.WriteLine(price.Attributes["amount"].InnerText);
}
if (releaseDate != null)
{
Console.WriteLine(releaseDate.Attributes["label"].InnerText);
}
}
For that specific feed you should get 10 entries.
It's in the docs as well:
If the XPath expression does not include a prefix, it is assumed that
the namespace URI is the empty namespace. If your XML includes a
default namespace, you must still use the XmlNamespaceManager and add
a prefix and namespace URI to it; otherwise, you will not get any
nodes selected. For more information, see Select Nodes Using XPath
Navigation.
Alternatively you can use a namespace-agnostic XPath (from here):
XmlNodeList items = doc.SelectNodes("//*[local-name() = 'entry']");
Finally, not sure why you said items is null. It cannot be. When running your original code you should get this:
I have been looking for ages to find a way to select nodes from an XmlNode(NOT AN XmlDocument) which has multiple namespaces.
Almost every post that I have searched has advised me to use an XmlNamespaceManager, however, XmlNamespaceManager needs an XmlNameTable which does not exists for an XmlNode.
I tried doing this with an XmlDocument and it worked since XmlDocument has a property XmlDocument.NameTable but it does not exists for XmlNode.
I tried creating a NameTable manually but it does not work as the same piece of code works when I use an XmlDocument. I guess I need to populate that NameTable with something or somehow bind it to the XmlNode to make this work. Please suggest.
Can you use
XPathNavigator nav = XmlNode.CreateNavigator();
XmlNamespaceManager man = new XmlNamespaceManager(nav.NameTable);
Including the rest in case it'll be helpful:
man.AddNamespace("app", "http://www.w3.org/2007/app"); //Gotta add each namespace
XPathNodeIterator nodeIter = nav.Select(xPathSearchString, man);
while (nodeIter.MoveNext())
{
var value = nodeIter.Current.Value;
}
http://msdn.microsoft.com/en-us/library/system.xml.xmlnode.createnavigator.aspx
For some reason the XmlNamespaceManager doesn't automatically load the defined namespaces in the document (This seems like a simple expectation). For some reason the namespace declarations are treated as attributes. I was able to automate adding the namespaces using the following code.
private static XmlNamespaceManager AddNamespaces(XmlDocument xmlDoc)
{
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
AddNamespaces(xmlDoc.ChildNodes, nsmgr);
return nsmgr;
}
private static void AddNamespaces(XmlNodeList nodes, XmlNamespaceManager nsmgr) {
if (nodes == null)
throw new ArgumentException("XmlNodeList is null");
if (nsmgr == null)
throw new ArgumentException("XmlNamespaceManager is null");
foreach (XmlNode node in nodes)
{
if (node.NodeType == XmlNodeType.Element)
{
foreach (XmlAttribute attr in node.Attributes)
{
if (attr.Name.StartsWith("xmlns:"))
{
String ns = attr.Name.Replace("xmlns:", "");
nsmgr.AddNamespace(ns, attr.Value);
}
}
if (node.HasChildNodes)
{
nsmgr.PushScope();
AddNamespaces(node.ChildNodes, nsmgr);
nsmgr.PopScope();
}
}
}
}
Sample call example:
XmlDocument ResponseXmlDoc = new System.Xml.XmlDocument();
...<Load your XML Document>...
XmlNamespaceManager nsmgr = AddNamespaces(ResponseXmlDoc);
And use the returned NamespaceManager
XmlNodeList list = ResponseXmlDoc.SelectNodes("//d:response", nsmgr);