Reputation: 2449
I need the know the xpath of an XElement in an XDocument, with the namespace as i need it to do a later lookup in the XDocument.
I am currently using the example from Get the XPath to an XElement? and it works fine for documents without namespaces. But not with xml documents with namespaces, and the implements that supports this do not give me the correct path.
example i have the xml document
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner" xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition">
<AutoRefresh>0</AutoRefresh>
<DataSources>
The following code would find path to be /:Report
and crash on XPathSelectElement
var x = XDocument.Load(@"file.xml");
var path = x.Elements().First().GetAbsoluteXPath();
var element = x.XPathSelectElement(path);
How is the path suppose to be? i have manually tried "/rd:Report" and it still failes to select the element.
The extention, found in the other topic:
public static class XExtensions
{
/// <summary>
/// Get the absolute XPath to a given XElement, including the namespace.
/// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]").
/// </summary>
public static string GetAbsoluteXPath(this XElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
Func<XElement, string> relativeXPath = e =>
{
int index = e.IndexPosition();
var currentNamespace = e.Name.Namespace;
string name;
if (currentNamespace == null)
{
name = e.Name.LocalName;
}
else
{
string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace);
name = namespacePrefix + ":" + e.Name.LocalName;
}
// If the element is the root, no index is required
return (index == -1) ? "/" + name : string.Format
(
"/{0}[{1}]",
name,
index.ToString()
);
};
var ancestors = from e in element.Ancestors()
select relativeXPath(e);
return string.Concat(ancestors.Reverse().ToArray()) +
relativeXPath(element);
}
/// <summary>
/// Get the index of the given XElement relative to its
/// siblings with identical names. If the given element is
/// the root, -1 is returned.
/// </summary>
/// <param name="element">
/// The element to get the index of.
/// </param>
public static int IndexPosition(this XElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
if (element.Parent == null)
{
return -1;
}
int i = 1; // Indexes for nodes start at 1, not 0
foreach (var sibling in element.Parent.Elements(element.Name))
{
if (sibling == element)
{
return i;
}
i++;
}
throw new InvalidOperationException
("element has been removed from its parent.");
}
}
Upvotes: 0
Views: 700
Reputation: 167401
Well, first of all /:Report
is not a valid XPath expression, if your used function creates that path then it does not output a valid XPath in case the XML document has a default namespace like xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition"
. In fact given such a default namespace declaration in the input any function that creates a path needs to generate a prefix as well (and multiple for different namespace in general) and bind it to the default namespace URI. Or you need to take a different approach and generate steps of the form *[local-name() = 'Report' and namespace-uri() = 'http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition']
. With XPath 1.0 that is the way I would take as the generation of prefixes and the binding of them to namespace URIs is depending on the particular XPath API you use while the other approach will work with any XPath API.
Upvotes: 2