[工作笔记] Dom4j 和 XPath 结合 选取 xml 中任意深度的(即所有的)特定元素

第一次写技术相关的文章,技术含量不高,而且肯定会有诸多不足,欢迎大家提出意见,批评指正。

本文主要包含三部分:

  1. 为什么要写这篇工作笔记?
  2. 怎样解决的此问题?
  3. 总结

1 为什么要写这篇工作笔记?

这一段简单地说就是:工作中遇到问题,在网上找到遇到相同问题的同学们也没有解决问题,所以写了这篇文章备忘并提供一种此类问题的解决方案,跟大家交流学习。

用 Dom4j 解析 xml 文件是很简单的一件事,之前我也写过根据 xml 配置信息文件和应用部署地理位置设置应用配置文件(也是 xml)的程序。但是在使用其 XPath 解析 JasperReports 的模板文件(.jrxml)时却遇到了这样的问题:我需要解析出 jrxml 文件中任意深度的 <parameter> 元素(例如:<parameter name="Country" class="java.lang.String"/>),但无论我从根节点选取(如:/jasperReport/parameter)还是从当前节点选取所有的 <parameter>元素而不考虑其位置(如://jasperReport//parameter),得到的结果集 size 都是0。

不知道是不是我使用的 Dom4j 版本(1.6.1)中 XPath 比较特殊?不过我还是先去复习了 XPath 的语法等基础知识(可参考 w3school 的 XPath 教程:http://www.w3school.com.cn/xpath/xpath_syntax.asp),在程序中换了各种写法,仍然不行。在网上几大搜索引擎都搜索了一遍,看了十几个网页(有提问、技术文章、教程等),尝试了其中的各种方法,也没有解决问题,我看到的相关提问的回答中用过的方法我都用过了,均不奏效。

最后回到 Dom4j 本身,我仔细查看了一遍API,从其中可调用的方法中发现了可以让我查看其 XPath 格式的方法,打印出该 XPath 是从根节点选取的,把 “/” 修改成 “//” 就能选择任意深度的 <parameter> 元素了。下面说下具体过程,一方面给自己做个笔记,另一方面给前面说的那些提出问题的同学们提供一个解答,同时也弥补相关中文技术文章的空白。

2 怎样解决的此问题?

(跟解决的问题相比,解决问题的方式也许更有价值。)

2-1 调用 Dom4j 本身 Element 的 getPath() 方法查看其能理解的 XPath 格式

接着前面说的,下面我简要陈述一下本问题的关键解决过程。

打印出一个 Element 的 XPath 的代码如下:

1 Element parameterElement = (Element)parameterItr.next();
2 System.out.println(parameterElement.getPath());

下图显示了控制台打印出的结果:

可见 Dom4j 的 XPath 跟 XPath 的语法是比较一致的,还是本人功课做得不够。知道了 Dom4j 理解的 XPath 格式,只要稍加修改,我的问题就解决了.

2-2 获取 jrxml 文件中任意深度的 <parameter> 元素

获取 jrxml 文件中任意深度的 <parameter> 元素的一个实例即是:获取该 xml 文件中的所有的 <parameter> 元素,同样对于其中所有的 <field> 元素也是可以的。根据上面 Dom4j 理解的 XPath 格式,要获取所有 <parameter> 元素的 XPath 应写为://*[name()='parameter'],类似的,要获取所有 <field> 元素的 XPath 应写为://*[name()='field']。

下面展示一个执行实例。要解析的 jrxml 源码为:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="ShipmentsReport" pageWidth="842" pageHeight="595" orientation="Landscape" columnWidth="812" leftMargin="15" rightMargin="15" topMargin="10" bottomMargin="10">
 3     <property name="net.sf.jasperreports.export.pdf.tagged" value="true"/>
 4     <property name="net.sf.jasperreports.export.pdf.tag.language" value="EN-US"/>
 5     <style name="Sans_Normal" isDefault="true" fontName="DejaVu Sans" fontSize="8" isBold="false" isItalic="false" isUnderline="false" isStrikeThrough="false"/>
 6     <style name="Sans_Large" isDefault="false" style="Sans_Normal" fontSize="10"/>
 7     <style name="Sans_Bold" isDefault="false" fontName="DejaVu Sans" fontSize="8" isBold="true" isItalic="false" isUnderline="false" isStrikeThrough="false"/>
 8     <parameter name="reportTitle" class="java.lang.String"/>
 9     <subDataset name="Country_Orders">
10         <parameter name="Country" class="java.lang.String"/>
11         <queryString>
12             <![CDATA[SELECT ShippedDate, ShipRegion, ShipCity, Freight
13             FROM Orders
14             WHERE
15                 ShipCountry = $P{Country} AND
16                 ShippedDate IS NOT NULL]]>
17         </queryString>
18         <field name="ShippedDate" class="java.sql.Timestamp"/>
19         <field name="ShipRegion" class="java.lang.String"/>
20         <field name="ShipCity" class="java.lang.String"/>
21         <field name="Freight" class="java.lang.Float"/>
22     </subDataset>
23     <queryString>
24         <![CDATA[SELECT DISTINCT ShipCountry FROM Orders]]>
25     </queryString>
26     <field name="ShipCountry" class="java.lang.String"/>
27     ... <!-- 省略不包含 <parameter> 和 <field> 元素的部分 -->
28 </jasperReport>

进行解析的主要 Java 代码如下:

 1 SAXReader reader = new SAXReader();
 2 try {
 3     Document doc = reader.read(new File(uploadedTemplateFilePath));
 4     // 先选取所有的parameter元素
 5     List<?> parameterList = doc.selectNodes("//*[name()='parameter']");
 6     Iterator<?> parameterItr = parameterList.iterator();
 7     while(parameterItr.hasNext()) {
 8         Element parameterElement = (Element)parameterItr.next();
 9         System.out.println("Current parameterElement XPath: " + parameterElement.getPath());
10         String parameterName  = parameterElement.attributeValue("name");       // parameter name
11         String parameterClass = parameterElement.attributeValue("class");      // parameter class
12         System.out.println("parameterName = " + parameterName + "; parameterClass = " + parameterClass);
13         System.out.println("--------------------------------------------------------------------------");
14     }
15     // 再选取所有的field元素
16     List<?> fieldList = doc.selectNodes("//*[name()='field']");
17     Iterator<?> fieldItr = fieldList.iterator();
18     while(fieldItr.hasNext()) {
19         Element fieldElement = (Element)fieldItr.next();
20         System.out.println("Current fieldElement XPath: " + fieldElement.getPath());
21         String fieldName  = fieldElement.attributeValue("name");       // field name
22         String fieldClass = fieldElement.attributeValue("class");      // field class
23         System.out.println("--------------------------------------------------------------------------");
24     }
25 } catch (DocumentException e) {
26     e.printStackTrace();
27 }

执行以上程序解析该 jrxml 文件,在控制台输出以下结果:

结果中解析出的两个 <parameter> 和五个 <field> 都是与 jrxml 模板中对应的。

3 总结

有时候解决问题需要回到问题本身,这是我经常使用的办法。比如说这次:既然网上大家都遇到过类似问题,又没能很好解决,这时回到 Dom4j 本身,从它自身的 API 入手,果然峰回路转,解决了问题,同时也对自己知识的死角进行了修补。

希望我解决问题的思路能给大家以启发,同时也希望这篇文章能解答遇到类似问题的同学们的疑惑。

最后再啰嗦一下:第一次写技术类文章,肯定有很多不足,希望大家见谅。有意见请留言指出,我会积极改正的,谢谢 O(∩_∩)O!

 

                                                                                                                                                                           Payne Pandaroid Wang

                                                                                                                                                                           2012年12月27日 14:48

posted @ 2012-12-27 15:01  PayneWang@王沛  阅读(4192)  评论(1编辑  收藏  举报