2016-10-20 3 views
2

Программе должно быть разрешено читать из XML-файла с использованием выражений XPath. Я уже начал проект с использованием JDOM2, переключение на другой API нежелательно. Сложность в том, что программа не знает заранее, если она должна читать элемент или атрибут. Предоставляет ли API какую-либо функцию для получения содержимого (строки), просто предоставляя ему выражение XPath? Из того, что я знаю о XPath в JDOM2, он использует объекты разных типов для оценки выражений XPath, указывающих на атрибуты или элементы. Меня интересует только контент атрибута/элемента, на который указывает выражение XPath.Java XML JDOM2 XPath - чтение текстового значения из атрибута XML и элемента с использованием выражения XPath

Вот пример XML-файл:

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

Это то, что моя программа выглядит следующим образом:

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

Другая мысль заключается в поиске символа «@» в выражении XPath, чтобы определить, указывает ли он на атрибут или элемент. Это дает мне желаемый результат, хотя я бы хотел, чтобы было более элегантное решение. Предоставляет ли JDOM2 API что-нибудь полезное для этой проблемы? Может ли код быть переработан в соответствии с моими требованиями?

Спасибо заранее!

ответ

0

Выражения XPath трудно вводить, поскольку их необходимо скомпилировать в системе, которая чувствительна к типу возвращаемого значения функций/значений XPath, которые находятся в выражении. JDOM полагается на сторонний код, чтобы сделать это, и этот сторонний код не имеет механизма для корреляции этих типов во время компиляции кода JDOM. Обратите внимание, что выражения XPath могут возвращать несколько различных типов контента, включая String, boolean, Number и Node-List-like.

В большинстве случаев тип выражения выражения XPath известен до того, как выражение оценивается, а программист имеет «правильную» литье/ожидания для обработки результатов.

В вашем случае это не так, и выражение более динамично.

Я рекомендую вам объявить вспомогательные функции для обработки содержимого:

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

Это, по крайней мере, будет обметать ваш код, и если вы используете Java8 потоков, может быть весьма компактным:

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

Обратите внимание, что спецификация XPath для узлов Element заключается в том, что string-value является конкатенацией содержимого элемента Element text(), а также содержимого всех дочерних элементов. Таким образом, в следующем фрагменте кода XML:

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

getValue() на a элемент будет возвращать bilbo samwise frodo, но getText() вернется bilbo frodo. Выберите механизм, который вы используете для тщательного извлечения значений.

+0

Является ли 'Attribute' в JDOM2 подклассом' Content'? http://www.jdom.org/docs/apidocs/org/jdom2/Attribute.html не показывает, что поэтому я смущен, почему ваш ответ, кажется, предполагает, что 'XPathExpression xPathExpression = xPathFactory.compile (xPathExpressions.get (i), Filters.content()) 'обрабатывает элементы и атрибуты. –

+0

Ahhh .... дерьмо. Я забыл, что Атрибуты не довольны. Он имеет метод getValue(), и я предположил. Позвольте мне подумать об этом на мгновение. – rolfl

+0

Я не могу придумать, как лучше обрабатывать неоднозначные результаты XPath, чем проверять его. JDOM, возможно, упростил ситуацию, если оба узла Element и Attribute имеют общий предок, но есть и другие причины, по которым это невозможно. Я отредактировал ответ, чтобы рекомендовать извлечение функции, чтобы аккумулировать код, а не изменять основной механизм, описанный OP. – rolfl

0

У меня была одна и та же проблема, и я понял, когда атрибут находится в центре внимания Xpath. Я решил с двумя функциями.Первым удовлетворил XPathExpression для последующего использования:

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

Вторые оценивает и возвращает значение:

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

я подозреваю, его вопрос стиля кодирования, предпочитаете ли вы функция помощник предложил в предыдущем ответе или этот подход. Либо это сработает.

Смежные вопросы