Java如何解析某个目录下xml文件,将XML文件转换为报表数据源?

时间:2021-06-22 11:41:21

在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>

最终用于制作报表的数据源形式如下:

Java如何解析某个目录下xml文件,将XML文件转换为报表数据源?

对于这样的情况我们如何来实现呢?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内字段名称是一一对用的。详细代码如下:

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。

Java如何解析某个目录下xml文件,将XML文件转换为报表数据源?

6、使用程序数据源

在模板数据集窗口,点击预览按钮,弹出参数对话框,输入要显示的xml文件名称,点击确定则可以把Northwind.xml文件里面的数据读取出来转换报表数据源了,如下图:

Java如何解析某个目录下xml文件,将XML文件转换为报表数据源?