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


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

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

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

12 54

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 свободный, простой в использовании.

alt text

важно: если вы используете Windows 7/8 и не видите пункты меню File, Edit и Help, нажмите клавишу 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 и всегда использует пространство имен 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 "/ЦСИ:SRCExample/по умолчанию:блок/а:идентификатора docid/а:креветка" вернули мои результаты как я и хотела. Надеюсь, это поможет прояснить этот вопрос для других.

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

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