Jak odpytywać XML za pomocą przestrzeni nazw w Javie za pomocą XPath?

Gdy mój XML wygląda tak (no xmlns), mogę łatwo go odpytywać za pomocą XPath jak /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>

Ale kiedy tak wygląda to nie mogę

<?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>
Jakieś pomysły?
Author: Dan Atkinson, 2011-06-17

6 answers

W drugim przykładowym pliku XML elementy są powiązane z przestrzenią nazw. XPath próbuje adresować elementy, które są powiązane z domyślną przestrzenią nazw "brak przestrzeni nazw", więc nie pasują do siebie.

Preferowaną metodą jest rejestracja przestrzeni nazw za pomocą prefiksu przestrzeni nazw. To sprawia, że XPath jest znacznie łatwiejsze do opracowania, odczytania i utrzymania.

Nie jest jednak obowiązkowe rejestrowanie przestrzeni nazw i używanie prefiksu przestrzeni nazw w ścieżce XPath.

Ty Może sformułować wyrażenie XPath, które używa ogólnego dopasowania dla elementu i filtra predykatów, który ogranicza dopasowanie dla żądanych local-name() i namespace-uri(). Na przykład:

/*[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]

Jak widzisz, tworzy niezwykle długie i wyraziste oświadczenie XPath, które jest bardzo trudne do odczytania (i utrzymania).

Możesz również dopasować local-name() elementu i zignorować przestrzeń nazw. Na przykład:

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

Istnieje jednak ryzyko dopasowania złe elementy. Jeśli twój XML ma pomieszane słowniki (co może nie być problemem dla tej instancji), które używają tego samego local-name(), Twoja ścieżka XPath może dopasować się do niewłaściwych elementów i wybrać niewłaściwą treść:

 63
Author: Mads Hansen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-18 16:40:33

Twoim problemem jest domyślna przestrzeń nazw. Sprawdź ten artykuł, aby dowiedzieć się, jak radzić sobie z przestrzeniami nazw w ścieżce XPath: http://www.edankert.com/defaultnamespaces.html

Jeden z wniosków, które wyciągają, to:

Aby móc używać XPath wyrażenia na zawartość XML zdefiniowaną w (domyślnie) przestrzeń nazw, musimy określa przedrostek przestrzeni nazw mapowanie

Zauważ, że nie oznacza to, że musisz w jakikolwiek sposób zmieniać swój dokument źródłowy (choć możesz umieścić tam prefiksy przestrzeni nazw, jeśli chcesz). Brzmi dziwnie, prawda? To, co zrobisz , to stworzysz mapowanie prefiksu przestrzeni nazw w kodzie Javy i użyjesz tego prefiksu w wyrażeniu XPath. Tutaj utworzymy mapowanie z spreadsheet do domyślnej przestrzeni nazw.

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);
I voila...Teraz masz swój element zapisany w zmiennej result.

Zastrzeżenie: jeśli parsujesz XML jako DOM za pomocą standardowych klas JAXP, pamiętaj, aby wywołać setNamespaceAware(true) na Twoim DocumentBuilderFactory. W przeciwnym razie ten kod nie zadziała!

 55
Author: stevevls,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-18 20:05:27

Wszystkie przestrzenie nazw, które zamierzasz wybrać w źródłowym XML, muszą być powiązane z prefiksem w języku hosta. W Javie / JAXP jest to wykonywane przez podanie URI dla każdego prefiksu przestrzeni nazw przy użyciu instancji javax.xml.namespace.NamespaceContext. Niestety, w SDK nie ma żadnej implementacji z NamespaceContext.

Na szczęście bardzo łatwo jest napisać własne:

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();
    }

}

Użyj go tak:

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

Zauważ, że chociaż pierwsza przestrzeń nazw nie określa prefiksu w dokument źródłowy (tzn. jest to domyślna przestrzeń nazw) musisz go skojarzyć z prefiksem . Twoje wyrażenie powinno odwoływać się do węzłów w tej przestrzeni nazw, używając wybranego prefiksu, jak to:

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

Nazwy prefiksów, które chcesz powiązać z każdą przestrzenią nazw, są dowolne; nie muszą pasować do tego, co pojawia się w źródłowym XML. to mapowanie jest tylko sposobem na powiedzenie silnikowi XPath, że dana nazwa przedrostka w wyrażeniu koreluje z określona przestrzeń nazw w dokumencie źródłowym.

 33
Author: Wayne Burkett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-06 04:41:04

Jeśli używasz Springa, zawiera on już org.springframework.util.xml.SimpleNamespaceContext.

        import org.springframework.util.xml.SimpleNamespaceContext;
        ...

        XPathFactory xPathfactory = XPathFactory.newInstance();
        XPath xpath = xPathfactory.newXPath();
        SimpleNamespaceContext nsc = new SimpleNamespaceContext();

        nsc.bindNamespaceUri("a", "http://some.namespace.com/nsContext");
        xpath.setNamespaceContext(nsc);

        XPathExpression xpathExpr = xpath.compile("//a:first/a:second");

        String result = (String) xpathExpr.evaluate(object, XPathConstants.STRING);
 3
Author: kasi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-30 13:45:46

Upewnij się, że odwołujesz się do przestrzeni nazw w swoim 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"       >
 0
Author: cordsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-17 19:20:31

Napisałem prostą implementację NamespaceContext ( tutaj), która przyjmuje Map<String, String> jako wejście, gdzie key jest prefiksem, a value jest przestrzenią nazw.

Podąża za spesifikacją NamespaceContext , a jak to działa w testach jednostkowych .

Map<String, String> mappings = new HashMap<>();
mappings.put("foo", "http://foo");
mappings.put("foo2", "http://foo");
mappings.put("bar", "http://bar");

context = new SimpleNamespaceContext(mappings);

context.getNamespaceURI("foo");    // "http://foo"
context.getPrefix("http://foo");   // "foo" or "foo2"
context.getPrefixes("http://foo"); // ["foo", "foo2"]

zauważ, że ma zależność od Google Guava

 0
Author: tomaj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-28 10:43:15