在Java開發的報表工具FineReport中,假如在目錄下保存了幾個XML文件,希望把XML文件轉換為報表數據源,同時希望展示動態xml數據源的效果,這時可通過參數的方式,動態獲取xml字段中的值再作為報表數據源。
Northwind.xml記錄數據格式如下:
<?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字段數據進行解析,最終返回所希望的數據表。實現步驟如下:
1、 定義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;
}
}
2、定義XMLParseDemoDataModel.java類文件
定義XMLParseDemoDataModel.java類繼承AbstractDataModel接口,實現getColumnCount、getColumnName、getRowCount、getValueAt四個方法,詳細代碼如下:
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;
}
}
}
3、定義程序數據集XMLDemoTableData
通過參數filename,動態顯示xml文件內容,首先xml文件需要放到某個目錄下,如下代碼是放到D盤,并且定義需要解析的數據列,這邊定義的數據列名稱,根xml內字段名稱是一一對用的。詳細代碼如下:
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(new File(filePath));
// XMLEventReader reader = inputFactory.createXMLEventReader(in);
// readCol(reader,list);
BufferedInputStream in;
try {
in = new BufferedInputStream(new FileInputStream(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);
new XMLDemoTableData().readCol0(reader);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
5 配置程序數據源
新建報表,模板數據集>程序數據集,選擇我們定義好的程序數據集XMLDemoTableData.class文件,名字可以自定義,如程序1。
6、使用程序數據源
在模板數據集窗口,點擊預覽按鈕,彈出參數對話框,輸入要顯示的xml文件名稱,點擊確定則可以把Northwind.xml文件里面的數據讀取出來轉換報表數據源了,如下圖: