使用java程序导出word,此次使用的是模板值替换方式,而导出的word版本是xlsx。
直接替换文本和图片,可以直接使用一个word改造的ftl模板,替换启用的参数即可,但是对于图表就比较麻烦。
也参照了很多大佬的做法,针对xlsx07版本,主要是将xlsx文件解压,得到很多xml文件,其中文字内容是在document.xml中,如果要替换文本内容,将xml中参数使用freemarker替换即可。
如果是图片,则按照图片的先后顺序,保存在word/media/下,以image1.png这个格式开头,则在最后写入压缩包时替换相应的图片。
图表也就和文本document.xml的方式一样,word中的每个图表都会有一个chart[1].xml文件,将这个xml中的参数使用freemarker替换。
下面就是一个导出word,含图表的工具类,供参考
其中的几个DTO就是关于模板文件名称和参数。
public class WordWriter {
public static void write(String outFileName, WordTemplate wordTemplate, boolean zip, HttpServletResponse httpServletResponse){
//获取临时文件存放路径
outFileName = getWordFileName(outFileName);
File file = FileUtil.createFile(outFileName);
//创建xml临时存放文件夹
Path parent = file.toPath().getParent();
File xmlDir = Paths.get(parent.toString(), ExportConstant.xmlDirName).toFile();
if(!xmlDir.exists()){
boolean mkdir = xmlDir.mkdir();
log.debug("创建xml临时存放文件夹{}", mkdir);
}
//创建document.xml
WordXmlTemplateDTO documentDTO = wordTemplate.getDocumentDTO();
Path documentXmlOutPath = appendXmlPath(xmlDir, "document.xml");
FreemarkerUtils.process(documentDTO.getXmlName(), documentDTO.getDataList(), documentXmlOutPath.toString());
//替换chart.xml
List<WordXmlTemplateDTO> chartDTOList = wordTemplate.getChartDTOList();
List<Path> chartXmlPaths = new ArrayList<>();
if(!CollectionUtils.isEmpty(chartDTOList)){
int i = 1;
for (WordXmlTemplateDTO chartDTO : chartDTOList) {
Path chartXmlOutPath = appendXmlPath(xmlDir, "chart" + i++ + ".xml");
FreemarkerUtils.process(chartDTO.getXmlName(), chartDTO.getDataList(), chartXmlOutPath.toString());
chartXmlPaths.add(chartXmlOutPath);
}
}
//写入
Path templatePath = FreemarkerUtils.getTemplatePath();
Path originDocPath = Paths.get(templatePath.toString(), wordTemplate.getOriginDocName());
Path outDocPath = Paths.get(xmlDir.getPath(), outFileName);
replaceDocXml(documentXmlOutPath, wordTemplate.getWordImageDTO(), chartXmlPaths, originDocPath, outDocPath);
Path zipPath = null;
if(zip){
zipPath = FileUtil.zip(Lists.newArrayList(outDocPath.toFile()), outFileName);
}
if(httpServletResponse != null){
httpServletResponse.reset();
try (OutputStream outputStream = httpServletResponse.getOutputStream();){
if(zipPath != null){
httpServletResponse.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(zipPath.toFile().getName(), "utf-8"));
httpServletResponse.setHeader("Content-Type", "application/octet-stream");
Files.copy(zipPath, outputStream);
}else {
httpServletResponse.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(outDocPath.toFile().getName(), "utf-8"));
httpServletResponse.setHeader("Content-Type", "application/msword");
Files.copy(outDocPath, outputStream);
}
} catch (Exception e) {
log.error("导出文件失败" + outDocPath, e);
}
}else {
log.error("web导出Word,httpResponse为空");
}
log.debug(outDocPath.toString());
FileUtil.deleteAll(file.toPath().getParent());
}
private static void replaceDocXml(Path documentXmlOutPath, List<WordImageDTO> wordImageDTOList, List<Path> chartXmlPaths, Path originDocPath, Path outDocPath) {
int len;
byte[] buffer=new byte[1024];
try (ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(outDocPath.toFile()))){
ZipFile zipFile = new ZipFile(originDocPath.toFile());
Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
while(zipEntrys.hasMoreElements()) {
ZipEntry next = zipEntrys.nextElement();
String name = next.toString();
zipout.putNextEntry(new ZipEntry(name));
int chartIndex;
if ("word/document.xml".equals(name)) {
try (InputStream in = new FileInputStream(documentXmlOutPath.toFile())){
while ((len = in.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
}
} else if (!CollectionUtils.isEmpty(wordImageDTOList)
&& name.startsWith("word/media/")
&& getImageIndex(name) > 0) {
int lastIndexOf = name.lastIndexOf("/");
String imageName = name.substring(lastIndexOf + 1);
boolean flag = false;
for (WordImageDTO wordImageDTO : wordImageDTOList) {
byte[] bytes = wordImageDTO.getBytes();
String docxImageName = wordImageDTO.getDocxImageName();
if(imageName.equalsIgnoreCase(docxImageName)){
zipout.write(bytes);
flag = true;
break;
}
}
if(!flag){
originWrite(buffer, zipout, zipFile, next);
}
}else if(!CollectionUtils.isEmpty(chartXmlPaths)
&& name.startsWith("word/charts/")
&& (chartIndex = getChartIndex(name)) > 0
&& chartXmlPaths.size() >= chartIndex){
Path path = chartXmlPaths.get(chartIndex - 1);
try (InputStream in = new FileInputStream(path.toFile())){
while ((len = in.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
}
}else {
originWrite(buffer, zipout, zipFile, next);
}
zipout.flush();
}
} catch (Exception e) {
log.error("写入docx异常", e);
}
}
private static void originWrite(byte[] buffer, ZipOutputStream zipout, ZipFile zipFile, ZipEntry next) throws IOException {
int len;
try (InputStream is = zipFile.getInputStream(next)){
while((len = is.read(buffer))!=-1){
zipout.write(buffer,0,len);
}
}
}
/**
* docx解压包中,图片名称是按照image[1-9].[png|...]规律命名的
*/
private static int getImageIndex(String name){
int index = -1;
String reg = ".*image([0-9]+)\\.\\w+$";
Pattern pattern = Pattern.compile(reg);
Matcher matcher = pattern.matcher(name);
if (matcher.find()) {
index = Integer.valueOf(matcher.group(1));
}
return index;
}
/**
* docx解压包中,图表xml文件是按照chart[1-9].xml规律命名的
*/
private static int getChartIndex(String name){
int index = -1;
String reg = ".*chart([0-9]+).xml$";
Pattern pattern =Pattern.compile(reg);
Matcher matcher = pattern.matcher(name);
if (matcher.find()) {
index = Integer.valueOf(matcher.group(1));
}
return index;
}
private static Path appendXmlPath(File xmlDir, String fileName){
if(!fileName.endsWith(".xml")){
fileName = fileName + ".xml";
}
return Paths.get(xmlDir.toPath().toString(), fileName);
}
private static String getWordFileName(String fileName){
Assert.isTrue(fileName != null && fileName.trim().length() > 0, "导出文件名称不能为空");
if(!fileName.endsWith(ExportFileType.WORD.getSuffix())){
fileName = fileName + ExportFileType.WORD.getSuffix();
}
return fileName;
}
}