Использование Xpath с пространством имен по умолчанию в C#
У меня есть XML-документ с пространством имен по умолчанию. Я использую XPathNavigator для выбора набора узлов с помощью Xpath следующим образом:
XmlElement myXML = ...;
XPathNavigator navigator = myXML.CreateNavigator();
XPathNodeIterator result = navigator.Select("/outerelement/innerelement");
Я не получаю никаких результатов: я предполагаю, что это потому, что я не указываю пространство имен. Как включить пространство имен в мой select?
12 ответов
во - первых-вам не нужен навигатор; SelectNodes / SelectSingleNode должно быть достаточно.
однако вам может понадобиться диспетчер пространств имен - например:
XmlElement el = ...; //TODO
XmlNamespaceManager nsmgr = new XmlNamespaceManager(
el.OwnerDocument.NameTable);
nsmgr.AddNamespace("x", el.OwnerDocument.DocumentElement.NamespaceURI);
var nodes = el.SelectNodes(@"/x:outerelement/x:innerelement", nsmgr);
вы можете попробовать инструмент визуализатора XPath, чтобы помочь вам.
XPathVisualizer свободный, простой в использовании.
важно: если вы используете Windows 7/8 и не видите пункты меню "Файл", "Правка" и "справка", нажмите клавишу ALT.
для тех, кто ищет быстрое решение для взлома, особенно в тех случаях, когда вы знаю XML и не нужно беспокоиться о пространствах имен и все такое, вы можете обойти эту раздражающую маленькую "функцию", просто прочитав файл в строку и заменив оскорбительный атрибут:
XmlDocument doc = new XmlDocument();
string fileData = File.ReadAllText(fileName);
fileData = fileData.Replace(" xmlns=\"", " whocares=\"");
using (StringReader sr = new StringReader(fileData))
{
doc.Load(sr);
}
XmlNodeList nodeList = doc.SelectNodes("project/property");
Я нахожу это проще, чем все другие бессмысленные требования префикса для пространства имен по умолчанию, когда я имею дело с одним файлом. Надеюсь, это поможет.
при использовании XPath в .NET (через навигатор или SelectNodes / SelectSingleNode) в XML с пространствами имен вам нужно:
предоставьте свой собственный XmlNamespaceManager
и явно префикс всех элементов в выражении XPath, которые находятся в пространстве имен.
последнее (перефразировано из источника MS, связанного ниже): потому что XPath 1.0 игнорирует спецификации пространства имен по умолчанию (xmlns="some_namespace"). Поэтому, когда вы используете имя элемента без префикса предполагается нулевое пространство.
вот почему .NET-реализация XPath игнорирует пространство имен с префиксной строкой.Пустое в XmlNamespaceManager и allways использует пространство имен null.
посмотреть XmlNamespaceManager и UndefinedXsltContext не обрабатывают пространство имен по умолчанию для получения дополнительной информации.
Я нахожу эту "функцию" очень неудобной, потому что вы не можете сделать старое пространство имен XPath, просто добавив по умолчанию объявление пространства имен, но так это работает.
вы можете использовать оператор XPath без использования XmlNamespaceManager следующим образом:
...
navigator.Select("//*[ local-name() = 'innerelement' and namespace-uri() = '' ]")
...
это простой способ выбора элемента в XML с определенным пространством имен по умолчанию.
дело в том, чтобы использовать:
namespace-uri() = ''
который будет найден элемент с пространством имен по умолчанию без использования префиксов.
Если пространства имен различаются для outerelement и innerelement
XmlNamespaceManager manager = new XmlNamespaceManager(myXmlDocument.NameTable);
manager.AddNamespace("o", "namespaceforOuterElement");
manager.AddNamespace("i", "namespaceforInnerElement");
string xpath = @"/o:outerelement/i:innerelement"
// For single node value selection
XPathExpression xPathExpression = navigator.Compile(xpath );
string reportID = myXmlDocument.SelectSingleNode(xPathExpression.Expression, manager).InnerText;
// For multiple node selection
XmlNodeList myNodeList= myXmlDocument.SelectNodes(xpath, manager);
я столкнулся с аналогичной проблемой с пустым пространством имен по умолчанию. В этом примере XML у меня есть сочетание элементов с префиксами пространства имен и один элемент (DataBlock) без:
<src:SRCExample xmlns="urn:some:stuff:here" xmlns:src="www.test.com/src" xmlns:a="www.test.com/a" xmlns:b="www.test.com/b">
<DataBlock>
<a:DocID>
<a:IdID>7</a:IdID>
</a:DocID>
<b:Supplimental>
<b:Data1>Value</b:Data1>
<b:Data2/>
<b:Extra1>
<b:More1>Value</b:More1>
</b:Extra1>
</b:Supplimental>
</DataBlock>
</src:SRCExample>
Я попытался использовать XPath, который работал в визуализаторе XPath, но не работал в моем коде:
XmlDocument doc = new XmlDocument();
doc.Load( textBox1.Text );
XPathNavigator nav = doc.DocumentElement.CreateNavigator();
XmlNamespaceManager nsman = new XmlNamespaceManager( nav.NameTable );
foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
nsman.AddNamespace( nskvp.Key, nskvp.Value );
}
XPathNodeIterator nodes;
XPathExpression failingexpr = XPathExpression.Compile( "/src:SRCExample/DataBlock/a:DocID/a:IdID" );
failingexpr.SetContext( nsman );
nodes = nav.Select( failingexpr );
while ( nodes.MoveNext() ) {
string testvalue = nodes.Current.Value;
}
Я сузили его до "блок" элемент по XPath, но не мог заставить его работать, кроме как просто подстановочных знаков блок элемент:
XPathExpression workingexpr = XPathExpression.Compile( "/src:SRCExample/*/a:DocID/a:IdID" );
failingexpr.SetContext( nsman );
nodes = nav.Select( failingexpr );
while ( nodes.MoveNext() ) {
string testvalue = nodes.Current.Value;
}
после многих заголовков и googling (что привело меня сюда) я решил заняться пространством имен по умолчанию непосредственно в моем загрузчике XmlNamespaceManager, изменив его на:
foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
nsman.AddNamespace( nskvp.Key, nskvp.Value );
if ( nskvp.Key == "" ) {
nsman.AddNamespace( "default", nskvp.Value );
}
}
теперь "default" и "" указывают на одно и то же пространство имен. Как только я это сделал, XPath "/src:SRCExample/default:DataBlock/a:DocID/a:IdID" вернул мои результаты так, как я хотел. Надеюсь, это поможет прояснить этот вопрос для других.
мой ответ расширяет предыдущий ответ Брэндон. Я использовал его пример для создания метода расширения следующим образом:
static public class XmlDocumentExt
{
static public XmlNamespaceManager GetPopulatedNamespaceMgr(this System.Xml.XmlDocument xd)
{
XmlNamespaceManager nmsp = new XmlNamespaceManager(xd.NameTable);
XPathNavigator nav = xd.DocumentElement.CreateNavigator();
foreach (KeyValuePair<string,string> kvp in nav.GetNamespacesInScope(XmlNamespaceScope.All))
{
string sKey = kvp.Key;
if (sKey == "")
{
sKey = "default";
}
nmsp.AddNamespace(sKey, kvp.Value);
}
return nmsp;
}
}
затем в моем XML-коде синтаксического анализа я просто добавляю одну строку:
XmlDocument xdCandidate = new XmlDocument();
xdCandidate.Load(sCandidateFile);
XmlNamespaceManager nmsp = xdCandidate.GetPopulatedNamespaceMgr(); // 1-line addition
XmlElement xeScoreData = (XmlElement)xdCandidate.SelectSingleNode("default:ScoreData", nmsp);
мне очень нравится этот метод, потому что он полностью динамичен с точки зрения загрузки пространств имен из исходного XML-файла, и он не полностью игнорирует концепцию пространств имен XML, поэтому это можно использовать с XML, который требует нескольких пространств имен для конфликтных ситуаций.
в моем случае добавление префикса не было практичным. Слишком много xml или xpath были определены во время выполнения. В конце концов я расширил methds на XmlNode. Это не было оптимизировано для производительности, и он, вероятно, не обрабатывает каждый случай, но он работает для меня до сих пор.
public static class XmlExtenders
{
public static XmlNode SelectFirstNode(this XmlNode node, string xPath)
{
const string prefix = "pfx";
XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
string prefixedPath = GetPrefixedPath(xPath, prefix);
return node.SelectSingleNode(prefixedPath, nsmgr);
}
public static XmlNodeList SelectAllNodes(this XmlNode node, string xPath)
{
const string prefix = "pfx";
XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
string prefixedPath = GetPrefixedPath(xPath, prefix);
return node.SelectNodes(prefixedPath, nsmgr);
}
public static XmlNamespaceManager GetNsmgr(XmlNode node, string prefix)
{
string namespaceUri;
XmlNameTable nameTable;
if (node is XmlDocument)
{
nameTable = ((XmlDocument) node).NameTable;
namespaceUri = ((XmlDocument) node).DocumentElement.NamespaceURI;
}
else
{
nameTable = node.OwnerDocument.NameTable;
namespaceUri = node.NamespaceURI;
}
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nameTable);
nsmgr.AddNamespace(prefix, namespaceUri);
return nsmgr;
}
public static string GetPrefixedPath(string xPath, string prefix)
{
char[] validLeadCharacters = "@/".ToCharArray();
char[] quoteChars = "\'\"".ToCharArray();
List<string> pathParts = xPath.Split("/".ToCharArray()).ToList();
string result = string.Join("/",
pathParts.Select(
x =>
(string.IsNullOrEmpty(x) ||
x.IndexOfAny(validLeadCharacters) == 0 ||
(x.IndexOf(':') > 0 &&
(x.IndexOfAny(quoteChars) < 0 || x.IndexOfAny(quoteChars) > x.IndexOf(':'))))
? x
: prefix + ":" + x).ToArray());
return result;
}
}
тогда в вашем коде просто используйте что-то вроде
XmlDocument document = new XmlDocument();
document.Load(pathToFile);
XmlNode node = document.SelectFirstNode("/rootTag/subTag");
надеюсь, что это помогает
я использовал хакерский, но полезный подход, описанный SpikeDog выше. Он работал очень хорошо, пока я не бросил на него выражение xpath, которое использовало трубы для объединения нескольких путей.
поэтому я переписал его с помощью регулярных выражений и решил поделиться:
public string HackXPath(string xpath_, string prefix_)
{
return System.Text.RegularExpressions.Regex.Replace(xpath_, @"(^(?![A-Za-z0-9\-\.]+::)|[A-Za-z0-9\-\.]+::|[@|/|\[])(?'Expression'[A-Za-z][A-Za-z0-9\-\.]*)", x =>
{
int expressionIndex = x.Groups["Expression"].Index - x.Index;
string before = x.Value.Substring(0, expressionIndex);
string after = x.Value.Substring(expressionIndex, x.Value.Length - expressionIndex);
return String.Format("{0}{1}:{2}", before, prefix_, after);
});
}
или, если кто-то должен использовать XPathDocument, как я:
XPathDocument xdoc = new XPathDocument(file);
XPathNavigator nav = xdoc.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable);
nsmgr.AddNamespace("y", "http://schemas.microsoft.com/developer/msbuild/2003");
XPathNodeIterator nodeIter = nav.Select("//y:PropertyGroup", nsmgr);
в этом случае, вероятно, это разрешение пространства имен, которое является причиной проблемы, но также возможно, что ваше выражение XPath не является правильным само по себе. Возможно, вы захотите сначала оценить его.
вот код, использующий XPathNavigator.
//xNav is the created XPathNavigator.
XmlNamespaceManager mgr = New XmlNamespaceManager(xNav.NameTable);
mgr.AddNamespace("prefix", "http://tempuri.org/");
XPathNodeIterator result = xNav.Select("/prefix:outerelement/prefix:innerelement", mgr);