Java XML JDOM2 XPath - 使用 XPath 表达式从 XML 属性和元素中读取文本值

Java XML JDOM2 XPath - Read text value from XML attribute and element using XPath expression

应允许程序使用 XPath 表达式从 XML 文件中读取。 我已经使用 JDOM2 启动了该项目,不需要切换到另一个 API。 困难在于,程序事先不知道它是否必须读取元素或属性。 API 是否提供任何功能来仅通过给它 XPath 表达式来接收内容(字符串)? 根据我对 JDOM2 中 XPath 的了解,它使用不同类型的对象来评估指向属性或元素的 XPath 表达式。 我只对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 是否提供了对这个问题有用的东西? 能否重新设计代码以满足我的要求?

提前致谢!

XPath 表达式很难 type/cast 因为它们需要在对表达式中 XPath functions/values 的 return 类型敏感的系统中编译。 JDOM 依赖第三方代码来完成此操作,而该第三方代码没有在 JDOM 代码的编译时关联这些类型的机制。请注意,XPath 表达式可以 return 多种不同类型的内容,包括字符串、布尔值、数字和类似节点列表的内容。

在大多数情况下,XPath 表达式 return 类型在表达式被求值之前是已知的,并且程序员有 "right" casting/expectations 来处理结果。

在你的情况下,你没有,并且表达更加动态。

我建议你声明一个辅助函数来处理内容:

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 规范是 string-value 是元素的 text() 内容以及所有子元素内容的串联。因此,在下面的 XML 片段中:

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

a 元素上的 getValue() 将 return bilbo samwise frodo,但 getText() 将 return bilbo frodo。仔细选择您使用的价值提取机制。

我遇到了完全相同的问题,并采用了识别属性何时是 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);
    }

第二个评估 returns 一个值:

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

我怀疑您更喜欢上一个答案中建议的辅助函数还是这种方法,这与编码风格有关。两者都可以。