Подтвердить что ты не робот

Как использовать XPath с пространством имен по умолчанию без префикса?

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

<?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.

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

4b9b3361

Ответ 1

Элемент 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), которые используют одно и то же имя.

Ответ 2

Вам нужно использовать 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);
   }

Ответ 3

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 4

Вот пример того, как сделать пространство имен доступным для выражения 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);
     }
   }
 }
}

Ответ 5

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

using Wmhelp.XPath2;

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

Смотрите:

XPath и XSLT 2.0 для .NET?

Ответ 6

Мне нравится @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);
        }
    }