web-dev-qa-db-fra.com

Comment récupérer des espaces de noms dans des fichiers XML à l'aide de Xpath

J'ai un fichier XML qui commence comme ceci:

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

Je vais devoir ouvrir beaucoup de ces fichiers. Chacun d'eux a un espace de noms différent mais n'aura qu'un seul espace de noms à la fois (je ne trouverai jamais deux espaces de noms définis dans un fichier xml).

En utilisant XPath, j'aimerais avoir un moyen automatique d'ajouter l'espace de noms donné au gestionnaire d'espaces de noms. Jusqu'à présent, je n'ai pu obtenir l'espace de noms qu'en analysant le fichier xml, mais j'ai une instance XPathNavigator et cela devrait avoir un moyen agréable et propre d'obtenir les espaces de noms, non?

- OR -

Étant donné que je n'ai qu'un seul espace de noms, faites en sorte que XPath utilise le seul qui soit présent dans le xml, évitant ainsi d'encombrer le code en ajoutant toujours l'espace de noms.

37
Luis Filipe

Il existe quelques techniques que vous pourriez essayer; que vous utilisez dépendra exactement des informations dont vous avez besoin pour sortir du document, de votre rigueur et de la conformité de l'implémentation XPath que vous utilisez.

Une façon d'obtenir l'URI de l'espace de noms associé à un préfixe particulier est d'utiliser l'axe namespace::. Cela vous donnera un nœud d'espace de noms dont le nom est le préfixe et dont la valeur est l'URI de l'espace de noms. Par exemple, vous pouvez obtenir l'URI de l'espace de noms par défaut sur l'élément de document en utilisant le chemin:

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

Vous pourrez peut-être l'utiliser pour configurer les associations d'espaces de noms pour votre XPathNavigator. Soyez averti, cependant, que l'axe namespace:: Est l'un de ces coins de XPath 1.0 qui n'est pas toujours implémenté.

Une deuxième façon d'obtenir cet URI d'espace de noms consiste à utiliser la fonction namespace-uri() sur l'élément de document (dont vous avez dit qu'il sera toujours dans cet espace de noms). L'expression:

namespace-uri(/*)

vous donnera cet espace de noms.

Une alternative serait d'oublier d'associer un préfixe à cet espace de noms et de rendre votre chemin sans espace de noms. Vous pouvez le faire en utilisant la fonction local-name() chaque fois que vous avez besoin de faire référence à un élément dont vous ne connaissez pas l'espace de noms. Par exemple:

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

Vous pouvez aller plus loin et tester l'URI de l'espace de noms de l'élément par rapport à celui de l'élément de document, si vous le vouliez vraiment:

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

Une dernière option, étant donné que l'espace de noms semble ne rien signifier pour vous, serait d'exécuter votre XML via un filtre qui supprime les espaces de noms. Vous n'aurez alors plus à vous en préoccuper dans votre XPath. La façon la plus simple de le faire serait simplement de supprimer l'attribut xmlns avec une expression régulière, mais vous pourriez faire quelque chose de plus complexe si vous aviez besoin de faire d'autres rangements en même temps.

83
JeniT

Cette transformation xslt de 40 lignes fournit toutes les informations utiles sur les espaces de noms dans un document XML donné:

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

Lorsqu'il est appliqué sur le document XML suivant:

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

le résultat souhaité est produit:

<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>
10
Dimitre Novatchev

Malheureusement, XPath n'a aucun concept de "namespace par défaut". Vous devez enregistrer des espaces de noms avec des préfixes dans le contexte XPath, puis utiliser ces préfixes dans vos expressions XPath. Cela signifie pour xpath très verbeux, mais c'est une lacune de base de XPath 1. Apparemment, XPath 2 corrigera cela, mais cela ne vous est pas utile pour le moment.

Je vous suggère d'examiner par programme votre document XML pour l'espace de noms, d'associer cet espace de noms à un préfixe dans le contexte XPath, puis d'utiliser le préfixe dans les expressions xpath.

4
skaffman