利用Java實(shí)現(xiàn)word導(dǎo)入導(dǎo)出富文本(含圖片)的詳細(xì)代碼
主要有以下幾點(diǎn):
1、解決富文本導(dǎo)入導(dǎo)出依賴兼容問(wèn)題
2、處理富文本和非富文本內(nèi)容
3、解決webp格式通過(guò)java下載不了問(wèn)題,如果要用到富文本導(dǎo)出,將來(lái)勢(shì)必是會(huì)碰到的bug,這里提前給提出來(lái)并解決,測(cè)試用例中有給圖片測(cè)試。
4、在原有方法上優(yōu)化,比如處理等比縮小圖片、將圖片本地路徑,替換為minio或者base64格式
gitee測(cè)試用例:
鏈接: https://gitee.com/muyangrenOvo/word-import-export
注意:與文章代碼有出入,但思路是一樣的。只是獲取文件的方式變了,一個(gè)是前端調(diào)用組件傳的,一個(gè)是自己new file。
1)引入pom.xml依賴
<!--處理富文本導(dǎo)出導(dǎo)入word文檔,勿修改依賴--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml-schemas</artifactId> <version>4.1.2</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency> <dependency> <groupId>fr.opensagres.xdocreport</groupId> <artifactId>xdocreport</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>4.1.2</version> </dependency> <dependency> <groupId>io.github.draco1023</groupId> <artifactId>poi-tl-ext</artifactId> <version>0.4.2</version> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.15.3</version> </dependency> <!--解決ImageIO.read讀取不了webp格式--> <dependency> <groupId>com.github.nintha</groupId> <artifactId>webp-imageio-core</artifactId> <version>0.1.0</version> <!--第一次先取消下面兩行注釋,加載成功后,在恢復(fù)注釋,并重新加載--> <!--<scope>system</scope>--> <!--<systemPath>${project.basedir}/libs/webp-imageio-core-0.1.0.jar</systemPath>--> </dependency>
2) word文檔導(dǎo)入帶樣式(含圖片)
例如這是word文檔,我們要通過(guò)波浪線去截取對(duì)應(yīng)內(nèi)容
Controller層
@ApiLog("導(dǎo)入模板") @PostMapping("/importTemplate") @ApiOperation(value = "導(dǎo)入模板", notes = "傳file") public R<CaseInfoVO> importCase(@RequestParam MultipartFile file) { return R.data(caseInfoService.importTemplate(file)); }
service層
import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; import com.deepoove.poi.config.ConfigureBuilder; import fr.opensagres.poi.xwpf.converter.core.FileImageExtractor; import fr.opensagres.poi.xwpf.converter.core.FileURIResolver; import fr.opensagres.poi.xwpf.converter.core.XWPFConverterException; import fr.opensagres.poi.xwpf.converter.xhtml.XHTMLConverter; import fr.opensagres.poi.xwpf.converter.xhtml.XHTMLOptions; import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.ddr.poi.html.HtmlRenderPolicy; @Override public CaseInfoVO importTemplate(MultipartFile file){ try { caseInfoVO = new CaseInfoVO(); //1、處理非富文本內(nèi)容基本信息(講解的是富文本導(dǎo)入,所以該內(nèi)容略過(guò)) //List<Map<String, String>> mapList = WordUtil.readWord(file); //assert mapList != null; //dealWithCaseBasicInfo(caseInfoVO, mapList); //2、下載文件到本地 File destFile = fileDownloadToLocalPath(file); //3、處理案例富文本信息 dealWithCaseInfoRichText(caseInfoVO, destFile); //4、替換案例富文本信息中的圖片(如果有)路徑并刪除臨時(shí)文件和臨時(shí)圖片 dealWithCaseInfoRichTextToPicture(caseInfoVO); } catch (Exception e) { e.printStackTrace(); } return caseInfoVO; } private void dealWithCaseInfoRichText(CaseInfoVO caseInfoVO, File destFile) { if (!destFile.exists()) { throw new ServiceException("導(dǎo)入模板失敗,請(qǐng)重新上傳!"); } else { //判斷是否為docx文件 if (destFile.getName().endsWith(".docx") || destFile.getName().endsWith(".DOCX")) { // 1)加載word文檔生成XWPFDocument對(duì)象 try (FileInputStream in = new FileInputStream(destFile); XWPFDocument document = new XWPFDocument(in)) { // 2)解析XHTML配置(這里設(shè)置IURIResolver來(lái)設(shè)置圖片存放的目錄) File imageFolderFile = new File(String.valueOf(destFile.getParentFile())); XHTMLOptions options = XHTMLOptions.create().URIResolver(new FileURIResolver(imageFolderFile)); options.setExtractor(new FileImageExtractor(imageFolderFile)); options.setIgnoreStylesIfUnused(false); options.setFragment(true); //使用字符數(shù)組流獲取解析的內(nèi)容 ByteArrayOutputStream baos = new ByteArrayOutputStream(); XHTMLConverter.getInstance().convert(document, baos, options); //帶樣式的內(nèi)容(富文本) String conTent = baos.toString(); //通過(guò)波浪線分割,然后通過(guò)debug去看自己需要的內(nèi)容的下標(biāo)位置 然后獲取即可(如果不懂,私信) String[] tableSplit = conTent.split("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</span></p>"); int length = tableSplit.length; //最好是判斷下length預(yù)期長(zhǎng)度,否則模板用于定位的波浪線給破壞了,拿的內(nèi)容也就變了 caseInfoVO.setCriminalBaseInfoSituation(tableSplit[2]); caseInfoVO.setCriminalEducationTransformPlan(tableSplit[4]); caseInfoVO.setCriminalEducationTransformResult(tableSplit[6]); } } catch (IOException | XWPFConverterException e) { e.printStackTrace(); } finally { FileUtil.deleteQuietly(destFile); } } } } private String dealWithCaseInfoRichTextToPictureChild(String content, OssBuilder ossBuilder,Set<File> files) { List<String> imagesFiles = HtmlUtil.regexMatchPicture(content); if (Func.isNotEmpty(imagesFiles)) { for (String imagesFile : imagesFiles) { File file = new File(imagesFile); MultipartFile fileItem = createFileItem(file, file.getName()); boolean aBoolean = true; //此處選擇循環(huán)調(diào)用,避免minio上傳失敗返回空(主要看需求)。 while (Boolean.TRUE.equals(aBoolean)) { BladeFile bladeFile = ossBuilder.template().putFile(fileItem); if (Func.isNotEmpty(bladeFile)) { String link = bladeFile.getLink(); content = content.replace(imagesFile, link); //刪除臨時(shí)圖片(統(tǒng)一刪除 如上傳同一張圖片,第二次會(huì)找不到圖片) files.add(file); aBoolean = false; } } } } return content; } //最好是定義一個(gè)工具類,這里圖看起來(lái)比較直觀,就單獨(dú)拿出來(lái)了 /** * 下載到本地路徑 * @param file * @return * @throws IOException */ public File fileDownloadToLocalPath(MultipartFile file) { File destFile = null; try { String fileName = file.getOriginalFilename(); //獲取文件后綴 String pref = fileName.lastIndexOf(".") != -1 ? fileName.substring(fileName.lastIndexOf(".") + 1) : null; //臨時(shí)文件 //臨時(shí)文件名避免重復(fù) String uuidFile = UUID.randomUUID().toString().replace("-", "") + "." + pref; destFile = new File(FileChangeUtils.getProjectPath() + uuidFile); if (!destFile.getParentFile().exists()) { destFile.getParentFile().mkdirs(); } file.transferTo(destFile); } catch (IOException e) { e.printStackTrace(); } return destFile; } /** * 創(chuàng)建FileItem * @param file * @param fieldName * @return */ public MultipartFile createFileItem(File file, String fieldName) { FileItemFactory factory = new DiskFileItemFactory(16, null); FileItem item = factory.createItem(fieldName, ContentType.MULTIPART_FORM_DATA.toString(), true, file.getName()); int bytesRead = 0; byte[] buffer = new byte[8192]; try { FileInputStream fis = new FileInputStream(file); OutputStream os = item.getOutputStream(); while ((bytesRead = fis.read(buffer, 0, 8192)) != -1) { os.write(buffer, 0, bytesRead); } os.close(); fis.close(); } catch (IOException e) { e.printStackTrace(); } return new CommonsMultipartFile(item); }
HtmlUtil工具類
import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author: muyangren * @Date: 2022/12/14 * @Description: * @Version: 1.0 */ public class HtmlUtil { /** * 通過(guò)正則表達(dá)式去獲取html中的src * * @param content * @return */ public static List<String> regexMatchPicture(String content) { //用來(lái)存儲(chǔ)獲取到的圖片地址 List<String> srcList = new ArrayList<>(); //匹配字符串中的img標(biāo)簽 Pattern p = Pattern.compile("<(img|IMG)(.*?)(>|></img>|/>)"); Matcher matcher = p.matcher(content); boolean hasPic = matcher.find(); //判斷是否含有圖片 if (hasPic) { //如果含有圖片,那么持續(xù)進(jìn)行查找,直到匹配不到 while (hasPic) { //獲取第二個(gè)分組的內(nèi)容,也就是 (.*?)匹配到的 String group = matcher.group(2); //匹配圖片的地址 Pattern srcText = Pattern.compile("(src|SRC)=(\"|\')(.*?)(\"|\')"); Matcher matcher2 = srcText.matcher(group); if (matcher2.find()) { //把獲取到的圖片地址添加到列表中 srcList.add(matcher2.group(3)); } //判斷是否還有img標(biāo)簽 hasPic = matcher.find(); } } return srcList; } /** * 通過(guò)正則表達(dá)式去獲取html中的src中的寬高 * * @param content * @return */ public static List<HashMap<String, String>> regexMatchWidthAndHeight(String content) { //用來(lái)存儲(chǔ)獲取到的圖片地址 List<HashMap<String, String>> srcList = new ArrayList<>(); //匹配字符串中的img標(biāo)簽 Pattern p = Pattern.compile("<(img|IMG)(.*?)(>|></img>|/>)"); //匹配字符串中的style標(biāo)簽中的寬高 String regexWidth = "width:(?<width>\\d+([.]\\d+)?)(px|pt)"; String regexHeight = "height:(?<height>\\d+([.]\\d+)?)(px;|pt;)"; Matcher matcher = p.matcher(content); boolean hasPic = matcher.find(); //判斷是否含有圖片 if (hasPic) { //如果含有圖片,那么持續(xù)進(jìn)行查找,直到匹配不到 while (hasPic) { HashMap<String, String> hashMap = new HashMap<>(); //獲取第二個(gè)分組的內(nèi)容,也就是 (.*?)匹配到的 String group = matcher.group(2); hashMap.put("fileUrl", group); //匹配圖片的地址 Pattern srcText = Pattern.compile(regexWidth); Matcher matcher2 = srcText.matcher(group); String imgWidth = null; String imgHeight = null; if (matcher2.find()) { imgWidth = matcher2.group("width"); } srcText = Pattern.compile(regexHeight); matcher2 = srcText.matcher(group); if (matcher2.find()) { imgHeight = matcher2.group("height"); } hashMap.put("width", imgWidth); hashMap.put("height", imgHeight); srcList.add(hashMap); //判斷是否還有img標(biāo)簽 hasPic = matcher.find(); } for (HashMap<String, String> imagesFile : srcList) { String height = imagesFile.get("height"); String width = imagesFile.get("width"); String fileUrl = imagesFile.get("fileUrl"); //1厘米=25px(像素) 17厘米(650px) word最大寬值 if (Func.isNotEmpty(width)) { BigDecimal widthDecimal = new BigDecimal(width); BigDecimal maxWidthWord = new BigDecimal("650.0"); if (widthDecimal.compareTo(maxWidthWord) > 0) { BigDecimal divide = widthDecimal.divide(maxWidthWord, 2, RoundingMode.HALF_UP); fileUrl = fileUrl.replace("width:" + width, "width:" + maxWidthWord); if (Func.isNotEmpty(height)) { BigDecimal heightDecimal = new BigDecimal(height); BigDecimal divide1 = heightDecimal.divide(divide, 1, RoundingMode.HALF_UP); fileUrl = fileUrl.replace("height:" + height, "height:" + divide1); } else { fileUrl = fileUrl.replace("height:auto", "height:350px"); } imagesFile.put("newFileUrl", fileUrl); } else { imagesFile.put("newFileUrl", ""); } } } } return srcList; } }
3) 富文本導(dǎo)出word文檔(含圖片)
參考文獻(xiàn)
鏈接: https://github.com/draco1023/poi-tl-ext
模板如圖所示
Controller層
@ApiLog("模板-下載") @GetMapping("/downloadTemplate") @ApiOperation(value = "模板-下載") public void downloadCaseInfo(HttpServletResponse response,CaseInfoDTO caseInfoDTO) { caseInfoService.downloadTemplate(response,caseInfoDTO); }
Service層
@Override public void downloadTemplate(HttpServletResponse response, CaseInfoDTO caseInfoDTO) { try { //查詢需要導(dǎo)入的數(shù)據(jù) List<CaseInfoVO> caseInfoVOS = baseMapper.caseQueryPage(null, null, caseInfoDTO, AuthUtil.getUserId()); CaseInfoVO caseInfoVO = caseInfoVOS.get(0); //處理作者名稱 dealWithCaseAuthorName(caseInfoVOS); Integer formatType = caseInfoVO.getFormatType(); org.springframework.core.io.Resource resource; HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy(); ConfigureBuilder builder = Configure.builder(); Configure config = builder.build(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Map<String, Object> data = new HashMap(8); data.put("caseTitle", caseInfoVO.getCaseTitle()); data.put("typeName", caseInfoVO.getTypeName()); resource = new ClassPathResource("document" + File.separator + "word" + File.separator + "導(dǎo)出模板.docx"); config.customPolicy("criminalBaseInfoSituation", htmlRenderPolicy); data.put("criminalBaseInfoSituation", dealWithPictureWidthAndHeight(caseInfoVO.getCriminalBaseInfoSituation())); //輸出到瀏覽器|下載到本地路徑 StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(caseInfoVO.getTenantName()).append("-").append(caseInfoVO.getTypeName()).append("-《").append(caseInfoVO.getCaseTitle()).append("》").append("案例"); response.setContentType("application/octet-stream"); response.setHeader("Content-disposition", "attachment;filename=\"" + new String(stringBuilder.toString().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1) + ".docx" + "\""); OutputStream out = response.getOutputStream(); XWPFTemplate.compile(resource.getInputStream(), config).render(data).writeAndClose(out); out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); } } //處理圖片超過(guò)word寬度問(wèn)題,等比縮小 private String dealWithPictureWidthAndHeight(String content) { List<HashMap<String, String>> imagesFiles = HtmlUtil.regexMatchWidthAndHeight(content); if (Func.isNotEmpty(imagesFiles)) { for (HashMap<String, String> imagesFile : imagesFiles) { String newFileUrl = imagesFile.get("newFileUrl"); String fileUrl = imagesFile.get("fileUrl"); if (Func.isNotEmpty(newFileUrl)){ content = content.replace(fileUrl, newFileUrl); } } } return content; }
以上就是利用Java實(shí)現(xiàn)word導(dǎo)入導(dǎo)出富文本(含圖片)的詳細(xì)代碼的詳細(xì)內(nèi)容,更多關(guān)于Java word導(dǎo)入導(dǎo)出富文本的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot整合Pulsar的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot整合Pulsar的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07springmvc使用JSR-303進(jìn)行數(shù)據(jù)校驗(yàn)實(shí)例
本篇文章主要介紹了詳解springmvc使用JSR-303進(jìn)行數(shù)據(jù)校驗(yàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02Spring自動(dòng)配置之condition條件判斷上篇
這篇文章主要為大家介紹了SpringBoot condition條件判斷功能的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08