Как использовать 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?
Ответы
Ответ 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);
}
}