在Java开发的报表工具FineReport中,假如在目录下保存了几个XML文件,希望把XML文件转换为报表数据源,同时希望展示动态xml数据源的效果,这时可通过参数的方式,动态获取xml字段中的值再作为报表数据源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
Northwind.xml记录数据格式如下:
<?xmlversion=
"1.0"
encoding=
"UTF-8"
?>
<Northwind>
<Customers>
<CustomerID>ALFKI</CustomerID>
<CompanyName>ALfredsFutterkiste</CompanyName>
<ContactName>MariaAnders</ContactName>
<ContactTitle>SalesRepresentative</ContactTitle>
<Address>ObereStr.
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,供其他类直接调用,安全性比较高,详细代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
packagecom.fr.data;
publicclass 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四个方法,详细代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
packagecom.fr.data;
importjava.io.File;
importjava.util.ArrayList;
importjava.util.List;
importjavax.xml.parsers.SAXParser;
importjavax.xml.parsers.SAXParserFactory;
importorg.xml.sax.Attributes;
importorg.xml.sax.SAXException;
importorg.xml.sax.helpers.DefaultHandler;
importcom.fr.base.FRContext;
importcom.fr.data.AbstractDataModel;
importcom.fr.general.ComparatorUtils;
importcom.fr.general.data.TableDataException;
/**
* XMLParseDemoDataModel
*
* DataModel是获取数据的接口
*
* 这里通过init方法一次性取数后,构造一个二维表对象来实现DataModel的各个取数方法
*/
publicclass 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(Stringfilename, String[] xPath,
XMLColumnNameType4Demo[] columns){
this
.filePath = filename;
this
.xPath = xPath;
this
.columns = columns;
}
/**
* 取出列的数量
*/
public
int
getColumnCount() throwsTableDataException {
return
columns.length;
}
/**
* 取出相应的列的名称
*/
public
String getColumnName(intcolumnIndex)
throws
TableDataException {
if
(columnIndex <
0
|| columnIndex>= columns.length)
return
null
;
String columnName =columns[columnIndex] ==
null
?
null
:columns[columnIndex].getName();
return
columnName;
}
/**
* 取出得到的结果集的总的行数
*/
public
int
getRowCount() throwsTableDataException {
this
.init();
return
row_list.size();
}
/**
* 取出相应位置的值
*/
public
Object getValueAt(
int
rowIndex, intcolumnIndex)
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() throwsTableDataException {
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(newFile(XMLParseDemoDataModel.
this
.filePath),
new
DemoHandler());
}
catch
(Exception e) {
e.printStackTrace();
FRContext.getLogger().error(e.getMessage(), e);
}
}
/**
* 基本原理就是解析器在遍历文件时发现节点开始标记时,调用startElement方法读取节点内部内容时,调用characters方法
* 发现节点结束标记时,调用endElement
*/
private
class
DemoHandler extendsDefaultHandler {
private
List levelList = newArrayList();
// 记录当前节点的路径
private
Object[] values;
// 缓存一条记录
private
int
recordIndex = -
1
;
// 当前记录所对应的列的序号,-1表示不需要记录
public
void
startElement(String uri,String localName, String qName,
Attributes attributes) throwsSAXException {
// 记录下
levelList.add(qName);
if
(isRecordWrapTag()) {
// 开始一条新数据的记录
values = newObject[XMLParseDemoDataModel.
this
.columns.length];
}
else
if
(needReadRecord()) {
// 看看其对应的列序号,下面的characters之后执行时,根据这个列序号来设置值存放的位置。
recordIndex =getColumnIndex(qName);
}
}
public
void
characters(
char
[] ch, intstart,
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 = newInteger(text);
}
else
if
(type.getType() ==COLUMN_TYPE_BOOLEAN) {
value = newBoolean(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(StringcolumnName) {
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内字段名称是一一对用的。详细代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
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里面某个标签名。
|
4、编译程序数据源
分别编译XMLColumnNameType4Demo.java、XMLParseDemoDataModel.java、XMLDemoTableData.java三个类文件,将生成的class文件放于%FR_HOME%\WebReport\WEB-INF\classes\com\fr\data下。
5 配置程序数据源
新建报表,模板数据集>程序数据集,选择我们定义好的程序数据集XMLDemoTableData.class文件,名字可以自定义,如程序1。
6、使用程序数据源
在模板数据集窗口,点击预览按钮,弹出参数对话框,输入要显示的xml文件名称,点击确定则可以把Northwind.xml文件里面的数据读取出来转换报表数据源了,如下图: