SpringBoot使用Freemarker导出word模板(OpenXML)
1、OpenXML
word.docx文档另存为xml之后会生成带有OpenXML标签的文档。
1.1、常用标签示意
标签 | 解释 |
---|---|
<w:wordDocument> | XML文档开头描述,包括各种命名空间的描述 |
<o:DocumentProperties> <o:CustomDocumentProperties> <w:fonts> <w:styles> <w:bgPict> <w:docPr> <w:body> | 在<w:wordDocument>中,包含的所有文档主体标签 |
<w:body> | 文档体 |
<w:sect> | 在<w:body>中,描述具体文档体 |
<w:sectPr> | 在<w:sect>中,描述文档样式,注:出现多个<w:sect>时可能会导致莫名其妙的分页 |
<w:t> | 表示真正的文本内容<w:t xml:space="preserve">的意思是无内容时空格会被忽略 |
<w:p> | 段落 |
<w:r> | 样式串,指明它包括的文本的显示样式 |
<w:hdr> | 页眉 |
<w:ftr> | 页脚 |
<w:val > | 一个值 |
<w:rPr> | 在<w:r> 中的标签,是r标签内的样式 |
<w:pPr> | 在<w:p>中的标签,是p标签内的样式 |
<w:b w:val=”on”> | 在样式标签内,描述字体问粗体 |
<w:jc w:val="right"/> | 在样式标签内,描述段落对齐方式为右对齐,可选值有右对齐rignt、左对齐left、居中对齐center、两端对齐both |
<w:vAlign w:val="center"/> | 在样式标签内,描述表格中的单元格上下对齐方式,可选值有:上top、中center、下bottom |
<w:sz w:val="40"/> | 在样式标签内,描述字号大小,sz 表示Non-Complex Script Font Size,简单理解的话,就是单字节字符(如ASCII编码字符等)的大小 |
<w:szCs w:val="40"/> | 字号,szCs 表示Complex Script Font Size,可以简单理解为双字节字符(如中日韩文字、阿拉伯文等)的大小 |
<w:attr> | 自定义XML属性 |
<w:bookmarkStart> <w:bookmarkEnd> | 书签开始、结束 |
<w:bCs> | 复合字体加粗 |
<w:rFronts> | 在样式标签内,描述字体 |
<w:hint> | 在样式标签内的w:rFonts标签内使用: <w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:cs="宋体" w:hint="fareast"/> |
<w:docPr> | 描述文档整个的样式 |
<w:zoom w:percent="100"/> | 在<w:docPr>样式标签下表示视图比例100% |
<w:view w:val="print"/> | 在<w:docPr>样式标签下表示文档视图是"print" |
<w:tbl> | 表格标签 |
<w:tblPr> | 在<w:tbl>中,表示表格样式标签 |
<w:tblBorders> | 在<w:tblPr>中,表示表格边框样式 |
<w:tblGrid> | 在<w:tbl>中,定义表格列数以 |
<w:gridCol w:w="715"/> | 在<w:tblGrid>中,定义表格每列的宽度 |
<w:tr> | 在<w:tbl>中,表示表格的行 |
<w:trPr> | 在<w:tr>中,表示表格行的样式 |
<w:tc> | 在<w:tr>中,表示表格的某一行的某个单元格 |
<w:tcPr> | 在<w:tc>中,描述单元格样式 |
<w:gridSpan w:val="2"/> | 左右合并单元格 |
<w:vmerge w:val="restart"/> <w:vmerge w:val="continue"/> | 上下合并单元格,合并的第一个单元格w:val="restart",下面需要合并的单元格都使用continue |
<w:br w:type="page"/> | 分页符 |
<w:pict> | 图片区域 |
<w:binData> | 在<w:pict>中,图片源(注:base64图片数据不带【data:image/png;base64,】前缀) <w:binData w:name="wordml://01.png" xml:space="preserve">base64图片数据</w:binData> |
<v:shape> | 图片引用占位符,引用的是<w:binData>图片(<v:imagedata src="wordml://01.png" 名称是已存在的图片源) <v:shape id="图片 10" o:spid="_x0000_s1026" o:spt="75" alt="001.png" style="xxx" filled="f" o:preferrelative="t" stroked="f" coordsize="21600,21600"> <v:path/> <v:fill on="f" focussize="0,0"/> <v:stroke on="f"/> <v:imagedata src="wordml://01.png" o:title="001.png"/> <o:lock v:ext="edit" aspectratio="t"/> </v:shape> |
1.2、文档大略结构
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument
xmlns:*="****">
<o:DocumentProperties>
<!--作者-->
<o:Author>xxx</o:Author>
<!--修改者-->
<o:LastAuthor>xxx</o:LastAuthor>
<!--创建时间-->
<o:Created>xxx</o:Created>
<!--修改时间-->
<o:LastSaved>xxx</o:LastSaved>
<!--时长-->
<o:TotalTime>0</o:TotalTime>
<!--页数-->
<o:Pages>0</o:Pages>
<!--字数-->
<o:Words>0</o:Words>
<!--字节数-->
<o:Characters>0</o:Characters>
<!--行数-->
<o:Lines>0</o:Lines>
<!--段落数-->
<o:Paragraphs>0</o:Paragraphs>
<!--空格数-->
<o:CharactersWithSpaces>0</o:CharactersWithSpaces>
<!--版本-->
<o:Version>14</o:Version>
</o:DocumentProperties>
<o:CustomDocumentProperties>
<!--KSO产品构建版本-->
<o:KSOProductBuildVer dt:dt="string">xxxx</o:KSOProductBuildVer>
<o:ICV dt:dt="string">xxxx</o:ICV>
</o:CustomDocumentProperties>
<!--字体组-->
<w:fonts>
<w:defaultFonts w:ascii="Calibri" w:fareast="宋体" w:h-ansi="Calibri" w:cs="Times New Roman"/>
<w:font w:name="宋体">
<w:panose-1 w:val="02010600030101010101"/>
<w:charset w:val="86"/>
<w:family w:val="Auto"/>
<w:pitch w:val="Default"/>
<w:sig w:usb-0="00000203" w:usb-1="288F0000" w:usb-2="00000006" w:usb-3="00000000" w:csb-0="00040001" w:csb-1="00000000"/>
</w:font>
</w:fonts>
<!--样式组-->
<w:styles>
<w:latentStyles w:defLockedState="off" w:latentStyleCount="260">
<w:lsdException w:name="Normal"/>
</w:latentStyles>
<w:style w:type="paragraph" w:styleId="a1" w:default="on">
<w:name w:val="Normal"/>
<w:pPr>
<w:widowControl w:val="off"/>
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:h-ansi="Calibri" w:fareast="宋体" w:cs="Times New Roman" w:hint="default"/>
<w:sz w:val="22"/>
<w:sz-cs w:val="22"/>
<w:lang w:val="EN-US" w:fareast="EN-US" w:bidi="AR-SA"/>
</w:rPr>
</w:style>
</w:styles>
<!--文档背景描述-->
<w:bgPict>
<w:background/>
<v:background id="_x0000_s1025">
<v:fill on="f" focussize="0,0"/>
</v:background>
</w:bgPict>
<!--文档样式-->
<w:docPr>
<!--视图-->
<w:view w:val="print"/>
<!--缩放-->
<w:zoom w:percent="100"/>
<!--字符间距-->
<w:characterSpacingControl w:val="CompressPunctuation"/>
<!--文档保护-->
<w:documentProtection w:enforcement="off"/>
<!--标点符号相关-->
<w:punctuationKerning/>
<!--不嵌入系统字体-->
<w:doNotEmbedSystemFonts/>
<!--边界不围绕头部-->
<w:bordersDontSurroundHeader/>
<!--边界不围绕尾部-->
<w:bordersDontSurroundFooter/>
<w:defaultTabStop w:val="420"/>
<!--绘图网格垂直间距-->
<w:drawingGridVerticalSpacing w:val="156"/>
<!--显示水平绘制网格间隔-->
<w:displayHorizontalDrawingGridEvery w:val="0"/>
<!--显示垂直绘制网格间隔-->
<w:displayVerticalDrawingGridEvery w:val="2"/>
<!--兼容性描述-->
<w:compat>
<!--调整表格中的线条高度-->
<w:adjustLineHeightInTable/>
<!--URL尾部空间-->
<w:ulTrailSpace/>
<!--不展开移位-->
<w:doNotExpandShiftReturn/>
<!--平衡单字节双字节宽度-->
<w:balanceSingleByteDoubleByteWidth/>
<!--使用EF布局-->
<w:useFELayout/>
<w:spaceForUL/>
<!--带双关语的包装文本-->
<w:wrapTextWithPunct/>
<!--表格换行兼容-->
<w:breakWrappedTables/>
<!--使用Asian规则-->
<w:useAsianBreakRules/>
<!--自动调整-->
<w:dontGrowAutofit/>
</w:compat>
</w:docPr>
<!--文档内容-->
<w:body>
<!--内容主体-->
<wx:sect>
<w:p>
<w:pPr>
<w:spacing w:line="360" w:line-rule="exact"/>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="default"/>
<w:b/>
<w:sz w:val="28"/>
<w:sz-cs w:val="28"/>
<w:lang w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="宋体" w:h-ansi="宋体" w:hint="fareast"/>
<w:b/>
<w:sz w:val="28"/>
<w:sz-cs w:val="28"/>
<w:lang w:fareast="ZH-CN"/>
</w:rPr>
<w:t>xxx</w:t>
</w:r>
</w:p>
</wx:sect>
<wx:sect>
<!--内容样式区域-->
<w:sectPr>
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0"/>
<w:cols w:space="720"/>
<w:docGrid w:type="lines" w:line-pitch="312"/>
</w:sectPr>
</wx:sect>
</w:body>
</w:wordDocument>
2、SpringBoot使用FreeMarker模板导出自定义样式的文档
1、新建Word,里面插入个Table
2、另存为xml文件
3、格式化xml文件并重命名为ftl后缀
可以使用在线格式化工具:在线 XML 格式化 | 菜鸟工具 (runoob.com)
4、修改ftl文件
将ftl文件中1,2,3单元格位置变成${param1},${param2},${param3}
5、java代码
这里使用SpringBoot2.7.4
5.1、添加pom依赖
<!-- freemarker依赖 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
5.2、application配置
【application.properties改成application.yml方便一些】
server:
port: 9090
spring:
#freemarker配置
#默认的classpath:/templates/?
freemarker:
template-loader-path: /ftl_templates
#后缀
suffix: .ftl
#编码
charset: utf-8
#RequestContext
request-context-attribute: request
5.3、工具类WordUtil
import freemarker.template.Configuration;
import freemarker.template.Template;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.Map;
public class WordUtil {
/**
* 生成word文件
*/
@SuppressWarnings("unchecked")
public static void createWord(HttpServletResponse response, Map dataMap, String templateName, String fileName, String fileSuffix){
File outFile=null;
Writer out=null;
InputStream fin=null;
ServletOutputStream out2=null;
try {
//创建配置实例
Configuration configuration = new Configuration(Configuration.getVersion());
//设置编码
configuration.setDefaultEncoding("UTF-8");
//ftl模板文件 取模板文件存放地址
configuration.setClassForTemplateLoading(WordUtil.class,"/ftl_templates");
//获取模板
Template template = configuration.getTemplate(templateName);
//创建临时文件
outFile = File.createTempFile(fileName, fileSuffix);
//获取临时文件目录方便后面使用
String tempFilePath = outFile.getAbsolutePath();
//将模板和数据模型合并生成文件
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"UTF-8"));
//生成文件 实际这里已经将文件生成在指定位置
template.process(dataMap, out);
//以下操作是将文件下载
File file = new File(tempFilePath);
fin = new FileInputStream(file);
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
// 设置浏览器以下载的方式,处理该文件名 ps:docx格式office可能存在打不开等问题
fileName = URLEncoder.encode(fileName+fileSuffix, "utf-8");
response.setHeader("Content-Disposition","attachment;filename="+fileName);
out2 = response.getOutputStream();
byte[] buffer = new byte[512];
int bytesToRead = -1;
// 通过循环将读入的Word文件的内容输出到浏览器中
while ((bytesToRead = fin.read(buffer)) != -1) {
out2.write(buffer, 0, bytesToRead);
}
//关闭流
out.flush();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (out != null) {
out.close();
}
if (fin != null) {
fin.close();
}
if (out2 != null) {
out2.close();
}
if(outFile!=null) {
outFile.delete();
}
}catch (Exception e){
}
}
}
}
5.4、Controller类
import com.example.ftldemo.utils.WordUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/demo")
public class DemoController {
@RequestMapping("/export")
public void exportDemo(HttpServletResponse response){
/** 用于组装word页面需要的数据 */
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("param1","111");
dataMap.put("param2","222");
dataMap.put("param3","333");
String fileName = "生成Word文档";
String fileSuffix=".doc";
/** 生成word 数据包装,模板名,文件生成路径,生成的文件名*/
WordUtil.createWord(response,dataMap, "导出wordDemo.ftl", fileName, fileSuffix);
}
}
5.5、运行使用浏览器
访问 localhost:9090/demo/export 可以下载示例word文档