Eigentlich ist dem auch so, allerdings gibt es ein paar Untiefen zu umschiffen damit man keinen halben oder ganzen Tag damit verbringen muss das "Schiff" wieder von der Sandbank zu bekommen.
Nehmen wir als Beispiel das folgende XML-Dokument:
<wurzel>
<elementA id="foo">
<elementB/>
<elementC>
<elementA id="bar">
</elementC>
</elementA>
</wurzel>
Möchte man jetzt beispielsweise an das innere <elementA> herankommen würde man das mit DOM folgendermassen machen:
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document xmlDom = db.parse("./foo.xml");
Anschliessend kann mittels DOM auf die Daten zugegriffen werden:
NodeList aElements = xmlDom.getElementsByTagName("elementA");
Element secondElementA = (Element)aElements.item(1);
String attributeId = secondElementA.getAttribute("id");
Obiger Code setzt allerdings die Kenntnis des Dokuments voraus, da wir dafür wissen müssen dass das gefragte Element das 2. in der NodeList ist. Wenn man beispielsweise an die Id eines <elementA> heran muss, das in einem <elementC> welches wiederum in einem <elementA> unterhalb des Wurzel-Elements steckt - und das ohne zu wissen ob sich nicht auf dem Weg weitere <elementA>-Elemente verstecken - dann gestaltet sich das noch etwas komplizierter:
NodeList temp = xmlDom.getDocumentElement().getElementsByTagName("elementA");
Element elementA_1 = (Element)temp.item(0));
NodeList temp2 = elementA_1.getElementsByTagName("elementC");
Element elementC = (Element)temp2.item(0);
NodeList temp3 = elementC.getElementsByTagName("elementA");
Element elementA_2 = (Element)temp3.item(0);
String idValue = elementA_2.getAttribute("id");
Schon bei diesem kleinen Beispiel verliert man schnell den Überblick. Sollte man gar auf die Idee kommen das obige Konstrukt in einen einzigen Aufruf zu packen ist es mit der Lesbarkeit endgültig vorbei:
String idValue = ((Element)(((Element)(((Element)(xmlDom.getDocumentElement().
getElementsByTagName("elementA")).item(0)).getElementsByTagName("elementC")).
item(0)).getElementsByTagName("elementA")).item(0)).getAttribute("id");
Arbeitet man hingegen mit XPath sieht das ganze folgendermassen aus:
XPath xpath = XPathFactory.newInstance().newXPath();
String idValue = xpath.evaluate("/wurzel/elementA/elementC/elementA/@id", xmlDom);
Beziehungsweise, für den Fall dass man das komplette Element als DOM-Objekt braucht:
Element elementA =
(Element)xpath.evaluate("/wurzel/elementA/elementC/elementA", xmlDom, XPathConstants.NODE);
Der dritte Parameter gibt dabei den Typ an den das Rückgabe-Objekt haben soll. Erlaubte Werte sind NODE, NODESET, BOOLEAN, STRING und NUMBER.
Ein Cast ist aber in jedem Fall nötig da lediglich ein Objekt vom Typ Object zurückgegeben wird.
Nun sollen dem gefragten Dokument Namspaces hinzugefügt werden:
<wurzel xmlns:bar="http://example.com/ns/bar">
<elementA id="foo">
<elementB/>
<elementC>
<elementA bar:id="bar">
</elementC>
</elementA>
</wurzel>
Eine Umwandlung der Anfrage zu
String idValues = xpath.evaluate("/wurzel/elementA/elementC/elementA/@bar:id", xmlDom);
bringt allerdings nicht das gewünschte Ergebniss, nämlich den Wert des Id-Attributs. Ein wenig Recherche führt einen schnell zu Artikeln oder Foren-Beiträgen die das Rätsel klären.
Will man die XPath-API mit Namespaces verwenden hat man ihr diese auch mitzuteilen. Für diesen Zweck existiert das Interface
javax.xml.namespace.NamespaceContext, das es zu implementieren gilt:
public class myNamespaceContext implements javax.xml.namespace.NamespaceContext {
public String getNamespaceURI(String prefix)
{
if (prefix.equals("bar"))
return "http://example.com/ns/bar";
else
return XMLConstants.NULL_NS_URI;
}
public String getPrefix(String namespace)
{
if (namespace.equals("http://example.com/ns/bar"))
return "bar";
else
return null;
}
public Iterator getPrefixes(String namespace)
{
return null;
}
}
Anschliessend muss der NamespaceContext noch dem XPath-Objekt mitgeteilt werden:
NamespaceContext nsc = new myNamespaceContext(); xpath.setNamespaceContext(nsc);
Jetzt möchte man meinen dass alles funktionieren sollte und macht einen Testlauf. Der liefert aber leider nicht das gewünschte Ergebniss. Dann fängt die grosse Suche an, und die kann eine ganze Weile kosten. Hat man beim NamespaceContext etwas falsch gemacht ? Hat man ein zu setzendes Flag oder Feature an der XPathFactory übersehen ? Wo könnte der Fehler liegen ?
Die Antwort ist subtil, und im Grunde auch recht einfach. Trotzalledem kann es gut und gerne sein dass man hier den Wald vor lauter Bäumen nicht sieht. Die Lösung ist nämlich die, dass man dem Parser nie mitgeteilt hat dass er Namespace-aware sein soll. Dadurch wird die Id mit dem Namespace bar im DOM behandelt wie ein Attribut namens "bar:id". Das zu verarbeitende Dokument muss also mit Namespace-Awareness geparst werden und somit stellt sich das erwartete und gewünschte Ergebniss ein.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document xmlDom = db.parse("./foo.xml");
