Excel 在日常工作中经常被用来存储用例信息,是一种非常便捷的数据存储工具有着众多的优点,我们就不一一介绍了。
今天来讲讲 Java 操作 Excel,总所周知 Java 是世界上最好的语言(不容反驳),操作一个 Excel 肯定是不在话下,咱们熟知的 POI,Apache 大佬出品的一款非常强大的 office 软件操作包。虽然 POI 强大,但是代码相对比较繁琐,在当前 python 引领的大潮下,简化代码势在必行。
那么如何简化代码呢?其实这些事情早就已经有人帮我们想好和做好了,比如阿里巴巴的 easyexcel,和我们今天的主角 esaypoi 都是非常好的解决方案。那为什么选择 easypoi 而不是阿里的 easyexcel 呢,当然是 easypoi 的读写导入和导出更加简单。接下来大家就跟随着我一起慢慢揭开 easypoi 的神秘面纱。
easypoi 功能如同名字 easy,主打的功能就是容易,让一个没见接触过 poi 的人员
就可以方便的写出 Excel 导出,Excel 模板导出,Excel 导入,Word 模板导出,通过简单的注解和模板。
官网:https://opensource.afterturn.cn/doc/easypoi.html
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
完成最简单的导入,只需要两步:一使用注解配置实体类,二调用工具类。
实体类(省略 get/set 方法):
public class API implements Serializable {
@Excel(name="接口名称")
private String name;
@Excel(name="接口编号")
private String id;
@Excel(name="接口提交方式")
private String type;
@Excel(name="接口地址")
private String url;
@Excel(name="参数类型")
private String contentType;
}
@Excel(name="接口名称")这个注解是啥意思呢?name 属性表示 Excel 表头。如图:
当我们导入 Excel 时,就会按照@Excel 注解的映射关系封装 API 实体类。
工具类:
FileInputStream fis=new FileInputStream(EXCEL_PATH);
//导入参数设置类
ImportParams params=new ImportParams();
List<API> importExcel=ExcelImportUtil.importExcel(fis, API.class, params);
总共三行代码,第一行加载 Excel 文件,第二行设置导入参数,第三行根据导入参数返回对应结果并封装成 List 集合。这三个代码中主要讲解第二行和第三行,第二行是导入参数设置,它能给我们提供什么设置呢?
参考下表:
属性 | 类型 | 默认值 | 功能 |
titleRows | int | 0 | 表格标题行数,默认 0 |
headRows | int | 1 | 表头行数,默认 1 |
startRows | int | 0 | 字段真正值和列标题之间的距离 默认 0 |
keyIndex | int | 0 | 主键设置,如何这个 cell 没有值,就跳过 或者认为这个是 list 的下面的值这一列必须有值,不然认为这列为无效数据 |
startSheetIndex | int | 0 | 开始读取的 sheet 位置,默认为 0 |
sheetNum | int | 1 | 上传表格需要读取的 sheet 数量,默认为 1 |
needSave | boolean | false | 是否需要保存上传的 Excel |
needVerfiy | boolean | false | 是否需要校验上传的 Excel |
saveUrl | String | "upload/excelUpload" | 保存上传的 Excel 目录,默认是 如 TestEntity 这个类保存路径就是 upload/excelUpload/Test/yyyyMMddHHmss****** 保存名称上传时间*五位随机数 |
verifyHanlder | IExcelVerifyHandler | null | 校验处理接口,自定义校验 |
lastOfInvalidRow | int | 0 | 最后的无效行数,不读的行数 |
readRows | int | 0 | 手动控制读取的行数 |
importFields | String[] | null | 导入时校验数据模板,是不是正确的 Excel |
keyMark | String | ":" | Key-Value 读取标记,以这个为 Key,后面一个 Cell 为 Value,多个改为 ArrayList |
readSingleCell | boolean | false | 按照 Key-Value 规则读取全局扫描 Excel,但是跳过 List 读取范围提升性能 仅仅支持 titleRows + headRows + startRows 以及 lastOfInvalidRow |
dataHanlder | IExcelDataHandler | null | 数据处理接口,以此为主,replace,format 都在这后面 |
对照完这张表之后,你会发现即使我们不对 ImportParams 做任何设置,也会有对应的默认值。那么第二句代码就能翻译成:读取第一个 Sheet 且只读取第一个,表头是 Sheet 的第一行且只有一行。最终我们就能得到第一个 Sheet 中每一行数据,并且每一行被封装成了 API 对象也就是一个 List。有了这个集合之后我们需要导入的数据就能任由我们如何处理了,是不是很简单。
List<API> list=new ArrayList<API>();
ExportParams exportParams=new ExportParams();
Workbook workbook=ExcelExportUtil.exportExcel(exportParams, API.class, list);
workbook.write(new FileOutputStream(EXCEL_PATH));
导出也只有四句代码。第一句是需要导出的数据集合,第二句导出参数,第三句获取导出 workbook 对象,第四句通过输出流导出数据到 Excel 中。其中第二句也是有很多设置的,我们就用默认设置也能是导出的。第三句也要用到 API 实体类中的注解映射关系。
通过 esaypoi 我们能够使用最少的代码完成基本的导入和导出,基本上能够应对实际工作中 80% 的需求了,如果需要对 Excel 修改的话,目前来说市面上的工具包都做的不太简单,所以还是需要通过编写原生 poi 代码完成,如果你需要修改 Excel 的代码可以留言哦~
朋友想知道excel 转html实现,安排!于是我在原文的基础的基础上新增加了excel转html的实现方式,可是在提交修改时被告知"修改篇幅过大 请减少修改篇幅",不让提交,无奈只能重新再发一篇文章了
修改篇幅过大 请减少修改篇幅
实现文档在线预览的方式除了上篇文章《文档在线预览(一)通过将txt、word、pdf转成图片实现在线预览功能》说的将文档转成图片的实现方式外,还有转成pdf,前端通过pdf.js、pdfobject.js等插件来实现在线预览,以及本文将要说到的将文档转成html的方式来实现在线预览。代码基于 aspose-words(用于word转html),pdfbox(用于pdf转html),所以事先需要在项目里下面两个依赖:
<dependency>
<groupId>com.luhuiguo</groupId>
<artifactId>aspose-words</artifactId>
<version>23.1</version></dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.4</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 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 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>
有时我们是需要的不仅仅返回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文件,代码调整如下:
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();
}
}
word原文件效果:
word文件转换成html效果:
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原文件效果:
文字版PDF文件验证结果:
public static 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);
}
}
excel原文件效果:
excel文件转换成html文件验证效果:
从上述的效果展示我们可以发现其实转成html效果不是太理想,很多细节样式没有还原,这其实是因为这类转换往往都是追求目标是通过使用文档中的语义信息并忽略其他细节来生成简单干净的 HTML,所以在转换过程中复杂样式被忽略,比如居中、首行缩进、字体,文本大小,颜色。举个例子在转换是 会将应用标题 1 样式的任何段落转换为 h1 元素,而不是尝试完全复制标题的样式。所以转成html的显示效果往往和原文档不太一样。这意味着对于较复杂的文档而言,这种转换不太可能是完美的。但如果都是只使用简单样式文档或者对文档样式不太关心的这种方式也不妨一试。
Java编程生成word文档这种操作一般是常规操作比较常见,主要采用Apache的POI Word 这个库操作的比较多,还有的用Spire.Doc,但是这个库有些稍微难点的功能要收费,看了下费用还不低,周末朋友问起是否有用java操作word加上下标的经验,我是没有的,不过刚好借机研究下。 本文主要聊下POI,毕竟是免费的,更值得研究。
word 格式说明和POI支持情况: HWPF: MS-Word 97-2003(.doc),基于BIFF8格式的JAVA接口。只支持.doc文件简单的操作,读写能力有限。本API为POI项目早期开发,很不幸的 是主要负责HWPF模块开发的工程师-"Ryan Ackley"已经离开Apache组织,现在该模块没有人维护、更新、完善。 XWPF:MS-Word 2007+(.docx),基于OOXML格式的JAVA接口。较HWPF功能完善。
都是利用的jar,不存在什么安装,直接配置pom文件即可。 添加POI的word的jar:
<!-- 操作excel的库 注意版本保持一致 poi poi-ooxml poi-scratchpad -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
<scope>compile</scope>
</dependency>
<!--poi-ooxml和*poi-ooxml-schemas*是poi对2007及以上版本的扩充。-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
<scope>compile</scope>
</dependency>
<!--poi-ooxml和*poi-ooxml-schemas*是poi对2007及以上版本的扩充。-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
<scope>compile</scope>
</dependency>
<!-- WordToHtml .doc .odcx poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>4.1.2</version>
</dependency>
最核心的三个类: XWPFDocument : 文档本身的抽象、代表整个文档; XWPFParagraph: 段落、整个文档可以看做一个个段落组成、简单对应我们文章的段落; XWPFRun: 可以看做一个片段,整个段落由多个Run组成、每个Run可以有自己的文字内容、样式等。
XWPFTable: 表格,和段落平级;
创建文档
XWPFDocument doc=new XWPFDocument();
或
XWPFDocument doc=new XWPFDocument(new FileInputStream("./deepoove.docx"));
获取文档上的元素、比如段落、表格、图片等。
// 段落
List<XWPFParagraph> paragraphs=doc.getParagraphs();
// 表格
List<XWPFTable> tables=doc.getTables();
// 图片
List<XWPFPictureData> allPictures=doc.getAllPictures();
// 页眉
List<XWPFHeader> headerList=doc.getHeaderList();
// 页脚
List<XWPFFooter> footerList=doc.getFooterList();
生成文档
try (FileOutputStream out=new FileOutputStream("simple.docx")) {
doc.write(out);
}
创建段落
XWPFParagraph p1=doc.createParagraph();
段落样式设置:
// 对齐方式
p1.setAlignment(ParagraphAlignment.CENTER);
// 边框
p1.setBorderBottom(Borders.DOUBLE);
p1.setBorderTop(Borders.DOUBLE);
p1.setBorderRight(Borders.DOUBLE);
p1.setBorderLeft(Borders.DOUBLE);
p1.setBorderBetween(Borders.SINGLE);
Run是段落的基本组成单元,可以是一段文字、也可以是一张图片、可以设置自己的不同样式风格。
获取段落内容
// 获取文字
String text=paragraph.getText();
// 获取段落内所有XWPFRun
List<XWPFRun> runs=paragraph.getRuns();
创建run
// 段落末尾创建XWPFRun
XWPFRun run=paragraph.createRun();
run.setText("为这个段落追加文本");
// 颜色
run.setColor("00ff00");
// 斜体
run.setItalic(true);
// 粗体
run.setBold(true);
// 字体
run.setFontFamily("Courier");
// 下划线
run.setUnderline(UnderlinePatterns.DOT_DOT_DASH);
获取段落中run和修改run
// 获取文字
String text=paragraph.getText();
// 获取段落内所有XWPFRun
List<XWPFRun> runs=paragraph.getRuns();
// 段落起始插入XWPFRun
XWPFRun insertNewRun=paragraph.insertNewRun(0);
insertNewRun.setText("在段落起始位置插入这段文本");
修改run
List<XWPFRun> runs=paragraph.getRuns();
// setText默认为追加文本,参数0表示设置第0个位置的文本,覆盖上一次设置
runs.get(0).setText("追加文本", 0);
runs.get(0).setText("修改文本", 0);
文本换行
run.addCarriageReturn();
提取文档图片
List<XWPFPictureData> allPictures=doc.getAllPictures();
XWPFPicture pciture=allPictures.get(0);
byte[] data=pciture.getPictureData().getData();
// 接下来就可以将图片字节数组写入输出流
利用XWPFRun创建图片
List<XWPFPictureData> allPictures=doc.getAllPictures();
XWPFPicture pciture=allPictures.get(0);
byte[] data=pciture.getPictureData().getData();
// 接下来就可以将图片字节数组写入输出流
创建三行三列的表格
XWPFTable table=doc.createTable(3, 3);
设置单元格的文本 表格是由表格行XWPFRow构成,每行是由单元格XWPFCell构成,每个单元格内部又是由许多XWPFParagraph段落构成。
table.getRow(1).getCell(1).setText("EXAMPLE OF TABLE");
上面代码和下面代码等价:
XWPFParagraph p1=table.getRow(0).getCell(0).addParagraph();
XWPFRun r1=p1.createRun();
r1.setText("EXAMPLE OF TABLE")
设置单元格里面的图片,可以按照上述办法获取到单元格的XWPFRun然后按照run添加图片的办法进行。
设置单元格的样式
XWPFParagraph p1=table.getRow(0).getCell(0).addParagraph();
XWPFRun r1=p1.createRun();
r1.setText("EXAMPLE OF TABLE")
public class TestFirstDoc {
public static void main(String[] args) throws IOException, InvalidFormatException {
XWPFDocument document=new XWPFDocument();
FileOutputStream out=new FileOutputStream(new File("测试文档.docx"));
XWPFParagraph paragraph=document.createParagraph();
// 基本测试
XWPFRun paragraphOneRunOne=paragraph.createRun();
paragraphOneRunOne.setText("测试第一个Run");
paragraphOneRunOne.setBold(true);
paragraphOneRunOne.setItalic(true);
paragraphOneRunOne.addBreak();
// 样式测试
XWPFRun paragraphRun2=paragraph.createRun();
paragraphRun2.setText("为这个段落追加文本");
paragraphRun2.setFontSize(20);
paragraphRun2.setColor("00ff00");
paragraphRun2.setFontFamily("Courier");
paragraphRun2.setUnderline(UnderlinePatterns.DOT_DOT_DASH);
// 上下标测试
XWPFRun paragraphRun3=paragraph.createRun();
paragraphRun3.setText("设置正常文字");
XWPFRun paragraphRun4=paragraph.createRun();
paragraphRun4.setText("上标");
paragraphRun4.setItalic(true);
paragraphRun4.setSubscript(VerticalAlign.SUPERSCRIPT);
XWPFRun paragraphRun5=paragraph.createRun();
paragraphRun5.setText("下标");
paragraphRun5.setItalic(true);
paragraphRun5.setSubscript(VerticalAlign.SUBSCRIPT);
// 设置换页,表格和上面内容分开来
XWPFParagraph paragraph2=document.createParagraph();
paragraph2.setPageBreak(true);
XWPFTable table=document.createTable(3, 3);
table.getRow(0).getCell(0).setText("Head1");
table.getRow(0).getCell(1).setText("Head2");
table.getRow(0).getCell(2).setText("Head3");
table.getRow(1).getCell(0).setText("col1");
table.getRow(1).getCell(1).setText("col2");
table.getRow(1).getCell(2).setText("col3");
XWPFParagraph p1=table.getRow(2).getCell(0).addParagraph();
XWPFRun r1=p1.createRun();
InputStream stream=new FileInputStream("D:\\1.jpg");
r1.addPicture(stream, XWPFDocument.PICTURE_TYPE_PNG, "Generated", Units.toEMU(256), Units.toEMU(256));
document.write(out);
out.close();
System.out.println("测试完成");
}
}
效果图:
第二页:
*请认真填写需求信息,我们会在24小时内与您取得联系。