Xml-SelectNodes с пространством имен по умолчанию через XmlNamespaceManager не работает должным образом

У меня есть XML с пространством имен по умолчанию

<a xmlns='urn:test.Schema'><b/><b/></a>

и хочу посчитать количество <b/>

Как мне определить

XmlNamespaceManager nsmgr = ????
Assert.AreEqual(2, doc.SelectNodes("//b", nsmgr).Count);

чтобы утверждение стало правдой?

Я пытался до сих пор (с помощью nunit):

[Test]
[Ignore("Why does this not work?")]
public void __DoesNotWork_TestSelectWithDefaultNamespace()
{
    // xml to parse with defaultnamespace
    string xml = @"<a xmlns='urn:test.Schema'><b/><b/></a>";

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xml);

    // fails because xpath does not have the namespace
    //!!!!
    Assert.AreEqual(2, doc.SelectNodes("//b").Count);

    // using XPath defaultnamespace 
    XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
    nsmgr.AddNamespace("", "urn:test.Schema");

    // This will fail with dotnet 3.5sp1. Why?
    //!!!!
    Assert.AreEqual(2, doc.SelectNodes("//b", nsmgr).Count);
}

[Test]
public void TestSelectWithoutNamespaces_Ok()
{
    // xml to parse without namespace
    string xml = @"<a><b/><b/></a>";

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xml);

    // works ok
    Assert.AreEqual(2, doc.SelectNodes("//b").Count);

    // works ok
    XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
    Assert.AreEqual(2, doc.SelectNodes("//b", nsmgr).Count);
}

[Test]
public void TestSelectWithNamespacesPrefixed_Ok()
{
    // xml to parse with defaultnamespace
    string xml = @"<a xmlns='urn:test.Schema'><b/><b/></a>";

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xml);

    // using XPath namespace via alias "t". works ok but xpath is to complicated
    XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
    nsmgr.AddNamespace("t", doc.DocumentElement.NamespaceURI);
    Assert.AreEqual(2, doc.SelectNodes("//t:b", nsmgr).Count);
}

1 ответ

Решение
// This will fail with dotnet 3.5sp1. Why? 
//!!!! 
Assert.AreEqual(2, doc.SelectNodes("//b", nsmgr).Count);

Это часто задаваемые вопросы. В XPath предполагается, что любое имя без префикса находится в "пространстве имен". Чтобы выбрать элементы, которые принадлежат пространству имен, в любом выражении XPath их имена должны начинаться с префикса, связанного с этим пространством имен. AddNamespace() Метод служит именно этой цели. Он создает связь между конкретным пространством имен и конкретным префиксом. Затем, если этот префикс используется в выражении XPath, можно выбрать элемент с префиксом.

В спецификации XPath W3C написано: "QName в тесте узла раскрывается в расширенное имя с использованием объявлений пространства имен из контекста выражения. Это аналогично тому, как выполняется расширение для имен типов элементов в начальных и конечных тегах. за исключением того, что пространство имен по умолчанию, объявленное с помощью xmlns, не используется: если у QName нет префикса, то URI пространства имен имеет значение null".

Смотрите это по адресу: http://w3.org/TR/xpath/.

Таким образом, любое имя без префикса считается находящимся в "пространстве имен". В предоставленном XML-документе нет b элементы в "нет пространства имен", и именно поэтому выражение XPath //b не выбирает никаких узлов вообще.

Используйте:

XmlNamespaceManager nsmanager = new XmlNamespaceManager(doc.NameTable);
nsmanager.AddNamespace("x", "urn:test.Schema");

и позже:

Assert.AreEqual(2, doc.SelectNodes("//x:b", nsmanager).Count);

Помните: вся цель регистрации пространства имен состоит в том, чтобы иметь возможность использовать префикс (в этом случае x) в любом выражении XPath.

Другие вопросы по тегам