40

Why is this Xpath not working using XDocument.XPathSelectElement?

Xpath:

//Plugin/UI[1]/PluginPageCategory[1]/Page[1]/Group[1]/CommandRef[2]

XML

<Plugin xmlns="http://www.MyNamespace.ca/MyPath">
  <UI>
    <PluginPageCategory>
      <Page>
        <Group>
          <CommandRef>
            <Images>
            </Images>
          </CommandRef>
          <CommandRef>
            <Images>
            </Images>
          </CommandRef>
        </Group>
      </Page>
    </PluginPageCategory>
  </UI>
</Plugin>

C# Code:

myXDocument.XPathSelectElement("//Plugin/UI[1]/PluginPageCategory[1]/Page[1]/Group[1]/CommandRef[2]", myXDocument.Root.CreateNavigator());
Jean-Philippe Leclerc
  • 6,713
  • 5
  • 43
  • 66
  • 6
    There is no namespace information in the xpath query, which may be the cause. Try, to narrow this down, to remove the namespace on the XML and see if that gets you a result? – Cumbayah Apr 28 '11 at 13:30
  • 2
    It's probably due to the namespace - check if removing that from the XML fixes it, and if it does, you need to set up a NamespaceManager. – Jackson Pope Apr 28 '11 at 13:31
  • @Cumbayah: Yes it works, thank you. But now how do I handle namespaces correctly? – Jean-Philippe Leclerc Apr 28 '11 at 13:32
  • @Jean see this post http://weblogs.asp.net/wallen/archive/2003/04/02/4725.aspx – Bala R Apr 28 '11 at 13:38
  • possible duplicate of [Using Xpath With Default Namespace in C#](http://stackoverflow.com/questions/585812/using-xpath-with-default-namespace-in-c) –  Apr 28 '11 at 14:28
  • Why the downvote?! His answer deserves plenty of downvotes, but the question? +1 to (over-)compensate. – Jean-François Corbett Apr 29 '11 at 06:19

4 Answers4

35

When namespaces are used, these must be used in the XPath query also. Your XPath query would only work against elements with no namespace (as can be verified by removing the namespace from your XML).

Here's an example showing how you create and pass a namespace manager:

var xml = ... XML from your post ...;

var xmlReader = XmlReader.Create( new StringReader(xml) ); // Or whatever your source is, of course.
var myXDocument = XDocument.Load( xmlReader );
var namespaceManager = new XmlNamespaceManager( xmlReader.NameTable ); // We now have a namespace manager that knows of the namespaces used in your document.
namespaceManager.AddNamespace( "prefix", "http://www.MyNamespace.ca/MyPath" ); // We add an explicit prefix mapping for our query.

var result = myXDocument.XPathSelectElement(
    "//prefix:Plugin/prefix:UI[1]/prefix:PluginPageCategory[1]/prefix:Page[1]/prefix:Group[1]/prefix:CommandRef[2]",
    namespaceManager
); // We use that prefix against the elements in the query.

Console.WriteLine(result); // <CommandRef ...> element is printed.

Hope this helps.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Cumbayah
  • 4,415
  • 1
  • 25
  • 32
  • 3
    What is the point of loading the `NameTable` from the XmlReader if you have to explicitly add a prefix/ns combination anyways? I assume you are doing that because you don't know the prefix that the document is using for the desired namespace. But, wouldn't that also mean it's unnecessary to initialize the `XmlNamespaceManager` with the `NameTable`? – crush Feb 28 '18 at 19:51
  • I would just remove the namespace from the string. If you know that it is unique. – Donny V. Oct 11 '19 at 14:35
  • It's important to use setup namespace manager with nonempty namespace name because otherwise it'll be ignored by XPath parser engine. This namespace must be then specified for each node in XPath that is declared in default namespace. Also the namespace name should be different from other namespaces used inside XML. – SalgoMato Aug 15 '23 at 14:37
18

This should probably be a comment on @Cumbayah's post, but I can't seem to leave comments on anything.

You are probably better off using something like this instead of using XmlReader to get the nametable.

var xml = ... XML from your post ...;
var myXDocument = XDocument.Parse(xml);
var namespaceManager = new XmlNamespaceManager(new NameTable());
namespaceManager.AddNamespace("prefix", "http://www.MyNamespace.ca/MyPath");

var result = ...;
mdonoughe
  • 515
  • 5
  • 12
  • 3
    Just want to point issue that default namespace should be prefixed. I.e. namespaceManager.AddNamespace("","http://www.MyNamespace.ca/MyPath") will make DefaultNamespace property rigth as expected, but it will not be used by XPathSelectElement. Misleading!!! – Dzmitry Lahoda Aug 21 '12 at 12:52
11

The easiest way in your case is to use XPath axes and node test for node name and position to select the element. Your XPath selection:

myXDocument.XPathSelectElement("//Plugin/UI[1]/PluginPageCategory[1]/Page[1]/Group[1]/CommandRef[2]", myXDocument.Root.CreateNavigator());

Can be easily translate to:

myXDocument.XPathSelectElement("/child::node()[local-name()='Plugin']/child::node()[local-name()='UI'][position()=1]/child::node()[local-name()='PluginPageCategory'][position()=1]/child::node()[local-name()='Page'][position()=1]/child::node()[local-name()='Group'][position()=1]/child::node()[local-name()='CommandRef'][position()=2]");

There is no need to create and pass XmlNamespaceManager as parameter.

Yannis Stereo
  • 121
  • 1
  • 4
-4

There is a way to do it without any change to the xpath. The solution I've found is to remove the namespace when parsing the XML into the XDocument.

Here is an exemple:

var regex = @"(xmlns:?[^=]*=[""][^""]*[""])";
var myXDocument = XDocument.Parse(Regex.Replace("MyXmlContent", regex, "", RegexOptions.IgnoreCase | RegexOptions.Multiline))

Now that the namespace is gone, it is easyer to manipulate.

Jean-Philippe Leclerc
  • 6,713
  • 5
  • 43
  • 66
  • 6
    _"Now that the namespace is gone"_ you might not have a well formed XML document... –  Apr 29 '11 at 04:47
  • 2
    Well... it works perfectly for me. And note that it can be useful when you can't change the xpath. – Jean-Philippe Leclerc Apr 29 '11 at 13:48
  • 3
    It doesn't work perfectly in all cases, specifically the case where two elements without their namespaces would be identical. – PRMan Apr 19 '16 at 00:44