在Java開發的報表工具FineReport中,假如在目錄下保存了幾個XML文件,但願把XML文件轉換爲報表數據源,同時但願展現動態xml數據源的效果,這時可經過參數的方式,動態獲取xml字段中的值再做爲報表數據源。 Northwind.xml記錄數據格式以下:java
<?xml version="1.0" encoding="UTF-8"?> <Northwind> <Customers> <CustomerID>ALFKI</CustomerID> <CompanyName>ALfreds Futterkiste</CompanyName> <ContactName>Maria Anders</ContactName> <ContactTitle>Sales Representative</ContactTitle> <Address>Obere Str.57</Address> <City>Berlin</City> <PostalCode>12209</PostalCode> <Country>Germany</Country> <Phone>030-0074321</Phone> <Fax>030-0076545</Fax> </Customers> </Northwind>
最終用於製做報表的數據源形式以下:緩存
對於這樣的狀況咱們如何來實現呢?FineReport中能夠經過自定義程序數據集來對xml字段數據進行解析,最終返回所但願的數據表。實現步驟以下: 一、 定義XMLColumnNameType4Demo封裝類 首先定義參數name及type,供其餘類直接調用,安全性比較高,詳細代碼以下:安全
package com.fr.data; public class XMLColumnNameType4Demo { private int type = -1; private String name = null; public XMLColumnNameType4Demo(String name, int type) { this.name = name; this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getType() { return type; } public void setType(int type) { this.type = type; } }
二、定義XMLParseDemoDataModel.java類文件 定義XMLParseDemoDataModel.java類繼承AbstractDataModel接口,實現getColumnCount、getColumnName、getRowCount、getValueAt四個方法,詳細代碼以下:ide
package com.fr.data; import java.io.File; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import com.fr.base.FRContext; import com.fr.data.AbstractDataModel; import com.fr.general.ComparatorUtils; import com.fr.general.data.TableDataException; /** * XMLParseDemoDataModel * * DataModel是獲取數據的接口 * * 這裏經過init方法一次性取數後,構造一個二維表對象來實現DataModel的各個取數方法 */ public class XMLParseDemoDataModel extends AbstractDataModel { // 數據類型標識 public static final int COLUMN_TYPE_STRING = 0; public static final int COLUMN_TYPE_INTEGER = 1; public static final int COLUMN_TYPE_BOOLEAN = 2; // 緩存取出來的數據 protected List row_list = null; // 數據對應的節點路徑 private String[] xPath; // 節點路徑下包含的須要取數的節點 private XMLColumnNameType4Demo[] columns; private String filePath; public XMLParseDemoDataModel(String filename, String[] xPath, XMLColumnNameType4Demo[] columns) { this.filePath = filename; this.xPath = xPath; this.columns = columns; } /** * 取出列的數量 */ public int getColumnCount() throws TableDataException { return columns.length; } /** * 取出相應的列的名稱 */ public String getColumnName(int columnIndex) throws TableDataException { if (columnIndex < 0 || columnIndex >= columns.length) return null; String columnName = columns[columnIndex] == null ? null : columns[columnIndex].getName(); return columnName; } /** * 取出獲得的結果集的總的行數 */ public int getRowCount() throws TableDataException { this.init(); return row_list.size(); } /** * 取出相應位置的值 */ public Object getValueAt(int rowIndex, int columnIndex) throws TableDataException { this.init(); if (rowIndex < 0 || rowIndex >= row_list.size() || columnIndex < 0 || columnIndex >= columns.length) return null; return ((Object[]) row_list.get(rowIndex))[columnIndex]; } /** * 釋放一些資源,取數結束後,調用此方法來釋放資源 */ public void release() throws Exception { if (this.row_list != null) { this.row_list.clear(); this.row_list = null; } } /** ************************************************** */ /** ***********以上是實現DataModel的方法*************** */ /** ************************************************** */ /** ************************************************** */ /** ************如下爲解析XML文件的方法**************** */ /** ************************************************** */ // 一次性將數據取出來 protected void init() throws TableDataException { if (this.row_list != null) return; this.row_list = new ArrayList(); try { // 使用SAX解析XML文件, 使用方法請參見JAVA SAX解析 SAXParserFactory f = SAXParserFactory.newInstance(); SAXParser parser = f.newSAXParser(); parser.parse(new File(XMLParseDemoDataModel.this.filePath), new DemoHandler()); } catch (Exception e) { e.printStackTrace(); FRContext.getLogger().error(e.getMessage(), e); } } /** * 基本原理就是解析器在遍歷文件時 發現節點開始標記時,調用startElement方法 讀取節點內部內容時,調用characters方法 * 發現節點結束標記時,調用endElement */ private class DemoHandler extends DefaultHandler { private List levelList = new ArrayList(); // 記錄當前節點的路徑 private Object[] values; // 緩存一條記錄 private int recordIndex = -1; // 當前記錄所對應的列的序號,-1表示不須要記錄 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { // 記錄下 levelList.add(qName); if (isRecordWrapTag()) { // 開始一條新數據的記錄 values = new Object[XMLParseDemoDataModel.this.columns.length]; } else if (needReadRecord()) { // 看看其對應的列序號,下面的characters以後執行時,根據這個列序號來設置值存放的位置。 recordIndex = getColumnIndex(qName); } } public void characters(char[] ch, int start, int length) throws SAXException { if (recordIndex > -1) { // 讀取值 String text = new String(ch, start, length); XMLColumnNameType4Demo type = XMLParseDemoDataModel.this.columns[recordIndex]; Object value = null; if (type.getType() == COLUMN_TYPE_STRING) { value = text; } if (type.getType() == COLUMN_TYPE_INTEGER) { value = new Integer(text); } else if (type.getType() == COLUMN_TYPE_BOOLEAN) { value = new Boolean(text); } values[recordIndex] = value; } } public void endElement(String uri, String localName, String qName) throws SAXException { try { if (isRecordWrapTag()) { // 一條記錄結束,就add進list中 XMLParseDemoDataModel.this.row_list.add(values); values = null; } else if (needReadRecord()) { recordIndex = -1; } } finally { levelList.remove(levelList.size() - 1); } } // 正好匹配路徑,肯定是記錄外部的Tag private boolean isRecordWrapTag() { if (levelList.size() == XMLParseDemoDataModel.this.xPath.length && compareXPath()) { return true; } return false; } // 須要記錄一條記錄 private boolean needReadRecord() { if (levelList.size() == (XMLParseDemoDataModel.this.xPath.length + 1) && compareXPath()) { return true; } return false; } // 是否匹配設定的XPath路徑 private boolean compareXPath() { String[] xPath = XMLParseDemoDataModel.this.xPath; for (int i = 0; i < xPath.length; i++) { if (!ComparatorUtils.equals(xPath[i], levelList.get(i))) { return false; } } return true; } // 獲取該字段的序號 private int getColumnIndex(String columnName) { XMLColumnNameType4Demo[] nts = XMLParseDemoDataModel.this.columns; for (int i = 0; i < nts.length; i++) { if (ComparatorUtils.equals(nts[i].getName(), columnName)) { return i; } } return -1; } } }
三、定義程序數據集XMLDemoTableData 經過參數filename,動態顯示xml文件內容,首先xml文件須要放到某個目錄下,以下代碼是放到D盤,而且定義須要解析的數據列,這邊定義的數據列名稱,根xml內字段名稱是一一對用的。詳細代碼以下:函數
packagecom.fr.data; importjava.io.BufferedInputStream; importjava.io.ByteArrayInputStream; importjava.io.File; importjava.io.FileInputStream; importjava.io.FileNotFoundException; importjava.io.FileReader; importjava.io.InputStream; importjava.io.Reader; importjava.util.*; importjavax.xml.stream.XMLEventReader; importjavax.xml.stream.XMLInputFactory; importjavax.xml.stream.XMLStreamException; importjavax.xml.stream.events.XMLEvent; importcom.fr.base.Parameter; importcom.fr.data.AbstractParameterTableData; importcom.fr.general.data.DataModel; importcom.fr.script.Calculator; importcom.fr.stable.ParameterProvider; importcom.fr.stable.StringUtils; /** * XMLDemoTableData * * 這是一個按參數來解析不一樣地址XML文件的demo * * AbstractParameterTableData 包裝了有參數數據集的基本實現 */ publicclass XMLDemoTableData extends AbstractParameterTableData { // 構造函數 public XMLDemoTableData() { // 定義須要的參數,這裏定義一個參數,參數名爲filename,給其一個默認值"Northwind.xml" this.parameters = newParameter[1]; this.parameters[0] = newParameter("filename", "Northwind"); } /** * 返回獲取數據的執行對象 * 系統取數時,調用此方法來返回一個獲取數據的執行對象 * 注意!當數據集須要根據不一樣參數來屢次取數時,此方法在一個計算過程當中會被屢次調用。 */ @SuppressWarnings("unchecked") public DataModel createDataModel(Calculatorcalculator) { // 獲取傳進來的參數 ParameterProvider[] params =super.processParameters(calculator); // 根據傳進來的參數,等到文件的路徑 String filename = null; for (int i = 0; i < params.length;i++) { if (params[i] == null)continue; if("filename".equals(params[i].getName())) { filename =(String)params[i].getValue(); } } String filePath; if (StringUtils.isBlank(filename)){ filePath ="D://DefaultFile.xml"; } else { filePath = "D://" +filename + ".xml"; } // 定義須要解析的數據列,機器 // XMLColumnNameType4Demo[] columns = newXMLColumnNameType4Demo[7]; // columns[0] = newXMLColumnNameType4Demo("CustomerID",XMLParseDemoDataModel.COLUMN_TYPE_STRING); // columns[1] = newXMLColumnNameType4Demo("CompanyName",XMLParseDemoDataModel.COLUMN_TYPE_STRING); // columns[2] = newXMLColumnNameType4Demo("ContactName",XMLParseDemoDataModel.COLUMN_TYPE_STRING); // columns[3] = newXMLColumnNameType4Demo("ContactTitle",XMLParseDemoDataModel.COLUMN_TYPE_STRING); // columns[4] = newXMLColumnNameType4Demo("Address",XMLParseDemoDataModel.COLUMN_TYPE_STRING); // columns[5] = newXMLColumnNameType4Demo("City",XMLParseDemoDataModel.COLUMN_TYPE_STRING); // columns[6] = new XMLColumnNameType4Demo("Phone",XMLParseDemoDataModel.COLUMN_TYPE_STRING); List list=new ArrayList(); XMLInputFactory inputFactory =XMLInputFactory.newInstance(); InputStream in; try { in = new BufferedInputStream(newFileInputStream(new File(filePath))); XMLEventReader reader =inputFactory.createXMLEventReader(in); readCol(reader,list); in.close(); } catch (Exception e) { // TODO Auto-generated catchblock e.printStackTrace(); } XMLColumnNameType4Demo[]columns=(XMLColumnNameType4Demo[])list.toArray(newXMLColumnNameType4Demo[0]); // 定義解析的數據在xml文件結構中的位置 String[] xpath = new String[2]; xpath[0] = "Northwind"; xpath[1] = "Customers"; /* * 說明 * 提供的樣例xml文件的格式是: * <Notrhwind> * <Customers> * <CustomerID>ALFKI</CustomerID> * <CompanyName>AlfredsFutterkiste</CompanyName> * <ContactName>MariaAnders</ContactName> * <ContactTitle>SalesRepresentative</ContactTitle> * <Address>Obere Str. 57</Address> * <City>Berlin</City> * <PostalCode>12209</PostalCode> * <Country>Germany</Country> * <Phone>030-0074321</Phone> * <Fax>030-0076545</Fax> * </Customers> * </Northwind> * * 上面定義的意義就是 * /Northwind/Customers路徑所表示的一個Customers節點爲一條數據,它包含的節點中的CustomerID...等等是須要獲取的列值 */ // 構造一個實際去取值的執行對象 return new XMLParseDemoDataModel(filePath,xpath, columns); } private int deep=0; private static final int COL_DEEP=3; private boolean flag=false; private void readCol(XMLEventReader reader,List list) throws XMLStreamException { while (reader.hasNext()) { XMLEvent event =reader.nextEvent(); if (event.isStartElement()) { //deep是控制層數的,只把xml中對應的層的加入到列名中 deep++; //表示已經進入到了列名那一層 if(deep==COL_DEEP){ flag=true; } //若是在高層,而且已經進入到了col層,則退出 if(deep<COL_DEEP&&flag){ return; } if(deep!=COL_DEEP){ continue; } // println("name: " +event.asStartElement().getName()); XMLColumnNameType4Democolumn=new XMLColumnNameType4Demo(event.asStartElement().getName().toString(),XMLParseDemoDataModel.COLUMN_TYPE_STRING); list.add(column); readCol(reader,list); } else if (event.isCharacters()){ //對數據值不作處理 } else if (event.isEndElement()){ deep--; return; } } } private void readCol0(XMLEventReader reader) throws XMLStreamException { while (reader.hasNext()) { XMLEvent event = reader.nextEvent(); if (event.isStartElement()) { //deep是控制層數的,只把xml中對應的層的加入到列名中 deep++; //表示已經進入到了列名那一層 if(deep==COL_DEEP){ flag=true; } //若是在高層,而且已經進入到了col層,則退出 if(deep<COL_DEEP&&flag){ return; } if(deep!=COL_DEEP){ continue; } System.out.println("name:" + event.asStartElement().getName()); readCol0(reader); } else if (event.isCharacters()){ //對數據值不作處理 } else if (event.isEndElement()){ deep--; return; } } } public static void main(String[]args){ XMLInputFactory inputFactory =XMLInputFactory.newInstance(); // in = new FileReader(newFile(filePath)); // XMLEventReader reader = inputFactory.createXMLEventReader(in); // readCol(reader,list); BufferedInputStream in; try { in = new BufferedInputStream(newFileInputStream(new File("D:/tmp/f.xml"))); byte[] ba=new byte[3]; in.read(ba,0,3); // System.out.println(in) XMLEventReader reader =inputFactory.createXMLEventReader(in); newXMLDemoTableData().readCol0(reader); } catch (Exception e) { // TODO Auto-generated catchblock e.printStackTrace(); } } }
注:若是xml文件的格式上問題描述處所展現的xml格式不一致,則須要修改類中的deep變量,把列名所在的節點層數改爲相對應的數值。 注:XMLDemoTableData裏面的filename並非文件名,而是xml裏面某個標籤名。 四、編譯程序數據源 分別編譯XMLColumnNameType4Demo.java、XMLParseDemoDataModel.java、XMLDemoTableData.java三個類文件,將生成的class文件放於%FR_HOME%\WebReport\WEB-INF\classes\com\fr\data下。工具
5 配置程序數據源 新建報表,模板數據集>程序數據集,選擇咱們定義好的程序數據集XMLDemoTableData.class文件,名字能夠自定義,如程序1。this
六、使用程序數據源 在模板數據集窗口,點擊預覽按鈕,彈出參數對話框,輸入要顯示的xml文件名稱,點擊肯定則能夠把Northwind.xml文件裏面的數據讀取出來轉換報表數據源了,以下圖:code