主要有以下幾點:
1、解決富文本導(dǎo)入導(dǎo)出依賴兼容問題
2、處理富文本和非富文本內(nèi)容
3、解決webp格式通過java下載不了問題,如果要用到富文本導(dǎo)出,將來勢必是會碰到的bug,這里提前給提出來并解決,測試用例中有給圖片測試。
4、在原有方法上優(yōu)化,比如處理等比縮小圖片、將圖片本地路徑,替換為minio或者base64格式
gitee測試用例:
鏈接: https://gitee.com/muyangrenOvo/word-import-export
注意:與文章代碼有出入,但思路是一樣的。只是獲取文件的方式變了,一個是前端調(diào)用組件傳的,一個是自己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文檔,我們要通過波浪線去截取對應(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)容略過)
//List<Map<String, String>> mapList = WordUtil.readWord(file);
//assert mapList != null;
//dealWithCaseBasicInfo(caseInfoVO, mapList);
//2、下載文件到本地
File destFile = fileDownloadToLocalPath(file);
//3、處理案例富文本信息
dealWithCaseInfoRichText(caseInfoVO, destFile);
//4、替換案例富文本信息中的圖片(如果有)路徑并刪除臨時文件和臨時圖片
dealWithCaseInfoRichTextToPicture(caseInfoVO);
} catch (Exception e) {
e.printStackTrace();
}
return caseInfoVO;
}
private void dealWithCaseInfoRichText(CaseInfoVO caseInfoVO, File destFile) {
if (!destFile.exists()) {
throw new ServiceException("導(dǎo)入模板失敗,請重新上傳!");
} else {
//判斷是否為docx文件
if (destFile.getName().endsWith(".docx") || destFile.getName().endsWith(".DOCX")) {
// 1)加載word文檔生成XWPFDocument對象
try (FileInputStream in = new FileInputStream(destFile); XWPFDocument document = new XWPFDocument(in)) {
// 2)解析XHTML配置(這里設(shè)置IURIResolver來設(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();
//通過波浪線分割,然后通過debug去看自己需要的內(nèi)容的下標(biāo)位置 然后獲取即可(如果不懂,私信)
String[] tableSplit = conTent.split("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</span></p>");
int length = tableSplit.length;
//最好是判斷下length預(yù)期長度,否則模板用于定位的波浪線給破壞了,拿的內(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);
//刪除臨時圖片(統(tǒng)一刪除 如上傳同一張圖片,第二次會找不到圖片)
files.add(file);
aBoolean = false;
}
}
}
}
return content;
}
//最好是定義一個工具類,這里圖看起來比較直觀,就單獨拿出來了
/**
* 下載到本地路徑
* @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;
//臨時文件
//臨時文件名避免重復(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 {
/**
* 通過正則表達(dá)式去獲取html中的src
*
* @param content
* @return
*/
public static List<String> regexMatchPicture(String content) {
//用來存儲獲取到的圖片地址
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) {
//獲取第二個分組的內(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;
}
/**
* 通過正則表達(dá)式去獲取html中的src中的寬高
*
* @param content
* @return
*/
public static List<HashMap<String, String>> regexMatchWidthAndHeight(String content) {
//用來存儲獲取到的圖片地址
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<>();
//獲取第二個分組的內(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
模板如圖所示文章來源:http://www.zghlxwxcb.cn/news/detail-778427.html
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();
}
}
//處理圖片超過word寬度問題,等比縮小
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;
}
如果覺得文章對您有幫助,麻煩點個贊再走哈
搬運(yùn)麻煩標(biāo)注出處文章來源地址http://www.zghlxwxcb.cn/news/detail-778427.html
到了這里,關(guān)于java實現(xiàn)word導(dǎo)入導(dǎo)出富文本(含圖片)-附完整測試用例的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!