Использование Xpath с пространством имен по умолчанию в C # - PullRequest
59 голосов
/ 25 февраля 2009

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

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

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

Ответы [ 13 ]

78 голосов
/ 25 февраля 2009

Первый - вам не нужен навигатор; 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);
48 голосов
/ 12 декабря 2009

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

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

alt text

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

24 голосов
/ 18 апреля 2014

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

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

19 голосов
/ 26 мая 2010

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

  • укажите свой собственный XmlNamespaceManager

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

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

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

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

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

6 голосов
/ 14 марта 2012

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

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

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

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

namespace-uri() = ''

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

5 голосов
/ 04 мая 2016

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

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

5 голосов
/ 27 сентября 2011

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

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

5 голосов
/ 25 февраля 2009

В случае, если пространства имен различаются для внешнего элемента и элемента 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);
3 голосов
/ 13 января 2010

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

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

1 голос
/ 03 июля 2012

Или, если кто-то должен использовать 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);
...