it-swarm.dev

Como eu uso o XPath com um namespace padrão sem prefixo?

O que é o XPath (em C # API para XDocument.XPathSelectElements (xpath, nsman) se for importante) para consultar todos os MyNodes deste documento?

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <MyNode xmlns="lcmp" attr="true">
    <subnode />
  </MyNode>
</configuration>
  • Eu tentei /configuration/MyNode que está errado porque ignora o namespace.
  • Eu tentei /configuration/lcmp:MyNode que está errado porque lcmp é o URI, não o prefixo.
  • Eu tentei /configuration/{lcmp}MyNode que falhou porque Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

EDIT: eu não posso usar mgr.AddNamespace("df", "lcmp"); como alguns dos respondedores sugeriram. Isso requer que o programa de análise XML conheça todos os namespaces que pretendo usar antecipadamente. Como isso deve ser aplicável a qualquer arquivo de origem, não sei quais namespaces adicionar manualmente os prefixos. Parece que {my uri} é a sintaxe XPath, mas a Microsoft não se preocupou em implementar isso ... true?

34
Scott Stafford

O elemento configuration está no namespace sem nome, e o MyNode é ligado ao namespace lcmp sem um prefixo de namespace.

Esta instruçãoXPATHlhe permitirá endereçar o elemento MyNode sem ter declarado o namespace lcmp ou usar um prefixo de namespace no seu XPATH:

/configuration/*[namespace-uri()='lcmp' and local-name()='MyNode']

Ele corresponde a qualquer elemento que é filho de configuration e, em seguida, usa um arquivador de predicado com namespace-uri() e local-name() functions para restringi-lo ao elemento MyNode.

Se você não sabe qual namespace-uri será usado para os elementos, então você pode fazer oXPATHmais genérico e apenas combinar na local-name():

/configuration/*[local-name()='MyNode']

No entanto, você corre o risco de combinar diferentes elementos em diferentes vocabulários (ligados a diferentes namespace-uri's) que usam o mesmo nome.

37
Mads Hansen

Você precisa usar um XmlNamespaceManager da seguinte maneira:

   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);
   }
12
Martin Honnen

O XPath não é (deliberadamente) projetado para o caso em que você deseja usar a mesma expressão XPath para alguns espaços de nomes desconhecidos que residem apenas no documento XML. Espera-se que você conheça o namespace antes do tempo, declare o namespace para o processador XPath e use o nome em sua expressão. As respostas de Martin e Dan mostram como fazer isso em C #. 

O motivo dessa dificuldade é melhor expresso nos namespaces XML ​​ spec:

Nós imaginamos aplicações de Extensible Markup Language (XML) onde um único documento XML pode conter elementos e atributos (aqui referidos como um "vocabulário de marcação") que são definidos e utilizados por múltiplos módulos de software. Uma motivação para isso é a modularidade: se existe um vocabulário de marcação que seja bem compreendido e para o qual haja software útil disponível, é melhor reutilizar essa marcação em vez de reinventá-la.

Esses documentos, contendo vários vocabulários de marcação, apresentam problemas de reconhecimento e colisão. Módulos de software precisam ser capazes de reconhecer os elementos e atributos que eles são projetados para processar, mesmo em face de "colisões" que ocorrem quando a marcação destinada a algum outro pacote de software usa o mesmo nome de elemento ou nome de atributo.

Essas considerações exigem que as construções de documentos tenham nomes construídos de modo a evitar conflitos entre nomes de diferentes vocabulários de marcação. Esta especificação descreve um mecanismo, namespaces XML, que faz isso atribuindo nomes expandidos a elementos e atributos. 

Ou seja, os namespaces devem ser usados ​​para garantir que você saiba sobre o que seu documento está falando: o elemento <head> está falando sobre o preâmbulo de um documento XHTML ou algum objeto de cabeça em um documento AnatomyML? Você nunca "deveria" ser agnóstico sobre o namespace e é basicamente a primeira coisa que você deve definir em qualquer vocabulário XML. 

Deve ser possível fazer o que você quer, mas eu não acho que isso pode ser feito em uma única expressão XPath. Primeiro de tudo, você precisa vasculhar o documento e extrair todos os namespaceURIs, depois adicioná-los ao gerenciador de namespaces e, em seguida, executar a expressão XPath real desejada (e você precisa saber algo sobre a distribuição dos namespaces no documento neste documento). ponto, ou você tem um monte de expressões para executar). Eu acho que você provavelmente é melhor usar algo diferente de XPath (por exemplo, uma API DOM ou SAX-like) para encontrar o namespaceURIs, mas você também pode explorar o eixo namespace XPath (no XPath 1.0), use o namespace-uri-from-QName function (no XPath 2.0) ou use expressões como "configuration/*[local-name() = 'MyNode']" de Oleg. De qualquer forma, eu acho que sua melhor aposta é tentar evitar escrever XPath agnóstico de namespace! Por que você não conhece seu namespace antes do tempo? Como você vai evitar combinar coisas que você não pretende combinar?

Editar - você conhece o namespaceURI?

Então, a sua pergunta nos confundiu a todos. Aparentemente, você conhece o URI do namespace, mas não conhece o prefixo de namespace usado no documento XML. De fato, nesse caso, nenhum prefixo de namespace é usado e o URI se torna o espaço padrão onde ele é definido. A principal coisa a saber é que o prefixo escolhido (ou a falta de um prefixo) é irrelevante para sua expressão XPath (e análise XML em geral). O atributo prefixo/xmlns é apenas uma maneira de associar um nó a um URI de namespace quando o documento é expresso como texto. Você pode querer dar uma olhada em esta resposta , onde eu tento esclarecer prefixos de namespace. 

Você deve tentar pensar no documento XML da mesma maneira que o analisador pensa - cada nó tem um URI de namespace e um nome local. As regras de prefixo/herança do espaço de nomes apenas poupam a escrita do URI muitas vezes. Uma maneira de escrever isso é na notação de Clark: isto é, você escreve { http://www.example.com/namespace/example } LocalNodeName, mas essa notação é normalmente usada apenas para documentação - XPath não sabe nada sobre essa notação.

Em vez disso, o XPath usa seus próprios prefixos de namespace. Algo como /ns1:root/ns2:node. Mas estes são completamente separados e não têm nada a ver com quaisquer prefixos que possam ser usados ​​no documento XML original. Qualquer implementação XPath terá uma maneira de mapear seus próprios prefixos com URIs de namespace. Para a implementação do C # você usa um XmlNamespaceManager, em Perl você fornece um hash, xmllint pega argumentos da linha de comando ... Então tudo que você precisa fazer é criar um prefixo arbitrário para o URI do namespace que você conhece, e usar este prefixo na expressão XPath . Não importa qual prefixo você usa, em XML você só se preocupa com a combinação do URI e do localName. 

A outra coisa a lembrar (muitas vezes é uma surpresa) é que o XPath não faz herança de namespace. Você precisa adicionar um prefixo para cada um que tenha um namespace, independentemente de o namespace vir de herança, de um atributo xmlns ou de um prefixo de namespace. Além disso, embora você deva sempre pensar em termos de URIs e localNames, também há maneiras de acessar o prefixo de um documento XML. É raro ter que usar isso. 

6
Andrew Walker

Veja um exemplo de como disponibilizar o namespace para a expressão XPath no método de extensão 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);
     }
   }
 }
}
4
Dan Blanchard

Exemplo com Xpath 2.0 + uma biblioteca:

using Wmhelp.XPath2;

doc.XPath2SelectElements("/*:configuration/*:MyNode");

Vejo :

XPath e XSLT 2.0 para .NET?

1
Akli

Eu gosto de @ mads-hansen, sua resposta, tão bem que eu escrevi esses membros de classe utilitária de propósito geral:

    /// <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);
        }
    }
0
rasx