how to use XPath with XDocument?

C#.NetXmlXpathLinq to-Xml

C# Problem Overview


There is a similar question, but it seems that the solution didn't work out in my case: https://stackoverflow.com/questions/3715936/weirdness-with-xdocument-xpath-and-namespaces

Here is the XML I am working with:

<?xml version="1.0" encoding="utf-8"?>
<Report Id="ID1" Type="Demo Report" Created="2011-01-01T01:01:01+11:00" Culture="en" xmlns="http://demo.com/2011/demo-schema">
    <ReportInfo>
        <Name>Demo Report</Name>
        <CreatedBy>Unit Test</CreatedBy>
    </ReportInfo>
</Report>

And below is the code that I thought it should be working but it didn't...

XDocument xdoc = XDocument.Load(@"C:\SampleXML.xml");
XmlNamespaceManager xnm = new XmlNamespaceManager(new NameTable()); 
xnm.AddNamespace(String.Empty, "http://demo.com/2011/demo-schema");
Console.WriteLine(xdoc.XPathSelectElement("/Report/ReportInfo/Name", xnm) == null);

Does anyone have any ideas? Thanks.

C# Solutions


Solution 1 - C#

If you have XDocument it is easier to use LINQ-to-XML:

var document = XDocument.Load(fileName);
var name = document.Descendants(XName.Get("Name", @"http://demo.com/2011/demo-schema")).First().Value;

If you are sure that XPath is the only solution you need:

using System.Xml.XPath;

var document = XDocument.Load(fileName);
var namespaceManager = new XmlNamespaceManager(new NameTable());
namespaceManager.AddNamespace("empty", "http://demo.com/2011/demo-schema");
var name = document.XPathSelectElement("/empty:Report/empty:ReportInfo/empty:Name", namespaceManager).Value;

Solution 2 - C#

XPath 1.0, which is what MS implements, does not have the idea of a default namespace. So try this:

XDocument xdoc = XDocument.Load(@"C:\SampleXML.xml");
XmlNamespaceManager xnm = new XmlNamespaceManager(new NameTable()); 
xnm.AddNamespace("x", "http://demo.com/2011/demo-schema");
Console.WriteLine(xdoc.XPathSelectElement("/x:Report/x:ReportInfo/x:Name", xnm) == null);

Solution 3 - C#

you can use the example from Microsoft - for you without namespace:

using System.Xml.Linq;
using System.Xml.XPath;
var e = xdoc.XPathSelectElement("./Report/ReportInfo/Name");     

should do it

Solution 4 - C#

To work w/o default namespace suffix, I automatically expand the path.

Usage: SelectElement(xdoc.Root, "/Report/ReportInfo/Name");

private static XElement SelectElement(XElement startElement, string xpathExpression, XmlNamespaceManager namespaceManager = null) {
    // XPath 1.0 does not have support for default namespace, so we have to expand our path.
    if (namespaceManager == null) {
        var reader = startElement.CreateReader();
        namespaceManager = new XmlNamespaceManager(reader.NameTable);
    }
    var defaultNamespace = startElement.GetDefaultNamespace();
    var defaultPrefix = namespaceManager.LookupPrefix(defaultNamespace.NamespaceName);
    if (string.IsNullOrEmpty(defaultPrefix)) {
        defaultPrefix = "ᆞ";
        namespaceManager.AddNamespace(defaultPrefix, defaultNamespace.NamespaceName);
    }
    xpathExpression = AddPrefix(xpathExpression, defaultPrefix);
    var selected = startElement.XPathSelectElement(xpathExpression, namespaceManager);
    return selected;
}

private static string AddPrefix(string xpathExpression, string prefix) {
    // Implementation notes:
    // * not perfect, but it works for our use case.
    // * supports: "Name~~" "~~/Name~~" "~~@Name~~" "~~[Name~~" "~~[@Name~~"
    // * does not work in complex expressions like //*[local-name()="HelloWorldResult" and namespace-uri()='http://tempuri.org/']/text()
    // * does not exclude strings like 'string' or function like func()
    var s = Regex.Replace(xpathExpression, @"(?<a>/|\[@|@|\[|^)(?<name>\w(\w|[-])*)", "${a}${prefix}:${name}".Replace("${prefix}", prefix));
    return s;
}

If anyone has a better solution to find element and attribute names, feel free to change this post.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjojoView Question on Stackoverflow
Solution 1 - C#Alex AzaView Answer on Stackoverflow
Solution 2 - C#Richard SchneiderView Answer on Stackoverflow
Solution 3 - C#BernhardView Answer on Stackoverflow
Solution 4 - C#kuxView Answer on Stackoverflow