历史版本1 :解析某个目录下 XML 文件 返回文档
编辑时间: 内容长度:图片数:目录数: 修改原因:

1. 问题描述编辑

D盘目录下保存了几个XML文件,希望把XML文件转换为报表数据源,想通过程序数据集的方法实现,同时希望展示动态xml数据源的效果,这时可通过参数的方式,动态获取xml字段中的值再作为报表数据源。
XML记录数据格式如下:
最终用于制作报表的数据源形式如下:
对于这样的情况我们如何来实现呢?FineReport中可以通过自定义程序数据集来对xml字段数据进行解析,最终返回所希望的数据表。实现步骤如下:

2. 示例编辑

2.1 定义XMLColumnNameType4Demo封装类
首先定义参数name及type,供其他类直接调用,安全性比较高,详细代码如下:
HTML/XML代码
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;    
    }    
}  
注:最新的代码链接fine-help
2.2 定义XMLParseDemoDataModel.java类文件
定义XMLParseDemoDataModel.java类继承AbstractDataModel接口,实现getColumnCount、getColumnName、getRowCount、getValueAt四个方法,详细代码如下:
HTML/XML代码
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;    
        }    
    }    
}  
注:最新的代码链接fine-help
2.3 定义程序数据集XMLDemoTableData
通过参数filename,动态显示xml文件内容,首先xml文件需要放到某个目录下,如下代码是放到D盘,并且定义需要解析的数据列,这边定义的数据列名称,根xml内字段名称是一一对用的。详细代码如下:
HTML/XML代码
package com.fr.data;      
  
import java.io.BufferedInputStream;    
import java.io.ByteArrayInputStream;    
import java.io.File;    
import java.io.FileInputStream;    
import java.io.FileNotFoundException;    
import java.io.FileReader;    
import java.io.InputStream;    
import java.io.Reader;    
import java.util.*;   
import javax.xml.stream.XMLEventReader;    
import javax.xml.stream.XMLInputFactory;    
import javax.xml.stream.XMLStreamException;    
import javax.xml.stream.events.XMLEvent;   
import com.fr.base.Parameter;    
import com.fr.data.AbstractParameterTableData;  
import com.fr.general.data.DataModel;  
import com.fr.script.Calculator;      
import com.fr.stable.StringUtils;  
      
/**   
 *  XMLDemoTableData   
 *    
 *  这是一个按参数来解析不同地址XML文件的demo   
 *    
 *  AbstractParameterTableData 包装了有参数数据集的基本实现   
 */      
public class XMLDemoTableData extends AbstractParameterTableData {      
          
    // 构造函数      
    public XMLDemoTableData() {      
        // 定义需要的参数,这里定义一个参数,参数名为filename,给其一个默认值"Northwind.xml"      
        this.parameters = new Parameter[1];      
        this.parameters[0] = new Parameter("filename", "Northwind");      
    }      
      
    /**   
     * 返回获取数据的执行对象   
     * 系统取数时,调用此方法来返回一个获取数据的执行对象   
     * 注意! 当数据集需要根据不同参数来多次取数时,此方法在一个计算过程中会被多次调用。   
     */      
    @SuppressWarnings("unchecked")    
    public DataModel createDataModel(Calculator calculator) {      
        // 获取传进来的参数      
        Parameter[] 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 = new XMLColumnNameType4Demo[7];      
//        columns[0] = new XMLColumnNameType4Demo("CustomerID", XMLParseDemoDataModel.COLUMN_TYPE_STRING);      
//        columns[1] = new XMLColumnNameType4Demo("CompanyName", XMLParseDemoDataModel.COLUMN_TYPE_STRING);      
//        columns[2] = new XMLColumnNameType4Demo("ContactName", XMLParseDemoDataModel.COLUMN_TYPE_STRING);      
//        columns[3] = new XMLColumnNameType4Demo("ContactTitle", XMLParseDemoDataModel.COLUMN_TYPE_STRING);      
//        columns[4] = new XMLColumnNameType4Demo("Address", XMLParseDemoDataModel.COLUMN_TYPE_STRING);      
//        columns[5] = new XMLColumnNameType4Demo("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(new FileInputStream(new File(filePath)));    
            XMLEventReader reader = inputFactory.createXMLEventReader(in);    
            readCol(reader,list);    
            in.close();    
        } catch (Exception e) {    
            // TODO Auto-generated catch block    
            e.printStackTrace();    
        }    
        XMLColumnNameType4Demo[] columns=(XMLColumnNameType4Demo[])list.toArray(new XMLColumnNameType4Demo[0]);    
            
            
        // 定义解析的数据在xml文件结构中的位置      
        String[] xpath = new String[2];      
        xpath[0] = "Northwind";      
        xpath[1] = "Customers";      
        /*   
         * 说明   
         * 提供的样例xml文件的格式是:   
         * <Notrhwind>   
         *     <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>   
         *    
         * 上面定义的意义就是   
         * /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());    
                XMLColumnNameType4Demo column=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(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();    
            }    
    }    
}  
注:如果xml文件的格式上问题描述处所展示的xml格式不一致,则需要修改类中的deep变量,把列名所在的节点层数改成相对应的数值。
注:XMLDemoTableData里面的filename并不是文件名,而是xml里面某个标签名。
另:最新的代码链接fine-help
2.4 编译程序数据源
分别编译XMLColumnNameType4Demo.java、XMLParseDemoDataModel.java、XMLDemoTableData.java三个类文件,将生成的class文件放于WEB-INF/classes/com/fr/data下。
2.5 配置程序数据源
新建报表,模板数据集>程序数据集,选择我们定义好的程序数据集XMLDemoTableData.class文件,名字可以自定义,如程序1。
2.6 使用程序数据源
在模板数据集窗口,点击预览按钮,弹出参数对话框,输入要显示的xml文件名称,如下图:

确定则可以把Northwind.xml文件里面的数据读取出来转换报表数据源了。