言
Hi,大家好,我是希留。
在项目的开发工程中,经常有导入导出数据的常见功能场景,Apache的POI是处理导入导出中最常用的,但是其原生的用法太复杂,很繁琐,总是在Copy… ,无意间发现一款简单粗暴的神器EasyPoi,EasyPoi也是基于POI的,在SpringBoot中也是做了很好的封装,让我们能够在SpringBoot 快速地使用 EasyPoi 进行开发,很方便,而且支持多种格式的导入导出。
本篇文章就给大家介绍下EasyPoi。如果对你有帮助的话,还不忘点赞支持一下,感谢!文末附有源码
目录
一、EasyPoi简介
EasyPoi功能如同名字easy,主打的功能就是容易,让一个没见接触过poi的人员就可以方便的写出Excel导出,Excel模板导出,Excel导入,Word模板导出,通过简单的注解和模板语言(熟悉的表达式语法),完成以前复杂的写法。
最新官方文档:
http://doc.wupaas.com/docs/easypoi/easypoi-1c0u6ksp2r091
二、EasyPoi主要功能
三、EasyPoi注解介绍
easypoi起因就是Excel的导入导出,最初的模板是实体和Excel的对应,model–row,filed–col 这样利用注解我们可以和容易做到excel到导入导出。
这个是必须使用的注解,如果需求简单只使用这一个注解也是可以的,涵盖了常用的Excel需求。
属性 | 类型 | 默认值 | 功能 |
name | String | null | 列名 |
orderNum | String | “0” | 列的排序 |
replace | String[] | {} | 值的替换 {a_id,b_id} |
type | int | 1 | 导出类型 1 是文本 2 是图片,3 是函数, |
exportFormat | String | “” | 导出的时间格式,以这个是否为空来判断 |
importFormat | String | “” | 导入的时间格式,以这个是否为空来判断是否需要格式化日期 |
format | String | “” | 时间格式,相当于同时设置了exportForm |
suffix | String | “” | 文字后缀,如% 90 变成90% |
isHyperlink | boolean | false | 超链接,如果是需要实现接口返回对象 |
isImportField | boolean | true | 校验字段,看看这个字段是不是导入 |
一对多的集合注解,用以标记集合是否被数据以及集合的整体排序
属性 | 类型 | 默认值 | 功能 |
id | String | null | 定义ID |
name | String | null | 定义集合列名, |
orderNum | int | 0 | 排序,支持name_id |
type | Class<?> | ArrayList.class | 导入时创建对象使用 |
四、开始使用
Maven 依赖:
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-spring-boot-starter</artifactId>
<version>4.2.0</version>
</dependency>
首先定义需要导入的数据类型UserImportVO,并使用@Excel注解与excel列映射,导入的时候通常需要对导入的数据进行一些校验
EasyPoi的校验使用也很简单,对象上加上通用的校验规则,配置下需要校验就可以了,校验主要是JSR 303 规范,可结合Hibernate Validator使用
导入类对象实现IExcelModel、IExcelDataModel 接口,可获取到错误校验信息。
@Data
public class UserImportVO implements Serializable,IExcelModel, IExcelDataModel {
@NotBlank
@Excel(name="姓名")
private String realName;
@Excel(name="性别", replace={ "男_1", "女_2" })
private Integer sex;
@Excel(name="出生日期", format="yyyy-MM-dd")
private Date birthday;
@Length(min=1, max=11, message="请填写正确的手机号")
@Excel(name="手机号码")
private String phone;
@Excel(name="固定电话")
private String tel;
@Email(message="请填写正确的邮箱地址")
@Excel(name="邮箱")
private String email;
@Excel(name="头像地址")
private String avatar;
@Excel(name="信息")
private String errorMsg;
private Integer rowNum;
@Override
public Integer getRowNum() {
return this.rowNum;
}
@Override
public void setRowNum(Integer i) {
this.rowNum=i;
}
@Override
public String getErrorMsg() {
return this.errorMsg;
}
@Override
public void setErrorMsg(String s) {
this.errorMsg=s;
}
}
在编写controller层导入方法
@Autowired
private IUserService userService;
@PostMapping("/importExcel")
public String importExcel(@RequestParam("file") MultipartFile file) {
try {
String result=userService.importExcel(file);
return result;
} catch (Exception e) {
return "导入失败";
}
}
具体的实现是在service层
@Autowired
private UserVerifyHandler userVerifyHandler;
@Override
public String importExcel(MultipartFile file) throws Exception{
ImportParams importParams=new ImportParams();
//表格标题行数,默认0
importParams.setTitleRows(1);
//是否需要校验上传的Excel
importParams.setNeedVerify(true);
//告诉easypoi我们自定义的验证器
importParams.setVerifyHandler(userVerifyHandler);
ExcelImportResult<UserImportVO> result=ExcelImportUtil.importExcelMore(file.getInputStream(),UserImportVO.class,importParams);
if (!result.isVerifyFail() && !CollectionUtils.isEmpty(result.getList())) {
for (UserImportVO vo : result.getList()) {
log.info("从Excel导入数据到数据库的详细为 :{}", vo);
User user=new User();
BeanUtil.copyProperties(vo,user);
this.save(user);
}
} else {
for (UserImportVO vo : result.getFailList()) {
log.info("校验失败的详细为 :{}", vo);
}
return "文档校验失败";
}
return "导入成功";
}
2.4 ImportParams 参数
导入参数介绍下
属性 | 类型 | 默认值 | 功能 |
titleRows | int | 0 | 表格标题行数,默认0 |
headRows | int | 1 | 表头行数,默认1 |
startRows | int | 0 | 字段真正值和列标题之间 |
startSheetIndex | int | 0 | 开始读取的sheet位置, |
needVerfiy | boolean | false | 是否需要校验上传的Excel |
needSave | boolean | false | 是否需要保存上传的Excel |
saveUrl | String | “upload | 保存上传的Excel目录,默认 |
importFields | String[] | null | 导入时校验数据模板,是不是正确的Excel |
verifyHanlder | IExcelVerifyHandler | null | 校验处理接口,自定义校验 |
dataHanlder | IExcelDataHandler | null | 数据处理接口,以此为主,replace,format都在这后面 |
通用的校验满足不了所有的校验,例如还需要通过查询数据库,校验数据的唯一性,此时需要自定义一个校验规则,实现IExcelVerifyHandler接口。
@Component
public class UserVerifyHandler implements IExcelVerifyHandler<UserImportVO> {
@Autowired
private IUserService userService;
@Override
public ExcelVerifyHandlerResult verifyHandler(UserImportVO vo) {
ExcelVerifyHandlerResult result=new ExcelVerifyHandlerResult();
//假设我们要添加用户,现在去数据库查询realName,如果存在则表示校验不通过。
User user=userService.getOne(new LambdaQueryWrapper<User>().eq(User::getRealName,vo.getRealName()));
if (user!=null) {
result.setMsg("唯一校验失败");
result.setSuccess(false);
return result;
}
result.setSuccess(true);
return result;
}
}
1.此时,基本上的代码就编写完了,开始测试:
使用postman工具进行导入测试,先填充一些不符合规则的数据,可以看到控制台输出的校验错误的信息。
图1-1 测试导入的execl
图1-2 postman工具请求接口返回
图1-3 控制台输出
2.再填充一些符合规则的数据,可以看到导入成功,数据库成功插入数据。
图2-1 测试导入的execl
图2-2 postman工具请求接口返回
图2-3 数据库数据
导出类不需要配置校验规则,只需定义要导出的信息
@Data
public class UserExportVO implements Serializable {
@Excel(name="姓名")
private String realName;
@Excel(name="性别", replace={ "男_1", "女_2" }, suffix="生")
private Integer sex;
@Excel(name="出生日期", format="yyyy-MM-dd")
private Date birthday;
@Excel(name="手机号码")
private String phone;
@Excel(name="固定电话")
private String tel;
@Excel(name="邮箱")
private String email;
@Excel(name="头像地址")
private String avatar;
}
编写controller层导出方法
@GetMapping("/exportExcel")
public void export(HttpServletResponse response) {
//查询要导出的数据
List<UserExportVO> users=userService.getUserExportList();
ExcelUtil.exportExcelX(users, "测试导出表", "sheet1", UserExportVO.class, "测试导出表.xlsx", response);
}
3.3 service层
编写service层查询需要导出的数据,把查询出来的集合转化成导出VO集合。
@Override
public List<UserExportVO> getUserExportList() {
List<User> users=this.list();
//users集合转成export集合
List<UserExportVO> exportVOList=users.stream().map(user -> {
UserExportVO vo=new UserExportVO();
BeanUtils.copyProperties(user, vo);
return vo;
}).collect(Collectors.toList());
return exportVOList;
}
直接浏览器请求导出接口,成功导出。
图3-1 请求导出接口
图3-2 导出结果
结语
好了,以上就是今天要讲的内容,本文仅仅简单介绍了使用EasyPoi导入导出功能的使用,而EasyPoi还提供了模板的导出、图片的导出、word的导出等等功能,感兴趣的朋友,可查阅官方文档进一步探究。
本次demo源码:
Gitee地址:https://gitee.com/huoqstudy/java-sjzl-demo/tree/master/springboot-easypoi-demo
Github地址:https://github.com/277769738/java-sjzl-demo/tree/master/springboot-easypoi-demo
产品期望实现【公文管理】其中发文拟文一块内容,用户上传正文(word),再选择不同套红模板,最后拼接为一个对外发文,的公文格式。
基于上次使用vue实现在线编辑功能,产品不太满意,重新学习java如何操作word文档,之前地址:(vue使用Tinymce富文本模拟在线word文档)juejin.cn/post/723665…
有两个文件test1.docx (作为红头模板)和test2.docx(作为正文);期望实现效果:用户选择红头模板加上自己写的正文内容,最终生成可编辑的word文档。
create_table.docx 合并之后最终效果“
test1.docx 红头模板:
test2.docx 正文(用户撰写上传的内容):
如标题所示,最终采用Apache POI来实现这个功能主要原因:
主要是对于我这种初学者来说是非常友好的,太复杂玩不转。Apache POI 的使用非常简单,只需添加它的 jar 包到项目中,并调用相应的 API 即可实现文档读写。同时,Apache POI 提供了丰富的文档和官方网站上的文档也很详细。
Apache POI,分别为 Word、Excel、PowerPoint 等各种格式提供不同的类方法,我们需要操作Word文档的功能,所以使用(Java API for Microsoft Documents)中的XWPFDocument类,实现文档合并功能。
整理不同格式文档操作类
注意:word文档目前有两种不同格式,一种是以doc结尾的,另一种以docx结尾,本次功能主要讲解docx格式文档操作,doc格式文档调用的类和函数HWPF开头。
两者区别:doc是Word2007及以下版的文件扩展名,而docx是Word2007及以上版本的文件扩展名,docx版本兼容性较高,而且比doc文件所占用空间更小。
在pom.xml文件中引入maven依赖,
<!-- WordToHtml .doc .odcx poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>4.1.2</version>
</dependency>
<!-- 操作excel的库 注意版本保持一致 poi poi-ooxml poi-scratchpad -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<!--poi-ooxml和*poi-ooxml-schemas*是poi对2007及以上版本的扩充。-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
简化整体流程,创建两个word文件test1.docx和test2.docx。将下列file对应的文件路径换成自己创建的文件路径创,建单个java文件(带main),直接运行main方法输出creat_table.docx文件。 先上代码,再进行讲解:
package org.ssssssss.magicboot;
import org.apache.poi.xwpf.usermodel.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
public class WordDocumentTest {
public static void main(String[] args) throws Exception
{
//获取文件转io流
File file1=new File("D:/IDM/test1.docx");
File file2=new File("D:/IDM/test2.docx");
FileInputStream fis1=new FileInputStream(file1);
FileInputStream fis2=new FileInputStream(file2);
//最终文件输出的路径
FileOutputStream out=new FileOutputStream(new File("D:/IDM/create_table.docx"));
//转为word文档元素
XWPFDocument dcx1=new XWPFDocument(fis1);
XWPFDocument dcx2=new XWPFDocument(fis2);
//创建一个新的文档
XWPFDocument document=new XWPFDocument();
//将第一个文档元素复制新创建的文档中;
document=dcx1;
//换行 document.createParagraph().createRun().addBreak();
//将第二个docx内容添加到新创建的文档中
mergeParagraphs(dcx2.getParagraphs(), document);
//结束关闭io流
document.write(out);
out.close();
fis1.close();
fis2.close();
System.out.println("create_table document written success.");
}
// 合并文本段落
private static void mergeParagraphs(List<XWPFParagraph> paragraphs, XWPFDocument outDoc) {
for (XWPFParagraph para : paragraphs) {
XWPFParagraph newPara=outDoc.createParagraph();
newPara.getCTP().setPPr(para.getCTP().getPPr());
//判断是否是文本、段落、图片 //.getRPr() para.tables !=null ,iruns 获取图片
for (XWPFRun run : para.getRuns()) {
XWPFRun newRun=newPara.createRun();
newRun.getCTR().setRPr(run.getCTR().getRPr());
newRun.setText(run.getText(0));
}
}
}
实现流程主要看代码中的注释,包含以下几个步骤
核心重点将第二个docx内容添加到新创建的文档中封装mergeParagraphs方法,在这个方法中传入了两个参数(List paragraphs, XWPFDocument outDoc) 其中List<XWPFParagraph> paragraphs=dcx2.getParagraphs() 意思是将dcx2文档所有段落取出来用一个数组存放,再进行循环段落;通过XWPFParagraph newPara=outDoc.createParagraph();给新的文档创建一个新的段落;给新的段落添加对应的样式newPara.getCTP().setPPr(para.getCTP().getPPr());最后由于段落中会划分不同的XWPFRun再进行循环设置文本的字体、大小、颜色、加粗、斜体、下划线等格式。 官方api介绍
刚才对XWPFRun没有进行很好的解释,这里重新举例说明下,例如以下标红的段落
按照正常理解这一个段落内容应该是一个东西,其实在XWPF中会划分为不同的XWPFRun,使用idea打断点查看数据
可以看出它将一段文字划分为不同模块,为什么会这样,在一段文字中也存在不同的区别,例如文字的字体,像下图中“根据”“2023”属于不同的字体,所以会划分为不同的XWPFRun,理解这个概念后同理明白为什么一个段落会划分为29模块
在前面其实已经实现第一个模块最终效果的docx文档合并的功能,所以在这个模块讲解在实现这个过程中记录有意思的内容。
接着上面讲XWPFRun这个函数,XWPFRun用于在 Word 文档中添加或修改单个本 Run 或 Run 中的文字格式。它是文本段落(XWPFParagraph)中的最小单元,用于精细控制文本的格式和样式。可以使用 XWPFRun 类的各种方法来设置文本的字体、大小、颜色、加粗、斜体、下划线等格式。 下列是在使用过程中记录的一些属性,以及这些属性对应能够设置的格式注释。
XWPFRun run=firstParagraph.createRun();
XWPFRun tempRun=xwpfRuns.get(i);
//默认:宋体(wps)/等线(office2016) 5号 两端对齐 单倍间距
run.setText(tempRun.text());
//加粗
run.setBold(tempRun.isBold());
//我也不知道这个属性做啥的
run.setCapitalized(tempRun.isCapitalized());
//设置颜色--十六进制
run.setColor(tempRun.getColor());
//这个属性报错
run.setCharacterSpacing(tempRun.getCharacterSpacing());
//浮雕字体----效果和印记(悬浮阴影)类似
run.setEmbossed(tempRun.isEmbossed());
//双删除线
run.setDoubleStrikethrough(tempRun.isDoubleStrikeThrough());
run.setEmphasisMark(tempRun.getEmphasisMark().toString());
//字体,//字体,范围----效果不详
run.setFontFamily(tempRun.getFontFamily());
//字体大小,没有设置默认是-1,
if(tempRun.getFontSize() !=-1){
run.setFontSize(tempRun.getFontSize());
}
//印迹(悬浮阴影)---效果和浮雕类似
run.setImprinted(tempRun.isImprinted());
//斜体(字体倾斜)
run.setItalic(tempRun.isItalic());
//字距调整----这个好像没有效果
run.setKerning(tempRun.getKerning());
//阴影---稍微有点效果(阴影不明显)
run.setShadow(tempRun.isShadowed());
//小型股------效果不清楚
run.setSmallCaps(tempRun.isSmallCaps());
//单删除线(废弃)
run.setStrike(tempRun.isStrike());
//单删除线(新的替换Strike)
run.setStrikeThrough(tempRun.isStrikeThrough());
//下标(吧当前这个run变成下标)---枚举
run.setSubscript(tempRun.getSubscript());
//设置两行之间的行间距
run.setTextPosition(tempRun.getTextPosition());
//各种类型的下划线(枚举)
run.setUnderline(tempRun.getUnderline());
run.setVerticalAlignment(tempRun.getVerticalAlignment().toString());
run.setVanish(tempRun.isVanish());
run.setUnderlineThemeColor(tempRun.getUnderlineColor());
run.setUnderlineColor(tempRun.getUnderlineColor());
run.setTextScale(tempRun.getTextScale());
run.setTextPosition(tempRun.getTextPosition());
run.setTextHighlightColor(tempRun.getTextHightlightColor().toString());
// run.setStyle(tempRun.gets); 没找到这个属性
run.setLang(tempRun.getLang());
XWPFParagraph 是 Apache POI 库中 XWPF 模块的一部分,用于创建或修改 Word 文档中的段落。它可以添加不同的文本格式,并且可以添加图片、表格、超链接等内容。XWPFParagraph 类可以控制段落的样式和格式,包括字体、字号、行距、首行缩进、对齐方式等。可以使用 XWPFParagraph 类的各种方法来设置段落的格式和样式。
常用方法:
//创建一个新的 XWPFRun 对象,用于在段落中添加文本或修改文本格式。
createRun()
//设置段落的对齐方式,align 参数可以是 LEFT、CENTER、RIGHT、JUSTIFY 等值。
setAlignment(ParagraphAlignment align)
//设置段落的行距和行距规则,lineSpacing 参数是行距大小(以磅为单位),lineSpacingRule 参数可以是 EXACT、AT_LEAST、AUTO 等值。
setSpacingBetween(int lineSpacing, LineSpacingRule lineSpacingRule)
//设置段落的左缩进大小(以磅为单位)。
setIndentationLeft(int indentation)
//设置段落的右缩进大小(以磅为单位)。
setIndentationRight(int indentation)
//设置段落的编号 ID。
setNumID(BigInteger numId)
//设置段落的编号格式,numFmt 参数可以是 DECIMAL、LOWERCASE_LETTER、UPPERCASE_LETTER 等值。
setNumFmt(NumberFormat numFmt)
//在段落中添加图片,pictureType 参数是图片类型,filename 参数是图片文件名,width 和 height 参数是图片宽度和高度。
createPicture(XWPFRun run, int pictureType, String filename, int width, int height)
其他方法:
//指定应显示在左边页面指定段周围的边界。
setBorderBottom(Borders.APPLES);
//指定应显示在下边页面指定段周围的边界。
setBorderLeft(Borders.APPLES);
//指定应显示在右侧的页面指定段周围的边界。
setBorderRight(Borders.ARCHED_SCALLOPS);
//指定应显示上方一组有相同的一组段边界设置的段落的边界。这几个是对段落之间的格式的统一,相当于格式刷
setBorderTop(Borders.ARCHED_SCALLOPS);
//---正文宽度会稍微变窄
p1.setFirstLineIndent(99);
//---段落的对齐方式 1左 2中 3右 4往上 左 不可写0和负数
p1.setFontAlignment(1);
//---首行缩进,指定额外的缩进,应适用于父段的第一行。
p1.setIndentationFirstLine(400);
//---首行前进,指定的缩进量,应通过第一行回到开始的文本流的方向上移动缩进从父段的第一行中删除。
p1.setIndentationHanging(400);
//---整段右移
p1.setIndentFromLeft(400);
//--此方法提供了样式的段落,这非常有用。
p1.setStyle("");
//--此元素指定是否消费者应中断超过一行的文本范围,通过打破这个词 (打破人物等级) 的两行或通过移动到下一行 (在词汇层面上打破) 这个词的拉丁文字。
p1.setWordWrapped(true);
//---指定的文本的垂直对齐方式将应用于此段落中的文本
p1.setVerticalAlignment(TextAlignment.CENTER);
//--指定行之间的间距如何计算存储在行属性中。
p1.setSpacingLineRule(LineSpacingRule.AT_LEAST);
//--指定应添加在此线单位在文档中的段落的第一行之前的间距。
p1.setSpacingBeforeLines(6);
//--指定应添加上面这一段文档中绝对单位中的第一行的间距。
p1.setSpacingBefore(6);
//--指定应添加在此线单位在文档中的段落的最后一行之后的间距。
p1.setSpacingAfterLines(6);
//--指定应添加在文档中绝对单位这一段的最后一行之后的间距。
p1.setSpacingAfter(6);
//--指定当渲染此分页视图中的文档,这一段的内容都呈现在文档中的新页的开始。
p1.setPageBreak(true);
刚在展示活动内容都是根据自身的需求写小demo,实际项目远远不止这些内容,其中还是存在不足之处,例如word中的表格、图片都是需要单独处理,表格有个专门类XWPFTable;图片也有XWPFPictureData、 XWPFPicture,
Apache POI 官方网站提供了完整的 API 文档:poi.apache.org/apidocs/dev…
Apache POI 的 GitHub 仓库中查看示例代码和文档:github.com/apache/poi
Java POI 生成Word文档:blog.csdn.net/qq_34755766…
Apache POI 中文版download.csdn.net/download/qq…
作者:沐游虞
转自:https://juejin.cn/post/7237487091554730021
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
实现文档在线预览的方式除了上篇文章 文档在线预览新版(一)通过将文件转成图片实现在线预览功能说的将文档转成图片的实现方式外,还有转成pdf,前端通过pdf.js、pdfobject.js等插件来实现在线预览,以及本文将要说到的将文档转成html的方式来实现在线预览。
以下代码分别提供基于aspose、pdfbox、spire来实现来实现txt、word、pdf、ppt、word等文件转图片的需求。
Aspose 是一家致力于.Net ,Java,SharePoint,JasperReports和SSRS组件的提供商,数十个国家的数千机构都有用过aspose组件,创建、编辑、转换或渲染 Office、OpenOffice、PDF、图像、ZIP、CAD、XPS、EPS、PSD 和更多文件格式。注意aspose是商用组件,未经授权导出文件里面都是是水印(尊重版权,远离破解版)。
需要在项目的pom文件里添加如下依赖
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>23.1</version>
</dependency>
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-pdf</artifactId>
<version>23.1</version>
</dependency>
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-cells</artifactId>
<version>23.1</version>
</dependency>
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-slides</artifactId>
<version>23.1</version>
</dependency>
因为aspose和spire虽然好用,但是都是是商用组件,所以这里也提供使用开源库操作的方式的方式。
POI是Apache软件基金会用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能。
Apache PDFBox是一个开源Java库,支持PDF文档的开发和转换。 使用此库,您可以开发用于创建,转换和操作PDF文档的Java程序。
需要在项目的pom文件里添加如下依赖
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-excelant</artifactId>
<version>5.2.0</version>
</dependency>
spire一款专业的Office编程组件,涵盖了对Word、Excel、PPT、PDF等文件的读写、编辑、查看功能。spire提供免费版本,但是存在只能导出前3页以及只能导出前500行的限制,只要达到其一就会触发限制。需要超出前3页以及只能导出前500行的限制的这需要购买付费版(尊重版权,远离破解版)。这里使用免费版进行演示。
spire在添加pom之前还得先添加maven仓库来源
<repository>
<id>com.e-iceblue</id>
<name>e-iceblue</name>
<url>https://repo.e-iceblue.cn/repository/maven-public/</url>
</repository>
接着在项目的pom文件里添加如下依赖
免费版:
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.office.free</artifactId>
<version>5.3.1</version>
</dependency>
付费版版:
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.office</artifactId>
<version>5.3.1</version>
</dependency>
public static String wordToHtmlStr(String wordPath) {
try {
Document doc=new Document(wordPath); // Address是将要被转化的word文档
String htmlStr=doc.toString();
return htmlStr;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
验证结果:
public String wordToHtmlStr(String wordPath) throws TransformerException, IOException, ParserConfigurationException {
String htmlStr=null;
String ext=wordPath.substring(wordPath.lastIndexOf("."));
if (ext.equals(".docx")) {
htmlStr=word2007ToHtmlStr(wordPath);
} else if (ext.equals(".doc")){
htmlStr=word2003ToHtmlStr(wordPath);
} else {
throw new RuntimeException("文件格式不正确");
}
return htmlStr;
}
public String word2007ToHtmlStr(String wordPath) throws IOException {
// 使用内存输出流
try(ByteArrayOutputStream out=new ByteArrayOutputStream()){
word2007ToHtmlOutputStream(wordPath, out);
return out.toString();
}
}
private void word2007ToHtmlOutputStream(String wordPath,OutputStream out) throws IOException {
ZipSecureFile.setMinInflateRatio(-1.0d);
InputStream in=Files.newInputStream(Paths.get(wordPath));
XWPFDocument document=new XWPFDocument(in);
XHTMLOptions options=XHTMLOptions.create().setIgnoreStylesIfUnused(false).setImageManager(new Base64EmbedImgManager());
// 使用内存输出流
XHTMLConverter.getInstance().convert(document, out, options);
}
private String word2003ToHtmlStr(String wordPath) throws TransformerException, IOException, ParserConfigurationException {
org.w3c.dom.Document htmlDocument=word2003ToHtmlDocument(wordPath);
// Transform document to string
StringWriter writer=new StringWriter();
TransformerFactory tf=TransformerFactory.newInstance();
Transformer transformer=tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "html");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.transform(new DOMSource(htmlDocument), new StreamResult(writer));
return writer.toString();
}
private org.w3c.dom.Document word2003ToHtmlDocument(String wordPath) throws IOException, ParserConfigurationException {
InputStream input=Files.newInputStream(Paths.get(wordPath));
HWPFDocument wordDocument=new HWPFDocument(input);
WordToHtmlConverter wordToHtmlConverter=new WordToHtmlConverter(
DocumentBuilderFactory.newInstance().newDocumentBuilder()
.newDocument());
wordToHtmlConverter.setPicturesManager((content, pictureType, suggestedName, widthInches, heightInches) -> {
System.out.println(pictureType);
if (PictureType.UNKNOWN.equals(pictureType)) {
return null;
}
BufferedImage bufferedImage=ImgUtil.toImage(content);
String base64Img=ImgUtil.toBase64(bufferedImage, pictureType.getExtension());
// 带图片的word,则将图片转为base64编码,保存在一个页面中
StringBuilder sb=(new StringBuilder(base64Img.length() + "data:;base64,".length()).append("data:;base64,").append(base64Img));
return sb.toString();
});
// 解析word文档
wordToHtmlConverter.processDocument(wordDocument);
return wordToHtmlConverter.getDocument();
}
public String wordToHtmlStr(String wordPath) throws IOException {
try(ByteArrayOutputStream outputStream=new ByteArrayOutputStream()) {
Document document=new Document();
document.loadFromFile(wordPath);
document.saveToFile(outputStream, FileFormat.Html);
return outputStream.toString();
}
}
public static String pdfToHtmlStr(String pdfPath) throws IOException, ParserConfigurationException {
PDDocument document=PDDocument.load(new File(pdfPath));
Writer writer=new StringWriter();
new PDFDomTree().writeText(document, writer);
writer.close();
document.close();
return writer.toString();
}
验证结果:
public String pdfToHtmlStr(String pdfPath) throws IOException, ParserConfigurationException {
PDDocument document=PDDocument.load(new File(pdfPath));
Writer writer=new StringWriter();
new PDFDomTree().writeText(document, writer);
writer.close();
document.close();
return writer.toString();
}
public String pdfToHtmlStr(String pdfPath) throws IOException, ParserConfigurationException {
try(ByteArrayOutputStream outputStream=new ByteArrayOutputStream()) {
PdfDocument pdf=new PdfDocument();
pdf.loadFromFile(pdfPath);
return outputStream.toString();
}
}
public static String excelToHtmlStr(String excelPath) throws Exception {
FileInputStream fileInputStream=new FileInputStream(excelPath);
Workbook workbook=new XSSFWorkbook(fileInputStream);
DataFormatter dataFormatter=new DataFormatter();
FormulaEvaluator formulaEvaluator=workbook.getCreationHelper().createFormulaEvaluator();
Sheet sheet=workbook.getSheetAt(0);
StringBuilder htmlStringBuilder=new StringBuilder();
htmlStringBuilder.append("<html><head><title>Excel to HTML using Java and POI library</title>");
htmlStringBuilder.append("<style>table, th, td { border: 1px solid black; }</style>");
htmlStringBuilder.append("</head><body><table>");
for (Row row : sheet) {
htmlStringBuilder.append("<tr>");
for (Cell cell : row) {
CellType cellType=cell.getCellType();
if (cellType==CellType.FORMULA) {
formulaEvaluator.evaluateFormulaCell(cell);
cellType=cell.getCachedFormulaResultType();
}
String cellValue=dataFormatter.formatCellValue(cell, formulaEvaluator);
htmlStringBuilder.append("<td>").append(cellValue).append("</td>");
}
htmlStringBuilder.append("</tr>");
}
htmlStringBuilder.append("</table></body></html>");
return htmlStringBuilder.toString();
}
返回的html字符串:
<html><head><title>Excel to HTML using Java and POI library</title><style>table, th, td { border: 1px solid black; }</style></head><body><table><tr><td>序号</td><td>姓名</td><td>性别</td><td>联系方式</td><td>地址</td></tr><tr><td>1</td><td>张晓玲</td><td>女</td><td>11111111111</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>2</td><td>王小二</td><td>男</td><td>1222222</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>1</td><td>张晓玲</td><td>女</td><td>11111111111</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>2</td><td>王小二</td><td>男</td><td>1222222</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>1</td><td>张晓玲</td><td>女</td><td>11111111111</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>2</td><td>王小二</td><td>男</td><td>1222222</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>1</td><td>张晓玲</td><td>女</td><td>11111111111</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>2</td><td>王小二</td><td>男</td><td>1222222</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>1</td><td>张晓玲</td><td>女</td><td>11111111111</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>2</td><td>王小二</td><td>男</td><td>1222222</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>1</td><td>张晓玲</td><td>女</td><td>11111111111</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>2</td><td>王小二</td><td>男</td><td>1222222</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>1</td><td>张晓玲</td><td>女</td><td>11111111111</td><td>上海市浦东新区xx路xx弄xx号</td></tr><tr><td>2</td><td>王小二</td><td>男</td><td>1222222</td><td>上海市浦东新区xx路xx弄xx号</td></tr></table></body></html>
public String excelToHtmlStr(String excelPath) throws Exception {
FileInputStream fileInputStream=new FileInputStream(excelPath);
try (Workbook workbook=WorkbookFactory.create(new File(excelPath))){
DataFormatter dataFormatter=new DataFormatter();
FormulaEvaluator formulaEvaluator=workbook.getCreationHelper().createFormulaEvaluator();
org.apache.poi.ss.usermodel.Sheet sheet=workbook.getSheetAt(0);
StringBuilder htmlStringBuilder=new StringBuilder();
htmlStringBuilder.append("<html><head><title>Excel to HTML using Java and POI library</title>");
htmlStringBuilder.append("<style>table, th, td { border: 1px solid black; }</style>");
htmlStringBuilder.append("</head><body><table>");
for (Row row : sheet) {
htmlStringBuilder.append("<tr>");
for (Cell cell : row) {
CellType cellType=cell.getCellType();
if (cellType==CellType.FORMULA) {
formulaEvaluator.evaluateFormulaCell(cell);
cellType=cell.getCachedFormulaResultType();
}
String cellValue=dataFormatter.formatCellValue(cell, formulaEvaluator);
htmlStringBuilder.append("<td>").append(cellValue).append("</td>");
}
htmlStringBuilder.append("</tr>");
}
htmlStringBuilder.append("</table></body></html>");
return htmlStringBuilder.toString();
}
}
public String excelToHtmlStr(String excelPath) throws Exception {
try(ByteArrayOutputStream outputStream=new ByteArrayOutputStream()) {
Workbook workbook=new Workbook();
workbook.loadFromFile(excelPath);
workbook.saveToStream(outputStream, com.spire.xls.FileFormat.HTML);
return outputStream.toString();
}
}
有时我们是需要的不仅仅返回html字符串,而是需要生成一个html文件这时应该怎么做呢?一个改动量小的做法就是使用org.apache.commons.io包下的FileUtils工具类写入目标地址:
首先需要引入pom:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>
相关代码:
String htmlStr=FileConvertUtil.pdfToHtmlStr("D:\\书籍\\电子书\\小说\\历史小说\\最后的可汗.doc");
FileUtils.write(new File("D:\\test\\doc.html"), htmlStr, "utf-8");
除此之外,还可以对上面的代码进行一些调整,已实现生成html文件,代码调整如下:
word原文件效果:
public static void wordToHtml(String wordPath, String htmlPath) {
try {
File sourceFile=new File(wordPath);
String path=htmlPath + File.separator + sourceFile.getName().substring(0, sourceFile.getName().lastIndexOf(".")) + ".html";
File file=new File(path); // 新建一个空白pdf文档
FileOutputStream os=new FileOutputStream(file);
Document doc=new Document(wordPath); // Address是将要被转化的word文档
HtmlSaveOptions options=new HtmlSaveOptions();
options.setExportImagesAsBase64(true);
options.setExportRelativeFontSize(true);
doc.save(os, options);
} catch (Exception e) {
e.printStackTrace();
}
}
转换成html的效果:
public void wordToHtml(String wordPath, String htmlPath) throws TransformerException, IOException, ParserConfigurationException {
htmlPath=FileUtil.getNewFileFullPath(wordPath, htmlPath, "html");
String ext=wordPath.substring(wordPath.lastIndexOf("."));
if (ext.equals(".docx")) {
word2007ToHtml(wordPath, htmlPath);
} else if (ext.equals(".doc")){
word2003ToHtml(wordPath, htmlPath);
} else {
throw new RuntimeException("文件格式不正确");
}
}
public void word2007ToHtml(String wordPath, String htmlPath) throws TransformerException, IOException, ParserConfigurationException {
//try(OutputStream out=Files.newOutputStream(Paths.get(path))){
try(FileOutputStream out=new FileOutputStream(htmlPath)){
word2007ToHtmlOutputStream(wordPath, out);
}
}
private void word2007ToHtmlOutputStream(String wordPath,OutputStream out) throws IOException {
ZipSecureFile.setMinInflateRatio(-1.0d);
InputStream in=Files.newInputStream(Paths.get(wordPath));
XWPFDocument document=new XWPFDocument(in);
XHTMLOptions options=XHTMLOptions.create().setIgnoreStylesIfUnused(false).setImageManager(new Base64EmbedImgManager());
// 使用内存输出流
XHTMLConverter.getInstance().convert(document, out, options);
}
public void word2003ToHtml(String wordPath, String htmlPath) throws TransformerException, IOException, ParserConfigurationException {
org.w3c.dom.Document htmlDocument=word2003ToHtmlDocument(wordPath);
// 生成html文件地址
try(OutputStream outStream=Files.newOutputStream(Paths.get(htmlPath))){
DOMSource domSource=new DOMSource(htmlDocument);
StreamResult streamResult=new StreamResult(outStream);
TransformerFactory factory=TransformerFactory.newInstance();
Transformer serializer=factory.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
}
}
private org.w3c.dom.Document word2003ToHtmlDocument(String wordPath) throws IOException, ParserConfigurationException {
InputStream input=Files.newInputStream(Paths.get(wordPath));
HWPFDocument wordDocument=new HWPFDocument(input);
WordToHtmlConverter wordToHtmlConverter=new WordToHtmlConverter(
DocumentBuilderFactory.newInstance().newDocumentBuilder()
.newDocument());
wordToHtmlConverter.setPicturesManager((content, pictureType, suggestedName, widthInches, heightInches) -> {
System.out.println(pictureType);
if (PictureType.UNKNOWN.equals(pictureType)) {
return null;
}
BufferedImage bufferedImage=ImgUtil.toImage(content);
String base64Img=ImgUtil.toBase64(bufferedImage, pictureType.getExtension());
// 带图片的word,则将图片转为base64编码,保存在一个页面中
StringBuilder sb=(new StringBuilder(base64Img.length() + "data:;base64,".length()).append("data:;base64,").append(base64Img));
return sb.toString();
});
// 解析word文档
wordToHtmlConverter.processDocument(wordDocument);
return wordToHtmlConverter.getDocument();
}
转换成html的效果:
public void wordToHtml(String wordPath, String htmlPath) {
htmlPath=FileUtil.getNewFileFullPath(wordPath, htmlPath, "html");
Document document=new Document();
document.loadFromFile(wordPath);
document.saveToFile(htmlPath, FileFormat.Html);
}
转换成html的效果:
因为使用的是免费版,存在页数和字数限制,需要完整功能的的可以选择付费版本。PS:这回76页的文档居然转成功了前50页。
图片版pdf原文件效果:
文字版pdf原文件效果:
public static void pdfToHtml(String pdfPath, String htmlPath) throws IOException, ParserConfigurationException {
File file=new File(pdfPath);
String path=htmlPath + File.separator + file.getName().substring(0, file.getName().lastIndexOf(".")) + ".html";
PDDocument document=PDDocument.load(new File(pdfPath));
Writer writer=new PrintWriter(path, "UTF-8");
new PDFDomTree().writeText(document, writer);
writer.close();
document.close();
}
图片版PDF文件验证结果:
文字版PDF文件验证结果:
public void pdfToHtml(String pdfPath, String htmlPath) throws IOException, ParserConfigurationException {
String path=FileUtil.getNewFileFullPath(pdfPath, htmlPath, "html");
PDDocument document=PDDocument.load(new File(pdfPath));
Writer writer=new PrintWriter(path, "UTF-8");
new PDFDomTree().writeText(document, writer);
writer.close();
document.close();
}
图片版PDF文件验证结果:
文字版PDF原文件效果:
public void pdfToHtml(String pdfPath, String htmlPath) throws IOException, ParserConfigurationException {
htmlPath=FileUtil.getNewFileFullPath(pdfPath, htmlPath, "html");
PdfDocument pdf=new PdfDocument();
pdf.loadFromFile(pdfPath);
pdf.saveToFile(htmlPath, com.spire.pdf.FileFormat.HTML);
}
图片版PDF文件验证结果:
因为使用的是免费版,所以只有前三页是正常的。。。有超过三页需求的可以选择付费版本。
文字版PDF原文件效果:
报错了无法转换。。。
java.lang.NullPointerException
at com.spire.pdf.PdfPageWidget.spr┢?(Unknown Source)
at com.spire.pdf.PdfPageWidget.getSize(Unknown Source)
at com.spire.pdf.PdfPageBase.spr???—(Unknown Source)
at com.spire.pdf.PdfPageBase.getActualSize(Unknown Source)
at com.spire.pdf.PdfPageBase.getSection(Unknown Source)
at com.spire.pdf.general.PdfDestination.spr︻┎?—(Unknown Source)
at com.spire.pdf.general.PdfDestination.spr┻┑?—(Unknown Source)
at com.spire.pdf.general.PdfDestination.getElement(Unknown Source)
at com.spire.pdf.primitives.PdfDictionary.setProperty(Unknown Source)
at com.spire.pdf.bookmarks.PdfBookmark.setDestination(Unknown Source)
at com.spire.pdf.bookmarks.PdfBookmarkWidget.spr┭┘?—(Unknown Source)
at com.spire.pdf.bookmarks.PdfBookmarkWidget.getDestination(Unknown Source)
at com.spire.pdf.PdfDocumentBase.spr??(Unknown Source)
at com.spire.pdf.widget.PdfPageCollection.spr┦?(Unknown Source)
at com.spire.pdf.widget.PdfPageCollection.removeAt(Unknown Source)
at com.spire.pdf.PdfDocumentBase.spr┞?(Unknown Source)
at com.spire.pdf.PdfDocument.loadFromFile(Unknown Source)
excel原文件效果:
public void excelToHtml(String excelPath, String htmlPath) throws Exception {
htmlPath=FileUtil.getNewFileFullPath(excelPath, htmlPath, "html");
Workbook workbook=new Workbook(excelPath);
com.aspose.cells.HtmlSaveOptions options=new com.aspose.cells.HtmlSaveOptions();
workbook.save(htmlPath, options);
}
转换成html的效果:
public void excelToHtml(String excelPath, String htmlPath) throws Exception {
String path=FileUtil.getNewFileFullPath(excelPath, htmlPath, "html");
try(FileOutputStream fileOutputStream=new FileOutputStream(path)){
String htmlStr=excelToHtmlStr(excelPath);
byte[] bytes=htmlStr.getBytes();
fileOutputStream.write(bytes);
}
}
public String excelToHtmlStr(String excelPath) throws Exception {
FileInputStream fileInputStream=new FileInputStream(excelPath);
try (Workbook workbook=WorkbookFactory.create(new File(excelPath))){
DataFormatter dataFormatter=new DataFormatter();
FormulaEvaluator formulaEvaluator=workbook.getCreationHelper().createFormulaEvaluator();
org.apache.poi.ss.usermodel.Sheet sheet=workbook.getSheetAt(0);
StringBuilder htmlStringBuilder=new StringBuilder();
htmlStringBuilder.append("<html><head><title>Excel to HTML using Java and POI library</title>");
htmlStringBuilder.append("<style>table, th, td { border: 1px solid black; }</style>");
htmlStringBuilder.append("</head><body><table>");
for (Row row : sheet) {
htmlStringBuilder.append("<tr>");
for (Cell cell : row) {
CellType cellType=cell.getCellType();
if (cellType==CellType.FORMULA) {
formulaEvaluator.evaluateFormulaCell(cell);
cellType=cell.getCachedFormulaResultType();
}
String cellValue=dataFormatter.formatCellValue(cell, formulaEvaluator);
htmlStringBuilder.append("<td>").append(cellValue).append("</td>");
}
htmlStringBuilder.append("</tr>");
}
htmlStringBuilder.append("</table></body></html>");
return htmlStringBuilder.toString();
}
}
转换成html的效果:
public void excelToHtml(String excelPath, String htmlPath) throws Exception {
htmlPath=FileUtil.getNewFileFullPath(excelPath, htmlPath, "html");
Workbook workbook=new Workbook();
workbook.loadFromFile(excelPath);
workbook.saveToFile(htmlPath, com.spire.xls.FileFormat.HTML);
}
转换成html的效果:
从上述的效果展示我们可以发现其实转成html效果不是太理想,很多细节样式没有还原,这其实是因为这类转换往往都是追求目标是通过使用文档中的语义信息并忽略其他细节来生成简单干净的 HTML,所以在转换过程中复杂样式被忽略,比如居中、首行缩进、字体,文本大小,颜色。举个例子在转换是 会将应用标题 1 样式的任何段落转换为 h1 元素,而不是尝试完全复制标题的样式。所以转成html的显示效果往往和原文档不太一样。这意味着对于较复杂的文档而言,这种转换不太可能是完美的。但如果都是只使用简单样式文档或者对文档样式不太关心的这种方式也不妨一试。
PS:如果想要展示效果好的话,其实可以将上篇文章《文档在线预览(一)通过将txt、word、pdf转成图片实现在线预览功能》说的内容和本文结合起来使用,即将文档里的内容都生成成图片(很可能是多张图片),然后将生成的图片全都放到一个html页面里 ,用html+css来保持样式并实现多张图片展示,再将html返回。开源组件kkfilevie就是用的就是这种做法。
kkfileview展示效果如下:
下图是kkfileview返回的html代码,从html代码我们可以看到kkfileview其实是将文件(txt文件除外)每页的内容都转成了图片,然后将这些图片都嵌入到一个html里,再返回给用户一个html页面。
*请认真填写需求信息,我们会在24小时内与您取得联系。