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

Что такое XPath (в C # 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.

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


person Scott Stafford    schedule 26.03.2010    source источник
comment
Непонятно, чего именно вы хотите добиться. Какие критерии определяют, какие узлы вы ищете? Вы ищете элементы на основе их пространства имен? В этом случае ваш код будет знать пространство имен. Что касается {my uri} синтаксиса XPath, как вы думаете, где в спецификации XPath 1.0 был определен этот синтаксис? И независимо от того, помещаете ли вы URI пространства имен в фигурные скобки или передаете URI пространства имен методу AddNamespace, не имеет значения для вашего кода C #, в обоих случаях URI пространства имен должен быть доступен в виде строки.   -  person Martin Honnen    schedule 27.03.2010
comment
@Martin: я действительно хочу указать пространство имен в XPath, но у меня есть только URI пространства имен и нет префикса пространства имен. Я внимательно посмотрел на то, откуда я «изобрел» {}, и, возможно, просмотрел неправильно ... Я получил это из этой ссылки: jclark.com/xml/xmlns.htm. Спасибо что подметил это. Конечно, даже если это неверно, это кажется полезным, потому что уметь делать это легко ..;)   -  person Scott Stafford    schedule 27.03.2010
comment
Скотт, вам нужно будет выбрать любой разрешенный префикс, который вам нравится, связать его с URI пространства имен, которое вы используете с помощью AddNamespace (префикс, namespaceURI), и использовать выбранный префикс в своем выражении XPath. Так работает XPath, по крайней мере XPath 1.0. Префикс не обязательно должен существовать во входном XML или может отличаться от префикса, используемого во входном XML, выбор элемента будет происходить на основе совпадения пространства имен, а не префикса.   -  person Martin Honnen    schedule 27.03.2010
comment
Если вы хотите использовать нотацию Кларка, подумайте о том, чтобы не использовать XPath, а использовать методы оси LINQ to XML, такие как Descendants, которые принимают XName и поддерживают нотацию {}. Пример: foreach (XElement myNode in doc.Descendants("{lcmp}MyNode")). Конечно, вы также можете использовать переменную, например XNamespace df = "lcmp"; foreach (XElement myNode in doc.Descendants(df + "MyNode"))   -  person Martin Honnen    schedule 27.03.2010


Ответы (6)


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

person Mads Hansen    schedule 27.03.2010
comment
@Mads: Ах, интересно, я не знал о синтаксисе [namespace-uri () = 'lcmp' ... который должен работать, и если так (попробую в понедельник), я отмечу это как ответ. Знаете ли вы, что / configuration / {lcmp} MyNode действительно правильный и просто не поддерживается C #? - person Scott Stafford; 27.03.2010
comment
@Scott Нет, синтаксис, который вы пытались использовать, не является допустимым оператором XPATH и не поддерживается ни в одной известной мне реализации. Хотя он может расширяться до этого QName, вы не можете адресовать его таким образом в своем операторе XPATH. - person Mads Hansen; 27.03.2010
comment
Но если URI пространства имен известен (и Скотт теперь говорит, что это так), стоит отметить, что этот подход является ненужным хрупким по причине, которую утверждает Мэдс (вы рискуете сопоставить разные элементы в разных словарях). Тот факт, что это работает, не делает его хорошей идеей (если вы действительно не знаете URI). - person Andrew Walker; 29.03.2010
comment
@ Андрей: Я никогда не менял мелодию. URI пространства имен известен, как вы можете видеть в исходном вопросе. Команда xmlns = lcmp предоставляет URI пространства имен, а не префикс. И предложение @Mads - использовать local-name () И namespace-uri (), поэтому его ответ был правильным. Он продолжает говорить, что у вас есть возможность не использовать namespace-uri (), но это только запоздалая мысль. - person Scott Stafford; 29.03.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);
   }
person Martin Honnen    schedule 26.03.2010
comment
Да, думаю, это сработает, но я не могу этого сделать. Поскольку код синтаксического анализа XML не зависит от фактического файла XML и любых используемых им пространств имен, mgr.AddNamespace (df, lcmp); невозможно написать строчку ... - person Scott Stafford; 26.03.2010
comment
Но при синтаксическом анализе кода нельзя не учитывать имена элементов, верно? Пространство имен считается частью имени, поэтому игнорировать его - плохой дизайн, но если вы уверены, что не будет конфликтов пространства имен, вы можете сделать что-то вроде конфигурации / * [local-name () = 'MyNode'] - person Oleg Tkachenko; 26.03.2010
comment
Скотт, объясните, пожалуйста, как ваш код должен идентифицировать элемент, если URI пространства имен неизвестен? Что именно ищет ваш код, элементы с локальным именем MyNode в любом пространстве имен? Тогда воспользуйтесь предложением Олега. В противном случае более подробно объясните, какие именно элементы вы ищете. - person Martin Honnen; 26.03.2010
comment
/ Олег: XPath должен, конечно, указывать пространство имен, как вы говорите. Но XML, из которого я читаю, не является псевдонимом / префиксом пространства имен. / configuration / lcmp: MyNode неверен, потому что lcmp в этом XPath является префиксом пространства имен, а не URI пространства имен. / configuration / {lcmp} MyNode кажется правильным синтаксисом, но C # не поддерживает нотацию {}. - person Scott Stafford; 27.03.2010

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

Причина этой трудности лучше всего описана в спецификации пространств имен XML:

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

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

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

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

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

Изменить - вы знаете namespaceURI?

Получается, что ваш вопрос всех нас запутал. По-видимому, вы знаете 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-документа. Редко приходится использовать их.

person Andrew Walker    schedule 26.03.2010
comment
@Andrew: Я ЗНАЮ пространство имен заранее и могу поместить его в XPath. Я не знаю префикса пространства имен, который используется, когда вы говорите что-то вроде / configuration / lcmp: MyNode. / configuration / {lcmp} MyNode кажется подходящим синтаксисом для использования URI пространства имен вместо префикса, но C #, похоже, не поддерживает нотацию {}. И приставки у меня нет. - person Scott Stafford; 27.03.2010
comment
Ах я вижу. Я напишу новый ответ - в основном вам просто нужно знать, что префикс пространства имен в вашем XML-документе не имеет ничего общего с префиксом пространства имен в выражении XPath, за исключением того, что они оба должны сопоставляться с одним и тем же nsURI. - person Andrew Walker; 27.03.2010
comment
Очень информативное и подробное редактирование-запись, но я не думаю, что это действительно решает мой вопрос: что XPath находит этот узел? Кроме того, вы говорите, что если XML DID указывает префикс (а это не так), то запрос XPath, чтобы найти, что не может его использовать? - person Scott Stafford; 29.03.2010
comment
Что ж, ответ - какой бы префикс пространства имен XPath вы ни выбрали. Заявленный в XML-документе префикс отсутствия префикса вообще не имеет отношения к вашей проблеме. Только объявленный URI пространства имен. Вы выбираете соответствие между URI пространства имен и префиксом XPath, которое вы используете в своем выражении XPath. - person Andrew Walker; 29.03.2010
comment
Как указать префикс для использования в выражении XPath без написания кода C # и жесткого кодирования XmlNamespaceManager, чтобы знать все возможные URI? - person Scott Stafford; 29.03.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);
     }
   }
 }
}
person Dan Blanchard    schedule 26.03.2010
comment
@Dan: Да, я думаю, что это работает, но требует жесткого кодирования любых используемых пространств имен ... тогда как я могу управлять только XPath - см. Мой комментарий под ответом @Martin Honnen. - person Scott Stafford; 26.03.2010

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

using Wmhelp.XPath2;

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

Видеть :

XPath и XSLT 2.0 для .NET?

person Akli    schedule 19.03.2014

Мне настолько нравится @ 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);
        }
    }
person rasx    schedule 18.11.2015