第一次寫技術相關的文章,技術含量不高,而且肯定會有諸多不足,歡迎大家提出意見,批評指正。
本文主要包含三部分:
- 為什么要寫這篇工作筆記?
- 怎樣解決的此問題?
- 總結
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