Как использовать XPath с пространством имен по умолчанию без префикса? - PullRequest
34 голосов
/ 26 марта 2010

Что такое XPath (в C # API для XDocument.XPathSelectElements (xpath, nsman), если это имеет значение) для запроса всех MyNode из этого документа?

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <MyNode xmlns="lcmp" attr="true">
    <subnode />
  </MyNode>
</configuration>
  • Я попытался /configuration/MyNode, что неверно, поскольку он игнорирует пространство имен.
  • Я попытался /configuration/lcmp:MyNode, что неверно, поскольку lcmp - это URI, а не префикс.
  • Я пытался /configuration/{lcmp}MyNode, что не удалось, потому что Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

РЕДАКТИРОВАТЬ: я не могу использовать mgr.AddNamespace("df", "lcmp");, как предлагали некоторые из ответчиков. Это требует, чтобы программа синтаксического анализа XML знала все пространства имен, которые я планирую использовать заранее. Так как это должно быть применимо к любому исходному файлу, я не знаю, для каких пространств имен вручную добавляются префиксы. Похоже, {my uri} - это синтаксис XPath, но Microsoft не удосужилась реализовать это ... верно?

Ответы [ 6 ]

37 голосов
/ 27 марта 2010

Элемент configuration находится в безымянном пространстве имен, а MyNode привязан к пространству имен lcmp без префикса пространства имен.

Этот оператор XPATH позволит вам обратиться к элементу MyNode без объявления пространства имен lcmp или использовать префикс пространства имен в XPATH:

/configuration/*[namespace-uri()='lcmp' and local-name()='MyNode']

Соответствует любому элементу, который является потомком configuration, и затем использует файл-предикат с функциями namespace-uri() и local-name(), чтобы ограничить его MyNode элемент.

Если вы не знаете, какое пространство имен-uri будет использоваться для элементов, то вы можете сделать XPATH более универсальным и просто сопоставить с local-name():

/configuration/*[local-name()='MyNode']

Однако вы рискуете сопоставить разные элементы в разных словарях (связанных с разными пространствами имен-uri), которые случайно используют одно и то же имя.

12 голосов
/ 26 марта 2010

Вам необходимо использовать XmlNamespaceManager следующим образом:

   XDocument doc = XDocument.Load(@"..\..\XMLFile1.xml");
   XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable());
   mgr.AddNamespace("df", "lcmp");
   foreach (XElement myNode in doc.XPathSelectElements("configuration/df:MyNode", mgr))
   {
       Console.WriteLine(myNode.Attribute("attr").Value);
   }
7 голосов
/ 26 марта 2010

XPath (намеренно) не предназначен для случая, когда вы хотите использовать то же выражение XPath для некоторых неизвестных пространств имен, которые существуют только в документе XML. Предполагается, что вы заранее знаете пространство имен, объявите пространство имен процессору XPath и будете использовать имя в своем выражении. Ответы Мартина и Дэна показывают, как это сделать в C #.

Причина этой трудности лучше всего выражена в пространствах имен XML spec:

Мы предполагаем приложения расширяемого языка разметки (XML), в которых один XML-документ может содержать элементы и атрибуты (здесь называемые «словарь разметки»), которые определены и используются несколькими программными модулями. Одной из причин этого является модульность: если существует такой словарь разметки, который понятен и для которого имеется полезное программное обеспечение, лучше повторно использовать эту разметку, а не заново ее изобретать.

Такие документы, содержащие несколько словарей разметки, создают проблемы распознавания и коллизий. Программные модули должны иметь возможность распознавать элементы и атрибуты, которые они предназначены для обработки, даже в условиях «коллизий», возникающих, когда разметка, предназначенная для какого-либо другого программного пакета, использует то же имя элемента или имя атрибута.

Эти соображения требуют, чтобы конструкции документа имели имена, сконструированные так, чтобы избежать столкновений между именами из разных словарей разметки. Эта спецификация описывает механизм, пространства имен XML, который выполняет это, назначая расширенные имена элементам и атрибутам.

То есть пространства имен должны использоваться, чтобы удостовериться, что вы знаете, о чем говорит ваш документ: элемент <head> говорит о преамбуле к документу XHTML или заголовке somebody в документе AnatomyML? Вы никогда не «должны» быть агностиком в отношении пространства имен, и это почти первое, что вы должны определить в любом словаре XML.

Должна быть возможность делать то, что вы хотите, но я не думаю, что это можно сделать в одном выражении XPath. Прежде всего вам нужно покопаться в документе и извлечь все namespaceURI, затем добавить их в менеджер пространства имен и затем запустить нужное выражение XPath (и вам нужно кое-что узнать о распределении пространств имен в документе указать, или у вас есть много выражений для запуска). Я думаю, что вам, вероятно, лучше всего использовать что-то кроме XPath (например, DOM или SAX-подобный API) для поиска namespaceURI, но вы также можете изучить ось пространства имен XPath (в XPath 1.0), используйте namespace-uri-from-QName функцию (в XPath 2.0) или используйте выражения, как у Олега "configuration/*[local-name() = 'MyNode']". В любом случае, я думаю, что вам лучше всего избегать написания XPath, независимого от пространства имен! Почему вы не знаете свое пространство имен раньше времени? Как вы собираетесь избегать совпадений с вещами, которые вы не собираетесь сопоставлять?

Редактировать - вы знаете пространство именURI?

Так что получается, что ваш вопрос смутил нас всех. Очевидно, вы знаете URI пространства имен, но вы не знаете префикс пространства имен, который используется в документе XML. Действительно, в этом случае префикс пространства имен не используется, и URI становится пространством имен по умолчанию, где он определен. Главное, что нужно знать, это то, что выбранный префикс (или отсутствие префикса) не имеет отношения к вашему выражению XPath (и синтаксическому анализу XML в целом). Атрибут prefix / xmlns - это всего лишь один из способов связать узел с URI пространства имен, когда документ выражен в виде текста. Возможно, вы захотите взглянуть на этот ответ , где я попытаюсь уточнить префиксы пространства имен.

Вы должны попытаться представить XML-документ таким же образом, как и анализатор - у каждого узла есть URI пространства имен и локальное имя. Правила префикса / наследования пространства имен просто экономят при наборе URI много раз. Один из способов записать это в нотации Кларка: то есть вы пишете {http://www.example.com/namespace/example}LocalNodeName,, но эта нотация обычно используется только для документации - XPath ничего не знает об этой нотации.

Вместо этого XPath использует свои собственные префиксы пространства имен. Что-то вроде /ns1:root/ns2:node. Но они полностью отделены и не имеют ничего общего с какими-либо префиксами, которые могут использоваться в исходном документе XML. Любая реализация XPath будет иметь возможность сопоставить свои собственные префиксы с URI пространства имен. Для реализации C # вы используете XmlNamespaceManager, в Perl вы предоставляете хеш, xmllint принимает аргументы командной строки ... Так что все, что вам нужно сделать, - это создать произвольный префикс для известного вам URI пространства имен и использовать этот префикс в XPath выражение. Неважно, какой префикс вы используете, в XML вы просто заботитесь о сочетании URI и localName.

Еще одна вещь, которую нужно помнить (это часто удивляет), это то, что XPath не выполняет наследование пространства имен. Вам необходимо добавить префикс для каждого, у которого есть пространство имен, независимо от того, происходит ли пространство имен от наследования, атрибута xmlns или префикса пространства имен. Кроме того, хотя вы всегда должны думать с точки зрения URI и localNames, существуют также способы доступа к префиксу из XML-документа. Это редко приходится использовать их.

4 голосов
/ 26 марта 2010

Вот пример того, как сделать пространство имен доступным для выражения XPath в Метод расширения XPathSelectElements:

using System;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Xml;
namespace XPathExpt
{
 class Program
 {
   static void Main(string[] args)
   {
     XElement cfg = XElement.Parse(
       @"<configuration>
          <MyNode xmlns=""lcmp"" attr=""true"">
            <subnode />
          </MyNode>
         </configuration>");
     XmlNameTable nameTable = new NameTable();
     var nsMgr = new XmlNamespaceManager(nameTable);
     // Tell the namespace manager about the namespace
     // of interest (lcmp), and give it a prefix (pfx) that we'll
     // use to refer to it in XPath expressions. 
     // Note that the prefix choice is pretty arbitrary at 
     // this point.
     nsMgr.AddNamespace("pfx", "lcmp");
     foreach (var el in cfg.XPathSelectElements("//pfx:MyNode", nsMgr))
     {
         Console.WriteLine("Found element named {0}", el.Name);
     }
   }
 }
}
1 голос
/ 20 марта 2014

Пример с Xpath 2.0 + библиотека:

using Wmhelp.XPath2;

doc.XPath2SelectElements("/*:configuration/*:MyNode");

См .:

XPath и XSLT 2.0 для .NET?

0 голосов
/ 19 ноября 2015

Мне нравится @ mads-hansen, его ответ, настолько хорошо, что я написал следующие члены общего назначения:

    /// <summary>
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
    /// </summary>
    /// <param name="childElementName">Name of the child element.</param>
    /// <returns></returns>
    public static string GetLocalNameXPathQuery(string childElementName)
    {
        return GetLocalNameXPathQuery(namespacePrefixOrUri: null, childElementName: childElementName, childAttributeName: null);
    }

    /// <summary>
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
    /// </summary>
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param>
    /// <param name="childElementName">Name of the child element.</param>
    /// <returns></returns>
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName)
    {
        return GetLocalNameXPathQuery(namespacePrefixOrUri, childElementName, childAttributeName: null);
    }

    /// <summary>
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query.
    /// </summary>
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param>
    /// <param name="childElementName">Name of the child element.</param>
    /// <param name="childAttributeName">Name of the child attribute.</param>
    /// <returns></returns>
    /// <remarks>
    /// This routine is useful when namespace-resolving is not desirable or available.
    /// </remarks>
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName, string childAttributeName)
    {
        if (string.IsNullOrEmpty(childElementName)) return null;

        if (string.IsNullOrEmpty(childAttributeName))
        {
            return string.IsNullOrEmpty(namespacePrefixOrUri) ?
                string.Format("./*[local-name()='{0}']", childElementName)
                :
                string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']", namespacePrefixOrUri, childElementName);
        }
        else
        {
            return string.IsNullOrEmpty(namespacePrefixOrUri) ?
                string.Format("./*[local-name()='{0}']/@{1}", childElementName, childAttributeName)
                :
                string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']/@{2}", namespacePrefixOrUri, childElementName, childAttributeName);
        }
    }
...