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

Использование Xpath с пространством имен по умолчанию в С#

У меня есть XML-документ с пространством имен по умолчанию. Я использую XPathNavigator для выбора набора узлов с использованием Xpath следующим образом:

XmlElement myXML = ...;  
XPathNavigator navigator = myXML.CreateNavigator();
XPathNodeIterator result = navigator.Select("/outerelement/innerelement");

Я не получаю никаких результатов: я предполагаю, что это потому, что я не указываю пространство имен. Как включить пространство имен в мой выбор?

4b9b3361

Ответ 1

Сначала вам не нужен навигатор; 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);

Ответ 2

Возможно, вы захотите попробовать инструмент XPath Visualizer, чтобы помочь вам.

XPathVisualizer является бесплатным, простым в использовании.

alt text

ВАЖНО: Если вы используете Windows 7/8 и не видите пункты меню "Файл", "Редактировать" и "Справка", нажмите клавишу ALT.

Ответ 3

Для тех, кто ищет быстрое решение для взлома, особенно в тех случаях, когда вы знаете 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");

Я нахожу это проще, чем все другие не-смысл, требующие префикса для пространства имен по умолчанию, когда я имею дело с одним файлом. Надеюсь, это поможет.

Ответ 4

При использовании XPath в .NET(через навигатор или SelectNodes/SelectSingleNode) в XML с пространствами имен вам необходимо:

  • предоставить свой собственный XmlNamespaceManager

  • и явно префикс всех элементов в выражении XPath, которые находятся в пространстве имен.

Последнее (перефразировано из источника MS, связанного ниже): поскольку XPath 1.0 игнорирует спецификации пространства имен по умолчанию (xmlns = "some_namespace" ). Поэтому, когда вы используете имя элемента без префикса, он принимает пустое пространство имен.

Для чего .NET-реализация XPath игнорирует пространство имен с префиксом String.Empty в XmlNamespaceManager и всегда использует нулевое пространство имен.

См. XmlNamespaceManager и UndefinedXsltContext не обрабатывают пространство имен по умолчанию для получения дополнительной информации.

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

Ответ 5

Вы можете использовать инструкцию XPath без использования XmlNamespaceManager, например:

...
navigator.Select("//*[ local-name() = 'innerelement' and namespace-uri() = '' ]")
...

Это простой способ выбора элемента в XML с определением пространства имен по умолчанию.

Цель состоит в том, чтобы использовать:

namespace-uri() = ''

который найдет элемент с пространством имен по умолчанию без использования префиксов.

Ответ 6

Если пространства имен отличаются для 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);

Ответ 7

Я столкнулся с аналогичной проблемой с пустым пространством имен по умолчанию. В этом примере 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 Visualizer, но не работал в моем коде:

  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;
  }

Я сузил его до элемента "DataBlock" XPath, но не смог заставить его работать, за исключением простого подстановки элемента DataBlock:

  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" вернул мои результаты так, как я хотел. Надеюсь, это поможет прояснить проблему для других.

Ответ 8

Мой ответ расширяет предыдущий ответ Брэндона. Я использовал его пример для создания метода расширения следующим образом:

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, для которого требуется несколько пространств имен для deconfliction.

Ответ 9

В моем случае добавление префикса было непрактичным. Слишком большая часть xml или xpath была определена во время выполнения. В конце концов я расширил методы на 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");

Надеюсь, что это поможет

Ответ 10

Я использовал взломанный, но полезный подход, описанный 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);
                });
}

Ответ 11

Или, если кто-то должен использовать XPathDocument, например me:

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);

Ответ 12

В этом случае это, вероятно, разрешение пространства имен, которое является причиной проблемы, но также возможно, что ваше выражение 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);

Ответ 13

1] Если у вас есть XML файл без префикса в пространстве имен:

<bookstore xmlns="http://www.contoso.com/books">
…
</bookstore>

у вас есть этот обходной путь:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
// ignore the namespace as there is a single default namespace:
reader.Namespaces = false;
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

2] Если у вас есть XML файл с префиксом в пространстве имен:

<bookstore xmlns:ns="http://www.contoso.com/books">
…
</bookstore>

Использовать этот:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

Конечно, вы можете использовать управление именами пространства при необходимости:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(reader.NameTable);
nsmgr.AddNamespace("ns", "http://www.contoso.com/book");
XPathNodeIterator nodes = navigator.Select("//book", nsmgr);

Я думаю, что это самый простой способ заставить код работать в большинстве случаев.

Я надеюсь, что это поможет решить эту проблему Microsoft...