何將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小時內與您取得聯系。