2016-10-20 10 views
2

Das Programm sollte mithilfe von XPath-Ausdrücken aus einer XML-Datei lesen dürfen. Ich habe das Projekt bereits mit JDOM2 gestartet, der Wechsel zu einer anderen API ist unerwünscht. Die Schwierigkeit ist, dass das Programm vorher nicht weiß, ob es ein Element oder ein Attribut lesen muss. Bietet die API irgendeine Funktion, um den Inhalt (String) zu erhalten, indem Sie einfach den XPath-Ausdruck angeben? Von dem, was ich über XPath in JDOM2 weiß, verwendet es Objekte verschiedener Typen, um XPath-Ausdrücke auszuwerten, die auf Attribute oder Elemente zeigen. Ich bin nur am Inhalt des Attributs/Elements interessiert, auf das der XPath-Ausdruck zeigt. HierJava XML JDOM2 XPath - Lesen von Textwerten aus XML-Attributen und -Elementen mit XPath-Ausdruck

ist ein Beispiel XML-Datei:

<?xml version="1.0" encoding="UTF-8"?> 
<bookstore> 
    <book category="COOKING"> 
    <title lang="en">Everyday Italian</title> 
    <author>Giada De Laurentiis</author> 
    <year>2005</year> 
    <price>30.00</price> 
    </book> 
    <book category="CHILDREN"> 
    <title lang="en">Harry Potter</title> 
    <author>J K. Rowling</author> 
    <year>2005</year> 
    <price>29.99</price> 
    </book> 
    <book category="WEB"> 
    <title lang="en">XQuery Kick Start</title> 
    <author>James McGovern</author> 
    <author>Per Bothner</author> 
    <author>Kurt Cagle</author> 
    <author>James Linn</author> 
    <author>Vaidyanathan Nagarajan</author> 
    <year>2003</year> 
    <price>49.99</price> 
    </book> 
    <book category="WEB"> 
    <title lang="en">Learning XML</title> 
    <author>Erik T. Ray</author> 
    <year>2003</year> 
    <price>39.95</price> 
    </book> 
</bookstore> 

Dies ist, was mein Programm wie folgt aussieht:

package exampleprojectgroup; 

import java.io.IOException; 
import java.util.LinkedList; 
import java.util.List; 
import org.jdom2.Attribute; 
import org.jdom2.Document; 
import org.jdom2.Element; 
import org.jdom2.JDOMException; 
import org.jdom2.filter.Filters; 
import org.jdom2.input.SAXBuilder; 
import org.jdom2.input.sax.XMLReaders; 
import org.jdom2.xpath.XPathExpression; 
import org.jdom2.xpath.XPathFactory; 


public class ElementAttribute2String 
{ 
    ElementAttribute2String() 
    { 
     run(); 
    } 

    public void run() 
    { 
     final String PATH_TO_FILE = "c:\\readme.xml"; 
     /* It is essential that the program has to work with a variable amount of XPath expressions. */ 
     LinkedList<String> xPathExpressions = new LinkedList<>(); 
     /* Simulate user input. 
     * First XPath expression points to attribute, 
     * second one points to element. 
     * Many more expressions follow in a real situation. 
     */ 
     xPathExpressions.add("/bookstore/book/@category"); 
     xPathExpressions.add("/bookstore/book/price"); 

     /* One list should be sufficient to store the result. */ 
     List<Element> elementsResult = null; 
     List<Attribute> attributesResult = null; 
     List<Object> objectsResult = null; 
     try 
     { 
      SAXBuilder saxBuilder = new SAXBuilder(XMLReaders.NONVALIDATING); 
      Document document = saxBuilder.build(PATH_TO_FILE); 
      XPathFactory xPathFactory = XPathFactory.instance(); 
      int i = 0; 
      for (String string : xPathExpressions) 
      { 
       /* Works only for elements, uncomment to give it a try. */ 
//    XPathExpression<Element> xPathToElement = xPathFactory.compile(xPathExpressions.get(i), Filters.element()); 
//    elementsResult = xPathToElement.evaluate(document); 
//    for (Element element : elementsResult) 
//    { 
//     System.out.println("Content of " + string + ": " + element.getText()); 
//    } 

       /* Works only for attributes, uncomment to give it a try. */ 
//    XPathExpression<Attribute> xPathToAttribute = xPathFactory.compile(xPathExpressions.get(i), Filters.attribute()); 
//    attributesResult = xPathToAttribute.evaluate(document); 
//    for (Attribute attribute : attributesResult) 
//    { 
//     System.out.println("Content of " + string + ": " + attribute.getValue()); 
//    } 

       /* I want to receive the content of the XPath expression as a string 
       * without having to know if it is an attribute or element beforehand. 
       */ 
       XPathExpression<Object> xPathExpression = xPathFactory.compile(xPathExpressions.get(i)); 
       objectsResult = xPathExpression.evaluate(document); 
       for (Object object : objectsResult) 
       { 
        if (object instanceof Attribute) 
        { 
         System.out.println("Content of " + string + ": " + ((Attribute)object).getValue()); 
        } 
        else if (object instanceof Element) 
        { 
         System.out.println("Content of " + string + ": " + ((Element)object).getText()); 
        } 
       } 
       i++; 
      } 
     } 
     catch (IOException ioException) 
     { 
      ioException.printStackTrace(); 
     } 
     catch (JDOMException jdomException) 
     { 
      jdomException.printStackTrace(); 
     } 
    } 
} 

Ein anderer Gedanke ist für das Zeichen '@' in dem XPath-Ausdruck zu suchen, zu Bestimmen, ob es auf ein Attribut oder Element zeigt. Das gibt mir das gewünschte Ergebnis, obwohl ich wünschte, es gäbe eine elegantere Lösung. Bietet die JDOM2-API etwas Nützliches für dieses Problem? Könnte der Code neu gestaltet werden, um meine Anforderungen zu erfüllen?

Vielen Dank im Voraus!

Antwort

0

XPath-Ausdrücke sind schwierig zu schreiben/zu formulieren, da sie in einem System kompiliert werden müssen, das für den Rückgabetyp der XPath-Funktionen/-Werte im Ausdruck empfindlich ist. JDOM stützt sich dabei auf Code von Drittanbietern, und dieser Code von Drittanbietern verfügt nicht über einen Mechanismus, um diese Typen bei der Kompilierzeit des JDOM-Codes zu korrelieren. Beachten Sie, dass XPath-Ausdrücke eine Reihe verschiedener Inhaltstypen zurückgeben können, einschließlich String-, boolescher, Zahlen- und knotenlistenartiger Inhalte.

In den meisten Fällen ist der Rückgabetyp des XPath-Ausdrucks bekannt, bevor der Ausdruck ausgewertet wird, und der Programmierer hat die "richtigen" Erwartungen an die Verarbeitung der Ergebnisse.

In Ihrem Fall nicht, und der Ausdruck ist dynamischer.

Ich empfehle Ihnen, eine Hilfsfunktion zu erklären, den Inhalt zu verarbeiten:

private static final Function extractValue(Object source) { 
    if (source instanceof Attribute) { 
     return ((Attribute)source).getValue(); 
    } 
    if (source instanceof Content) { 
     return ((Content)source).getValue(); 
    } 
    return String.valueOf(source); 
} 

Das zumindest wird Ihr Code versäubern, und wenn Sie Java8 verwenden ströme, kann sehr kompakt sein:

List<String> values = xPathExpression.evaluate(document) 
         .stream() 
         .map(o -> extractValue(o)) 
         .collect(Collectors.toList()); 

Beachten Sie, dass die XPath-Spezifikation für Element-Knoten lautet, dass string-value die Konstellation des Inhalts text() des Elements sowie aller Inhalte von untergeordneten Elementen ist. So wird in der folgenden XML-Schnipsel:

<a>bilbo <b>samwise</b> frodo</a> 

die getValue() auf dem a Element wird bilbo samwise frodo zurück, aber die getText()bilbo frodo zurück. Wählen Sie den Mechanismus, den Sie für die Wertextraktion verwenden, sorgfältig aus.

+0

Ist 'Attribut' in JDOM2 eine Unterklasse von' Content'? http://www.jdom.org/docs/apidocs/org/jdom2/Attribute.html zeigt das nicht an, daher bin ich verwirrt, warum Ihre Antwort darauf hindeutet, dass XPathExpression xPathExpression = xPathFactory.compile (xPathExpressions.get (i), Filtert.content()) behandelt Elemente und Attribute. –

+0

Ahhh .... Mist. Ich hatte vergessen, dass Attribute nicht zufrieden sind. Es hat die 'getValue()' Methode und ich nahm an. Lass mich für einen Moment darüber nachdenken. – rolfl

+0

Ich kann mir keine bessere Möglichkeit vorstellen, mehrdeutige XPath-Ergebnisse zu verarbeiten, als sie zu überprüfen. JDOM könnte die Dinge ein wenig einfacher gemacht haben, wenn sowohl Element- als auch Attributknoten einen gemeinsamen Vorfahren haben, aber es gibt andere Gründe, warum das nicht machbar ist. Ich habe die Antwort bearbeitet, um eine Funktionsextraktion zu empfehlen, um den Code zu versäubern, anstatt den vom OP beschriebenen grundlegenden Mechanismus zu ändern. – rolfl

0

Ich hatte genau das gleiche Problem und nahm den Ansatz zu erkennen, wenn ein Attribut der Fokus des Xpath ist. Ich habe mit zwei Funktionen gelöst.Das erfüllte zunächst die XPathExpression für die spätere Verwendung:

XPathExpression xpExpression; 
    if (xpath.matches( ".*/@[\\w]++$")) { 
     // must be an attribute value we're after.. 
     xpExpression = xpfac.compile(xpath, Filters.attribute(), null, myNSpace); 
    } else { 
     xpExpression = xpfac.compile(xpath, Filters.element(), null, myNSpace); 
    } 

der zweiten auswertet und gibt einen Wert zurück:

Object target = xpExpression.evaluateFirst(baseEl); 
if (target != null) { 
    String value = null; 
    if (target instanceof Element) { 
     Element targetEl = (Element) target; 
     value = targetEl.getTextNormalize(); 
    } else if (target instanceof Attribute) { 
     Attribute targetAt = (Attribute) target; 
     value = targetAt.getValue(); 
    } 

ich seinen Verdacht, eine Frage der Codierung Stil, ob Sie die Hilfsfunktion vorgeschlagen in der vorherige Antwort bevorzugen oder dieser Ansatz. Beide werden funktionieren.

Verwandte Themen