67 votes

Comment interroger XML en utilisant des espaces de noms en Java avec XPath?

Lorsque mon XML ressemble à ceci (pas xmlns ), je peux facilement l'interroger avec XPath comme /workbook/sheets/sheet[1]

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook>
<sheets>
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
</sheets>
</workbook>
 

mais quand il ressemble à ceci, alors je ne peux pas

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets>
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
</sheets>
</workbook>
 

des idées?

71voto

Mads Hansen Points 24537

Dans le deuxième exemple de fichier XML les éléments sont liés à un espace de noms. Votre XPath est de tenter de traiter les éléments qui sont liés à la valeur par défaut "aucun espace de noms" espace de noms, de sorte qu'ils ne correspondent pas.

La méthode préférée consiste à enregistrer l'espace de noms avec un espace de noms de préfixe. Il rend votre XPath beaucoup plus facile à développer, à lire et à maintenir.

Cependant, il n'est pas obligatoire de vous inscrire à l'espace de noms et d'utiliser l'espace de noms de préfixe dans votre XPath.

Vous pouvez formuler une expression XPath qui utilise un générique de match pour un élément et d'un prédicat de filtre qui limite le match pour l' local-name() et de la namespace-uri(). Par exemple:

/*[local-name()='workbook'
    and namespace-uri()='http://schemas.openxmlformats.org/spreadsheetml/2006/main']
  /*[local-name()='sheets'
      and namespace-uri()='http://schemas.openxmlformats.org/spreadsheetml/2006/main']
  /*[local-name()='sheet'
      and namespace-uri()='http://schemas.openxmlformats.org/spreadsheetml/2006/main'][1]

Comme vous pouvez le voir, il produit une très longue et prolixe instruction XPath qui est très difficile à lire (et de maintenir).

Vous pourriez tout aussi bien correspondre à l' local-name() de l'élément et d'ignorer l'espace de noms. Par exemple:

/*[local-name()='workbook']/*[local-name()='sheets']/*[local-name()='sheet'][1]

Cependant, vous courez le risque de contrepartie, le mauvais éléments. Si votre XML a des vocabulaires (qui peut ne pas être un problème pour cet exemple) qui utilisent le même local-name(), votre XPath peut correspondre à du mauvais éléments et sélectionnez le mauvais contenu:

64voto

stevevls Points 6590

Votre problème, c'est l'espace de noms par défaut. Consultez cet article pour savoir comment traiter avec les espaces de noms dans votre XPath: http://www.edankert.com/defaultnamespaces.html

L'une des conclusions qu'ils en tirer est:

Donc, pour être en mesure d'utiliser XPath expressions sur le contenu XML défini dans un (par défaut) de l'espace de noms, nous avons besoin de spécifier un préfixe d'espace de noms de la cartographie

Notez que cela ne signifie pas que vous devez changer votre document source en aucune façon (si vous êtes libre de mettre les préfixes d'espace de noms si vous le désirez). Des sons étranges, non? Ce que vous aurez à faire est de créer un préfixe d'espace de noms de la cartographie dans votre code java et utiliser les préfixes dans l'expression XPath. Ici, nous allons créer une cartographie de l' spreadsheet de votre espace de noms par défaut.

XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();

// there's no default implementation for NamespaceContext...seems kind of silly, no?
xpath.setNamespaceContext(new NamespaceContext() {
    public String getNamespaceURI(String prefix) {
        if (prefix == null) throw new NullPointerException("Null prefix");
        else if ("spreadsheet".equals(prefix)) return "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
        else if ("xml".equals(prefix)) return XMLConstants.XML_NS_URI;
        return XMLConstants.NULL_NS_URI;
    }

    // This method isn't necessary for XPath processing.
    public String getPrefix(String uri) {
        throw new UnsupportedOperationException();
    }

    // This method isn't necessary for XPath processing either.
    public Iterator getPrefixes(String uri) {
        throw new UnsupportedOperationException();
    }
});

// note that all the elements in the expression are prefixed with our namespace mapping!
XPathExpression expr = xpath.compile("/spreadsheet:workbook/spreadsheet:sheets/spreadsheet:sheet[1]");

// assuming you've got your XML document in a variable named doc...
Node result = (Node) expr.evaluate(doc, XPathConstants.NODE);

Et voila...Maintenant vous avez votre élément enregistré dans l' result variable.

Mise en garde: si vous êtes d'analyse XML comme un DOM à la norme JAXP classes, faites appel à setNamespaceAware(true) sur votre DocumentBuilderFactory. Sinon, ce code ne marchera pas!

38voto

lwburk Points 29313

Tous les espaces de noms que vous avez l'intention de sélectionner à partir de la source XML doit être associé à un préfixe de la langue d'accueil. En Java/JAXP c'est fait en spécifiant l'URI pour chaque préfixe d'espace de noms à l'aide d'une instance de javax.xml.namespace.NamespaceContext. Malheureusement, il n'y a pas de mise en œuvre de l' NamespaceContext fourni dans le SDK.

Heureusement, il est très facile d'écrire votre propre:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.namespace.NamespaceContext;

public class SimpleNamespaceContext implements NamespaceContext {

    private final Map<String, String> PREF_MAP = new HashMap<String, String>();

    public SimpleNamespaceContext(final Map<String, String> prefMap) {
        PREF_MAP.putAll(prefMap);       
    }

    public String getNamespaceURI(String prefix) {
        return PREF_MAP.get(prefix);
    }

    public String getPrefix(String uri) {
        throw new UnsupportedOperationException();
    }

    public Iterator getPrefixes(String uri) {
        throw new UnsupportedOperationException();
    }

}

L'utiliser comme ceci:

XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
HashMap<String, String> prefMap = new HashMap<String, String>() {{
    put("main", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
    put("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
}};
SimpleNamespaceContext namespaces = new SimpleNamespaceContext(prefMap);
xpath.setNamespaceContext(namespaces);
XPathExpression expr = xpath
        .compile("/main:workbook/main:sheets/main:sheet[1]");
Object result = expr.evaluate(doc, XPathConstants.NODESET);

Notez que même si le premier espace de noms n'est pas spécifier un préfixe dans le document source (c'est à dire qu'il est l' espace de noms par défaut) , vous devez l'associer avec un préfixe de toute façon. Votre expression doit alors faire référence à des nœuds dans l'espace de nom à l'aide du préfixe que vous avez choisi, comme ceci:

/main:workbook/main:sheets/main:sheet[1]

Le préfixe des noms que vous choisissez d'associer à chaque espace de noms sont arbitraires; ils n'ont pas besoin de correspondre à ce qui apparaît dans la source XML. Cette cartographie est juste une façon de dire au moteur XPath qu'un préfixe de nom dans une expression est corrélée avec un espace de noms spécifique dans le document source.

6voto

Niek Points 168

Bel exemple incluant un résolveur non codé en dur:
http://www.ibm.com/developerworks/library/x-nmspccontext/

0voto

cordsen Points 1387

Assurez-vous de référencer l'espace de noms dans votre XSLT

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
             xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
             xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"       >
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X