Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
言
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小時內與您取得聯系。