Pergunta

Eu tenho um arquivo XML que começa assim:

<Elements name="Entities" xmlns="XS-GenerationToolElements">

Vou ter que abrir um monte de esses arquivos. Cada uma delas tem um espaço de nomes diferente, mas terá apenas um namespace de cada vez (eu nunca vou encontrar dois namespaces definidos em um arquivo xml).

Usando XPath Eu gostaria de ter uma maneira automática para adicionar o namespace dado ao gerente de namespace. Até agora, eu só poderia obter o namespace por analisar o arquivo XML, mas eu tenho uma instância de XPathNavigator e ele deve ter uma agradável e limpo maneira de obter os namespaces, certo?

- OU -

Uma vez que eu só tenho um espaço de nomes, de alguma forma fazer XPath usar a única que está presente no XML, evitando assim desordenar o código sempre acrescentando o namespace.

Foi útil?

Solução

Existem algumas técnicas que você pode tentar; que você uso vai depender exatamente o que informação que você precisa para sair do documento, como rigorosa você quer ser, e como conformant implementação XPath que você está usando é.

Uma maneira de obter o URI namespace associado com um prefixo especial está usando o eixo namespace::. Isto lhe dará um nó namespace cujo nome é o prefixo e cujo valor é o namespace URI. Por exemplo, você poderia obter o namespace padrão URI no elemento documento usando o caminho:

/*/namespace::*[name()='']

Você pode ser capaz de usar isso para configurar as associações de namespace para o seu XPathNavigator. Esteja avisado, porém, que o eixo namespace:: é um desses cantos do XPath 1.0, que nem sempre é implementada.

A segunda maneira de conseguir que namespace URI é usar a função namespace-uri() sobre o elemento de documento (o que você disse será sempre em que namespace). A expressão:

namespace-uri(/*)

lhe dará esse namespace.

Uma alternativa seria esquecer sobre como associar um prefixo com que namespace, e apenas fazer o seu caminho livre de namespace. Você pode fazer isso usando a função local-name() sempre que você precisa para se referir a um elemento cujo namespace que você não sabe. Por exemplo:

//*[local-name() = 'Element']

Você pode ir um passo adiante e testar o namespace URI do elemento contra a do elemento do documento, se você realmente queria:

//*[local-name() = 'Element' and namespace-uri() = namespace-uri(/*)]

A opção final, dado que o namespace parece não significam nada para você, seria para executar o seu XML através de um filtro que retira o namespaces. Então você não terá que se preocupar com eles em sua XPath em tudo. A maneira mais fácil de fazer isso seria simplesmente para remover o atributo xmlns com uma expressão regular, mas você poderia fazer algo mais complexo se você precisava fazer outra arrumação, ao mesmo tempo.

Outras dicas

Esta transformação xslt 40-line fornece todas as informações úteis sobre os namespaces em um determinado documento XML :

<xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:ext="http://exslt.org/common"
   exclude-result-prefixes="ext"
>

<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:strip-space elements="*"/>

<xsl:key name="kNsByNsUri" match="ns" use="@uri"/>

<xsl:variable name="vXmlNS" 
    select="'http://www.w3.org/XML/1998/namespace'"/>

<xsl:template match="/">
  <xsl:variable name="vrtfNamespaces">
    <xsl:for-each select=
      "//namespace::*
             [not(. = $vXmlNS)
             and
              . = namespace-uri(..)
           ]">
      <ns element="{name(..)}"
          prefix="{name()}" uri="{.}"/>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="vNamespaces"
    select="ext:node-set($vrtfNamespaces)/*"/>

  <namespaces>
          <xsl:for-each select=
           "$vNamespaces[generate-id()
                        =
                         generate-id(key('kNsByNsUri',@uri)[1])
                        ]">
            <namespace uri="{@uri}">
              <xsl:for-each select="key('kNsByNsUri',@uri)/@element">
                <element name="{.}" prefix="{../@prefix}"/>
              </xsl:for-each>
            </namespace>
          </xsl:for-each>
  </namespaces>
</xsl:template>

Quando aplicado no seguinte documento XML:

<a xmlns="my:def1" xmlns:n1="my:n1"
   xmlns:n2="my:n2" xmlns:n3="my:n3">
  <b>
    <n1:d/>
  </b>
  <n1:c>
    <n2:e>
      <f/>
    </n2:e>
  </n1:c>
  <n2:g/>
</a>

O resultado queria é produzido:

<namespaces>
   <namespace uri="my:def1">
      <element name="a" prefix=""/>
      <element name="b" prefix=""/>
      <element name="f" prefix=""/>
   </namespace>
   <namespace uri="my:n1">
      <element name="n1:d" prefix="n1"/>
      <element name="n1:c" prefix="n1"/>
   </namespace>
   <namespace uri="my:n2">
      <element name="n2:e" prefix="n2"/>
      <element name="n2:g" prefix="n2"/>
   </namespace>
</namespaces>

Infelizmente, XPath não tem qualquer conceito de "namespace default". Você precisa se registrar namespaces com prefixos com o contexto XPath, e depois usar esses prefixos em suas expressões XPath. Isso significa que para muito detalhado XPath, mas é uma lacuna básica de XPath 1. Aparentemente XPath 2 vai resolver isso, mas isso é inútil para você agora.

Eu sugiro que você examine programaticamente o documento XML para o namespace, associado que namespace com um prefixo no contexto XPath, em seguida, usar o prefixo nas expressões XPath.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top