[工作笔记] Dom4j 和 XPath 结合 选取 xml 中任意深度的(即所有的)特定元素
第一次写技术相关的文章,技术含量不高,而且肯定会有诸多不足,欢迎大家提出意见,批评指正。
本文主要包含三部分:
- 为什么要写这篇工作笔记?
- 怎样解决的此问题?
- 总结
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