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();
}
我怀疑您更喜欢上一个答案中建议的辅助函数还是这种方法,这与编码风格有关。两者都可以。
应允许程序使用 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();
}
我怀疑您更喜欢上一个答案中建议的辅助函数还是这种方法,这与编码风格有关。两者都可以。