Java 对Word文件的生成(基于Apache POI)
Apache POI 是一个开源的跨平台的对Microsoft Office格式档案具有读和写功能工具。
在Github上有一个开源的Word模版引擎poi-tl ,这个模版引擎是基于Apache POI。主要是为了解决下面的问题:
- java操作word使用apache poi的复杂性
- 使用freemarker,转化为xml操作word的难度
- 依赖服务器上安装软件openoffice来调用转化
- 依赖windows的word lib库,不具有跨平台性
注意!
HSSF - 提供读写Microsoft Excel XLS格式档案的功能。(*.doc),HWPFDocument类
XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能。(*.docx),XWPFDocument类
因为这个模版引擎是只使用XWPFDocument类,所以只对*.docx文档生效。
poi-tl demo
调用方法
/*
* datas 是你要渲染的数据
* datas 可以是JavaBean,也可以是Map<String, Object>
*/
XWPFTemplate template = XWPFTemplate.compile("~/file.docx").render(datas);
除了传入模版文件路径,还可以传入模版文件输入流
public static XWPFTemplate compile(InputStream inputStream) {
.....
}
datas TO Map<String, Object>
看看数据类转Map的实现
private static Map<String, Object> convert2Map(Object dataSrouce) {
Map<String, Object> ret = new HashMap<String, Object>();
try {
Class<?> clazz = dataSrouce.getClass();
while (clazz != Object.class) {
Field[] fields = clazz.getDeclaredFields();
PropertyDescriptor pd = null;
for (Field f : fields) {
pd = new PropertyDescriptor(f.getName(), dataSrouce.getClass());
Name annotation = f.getAnnotation(Name.class);
Object value = pd.getReadMethod().invoke(dataSrouce);
ret.put(null == annotation ? f.getName() : annotation.value(), value);
}
clazz = clazz.getSuperclass();
}
} catch (Exception e) {
logger.error("Convert datasource failed.", e);
throw new RenderException("Convert datasource failed.");
}
return ret;
}
利用反射,把datas类和它的父类的字段属性转成Map<String, Object>,Object除外。所以我们传的参数是JavaBean或Map<String, Object>就可以了(传Map参数,会调用同名的的重载方法)。
语法
普通文本
渲染数据为String或者TextRenderData
模版文件中使用:{{template}}
...
Map<String, Object> datas = new HashMap<String, Object>();
datas.put("template", "我是渲染的数据");
// 参数1:颜色 9d55b8;参数2:文本内容
datas.put("title", new TextRenderData("9d55b8", "Deeply in love with the things you love,\n just deepoove."));
...
- 文本中可用\n 来进行换行
图片
渲染数据为:PictureRenderData
模版文件中使用:{{@picture}}
/*
* 参数1:宽度;参数2:高度;参数3:图片路径
*/
datas.put("picture", new PictureRenderData(100, 120, "src/test/resources/logo.png"));
表格
渲染数据为:TableRenderData
模版文件中使用:{{#table}}
/**
* @param headers 表格头
* @param datas 表格数据
* @param noDatadesc 没有数据显示的文案
* @param width 宽度
*/
public TableRenderData(List<RenderData> headers, List<Object> datas,
String noDatadesc, int width) {
this.headers = headers;
this.datas = datas;
this.noDatadesc = noDatadesc;
this.width = width;
}
// 有表格头 有数据
datas.put("table", new TableRenderData(new ArrayList<RenderData>() {
{
add(new TextRenderData("1E915D", "province"));
add(new TextRenderData("1E915D", "city"));
}
}, new ArrayList<Object>() {
{
add("beijing;beijing");
add("zhejiang;hangzhou");
}
}, "no datas", 0));
}
如果没有数据,表格会显示“no datas”
更加详细的请参考:poi-tl处理Word表格(Table)的最佳实践
列表
渲染数据为:NumbericRenderData
模版文件中使用:{{numbering}}*
/**
* @param numFmt 编号字符
* @param fmtStyle 编号样式
* @param numbers 列表内容
*/
public NumbericRenderData(Pair<Enum, String> numFmt, Style fmtStyle, List<TextRenderData> numbers) {
this.numFmt = numFmt;
this.numbers = numbers;
this.fmtStyle = fmtStyle;
}
datas.put("unorderlist", new NumbericRenderData(new ArrayList<TextRenderData>(){{
add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
}}));
datas.put("orderlist", new NumbericRenderData(NumbericRenderData.FMT_DECIMAL, new ArrayList<TextRenderData>(){{
add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
}}));
NumbericRenderData类中有编号的符号常量
/**
* 1. 2. 3.
*/
public static final Pair<Enum, String> FMT_DECIMAL = Pair.of(STNumberFormat.DECIMAL, "%1.");
/**
* 1) 2) 3)
*/
public static final Pair<Enum, String> FMT_DECIMAL_PARENTHESES = Pair.of(STNumberFormat.DECIMAL,
"%1)");
/**
* ● ● ●
*/
public static final Pair<Enum, String> FMT_BULLET = Pair.of(STNumberFormat.BULLET, "●");
/**
* a. b. c.
*/
public static final Pair<Enum, String> FMT_LOWER_LETTER = Pair.of(STNumberFormat.LOWER_LETTER,
"%1.");
/**
* i ⅱ ⅲ
*/
public static final Pair<Enum, String> FMT_LOWER_ROMAN = Pair.of(STNumberFormat.LOWER_ROMAN,
"%1.");
/**
* A. B. C.
*/
public static final Pair<Enum, String> FMT_UPPER_LETTER = Pair.of(STNumberFormat.UPPER_LETTER,
"%1.");
/**
* Ⅰ Ⅱ Ⅲ
*/
public static final Pair<Enum, String> FMT_UPPER_ROMAN = Pair.of(STNumberFormat.UPPER_ROMAN,
"%1.");
样式
Style类
主要样式如下:
- 颜色
- 字体
- 字号
- 粗体
- 斜体
- 删除线
public class Style {
//颜色
private String color;
//字体
private String fontFamily;
//字号
private int fontSize;
//粗体
private Boolean isBold;
//斜体
private Boolean isItalic;
//删除线
private Boolean isStrike;
public Style() {
}
public Style(String color) {
this.color = color;
}
public Style(String fontFamily, int fontSize) {
this.fontFamily = fontFamily;
this.fontSize = fontSize;
}
......
}
poi-tl 的 Change log
v1.2.0 2017-10-12
- 新增api:
XWPFTemplate compile(InputStream inputStream)
- 不兼容升级:文本模板换行符由原先的\\n替换成更符合语言的\n
v1.1.0 2017-09-15
- 修复老版本office打开表格模板时出错
- 新增列表字符样式:设置编号颜色、字体、粗体、斜体等
v1.0.0
- 以插件的思想进行了重新设计
- 高度扩展性:语法即插件,像新增插件一样新增语法
- 新增工具类BytePictureUtils,便于操作图片的byte[]数据
- 新增Annotation @Name
- NiceXWPFDocument新增插入段落insertNewParagraph方法
- 新增代码生成工具类CodeGenUtils
V0.0.5
- bugfix: 解决0.0.4版本解析模板时CTSignedTwips类加载不到的问题
- new feature: 新增列表语法*,支持对有序列表和无序列表的插入
V0.0.4
- 增加新的api:XWPFTemplate.compile
- 渲染数据除了支持Map以外,还支持JavaBean渲染
- 升级poi组件至最新版本3.16
V0.0.3
- 新增表单语法#
- 支持表单插入
- 渲染器支持对table动态处理DynamicTableRenderPolicy
- 支持单元格的合并
- 丰富文本样式
Office Open XML -- OOXML
为了让微软的office用其他软件打开不会出现错乱的问题,所以出现了docx格式的文档(xlsx,pptx也是)。微软的OOXML文档格式已被批准为全球行业标准。
如果要了解比apache poi 更低层的ooxml,可以访问office open xml
在office open xml网站里,你会知道文件是怎样实现的,颜色、字体、粗体等是怎么设置的。
docx 文档转成xml后,普通文本就是这个样子的:
<w:r>
<w:rPr>
<w:b/>
<w:i/>
</w:rPr>
<w:t>我是文本</w:t>
</w:r>
设置文本为粗体:
<w:r>
<w:rPr>
<w:b w:val="true"/>
<w:i/>
</w:rPr>
<w:t>我是文本</w:t>
</w:r>
基本所有的属性,在office open xml都有详细的解析。
Word文档生成目录问题
我一直在找POI生成目录的方法,Jacob可以实现。如果你知道,麻烦告诉我!谢谢