使用说明
Java针对MS Office的操作的库屈指可数,比较有名的就是Apache的POI库。一些有名的开源工具如:hutool,也对POI进行了二次封装。我基于自己做项目时的一些需求,针对POI进行了二次封装,并进行了一系列改进,使Java操作Excel、Word等文件变得更加简单。本框架相比于hutool等更“轻”,没有十分复杂的设计模式、接口等,上手更加容易。
[toc]
环境&依赖
jdk1.8
poi3.17
idea中直接导入maven项目即可完成初始构建。
表格处理
源码实现主要在com.jzy.office.excel下
Demo示例
在com.jzy.demo.excel包下有一些示例程序。
SimpleReadExcelDemo:一个简单的读excel示例。修改相应的输入文件路径信息后即可运行,下同...
public class SimpleReadExcelDemo {
public static void main(String[] args) throws IOException, InvalidFileTypeException {
//改成你实际的文件路径
String filePath = "E:\\Engineering\\java\\idea\\excel-processing\\example\\test1.xlsx";
//通过文件绝对路径构造excel对象
DefaultExcel excel = new DefaultExcel(filePath);
...
}
}
SimpleWriteExcelDemo:一个简单的写excel示例。
ReadSeatTableDemo:读取座位表信息示例。
WriteSeatTableDemo:修改座位表信息示例。给定一个教室,一组学生姓名列表,要求在座位表模板的基础上只输出该教室的sheet并将列表中的学生按座位顺序依次填充。
.....
DefaultExcel——默认的excel处理类
DefaultExcel是处理excel默认类,支持解析.xlsx、.xls、.et文件于一体,直接使用DefaultExcel或者继承DefaultExcel都可以方便的读写excel。它又继承自类CommonExcel,该类是核心,继承自抽象类AbstractExcel,实现了大量对poi处理excel二次封装后的方法;ExcelValidity接口用于检验表格的合法性;Resettable接口用于体现表格各类参数可重置的特性。
构造DefaultExcel对象
要处理excel前我们首先要根据输入excel文件构造处理对象。一个最基础的方法就是使用DefaultExcel类。
简单地,你可以直接由文件绝对路径构造DefaultExcel对象。
String filePath="E:\\test1.xlsx";
//通过文件绝对路径构造excel对象
DefaultExcel excel=new DefaultExcel(filePath);
你也可以传入InputStream流对象,并指定excel的版本以构造DefaultExcel对象。
String filePath="E:\\\test1.xlsx";
//通过InputStream流对象,并指定excel的版本构造excel对象
DefaultExcel excel=new DefaultExcel(new FileInputStream(new File(filePath)), ExcelVersionEnum.VERSION_2007);
当然,如果你仅仅想创建一个新的excel,而不是读取已有的文件,你可以直接如下构造:
//创建一个新的excel2007文件
DefaultExcel excel2007 =new DefaultExcel(ExcelVersionEnum.VERSION_2007);
//创建一个新的excel2003文件
DefaultExcel excel2003 =new DefaultExcel(ExcelVersionEnum.VERSION_2003);
构造excel表格的版本通过枚举ExcelVersionEnum决定。
进行读操作
完成excel对象的构造后,就可以直接调用其已实现的方法来读取表格信息。
获取当前表格的sheet数量
int sheetCount = excel.getSheetCount();
获取第n张sheet的总行数,通过传入sheet索引数值n-1,下同。如第一张sheet索引值为0,类比数组。
int sheetIndex=0; // 第一张sheet
int rowCount = excel.getRowCount(sheetIndex);
获取指定sheet的名称。
String sheetName = excel.getSheetName(sheetIndex);
根据sheet名称获取该名称对应sheet的索引。
int targetSheetIndex = excel.getSheetIndex(sheetName);
获取指定sheet指定行的所有值。
int rowIndex = 0; //第1行
List rowValue = excel.readRow(sheetIndex, rowIndex);
获取指定sheet指定列的所有值(从指定行开始到最后)
int columnIndex = 0; //第1列
int startRow = 1; //第2行开始
List columnValue = excel.readColumn(sheetIndex, startRow, columnIndex);
获取指定sheet指定行指定列的单元格中的内容。
String cellValue = excel.read(sheetIndex, rowIndex, columnIndex);
获取指定sheet从指定行开始到指定行结束所有行的内容
int endRow = 3;
List> rowValues = excel.readRows(sheetIndex, startRow, endRow);
获取指定sheet的全部内容
List> allData = excel.read(sheetIndex);
更多方法请参见AbstractExcel类及其实现子类CommonExcel。
进行写操作
清除第指定sheet中所有内容。
int sheetIndex=0;
excel.clearSheet(sheetIndex);
往指定sheet的指定行批量写入数据
int rowIndex = 0;
excel.writeRow(sheetIndex, rowIndex, Arrays.asList("我是", "第一行", "新的数据"));
往指定sheet的指定列批量写入数据
int columnIndex=0;
excel.writeColumn(sheetIndex, columnIndex, Arrays.asList("我是", "第一列", "新的数据"));
往指定sheet的指定行和指定列的单元格写入数据
int targetRow = 1; //第2行
int targetColumn = 1; //第2列
excel.write(sheetIndex,targetRow, targetColumn, "你好");
往指定sheet的指定范围内的单元格写入同一个数据
int startRow = 3; //第4行开始
int endRow=5; //第6行结束
int startColumn = 0; //第1列开始
int endColumn=3; //第4列结束
excel.write(sheetIndex, startColumn, endColumn, startRow, endRow, "重复值");
删除指定sheet的指定行
int rowToRemove=5;
excel.removeRow(sheetIndex, rowToRemove);
删除指定sheet的多行
int rowToRemoveStart=3;
int rowToRemoveEnd=4;
//删除第4~5行
excel.removeRows(sheetIndex, rowToRemoveStart, rowToRemoveEnd);
保存修改后的文件。
//修改后另存为文件路径
String savePath="C:\\Users\\92970\\Desktop\\1.xlsx";
//excel.saveAndClose();//这样会直接覆盖更新原文件
excel.saveAndClose(savePath);
更多方法请参见AbstractExcel类及其实现子类CommonExcel。
AbstractInputExcel——要读取的目标表格抽象类
在实际的业务情景中,读取表格可能涉及更多更复杂的操作,如预先进行各种合法性校验等,而继承AbstractInputExcel抽象类并实现或重写其相应方法是一个不错的选择。AbstractInputExcel抽象类继承自DefaultExcel并实现了ExcelReadable接口,采用“模板模式”的设计模式。
故可以将对某一目标表格的某一种读取模式写在一个类中,而该类继承AbstractInputExcel类,实现其相应方法,然后调用抽象父类的testAndReadData方法即可以“模块化”地实现一次复杂的校验和读取。com.jzy.demo.excel.input包中的Test1InputExcel就是一个很好的示例。
子类可以重写的方法如下:
/**
* 非必须。重置所有暂存的读取结果(成员变量)
*/
@Override
public void resetOutput() {
}
/**
* 非必须。重置带读取的目标列索引值
*/
@Override
public void resetColumnIndex() {
}
/**
* 非必须。找到要读取的列的位置索引值
*
* @param sheetIx 要处理的sheet的索引
*/
@Override
protected void findColumnIndexOfSpecifiedName(int sheetIx) {
}
/**
* 非必须。测试判断要读取的目标列是否找到(子类实现)。如果没有,抛出异常。
*
* @return
* @throws ExcelColumnNotFoundException 规定名称的列未找到
*/
@Override
public boolean testColumnNameValidity() throws ExcelColumnNotFoundException {
return false;
}
/**
* 核心方法,具体的读取数据操作。将数据存储于类成员变量中。
*
* @param sheetIndex sheet索引
* @return 当前sheet的有效行数
*/
@Override
public int readData(int sheetIndex) {
return 0;
}
通过调用抽象父类的模板方法testAndReadData可以完成一次完整的校验和读取,而上述方法在抽象父类的模板方法的执行顺序是:
resetOutput
resetColumnIndex
findColumnIndexOfSpecifiedName
testColumnNameValidity
readData
AbstractTemplateExcel——模板表格抽象类
AbstractTemplateExcel是待输出的模板表格的抽象类。所谓模板类型的表格,即通常不用读取数据,只需根据当前模板做一定修改然后给用户保存。因此也不用对列名进行匹配检测等。AbstractInputExcel抽象类继承自DefaultExcel并实现了ExcelWriteable接口。你只需自定义一个类继承AbstractInputExcel抽象类,实现writeData方法然后调用writeData方法即可完成写入。
com.jzy.demo.excel.template包中的Test1TemplateExcel就是一个很好的示例。
/**
* 将预先存放好的数据执行写入到当前excel中。
*
* @return
*/
@Override
public boolean writeData() {
return true;
}
构造既可读又可写的表格类
有时我们读取输入表格的同时还需要对该表格修改,返回给用户,即又读又写。这种情况,优雅地,你可继承AbstractInputExcel的同时实现ExcelWriteable接口。
com.jzy.demo包中的ReadableAndWriteableTest1Excel就是一个很好的示例。
public class ReadableAndWriteableTest1Excel extends AbstractInputExcel implements ExcelWriteable {
@Override
public void resetOutput() {
}
@Override
public void resetColumnIndex() {
}
@Override
protected void findColumnIndexOfSpecifiedName(int sheetIx) {
}
@Override
public boolean testColumnNameValidity() throws ExcelColumnNotFoundException {
return true;
}
@Override
public int readData(int sheetIndex) {
return 0;
}
@Override
public boolean writeData() {
return true;
}
}
文档处理
源码实现主要在com.jzy.office.word下
Demo示例
在com.jzy.demo.word包下有一些示例程序。
SimpleReadExcelDemo:一个简单的读excel示例。修改相应的输入文件路径信息后即可运行,下同...
public class SimpleReadExcelDemo {
public static void main(String[] args) throws IOException, InvalidFileTypeException {
//改成你实际的文件路径
String filePath = "E:\\Engineering\\java\\idea\\excel-processing\\example\\test1.xlsx";
//通过文件绝对路径构造excel对象
DefaultExcel excel = new DefaultExcel(filePath);
...
}
}
SimpleWriteExcelDemo:一个简单的写excel示例。
ReadSeatTableDemo:读取座位表信息示例。
WriteSeatTableDemo:修改座位表信息示例。给定一个教室,一组学生姓名列表,要求在座位表模板的基础上只输出该教室的sheet并将列表中的学生按座位顺序依次填充。
.....
DefaultWord2003——默认的word 2003文档处理类
DefaultWord2003是处理word 2003文档默认类,支持解析.doc、.wps文件,直接使用DefaultWord2003或者继承DefaultWord2003都可以方便的读写word。它又继承自类Word2003,该类继承自抽象类AbstractWord,针对poi对word 2003的接口进行了个性化的二次封装。
构造DefaultWord2003对象
word对象的构建方法与excel异曲同工。
简单地,你可以直接由文件绝对路径构造DefaultWord2003对象。
String filePath = "E:\\Engineering\\java\\idea\\easy-office-process\\example\\1.doc";
//构造word 2003对象
DefaultWord2003 word2003 = new DefaultWord2003(filePath);
你也可以传入InputStream流对象,并指定excel的版本以构造DefaultExcel对象。
String filePath = "E:\\Engineering\\java\\idea\\easy-office-process\\example\\1.doc";
//通过InputStream流对象,并指定word的版本构造word对象
DefaultWord2003 excel=new DefaultWord2003(new FileInputStream(new File(filePath)), WordVersionEnum.VERSION_2003);
进行读操作
完成word对象的构造后,就可以直接调用其已实现的方法来读取文档信息。
获取当前文档的段落数量(包含表格中的段落)
int paraNum = word2003.getParagraphNum();
获取当前文档的表格数量
int tableNum = word2003.getTableNum();
获取所有段落的文本,依次存于list中
注意由于结构以及poi提供接口的不同:对word 2003子类实现对于段落的解析时,会把表格对象的段落也作为文档段落一并读出
List paraStrings = word2003.readParagraphsToList();
获取特定段落的文本
//获取第paraPos+1段的文本
int paraPos = 2;
String para2Text = word2003.readParagraph(paraPos);
获取指定表格的所有值。
//读取第tablePos+1个表格的内容
int tablePos = 0;
List>> table1 = word2003.readTable(tablePos);
这里的com.jzy.office.word.AbstractWord#readTable(int)方法返回的是当前文档对应表格的二维数据list,二维数组每个元素都是对应单元格的一个list,因为每个单元都可以认为是多个“段落”的结合,因此该list是这些段落的集合。下同理
/**
* 返回第pos+1个表格的文本。
* 按表格二维结构,返回对应结构的二维数组。每个二维数组元素都是对应单元格段落的list
* -------------------------------------------------------------
* | List | List | List |
* -------------------------------------------------------------
* | List | List | List |
* -------------------------------------------------------------
* ...
*
* @param pos 表格索引
* @return 表格二维数组
*/
public List>> readTable(int pos) {
checkTableRange(pos);
return readTable0(pos);
}
获取指定表格指定列的所有值。
//读取第1个表格第1列的文本
int tablePos = 0;
int columnPos = 0;
List> columnStrings = word2003.readTableColumn(tablePos, columnPos);
获取指定表格指定行的所有值。
//读取第1个表格第1行的文本
int tablePos = 0;
int rowPos = 0;
List> rowStrings = word2003.readTableRow(tablePos, rowPos);
获取指定表格指定行指定列的值。
//读取第1个表格第2行第3列的文本
int tablePos = 0;
int rowPos = 1;
int columnPos = 2;
List cellStrings = word2003.readTable(tablePos, rowPos, columnPos);
更多方法请参见AbstractWord类及其实现子类Word2003。
进行写操作
目前提供的接口主要支持替换文本的写操作。通过传入给定的HashMap作为替换书签集,以下方法为例,replaceInPara方法会遍历书签集,并在第3段文本中寻找是否包含了书签集的某个键,如果包含,替换为对应的值;并返回所有成功被替换的
//创建准备替换的书签集
HashMap bookmark = new HashMap<>();
bookmark.put("${table}", "0000");
bookmark.put("${label1}", "1111");
//使用bookmark替换第3段中内容
int paraPos = 2;
HashMap replacedBookmark = word2003.replaceInPara(paraPos, bookmark);
对全文所有段落使用指定书签集进行替换。
//这样对全文所有段落进行替换
HashMap replacedBookmark2 = word2003.replaceInParas(bookmark);
对指定的表格使用指定书签集进行替换。
//使用bookmark替换第1个表格中内容
int tablePos = 0;
HashMap replacedBookmark3 = word2003.replaceInTable(tablePos, bookmark);
对全文所有表格使用指定书签集进行替换。
//这样对全文所有表格进行替换
HashMap replacedBookmark4 = word2003.replaceInTables(bookmark);
对全文所有段落和表格使用指定书签集进行替换。注意由于word 2003子类实现对于段落的解析时,会把表格对象的段落也作为文档段落,因此使用该方法的效果和word2003.replaceInParas(bookmark)是一样的。
//这样对全文所有段落和表格进行替换
HashMap replacedBookmark5 =word2003.replaceInAll(bookmark);
删除指定sheet的指定行
int rowToRemove=5;
excel.removeRow(sheetIndex, rowToRemove);
删除指定sheet的多行
int rowToRemoveStart=3;
int rowToRemoveEnd=4;
//删除第4~5行
excel.removeRows(sheetIndex, rowToRemoveStart, rowToRemoveEnd);
保存修改后的文件。
String savePath = "C:\\Users\\92970\\Desktop\\1.doc";
//word2003.saveAndClose();//这样会直接覆盖更新原文件
word2003.saveAndClose(savePath);
更多方法请参见AbstractWord类及其实现子类Word2003。
DefaultWord2007——默认的word 2007文档处理类
DefaultWord2007与DefaultWord2003完全类似,是处理word 2007文档默认类,支持解析.docx文件。下面仅展示部分区别如下,其余使用类比参考2003:
构造DefaultWord2007对象
除了类名不同,构造原理都相同。
直接由文件绝对路径构造。
String filePath = "E:\\Engineering\\java\\idea\\easy-office-process\\example\\1.docx";
//构造word 2007对象
DefaultWord2007 word2007 = new DefaultWord2007(filePath);
传入InputStream流对象,并指定word的版本以构造。
String filePath = "E:\\Engineering\\java\\idea\\easy-office-process\\example\\1.docx";
//通过InputStream流对象,并指定word的版本构造word对象
DefaultWord2007 excel=new DefaultWord2007(new FileInputStream(new File(filePath)), WordVersionEnum.VERSION_2007);
进行读操作
在Word2007子类实现中,对段落读取时,与2003有所区别:他不会把表格对象的段落作为文档段落。
典型的如:
public abstract int getParagraphNum();
public abstract List readParagraphsToList();
其他方法均与Word2003类似。
更多方法请参见AbstractWord类及其实现子类Word2007。
进行写操作
需要区别于2003的是对于段落的替换,注意由于word 2003子类实现对于段落的解析时,会把表格对象的段落也作为文档段落;而2007,会区分表格和段落。因此仅需替换段落请使用word2003.replaceInParas(bookmark),需要同时替换所有段落和表格请使用word2003.replaceInAll(bookmark)。
另一方面,在替换操作上,Word2007子类引入了更高级的“标签匹配器”,可以更自由和优雅的对段落或表格进行替换操作。详细源码见
com.jzy.office.matcher包。下面仅介绍应用:
之前我们传入书签时,手动给定了${}的标识结构
bookmark.put("${table}", "0000");
bookmark.put("${label1}", "1111");
而标签匹配器的作用正是基于正则表达式的封装实现自定义标签的格式,更自由且优雅。
下面的代码使用的默认标签匹配器LabelMatchers.DEFAULT_LABEL_MATCHER进行替换,该匹配器使用的正则表达式为:"\$\{(.+?)\}"
它对输入的书签集中的标签,在段落文本中寻找${key}的标签。如table,它就会在指定段落中找到所有${table},并将其替换为对应值。注意对于段落中那些在bookmark没有找到的${key},方法会将其替换为空值。
//创建准备替换的书签集
HashMap bookmark = new HashMap<>();
bookmark.put("table", "0000");
bookmark.put("label1", "1111");
//使用bookmark使用默认标签匹配器替换第4段中内容
int paraPos = 3;
HashMap replacedBookmark = word2007.replaceInParaUsingLabelMatcher(paraPos, bookmark, LabelMatchers.DEFAULT_LABEL_MATCHER);
使用自定义标签匹配器,这里传入正则表达式"\{\{(.+?)\}\}"创建一个匹配器,该匹配器会解析所有{{key}}的标签。
//使用bookmark使用自定义标签匹配器替换第2段中内容
int paraPos2 = 2;
//使用匹配{{key}}的标签匹配器
LabelMatcher lMatcher = LabelMatchers.getLabelMatcher("\\{\\{(.+?)\\}\\}");
HashMap replacedBookmark2 = word2007.replaceInParaUsingLabelMatcher(paraPos2, bookmark, lMatcher);
表格的替换类似。
更多方法请参见AbstractWord类及其实现子类Word2007。