何将Word解析到富文本编辑器?
困扰:
前一段时间自己搭建了一个博客,但是有一个问题一直没有好的解决方案。
自己之前写的文章都是Word,怎么把Word导入到自己的博客呢?或者说如何将Word解析成HTML?
借鉴:
因为本身自己在发表文章,头条后台发表文章有一个功能,就是可以将Word文档导入,然后后台自动解析到富文本编辑器中,这个功能就完美的实现了我的需求。但是,如何实现这种功能呢?
目标:
将Word解析成HTML。
思路:
1先把Word上传到服务器。
2利用POI把Word解析成HTML。
3将HTML片段放到富文本框里显示。
问题:
关于Word中图片的处理。
方案1:
在Word解析HTML时将Word图片转换成base64插入HTML。
优点:
省事。
缺点:
大量图片会导致HTML体积过大。
方案2:
在Word解析HTML时将Word图片存储下来,HTML的img放入图片链接即可。
关于这个图片链接:
1上传到图片服务中返回一个图片地址。
2存储在自己服务中,自定义一个图片地址。
补充:
最后将解析好的HTML插入到富文本即可。
以上大体思路都捋清楚。
工具类代码过多,贴出部分demo,参考源码私信或留言。
XWPFDocument document=null;
try {
// 存放图片的临时文件夹
String filePath="D://tmpImage//";
File imageFile=new File(filePath);
// in Word流
document=new XWPFDocument(in);
// Word图片输出到D://tmpImage//
XHTMLOptions options=XHTMLOptions.create().URIResolver(new FileURIResolver(imageFile));
options.setIgnoreStylesIfUnused(false);
options.setFragment(true);
// 存放图片的文件夹
options.setExtractor(new FileImageExtractor(new File(filePath)));
// base64实现方式
/*options.setImageManager(new Base64EmbedImgManager());*/
// 重写URIResolver,目的将HTML img的路径修改为自定义路径
options.URIResolver((uri)->{
/*uri表示图片路径:word/media/image1.png*/
// 获取图片
File imgFile=new File("D://tmpImage//"+uri);
/*修改img name*/
String imgName=UUIDUtil.getUUID();
// 文件重命名 放到
String hz="." + "html";
imgFile.renameTo(new File("D://image/word/media/" + imgName + hz));
// 返回图片url,即HTML img src
return "localhost:8080/upload/image/word/media/" + imgName + hz;
}
数据报表是许多项目都有的模块,一般都是导出Excel或者PDF,这里记录下我在项目里用POI导出Excel。项目中,我需要根据页面jqgrid的机架查询条件导出对应的机架数据,jqgrid是分页的,但导出是要导出所有。
Apache POI - the Java API for Microsoft Documents,官网:http://poi.apache.org/
maven引入POI
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
或者
<dependency><!--Excel工具类(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.2.0</version>
</dependency>
<dependency><!--Excel工具类(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency><!--Excel工具类(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>3.2.0</version>
</dependency>
html、js调用
<dependency><!--Excel工具类(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.2.0</version>
</dependency>
<dependency><!--Excel工具类(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency><!--Excel工具类(Easy POI)-->
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>3.2.0</version>
</dependency>
//导出excel
function exportRackExcel() {
//获取当前jqGrid分页参数
var postData=$("#rack").jqGrid("getGridParam", "postData");
postData.page=1;
postData.rows=999999999;//设置每页9亿条记录(相当于无穷大,查询所有)
//ajax不支持Excel类型,使用location.href或者表单提交
//window.location.href,get提交,数据会暴露在URL,相对不安全
//创建临时的、隐藏的form表单,post提交,数据在请求体里,相对安全
var $form=$(document.createElement('form')).css({display: 'none'}).attr("method", "POST").attr("action", ctx + "/excel");
for (var key in postData) {
var $input=$(document.createElement('input')).attr('name', key).val(postData[key]);
$form.append($input);
}
$("body").append($form);
$form.submit();
//过河拆桥,提交完成后remove掉
$form.remove();
}
纯js写法
//其他操作,同上
let $form=document.createElement('form');
$form.style.display="none";
$form.method="POST";
$form.action=ctx + "/excel";
for (let key in postData) {
if(postData[key]){
let $input=document.createElement('input');
$input.name=key;
$input.value=postData[key];
$form.appendChild($input);
}
}
document.body.appendChild($form);
$form.submit();
//过河拆桥,提交完成后remove掉
$form.remove();
controller
/**
* 根据当前jqGrid分页情况,创建并导出Excel文件
*
* @param entity 机架实体,用来接收查询条件
* @return ResponseEntity
*/
@PostMapping("/excel")
public ResponseEntity createExcel(RackVo entity) {
//Excel对应的columnNames列名集合 { key,label }
String[][] excelMap={
{"no", "Rack Code"},
{"rackName", "Rack Name"},
{"roomName", "Room"},
{"idc", "IDC Center"},
{"clientName", "Customer"},
{"rackTypeName", "Type"},
{"existentialMode", "Existential Mode"},
{"maxPower", "Maximum Power(KVA)"},
{"status", "Status"},
{"administrate", "Administrate"},
};
return DownloadUtil.download(ExportExcelUtil.createExcel("Rack Management", excelMap, rackService.createExcel(entity).getData()).getData(), "机架数据报表");
}
两个工具类:导出Excel工具类 ExportExcelUtil,下载工具类 DownloadUtil
/**
* java POI 导出Excel表工具类
*/
public class ExportExcelUtil {
//禁止实例化
private ExportExcelUtil() {
}
/**
* 只支持一级表头
*
* @param titleName 表标题
* @param columnNames 列名集合,key是用来设置填充数据时对应单元格的值,label就是对应的列名,生成Excel表时,
* 第一维数组下标0对应值为Excel表最左边的列的列名 例:{ { key,label },{ key,label } }
* @param dataLists 数据集合,key对应的是列名集合的key,value是要填充到单元格的值 例:ArrayList<HashMap<String key, String vaule>>
* @return ResultModel<Workbook>
*/
public static ResultModel<Workbook> createExcel(String titleName, String[][] columnNames, ArrayList<HashMap<String, String>> dataLists) {
//创建HSSFWorkbook对象(excel的文档对象)
HSSFWorkbook wb=new HSSFWorkbook();
//建立新的sheet对象(excel的表单)
HSSFSheet sheet=wb.createSheet(titleName);//设置表单名
//1、标题名
//创建标题行,参数为行索引(excel的行),可以是0~65535之间的任何一个
HSSFRow row1=sheet.createRow(0);
//标题的字体
HSSFFont font1=wb.createFont();
font1.setFontHeightInPoints((short) 12);
font1.setFontName("黑体");
//标题的样式
HSSFCellStyle style1=wb.createCellStyle();
style1.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中
style1.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中
// 把字体 应用到当前样式
style1.setFont(font1);
//自动换行
style1.setWrapText(true);
//自定义填充颜色(天空蓝)
style1.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style1.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
// 设置边框
style1.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style1.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style1.setBorderRight(HSSFCellStyle.BORDER_THIN);
style1.setBorderTop(HSSFCellStyle.BORDER_THIN);
createCell(row1, 0, style1, titleName);
//合并单元格CellRangeAddress构造参数依次表示起始行,截至行,起始列, 截至列
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, columnNames.length - 1));
//2、列名
//创建列名行
//列名的字体
HSSFFont font2=wb.createFont();
font2.setFontHeightInPoints((short) 12);
font2.setFontName("新宋体");
//列名的样式
HSSFCellStyle style2=wb.createCellStyle();
style2.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中
style2.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中
// 把字体 应用到当前样式
style2.setFont(font2);
//自动换行
style2.setWrapText(true);
//自定义填充颜色(浅蓝色)
style2.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style2.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
// 设置边框
style2.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style2.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style2.setBorderRight(HSSFCellStyle.BORDER_THIN);
style2.setBorderTop(HSSFCellStyle.BORDER_THIN);
HSSFRow row2=sheet.createRow(1);
for (int i=0; i < columnNames.length; i++) {
//单元格宽度
sheet.setColumnWidth(i, 20 * 256);
createCell(row2, i, style2, columnNames[i][1]);//例:[[key,label],[key,label]] 取label
}
//3、填充数据
//内容的字体
HSSFFont font3=wb.createFont();
font3.setFontHeightInPoints((short) 12);
font3.setFontName("新宋体");
//内容的样式
HSSFCellStyle style3=wb.createCellStyle();
style3.setAlignment(HSSFCellStyle.ALIGN_CENTER);//水平居中
style3.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//垂直居中
// 把字体 应用到当前样式
style3.setFont(font3);
//自动换行
style3.setWrapText(true);
//默认无填充
style3.setFillPattern(FillPatternType.NO_FILL);
style3.setFillForegroundColor(IndexedColors.RED.getIndex());
// 设置边框
style3.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style3.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style3.setBorderRight(HSSFCellStyle.BORDER_THIN);
style3.setBorderTop(HSSFCellStyle.BORDER_THIN);
int index=2;//标题行、列名行,所以数据行默认从第三行开始
for (HashMap<String, String> map : dataLists) {
//创建内容行
HSSFRow row3=sheet.createRow(index);
for (int i=0; i < columnNames.length; i++) {
String val=map.get(columnNames[i][0]);
createCell(row3, i, style3, val==null ? "" : val);//例:[[key,label],[key,label]] 取key
}
index++;
}
return ResultModel.of(wb);
}
/**
* 创建一个单元格
*
* @param row 行
* @param column 列
* @param cellStyle 单元格样式
* @param text 值
*/
private static void createCell(Row row, int column, CellStyle cellStyle, String text) {
Cell cell=row.createCell(column); // 创建单元格
cell.setCellValue(text); // 设置值
cell.setCellStyle(cellStyle); // 设置单元格样式
}
}
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 文件下载工具类
*/
public class DownloadUtil{
/**
* 快速下载
*/
public static ResponseEntity download(byte[] fileBytes, String fileName) {
//设置文件
HttpHeaders headers=new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", new String(fileName.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1));
//下载文件
return new ResponseEntity<>(fileBytes, headers, HttpStatus.CREATED);
}
/**
* 快速下载
*/
public static ResponseEntity download(File file) {
return download(getByteArray(file), file.getName());
}
/**
* 快速下载
*/
public static ResponseEntity download(Workbook workbook, String fileName) {
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
try {
fileName=fileName + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xls";
workbook.write(outputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
return download(outputStream.toByteArray(), fileName);
}
//获取文件的字节数组
private static byte[] getByteArray(File file) {
if (!file.exists()) {
throw new RuntimeException("File Not Found:" + file.getPath());
}
ByteArrayOutputStream bos=new ByteArrayOutputStream((int) file.length());
BufferedInputStream in=null;
try {
in=new BufferedInputStream(new FileInputStream(file));
int buf_size=1024;
byte[] buffer=new byte[buf_size];
int len;
while (-1 !=(len=in.read(buffer, 0, buf_size))) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
try {
assert in !=null;
in.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//获取文件名后缀
private static String getSuffix(String fileName) {
int lastPointIndex=fileName.lastIndexOf(".");
if (StringUtils.isEmpty(fileName) || lastPointIndex==-1) {
return null;
}
return fileName.substring(lastPointIndex + 1);
}
}
获取封装数据的service层 createExcel,直接到取page分页方法,遍历机架数据集合,设置Map<key,value>,add到list<Map>中,最后将封装好的数据return回controller,传入工具类,最后下载。
/**
* 根据当前jqGrid分页情况,创建并导出Excel文件
*
* @param entity 查询条件
* @return 封装好的数据集合
*/
@Override
public ResultModel<ArrayList<HashMap<String, String>>> createExcel(RackVo entity) {
ArrayList<HashMap<String, String>> dataLists=new ArrayList<HashMap<String, String>>();
//直接调page分页方法,获取当前jqGrid分页条件对应的数据集合,
ResultModel<PageInfo<RackVo>> rm=page(entity);
if (rm.isFlag()) {
List<RackVo> rackVoList=rm.getData().getRows();
for (RackVo rackVo : rackVoList) {
HashMap<String, String> map=new HashMap<String, String>(16);
map.put("no", rackVo.getNo() !=null ? rackVo.getNo() : "");
map.put("rackName", rackVo.getName() !=null ? rackVo.getName() : "");
map.put("roomName", rackVo.getRoom() !=null ? rackVo.getRoom().getRoomname() : "");
map.put("idc", rackVo.getOrg() !=null ? rackVo.getOrg().getOrgName() : "");
map.put("clientName", rackVo.getCustomer() !=null ? rackVo.getCustomer().getClientname() : "");
map.put("rackTypeName", rackVo.getRacktype() !=null ? rackVo.getRacktype().getName() : "");
map.put("existentialMode", "1".equals(rackVo.getExistentialMode()) ? "Physical" : "Virtual");
map.put("maxPower", rackVo.getMaxpower() !=null ? rackVo.getMaxpower() : "");
String status=rackVo.getServiceStatus();
switch (status !=null ? status : "") {
case "1":
status="Idle";
break;
case "2":
status="Reserved";
break;
case "3":
status="Occupied";
break;
default:
status="";
break;
}
map.put("status", status);
String administrate=rackVo.getAdministrate();
switch (administrate !=null ? administrate : "") {
case "R":
administrate="Cust Own";
break;
case "U":
administrate="CTG Own";
break;
default:
administrate="";
break;
}
map.put("administrate", administrate);
dataLists.add(map);
}
}
return ResultModel.of(dataLists);
}
从开发阶段到测试阶段,导了无数次,没毛病
excelMap,Excel对应的columnNames列名集合 { key,label },可以不用再controller设置了,直接从页面jqgrid抓取,传入controller就行(滑稽脸~)
//获取jqgrid头部标题tr,有多少个tr就有多少级标题
var thead_tr=$(".ui-jqgrid-htable").find("tr.ui-jqgrid-labels");
//遍历thead_tr找出每一个标题,并保存到对象中
var titles=[];
thead_tr.each(function(index_tr,element_tr){
titles.push([]);
$(element_tr).find("th").each(function(index_th,element_th){
//内容
var label=$(element_th).text();
//所占行 rowspan 默认1
var rowspan=$(element_th).attr("rowspan") || 1;
//所占列 colspan 默认1
var colspan=$(element_th).attr("colspan") || 1;
//键
var key=$(element_th).attr("id");
key=key.substring(key.lastIndexOf("_")+1,key.length);
if(label){
titles[index_tr].push({
label:label,
key:key,
rowspan:rowspan,
colspan:colspan,
});
}
});
});
//JSON.stringify(titles)
console.log(titles);
2020-10-20更新
直接构造form表单提交,我们不能设置请求头信息,有些需求不能满足(例如在前后端分离的项目中,需要在请求头传递token令牌),当我们导出Excel功能需要设置请求头信息时应该如何操作呢?封装原生Ajax,利用responseType: 'blob'属性,接收二进制数据,构建Blob对象,将二进制数据转成文件,利用a标签下载文件
//封装原生Ajax
var Ajax={
get: function(options) {
let xhr=new XMLHttpRequest();
xhr.open('GET', options.url, true);
//设置请求头
xhr.setRequestHeader("Authorization", 'Bearer ' + store.getters.token);
xhr.onload=function() {
let response=null;
// responseType="" / "text"时,响应的结果从xhr.responseText获取
if(xhr.responseType==="" || xhr.responseType==="text"){
response=xhr.responseText;
}
//200 请求成功
if (xhr.status===200) {
options.success.call(response);
}
//其他情况,请求失败
if(options.error){
options.error.call(xhr.error);
}
};
xhr.send();
},
post: function (options) {
let xhr=new XMLHttpRequest();
xhr.open("POST", options.url, true);
//设置请求头
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", 'Bearer ' + store.getters.token);
//设置响应内容类型、超时时间
options.responseType ? xhr.responseType=options.responseType : xhr.responseType="text";
options.timeout ? xhr.timeout=options.timeout : xhr.timeout=30000;
xhr.onload=function() {
let response=null;
// responseType="" / "text"时,响应的结果从xhr.responseText获取
if(xhr.responseType==="" || xhr.responseType==="text"){
response=xhr.responseText;
}
//200 请求成功
if (xhr.status===200) {
options.success.call(response);
}
// responseType="blob"时,响应的是Blob二进制数据,直接调用下载
if(xhr.status===201){
download(xhr,options.success)
}
//其他情况,请求失败
if(options.error){
options.error.call(xhr.error);
}
};
xhr.send(JSON.stringify(options.data));
}
};
//Blob响应,转成文件下载
function download(response,callback) {
//创建一个隐藏的下载a标签
let url=window.URL.createObjectURL(new Blob([response.response]));
let link=document.createElement("a");
link.style.display="none";
link.href=url;
//设置文件名,文件名从响应头中获取(PS:可能会存在中文乱码、文件后缀多个下划线等问题)
let fileName=response.getAllResponseHeaders().split("\n")[4].split(":")[1].split(";")[2].split("=")[1].replace(/"/g,"");
fileName=decodeURIComponent(escape(fileName));
console.log("文件名:" + fileName);
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
//过河拆桥
link.remove();
if(callback){
callback();
}
}
使用
//获取当前分页参数
let postData=vue.getPageParameter();
postData.page=1;
postData.pageSize=999999999;//设置每页9亿条记录(相当于无穷大,查询所有)
console.log("开始导出...");
Ajax.post({
url:vue.excelUrl,
data:postData,
timeout: 30000,
responseType: 'blob',
success:function () {
console.log("导出完成,请您注意浏览器的下载管理器!");
}
});
效果
后缀多了个下划线,很奇怪...,删除下划线文件能正常打开,数据、单元格背景等正常
作者:huanzi-qch
出处:https://www.cnblogs.com/huanzi-qch
若标题中有“转载”字样,则本文版权归原作者所有。若无转载字样,本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利.
件导出
在管理系统(JAVA)开发中,经常会使用到数据的导入和导出,一般都是使用Apache POI工具进行操作,虽然其功能很强大,单API过于复杂。现在推荐一个EasyPOI,它对原有的API有进行了封装,使得对Excel/Word文件的操作变得更加简便,下面就介绍一下如何使用这个工具。
EasyPOI是一个比较优秀的开源软件,他对POI进行了深度封装,即使你没有接触过POI,也可以很容易的使用它对Excel文件进行导入导出。并且它还可以集成到SpringMVC,是文件的导出变得更加简单。
相关教程地址:http://www.afterturn.cn/doc/easypoi.html,http://easypoi.mydoc.io/。
项目使用Maven构建,相关依赖JAR包如下图所示,本示例使用的版本为2.4.0,其他版本代码可能略有差异。
Maven依赖
引入项目依赖之后就可以编写具体的代码了,首先时编辑导出数据对象类,使用注解的形式进行标记各个字段类型,具体代码如下(省略Get和Set方法):
数据导出对象类
本示例只使用了@Excel注解,用来标记需要导出的字段、类型、单元格宽度和长度以及字段值替换等信息,关于注解的详细描述在此不再赘述,详细可以参看上述文档地址中关于“注解”使用的说明。
编辑好实体类后就可以进行数据的导出操作,在导出前需要组装数据,一般都是从数据库中去查询,本示例只是简单添加两条数据,之后使用ExcelExportUtil工具类进行数据拼装,并指定列表的标题和工作表名称,使用Response输出流进行数据导出,具体代码如下图所示:
数据导出代码
编写好请求方法后,请求/downExcel.do方法就可以下载文件了,打开最终下载下来的文件,数据展示如下图所示:
导出的文件
EasyPOI还可以整合SpringMVC视图来使用,使得数据的导出更简单,同样在导出前先进行数据的整理,指定各种参数(具体如下图代码所示)使用PoiBaseView进行数据导出。
结合VIEW导出
导出的文件
由上面的代码可以看出,数据导出的操作代码非常简洁,让导出操作变得更加简单,同时它也支持对图片导出。本次分享就到这了,关于EasyPOI更多的使用方式,可以参考上面介绍的文档地址,相信会发现更多你想要的东西。
*请认真填写需求信息,我们会在24小时内与您取得联系。