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

StackOverflow https://stackoverflow.com/questions/122463

  •  02-07-2019
  •  | 
  •  

Question

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 espace de nom à la fois (je ne trouverai jamais deux espaces de noms définis dans un fichier xml).

Utilisation de XPath J'aimerais disposer d’un moyen automatique d’ajouter l’espace de noms donné au gestionnaire d’espaces de noms. Jusqu'à présent, je ne pouvais obtenir l'espace de noms qu'en analysant le fichier xml, mais j'ai une instance de XPathNavigator et il devrait disposer d'un moyen propre et agréable d'obtenir les espaces de noms, n'est-ce pas?

- OU -

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

Était-ce utile?

La solution

Vous pouvez essayer quelques techniques. que vous utiliserez dépendra des informations à extraire du document, de votre rigueur et de la conformité de l’implémentation XPath que vous utilisez.

L’un des moyens d’obtenir l’URI d’espace de nom associé à un préfixe particulier consiste à 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 d’espace de nom par défaut sur l’élément de document à l’aide du chemin:

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

Vous pourrez peut-être l'utiliser pour configurer les associations d'espace de nom de 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 autre façon d'obtenir cet URI d'espace de noms consiste à utiliser la fonction namespace-uri () sur l'élément de document (vous avez dit que ce 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 exempt d’espace de noms. Vous pouvez le faire en utilisant la fonction local-name () chaque fois que vous devez faire référence à un élément dont vous ne connaissez pas l'espace de nom. Par exemple:

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

Vous pouvez aller encore plus loin et tester l'URI d'espace de nom de l'élément par rapport à celui de l'élément document, si vous le souhaitiez réellement:

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

Une dernière option, étant donné que l’espace de nom ne semble rien dire pour vous, serait d’exécuter votre code XML à travers un filtre qui supprime les espaces de nom. Dans ce cas, vous n'aurez plus à vous soucier de cela dans votre XPath. Le moyen le plus simple consiste simplement à supprimer l'attribut xmlns avec une expression régulière, mais vous pouvez faire quelque chose de plus complexe si vous aviez besoin d'effectuer un autre rangement en même temps.

Autres conseils

Cette transformation xslt de 40 lignes fournit toutes les informations utiles sur les espaces de nom d'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>

Appliqué au 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 généré:

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

Malheureusement, XPath n’a aucune notion d’espace de nommage par défaut. Vous devez enregistrer les espaces de noms avec des préfixes avec le contexte XPath, puis utiliser ces préfixes dans vos expressions XPath. Cela signifie pour xpath très verbeux, mais c’est une lacune fondamentale de XPath 1. Apparemment, XPath 2 résoudra ce problème, mais cela ne vous sert à rien pour le moment.

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top