docx4j 因为方法实现过于底层,相关文档说明特别少,而很少被人熟知
当需要使用 docx4j 创建office文档时,往往要自己实现一些常用的基本功能,这带来了一定的开发难度和不必要的精力开销
在经历一轮 docx4j 的学习和开发工作后,总结了一些基本方法,大家可以拿去参考
这其中难免有些错误和纰漏,烦请批评和指正
先看下导出文件的效果吧:
------------- page1
------------- page2
------------- page3
------------- page4
------------- page5
对于页眉页脚要求很高、格式花俏的 word文档,完全没有必要全靠代码实现
可以先制作一个模板文件,并放到(Web)项目 resource 路径(当然你也可以指定本地路径)
代码实现上直接加载该模板,然后替换、添加。。
本例中使用到的 模板文件:
好了,下面是粘贴代码了。。
1、很多情况默认样式的标题编号并不能满足需要,为了“自动”生成“任意”格式编号的标题
自定义 HeadingStyle、HeadingFormat 和 HeadingTool 三个类:
(1)HeadingStyle 类方便实现标题文本样式、编号生成规则以及标题等级
(2)HeadingFormat 可用于生成标题的编号,以用户指定的规则
(3)HeadingTool 是标题工具类,只要实现了标题编号的自动叠加
HeadingStyle
/**
* word 文档中标题的样式,含编号样式和文字样式
*/
public class HeadingStyle {
/**
* 标题的等级,≥1
*/
private int grade;
/**
* 标题所在段落的整体样式,如 1、heading 2等
*/
private String pStyle;
/**
* 标题编码格式的正则表达式
* 目前仅支持 #.#,即数字之间以 . 分割
*/
private String hStyle;
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public String getPStyle() {
return pStyle;
}
public void setPStyle(String pStyle) {
this.pStyle = pStyle;
}
public String getHStyle() {
return hStyle;
}
public void setHStyle(String hStyle) {
this.hStyle = hStyle;
}
public HeadingStyle(int grade, String pStyle) {
super();
this.grade = grade;
this.pStyle = pStyle;
this.hStyle = "#.#";
}
public HeadingStyle(int grade, String pStyle, String hStyle) {
super();
this.grade = grade;
this.pStyle = pStyle;
this.hStyle = hStyle;
}
}
HeadingFormat
/**
* word 文档中标题的编码格式化工具类
*/
public class HeadingFormat {
/**
* 编码格式的正则表达式
* 目前仅支持 #.#,即数字之间以 . 分割
*/
private String pattern;
public HeadingFormat(String pattern) {
super();
this.pattern = pattern;
}
/**
* 给定Map生成等级编码,起始等级为默认1,正则表达式为默认 #.#
* @param gradeMaxVals 等级与该等级最大值Map,其中key是等级,value是该等级当前最大值
* @param endGrade 显示输出的结束等级
* @return
*/
public String format(Map<Integer, Integer> gradeMaxVals, int endGrade){
return format(gradeMaxVals, 1 ,endGrade, this.pattern);
}
/**
* 给定Map生成等级编码,起始等级为默认1
* @param gradeMaxVals 等级与该等级最大值Map,其中key是等级,value是该等级当前最大值
* @param endGrade 显示输出的结束等级
* @param pattern 编码格式的正则表达式
* @return
*/
public String format(Map<Integer, Integer> gradeMaxVals, int endGrade, String pattern){
return format(gradeMaxVals, 1 ,endGrade, pattern);
}
/**
* 给定Map生成等级编码
* @param gradeMaxVals 等级与该等级最大值Map,其中key是等级,value是该等级当前最大值
* @param startGrade 显示输出的起始等级
* @param endGrade 显示输出的结束等级
* @param pattern 编码格式的正则表达式
* @return
*/
public String format(Map<Integer, Integer> gradeMaxVals, int startGrade , int endGrade, String pattern){
if(gradeMaxVals ==null || gradeMaxVals.size() <1){
return "";
}
if(startGrade <1){
throw new ArrayIndexOutOfBoundsException("起始等级应当 ≥1!");
}
if(startGrade > endGrade){
throw new ArrayIndexOutOfBoundsException("起始等级应当小于等于结束等级!");
}
if(pattern != "#.#"){
throw new ArrayIndexOutOfBoundsException("非标准格式的暂未支持!");
}
int minGrade = CollectionUtils.getMinKey(gradeMaxVals);
int maxGrade = CollectionUtils.getMaxKey(gradeMaxVals);
if(startGrade < minGrade){
startGrade = minGrade;
}
if(endGrade > maxGrade){
endGrade = maxGrade;
}
StringBuilder headNum = new StringBuilder("");
for(int i= startGrade; i<= maxGrade; i++){
headNum.append(".");
headNum.append(gradeMaxVals.get(i));
}
String headNums = headNum.toString();
if(headNums.startsWith(".")){
headNums = headNums.replaceFirst(".", "");
}
return headNums;
}
/**
* 根据现有 编码 反推 gradeMaxVals,编码字符串的首个数字默认为等级1,正则表达式为默认 #.#
* @param headNum 符合 pattern 正则表达式的 编码字符串
* @return
*/
public Map<Integer, Integer> parse(String headNum){
return parse(headNum, 1, this.pattern);
}
/**
* 根据现有 编码 反推 gradeMaxVals
* @param headNum 符合 pattern 正则表达式的 编码字符串
* @param startGrade 编码字符串中首个数字所代表的编码等级
* @param pattern 编码格式的正则表达式
* @return
*/
public Map<Integer, Integer> parse(String headNum, int startGrade, String pattern){
if(headNum ==null || "".equals(headNum.trim())){
return null;
}
if(startGrade <1){
throw new ArrayIndexOutOfBoundsException("起始等级应当 ≥1!");
}
String[] numStrArray = headNum.split(".");
if(numStrArray.length <1){
throw new ArrayIndexOutOfBoundsException("编码字符无效!");
}
if(pattern != "#.#"){
throw new ArrayIndexOutOfBoundsException("非标准格式的暂未支持!");
}
int level = startGrade + numStrArray.length - 1;
Map<Integer, Integer> gradeMaxVals = new HashMap<>();
for(int i=0; i< level; i++){
if(i< startGrade){
gradeMaxVals.put(i+1, 0);
}else{
gradeMaxVals.put(i+1, new Integer(numStrArray[i-level+1]));
}
}
return gradeMaxVals;
}
}
HeadingTool
/**
* word 文档中标题样式、编码生成等相关的工具类
*/
public class HeadingTool {
/**
* 等级与该等级最大值Map,其中key是等级,value是该等级当前最大值
*/
Map<Integer, Integer> gradeMaxVals;
/**
* 等级与该等级的统一样式Map,其中key是等级,value是该等级的样式类
*/
Map<Integer, HeadingStyle> gradeHeadingStyles;
public Map<Integer, Integer> getGradeMaxVals(){
return gradeMaxVals;
}
public void setGradeMaxVals(Map<Integer, Integer> gradeMaxVals) {
this.gradeMaxVals = gradeMaxVals;
}
public Map<Integer, HeadingStyle> getGradeHeadingStyles() {
return gradeHeadingStyles;
}
public void setGradeHeadingStyles(Map<Integer, HeadingStyle> gradeHeadingStyles) {
this.gradeHeadingStyles = gradeHeadingStyles;
}
public HeadingTool() {
super();
this.gradeMaxVals = initGradeMaxVals();
this.gradeHeadingStyles = initGradeHeadingStyles();
}
public HeadingTool(Map<Integer, Integer> gradeMaxVals,
Map<Integer, HeadingStyle> gradeHeadingStyles) {
super();
this.gradeMaxVals = gradeMaxVals;
this.gradeHeadingStyles = gradeHeadingStyles;
}
/**
* 初始化 gradeMaxVals
* @return
*/
public Map<Integer, Integer> initGradeMaxVals(){
Map<Integer, Integer> _gradeMaxVals = new HashMap<>();
_gradeMaxVals.put(1, 0);
return _gradeMaxVals;
}
/**
* 初始化 gradeHeadingStyles
* @return
*/
public Map<Integer, HeadingStyle> initGradeHeadingStyles(){
Map<Integer, HeadingStyle> _gradeHeadingStyles = new HashMap<>();
HeadingStyle headingStyle1 = new HeadingStyle(1, "1");//1级标题应当声明1,其他等级方可声明Heading*
_gradeHeadingStyles.put(1, headingStyle1);
HeadingStyle headingStyle2 = new HeadingStyle(2, "Heading2");
_gradeHeadingStyles.put(2, headingStyle2);
HeadingStyle headingStyle3 = new HeadingStyle(3, "Heading3");
_gradeHeadingStyles.put(3, headingStyle3);
HeadingStyle headingStyle4 = new HeadingStyle(4, "Heading4");
_gradeHeadingStyles.put(4, headingStyle4);
HeadingStyle headingStyle5 = new HeadingStyle(5, "Heading5");
_gradeHeadingStyles.put(5, headingStyle5);
HeadingStyle headingStyle6 = new HeadingStyle(6, "Heading6");
_gradeHeadingStyles.put(6, headingStyle6);
HeadingStyle headingStyle7 = new HeadingStyle(7, "Heading7");
_gradeHeadingStyles.put(7, headingStyle7);
HeadingStyle headingStyle8 = new HeadingStyle(8, "Heading8");
_gradeHeadingStyles.put(8, headingStyle8);
HeadingStyle headingStyle9 = new HeadingStyle(9, "Heading9");
_gradeHeadingStyles.put(9, headingStyle9);
return _gradeHeadingStyles;
}
/**
* 以指定样式生成当前标题组下的特定自增等级的标题编码
* @param increGrade 特定自增等级
* @param headingFormat 指定的标题编码样式
* @return
*/
public String getAutoHeadingNum(int increGrade, HeadingFormat headingFormat){
return getAutoHeadingNum( increGrade, headingFormat, this.gradeMaxVals);
}
/**
* 以指定样式生成指定标题组下的特定自增等级的标题编码
* @param increGrade 特定自增等级
* @param headingFormat 指定的标题编码样式
* @param _gradeMaxVals 指定标题组
* @return
*/
public static String getAutoHeadingNum(int increGrade, HeadingFormat headingFormat,Map<Integer, Integer> _gradeMaxVals){
selfIncreGradeMaxVals(_gradeMaxVals, increGrade);
return headingFormat.format(_gradeMaxVals, increGrade);
}
/**
* 从指定等级上自增,并更新 gradeMaxVals
* @param increGrade 指定的等级
*/
public void selfIncreGradeMaxVals(int increGrade){
selfIncreGradeMaxVals(this.gradeMaxVals, increGrade);
}
/**
* 从指定等级上自增,并更新 gradeMaxVals
* @param _gradeMaxVals 原始 等级与该等级最大值Map,其中key是等级,value是该等级当前最大值
* @param increGrade 指定的等级
*/
public static void selfIncreGradeMaxVals(Map<Integer, Integer> _gradeMaxVals, int increGrade) {
int minGrade = CollectionUtils.getMinKey(_gradeMaxVals);
int maxGrade = CollectionUtils.getMaxKey(_gradeMaxVals);
if(increGrade <minGrade){
throw new ArrayIndexOutOfBoundsException("自增等级应当 ≥该Map最小key!");
}
if(maxGrade < increGrade){//增加一个子级标题
_gradeMaxVals.put(increGrade, 1);
}else{
//新加父级标题
for(int i = maxGrade; i>increGrade ; i--){
_gradeMaxVals.remove(i);
}
Integer currentValue = _gradeMaxVals.get(increGrade);
if(currentValue ==null){
currentValue =0;
}
_gradeMaxVals.put(increGrade, currentValue + 1);
}
}
}
2、自定义实现的工具类,代码核心部分
WmlTool
/**
* word 文档的创建工具
*/
public class WmlTool {
/**
* 以下备注仅为方便认识 docx4j 相关类
*/
//wml:无线置标语言,用以处理word文档创建
//P:段落 paragraph
//R:行 row
//Br:换行
//fldChar:Field Charend
//TOC:内容标题 Title of Content
//Tbl:表格Table
//Tc:Table Cell表单元格
//Tr:Table Row表格的行
//*Pr:property,属性、样式设置
/**
* wml 的文档包,文档操作的主要对象
*/
private WordprocessingMLPackage wmlPackage;
/**
* wml 的文档工厂,是用以创建相关 element 的工具类
*/
private ObjectFactory wmlFactory;
/**
* wml 的文档主体部分
*/
private MainDocumentPart wmlMainPart;
/**
* wml 文档内的所有内容
*/
private List<Object> wmlAllContents;
/**
* 标题相关工具包
*/
private HeadingTool headingTool;
public WordprocessingMLPackage getWmlPackage() {
return wmlPackage;
}
public void setWmlPackage(WordprocessingMLPackage wmlPackage) {
this.wmlPackage = wmlPackage;
}
public ObjectFactory getWmlFactory() {
return wmlFactory;
}
public void setWmlFactory(ObjectFactory wmlFactory) {
this.wmlFactory = wmlFactory;
}
public MainDocumentPart getWmlMainPart() {
return wmlMainPart;
}
public void setWmlMainPart(MainDocumentPart wmlMainPart) {
this.wmlMainPart = wmlMainPart;
}
public List<Object> getWmlAllContents() {
return wmlAllContents;
}
public void setWmlAllContents(List<Object> wmlAllContents) {
this.wmlAllContents = wmlAllContents;
}
public HeadingTool getHeadingTool() {
return headingTool;
}
public void setHeadingTool(HeadingTool headingTool) {
this.headingTool = headingTool;
}
/**
* 文档工厂、文档包等变量的初始化
* @param fileResource 放在resource路径的模板文件名称,含文件后缀
*/
public WmlTool(String fileResource) throws FileNotFoundException, Docx4JException {
URL url = this.getClass().getClassLoader().getResource(fileResource);
this.wmlPackage = WordprocessingMLPackage.load(new FileInputStream(new File(url.getPath())));
this.wmlMainPart = this.wmlPackage.getMainDocumentPart();
this.wmlAllContents = this.wmlMainPart.getJaxbElement().getBody().getContent();
this.wmlFactory = Context.getWmlObjectFactory();
this.headingTool = new HeadingTool();
}
/**
* 获取当前文档下所有表格
*/
public List<Tbl> getAllTables(){
List<Object> elements = getClazzChildren4Element(this.wmlMainPart, Tbl.class);
List<Tbl> tables = new ArrayList<Tbl>(elements.size());
for(Object element:elements){
if(element ==null){
continue;
}
tables.add((Tbl)element);
}
return tables;
}
/**
* 获取当前表格下所有文本域
* @param table 表格
*/
public static List<Text> getAllTexts4Table(Tbl table){
List<Object> elements = getClazzChildren4Element(table, Text.class);
List<Text> texts = new ArrayList<Text>(elements.size());
for(Object element:elements){
if(element ==null){
continue;
}
texts.add((Text)element);
}
return texts;
}
/**
* 获取元素下的所有clazz类型子元素,可包含其本身
* 例(1)元素 MainDocumentPart 下查找 Tbl.class
* (2)元素 Tbl 下查找 Text.class
* @param element
* @param clazz
* @return
*/
public static List<Object> getClazzChildren4Element(Object element, Class<?> clazz) {
List<Object> elements = new ArrayList<Object>();
if (element instanceof JAXBElement)
element = ((JAXBElement<?>) element).getValue();
if(element.getClass().equals(clazz)){
elements.add(element);
}else if (element instanceof ContentAccessor) {
List<?> children = ((ContentAccessor) element).getContent();
for (Object child : children) {
elements.addAll(getClazzChildren4Element(child, clazz));
}
}
return elements;
}
/**
* 增加分页,从当前行直接跳转到下页
*/
public void addPageBreak() {
addPageBreak(this.wmlMainPart, this.wmlAllContents, this.wmlFactory);
}
/**
* 增加分页,从当前行直接跳转到下页
* @param mainPart 文档主体
* @param contents 文档内容
*/
public static void addPageBreak(MainDocumentPart mainPart, List<Object> contents, ObjectFactory factory) {
Br br = new Br();//换行
br.setType(STBrType.PAGE);//换页方式
P paragraph = factory.createP();//段落
paragraph.getContent().add(br);
contents.add(paragraph);
}
/**
* 复杂字符域上边界
* @param paragraph
*/
public void addFieldBegin(P paragraph) {
addFieldBegin(paragraph, this.wmlFactory);
}
/**
* 复杂字符域上边界
* @param paragraph
* @param factory
*/
public static void addFieldBegin(P paragraph, ObjectFactory factory) {
FldChar fldChar = factory.createFldChar();//字符域
fldChar.setFldCharType(STFldCharType.BEGIN);
fldChar.setDirty(true);
R row = factory.createR();//行
row.getContent().add(getWrappedFldChar(fldChar));
paragraph.getContent().add(row);
}
/**
* 复杂字符域下边界
* @param paragraph
*/
public void addFieldEnd(P paragraph) {
addFieldEnd(paragraph, this.wmlFactory);
}
/**
* 复杂字符域下边界
* @param paragraph
* @param factory
*/
public static void addFieldEnd(P paragraph, ObjectFactory factory) {
FldChar fldChar = factory.createFldChar();//字符域
fldChar.setFldCharType(STFldCharType.END);
R row = factory.createR();//行
row.getContent().add(getWrappedFldChar(fldChar));
paragraph.getContent().add(row);
}
/**
* 包装复杂字符域
* @param fldChar 字符域
* @return
*/
public static JAXBElement getWrappedFldChar(FldChar fldChar) {
QName qName = new QName(Namespaces.NS_WORD12, "fldChar");
JAXBElement element = new JAXBElement(qName, FldChar.class, fldChar);
return element;
}
/**
* 创建目录信息
* @param showMaxGrade 目录显示的最大等级
*/
public void createCatalog(int showMaxGrade) throws JAXBException{
createCatalog(this.wmlPackage , this.wmlFactory, showMaxGrade);
}
/**
* 创建目录信息
* @param _package 文档包
* @param factory 文档工具类
* @param showMaxGrade 目录显示的最大等级
* @throws JAXBException
*/
public static void createCatalog(WordprocessingMLPackage _package , ObjectFactory factory, int showMaxGrade) throws JAXBException {
MainDocumentPart mainPart = _package.getMainDocumentPart();
List<Object> allContents = mainPart.getJaxbElement().getBody().getContent();
addPageBreak(mainPart, allContents, factory);
StringBuilder xml = new StringBuilder();
xml.append("<w:p w:rsidR='00C54076' w:rsidRDefault='00C54076' ");
xml.append(" xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'> ");
xml.append(" <w:pPr> ");
xml.append(" <w:pStyle w:val='TOC'/> ");
xml.append(" </w:pPr> ");
xml.append(" <w:r> ");
xml.append(" <w:t>目录</w:t> ");
xml.append(" </w:r> ");
xml.append("</w:p> ");
P toc = (P) XmlUtils.unmarshalString(xml.toString());//目录标题Title of catalog
_package.getMainDocumentPart().addObject(toc);
P catalog = factory.createP();
addFieldBegin(catalog, factory);
addTable4Catalog(catalog, showMaxGrade, factory);
addFieldEnd(catalog, factory);
allContents.add(catalog);
}
/**
* 添加目录详情,word内alt + f9可见
* @param catalog 目录段落
* @param grade 目录显示的最大等级
* @param factory 文档工具类
*/
public static void addTable4Catalog(P catalog, int grade, ObjectFactory factory) {
Text txt = new Text();
txt.setSpace("preserve");//目录中保留 heading 前后的空格
txt.setValue("TOC \\o \"1-"+grade+"\" \\h \\z \\u");
R row = factory.createR();
row.getContent().add(factory.createRInstrText(txt));
catalog.getContent().add(row);
}
/**
* 以指定文本样式创建段落标题 Title of Content,并以默认方式生成编码
* 注意:一级标题应当声明为1,其他标题方可声明Heading*
* @param grade 标题等级
* @param headingText 标题的文本信息(不含编号)
* @param headingStyle 标题的文本信息(不含编号)
* @throws JAXBException
*/
public void addHeading(String headingText, HeadingStyle headingStyle) throws JAXBException{
addHeading(headingText, headingStyle, new HeadingFormat("#.#")) ;
}
/**
* 创建段落标题 Title of Content,当前文档自增编号
* 注意:一级标题应当声明为1,其他标题方可声明Heading*
* @param headingFormat 标题编号格式化
* @param headingText 标题的文本信息(不含编号)
* @param headingStyle 标题的样式
* @throws JAXBException
*/
public void addHeading(String headingText, HeadingStyle headingStyle, HeadingFormat headingFormat ) throws JAXBException{
int grade = headingStyle.getGrade();
String headingNum = headingTool.getAutoHeadingNum(grade, headingFormat);
addHeading(headingNum, headingText, headingStyle);
//通过直接调用方法 addHeading(String headingNum, String headingText, HeadingStyle headingStyle) [以下简称方法1]创建标题,不会自动更新 标题组Map
//本方法 [以下简称方法2]因为使用 标题组Map 生成编码,故调用一次更新一次 标题组Map
//对同一 wmlTool 的操作,如果时而调用方法1,时而调用方法2,这会导致标题等级混乱
//TODO 区分开 生成编码方式,再在调用方法1 时更新或创建新 标题组Map,这要使用到 HeadingFormat.parse方法
}
/**
* 创建段落标题 Title of Content
* @param headingNum 标题的编号
* @param headingText 标题的文本信息(不含编号)
* @param headingStyle 标题的样式
*/
private void addHeading(String headingNum, String headingText, HeadingStyle headingStyle) throws JAXBException {
String heading = headingNum + " " +headingText;
heading = heading.trim();
String pStyle = headingStyle.getPStyle();
int blank = headingStyle.getGrade();
while (blank > 1) {// 按级缩进
heading = " " + heading;
blank--;
}
StringBuilder xml = new StringBuilder();
xml.append("<w:p xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' ");
xml.append(" w:rsidRDefault='00C54076' w:rsidP='00C54076' w:rsidR='00C54076'> ");
xml.append(" <w:pPr> ");
xml.append(" <w:pStyle w:val='" + pStyle + "'/> ");
xml.append(" <w:numPr> ");
xml.append(" <w:ilvl w:val='0'/> ");
xml.append(" <w:numId w:val='0'/> ");//如果pStyle是Heading类,不采用自带编号
xml.append(" </w:numPr> ");
xml.append(" <w:rPr> ");
xml.append(" <w:rFonts w:eastAsia='黑体' w:ascii='黑体'/> ");
xml.append(" <w:b w:val='false'/> ");
xml.append(" <w:sz w:val='21'/> ");
xml.append(" <w:szCs w:val='21'/> ");
xml.append(" </w:rPr> ");
xml.append(" </w:pPr> ");
xml.append(" <w:bookmarkStart w:name='_Toc343672792' w:id='0'/> ");
xml.append(" <w:r> ");
xml.append(" <w:rPr> ");
xml.append(" <w:rFonts w:eastAsia='黑体' w:ascii='黑体' w:hint='eastAsia'/> ");
xml.append(" <w:color w:val='000000'/> ");
xml.append(" <w:b w:val='false'/> ");
xml.append(" <w:sz w:val='21'/> ");
xml.append(" <w:szCs w:val='21'/> ");
xml.append(" </w:rPr> ");
xml.append(" <w:t xml:space='preserve'>" + heading + "</w:t> ");//属性preserve 保留空格
xml.append(" </w:r> ");
xml.append(" <w:bookmarkEnd w:id='0'/> ");
xml.append("</w:p> ");
P paragraph = (P) XmlUtils.unmarshalString(xml.toString());
this.wmlMainPart.addObject(paragraph);
}
/**
* 创建段落标题 Title of Content
* 建议使用方法 addHeading(String headingNum, String headingText, HeadingStyle headingStyle);
*/
@Deprecated
private void createHeading(String headingNum, String headingText, HeadingStyle headingStyle){
String rowTextVal = headingNum + " " +headingText;
rowTextVal = rowTextVal.trim();
int grade = headingStyle.getGrade();
int blank = grade;
while (blank > 1) {// 按级缩进
rowTextVal = " " + rowTextVal;
blank--;
}
RPr rowPr = setFontPr4Row(this.wmlFactory, "微软雅黑", 21, false, "000000");//黑色
R row = this.wmlFactory.createR();//行
row.setRPr(rowPr);
Text rowText = this.wmlFactory.createText();//行的文本内容
rowText.setValue(rowTextVal);
rowText.setSpace("preserve");//保留文本内容的中的空格
row.getContent().add(rowText);
PStyle pStyle = this.wmlFactory.createPPrBasePStyle();//段落样式
pStyle.setVal("Heading" + grade);//目录分级
Jc jc = this.wmlFactory.createJc();
jc.setVal(JcEnumeration.LEFT);//标题居左
PPr pPr = this.wmlFactory.createPPr();//段落属性
pPr.setPStyle(pStyle);
pPr.setJc(jc);
//不用自带编码:pPr.setNumPr(PPrBase.NumPr.NumId=0);
P paragraph = this.wmlFactory.createP();//段落
paragraph.getContent().add(row);
paragraph.setPPr(pPr);
this.wmlMainPart.addObject(paragraph);
}
/**
* 添加字符段落
* @param text 文本信息
*/
public void addTextParagraph(String text){
addTextParagraph(text, this.wmlMainPart);
}
/**
* 添加字符段落
* @param text 文本信息
* @param mainPart 文档主体
*/
public static void addTextParagraph(String text, MainDocumentPart mainPart){
mainPart.addParagraphOfText(text);
}
/**
* 在文档主体添加元素
* @param element
*/
public void addElement(Object element){
this.wmlMainPart.addObject(element);
}
/**
* 设置表格边框颜色及文字对齐方式
*/
public static void addTblBorders(Tbl table) {
CTBorder border = new CTBorder();
border.setColor("auto");
border.setSz(new BigInteger("4"));
border.setSpace(new BigInteger("0"));
border.setVal(STBorder.SINGLE);
TblBorders borders = new TblBorders();
borders.setTop(border);//上边框
borders.setBottom(border);//下边框
borders.setLeft(border);//左边框
borders.setRight(border);//右边框
borders.setInsideH(border);//纵向内边框
borders.setInsideV(border);//横向内边框
TblPr tblPr = new TblPr();//表格属性
tblPr.setTblBorders(borders);
table.setTblPr(tblPr);
Jc jc = new Jc();//文字对象方式
jc.setVal(JcEnumeration.CENTER);
table.getTblPr().setJc(jc);
}
/**
* 添加表单元格
* @param cellText 单元格文本
* @param width 单元格宽度
* @return
*/
public Tc createTblCell(String cellText, Integer width){
return createTblCell( cellText, width, this.wmlFactory);
}
/**
* 添加表单元格
* @param cellText 单元格文本
* @param width 单元格宽度
* @param factory 文档工具类
* @return
*/
public static Tc createTblCell(String cellText, Integer width, ObjectFactory factory) {
Text text = factory.createText();
text.setValue(cellText);
R row = factory.createR();
row.getContent().add(text);
P paragraph = factory.createP();
paragraph.getContent().add(row);
Tc tblCell = factory.createTc();
tblCell.getContent().add(paragraph);
if (width != null) {
TblWidth cellWidth = factory.createTblWidth();
cellWidth.setType("dxa");//横向宽度
cellWidth.setW(BigInteger.valueOf(width));
TcPr tcPr = factory.createTcPr();//Table cell property
tcPr.setTcW(cellWidth);
tblCell.setTcPr(tcPr);
}
return tblCell;
}
/**
* 设置表格宽度
* @param table 表格
* @param width 宽度
*/
public static void setTblWidth(Tbl table, int width) {
TblPr tblPr = table.getTblPr();
if (tblPr == null) {
tblPr = new TblPr();
table.setTblPr(tblPr);
}
TblWidth tblW = tblPr.getTblW();
if (tblW == null) {
tblW = new TblWidth();
tblPr.setTblW(tblW);
}
tblW.setType("dxa");//横向宽度
tblW.setW(new BigInteger(width+""));
}
/**
* 设置单元格背景色
* @param tblCell 单元格
* @param colorStr 颜色,如:FFFF00
*/
public void setTblCellColor(Tc tblCell, String colorStr){
setTblCellColor(tblCell, colorStr, this.wmlFactory);
}
/**
* 设置单元格背景色
* @param tblCell 单元格
* @param colorStr 颜色,如:FFFF00
* @param factory 文档工具类
*/
public static void setTblCellColor(Tc tblCell, String colorStr, ObjectFactory factory) {
CTShd shd = factory.createCTShd();
shd.setFill(colorStr);
TcPr tblCellPro = factory.createTcPr();
tblCellPro.setShd(shd);
tblCell.setTcPr(tblCellPro);
}
/**
* 获取文档的可用宽度
*/
public int getWritableWidth() throws NullPointerException{
return getWritableWidth(this.wmlPackage);
}
/**
* 获取文档的可用宽度
* @param _package 文档包
*/
public static int getWritableWidth(WordprocessingMLPackage _package) throws NullPointerException {
DocumentModel docModel = _package.getDocumentModel();//模板文件
List<SectionWrapper> sectionWrappers = docModel.getSections();//包装器
if(sectionWrappers !=null && sectionWrappers.size()>0){
PageDimensions pageDim = sectionWrappers.get(0).getPageDimensions();//页面尺寸
return pageDim.getWritableWidthTwips();
}
throw new NullPointerException();
}
/**
* 添加书签
* @param id 书签id
* @param name 书签名称
* @param paragraph 段落
* @param row 行
* @param factory 文档工具类
*/
public void addBookMark(String name,P paragraph, R row){
addBookMark( 0, name, paragraph, row, this.wmlFactory);
}
/**
* 添加书签
* @param id 书签id
* @param name 书签名称
* @param paragraph 段落
* @param row 行
* @param factory 文档工具类
*/
public static void addBookMark( int id, String name,P paragraph, R row, ObjectFactory factory) throws ArrayIndexOutOfBoundsException{
int index = paragraph.getContent().indexOf(row);
if (index < 0) {
throw new ArrayIndexOutOfBoundsException("The current Row does not exist!");
}
BigInteger _id = BigInteger.valueOf(id);
CTMarkupRange mr = factory.createCTMarkupRange();
mr.setId(_id);
JAXBElement<CTMarkupRange> bmEnd = factory.createBodyBookmarkEnd(mr);
paragraph.getContent().add(index + 1, bmEnd);
CTBookmark bm = factory.createCTBookmark();
bm.setId(_id);
bm.setName(name);
JAXBElement<CTBookmark> bmStart = factory.createBodyBookmarkStart(bm);
paragraph.getContent().add(index, bmStart);
}
/**
* 创建含有内联图片的段落
* @param picBytes 图片文件流
* @param width 图片宽度
* @param height 图片高度
*/
public P createPWithPicture(byte[] picBytes, long width, long height) throws Exception{
return createPWithPicture(picBytes, width, height, this.wmlPackage, this.wmlFactory);
}
/**
* 创建含有内联图片的段落
* @param picBytes 图片文件流
* @param width 图片宽度
* @param height 图片高度
* @param _package 文档包
* @param factory 文档工具类
*/
public static P createPWithPicture(byte[] picBytes, long width, long height,
WordprocessingMLPackage _package, ObjectFactory factory) throws Exception{
BinaryPartAbstractImage picPart = BinaryPartAbstractImage.createImagePart(_package, picBytes);
Inline inline = picPart.createImageInline("", "", 1, 2, UnitsOfMeasurement.twipToEMU(width),
UnitsOfMeasurement.twipToEMU(height), false);
Drawing drawing = factory.createDrawing();
drawing.getAnchorOrInline().add(inline);
R row = factory.createR();
row.getContent().add(drawing);
P paragraph = factory.createP();
paragraph.getContent().add(row);
return paragraph;
}
/**
* 设置行文字字体、大小、加粗、颜色
* @param factory wml 的文档工厂,是用以创建相关 element 的工具类
* @param fontVal 字体名称,如黑体、微软雅黑、宋体
* @param fontSize 字体大小,镑,如12
* @param isBlod 是否加粗
* @param colorVal 文字颜色,如 FFFF00
* @return
*/
private static RPr setFontPr4Row(ObjectFactory factory, String fontVal , int fontSize , boolean isBlod , String colorVal){
RPr rowPr = factory.createRPr();
RFonts rowFont = factory.createRFonts();
rowFont.setHint(STHint.EAST_ASIA);
rowFont.setAscii(fontVal);
rowFont.setHAnsi(fontVal);
rowPr.setRFonts(rowFont);
HpsMeasure _fontSize = factory.createHpsMeasure();
_fontSize.setVal(BigInteger.valueOf(fontSize));
rowPr.setSz(_fontSize);
rowPr.setSzCs(_fontSize);
BooleanDefaultTrue fontBold = factory.createBooleanDefaultTrue();
rowPr.setBCs(fontBold);
if(isBlod){
rowPr.setB(fontBold);
}
Color color = factory.createColor();
color.setVal(colorVal);
rowPr.setColor(color);
return rowPr;
}
}
3、测试用例
(1)业务相关po类
FileParam
/**
* 与文件的业务相关的参数类
*/
public class FileParam {
private List<byte[]> pictures;
public List<byte[]> getPictures() {
return pictures;
}
public void setPictures(List<byte[]> pictures) {
this.pictures = pictures;
}
}
(2)业务相关Service类
FileService
/**
* 文件相关的Service类
*/
public class FileService {
/**
* 第2步:更新封面
* @param wmlTool
* @param fileParam
*/
public void updateCover(WmlTool wmlTool, FileParam fileParam) {
List<Tbl> docTbls = wmlTool.getAllTables();
Tbl table1 = docTbls.get(0);
List<Text> tbl1Txts = WmlTool.getAllTexts4Table(table1);
Text tbl1Txt1 = tbl1Txts.get(0);
String tbl1Txt1Key = tbl1Txt1.getValue();
if (tbl1Txt1Key.equals("key0")) {
tbl1Txt1.setValue("文档标题(表1行1)");
}
Tbl table2 = docTbls.get(1);
List<Text> tbl2Txts = WmlTool.getAllTexts4Table(table2);
for(Text tbl2Txt:tbl2Txts){
String tbl2TxtVal = tbl2Txt.getValue();
if(tbl2TxtVal.indexOf("key") >=0){
tbl2Txt.setValue(tbl2TxtVal.replace("key", "value"));
}
}
}
/**
* 第4步:写入摘要信息
* @param wmlTool
* @param fileParam
*/
public void createAbstract(WmlTool wmlTool, FileParam fileParam) throws JAXBException{
wmlTool.addPageBreak();
wmlTool.addHeading("摘要", new HeadingStyle(1, "1"));
wmlTool.addTextParagraph("文本内容第1行");
wmlTool.addTextParagraph("文本内容第2行");
int tblNIndex =wmlTool.getAllTables().size() - 1;//size - 2 +1,2个封面tbl,1个当前tbl
String tblNTitle = "表" + tblNIndex + " 我是表标题";
wmlTool.addTextParagraph(tblNTitle);
ObjectFactory factory = wmlTool.getWmlFactory();
Tbl tblN = factory.createTbl();
WmlTool.addTblBorders(tblN);
wmlTool.addElement(tblN);
Tr tblRow1 = factory.createTr();
tblRow1.getContent().add(wmlTool.createTblCell("标题1", 5300));
tblRow1.getContent().add(wmlTool.createTblCell("标题2", 5300));
tblN.getContent().add(tblRow1);
Tr tblRowN = null;
int rowNum = 0;//行号
int colNum = 0;//列号
for (int i=1; i<=7; i++) {
colNum = (i%2 == 1 ? 1 : 2);
if (colNum == 1) {
tblRowN = factory.createTr();
rowNum ++;
}
tblRowN.getContent().add(wmlTool.createTblCell("我是行" + rowNum + "列" + colNum + "的内容", 5300));
if (colNum ==2 || i ==7) {
tblN.getContent().add(tblRowN);
}
}
wmlTool.addTextParagraph("这是一行");
wmlTool.addTextParagraph("这又是一行");
wmlTool.addTextParagraph("可以再来一行");
}
/**
* 第5步:创建图片表格和多级标题
* @param wmlTool
* @param fileParam
* @throws Exception
*/
public void createPictures(WmlTool wmlTool, FileParam fileParam) throws Exception{
wmlTool.addPageBreak();
wmlTool.addHeading("我是1级标题", new HeadingStyle(1, "1"));
wmlTool.addHeading("我是2级标题", new HeadingStyle(2, "Heading2"));
ObjectFactory factory = wmlTool.getWmlFactory();
Tbl picTbl = factory.createTbl();
WmlTool.addTblBorders(picTbl);
WmlTool.setTblWidth(picTbl, 9000);
List<byte[]> pictures = fileParam.getPictures();//至少2张图片
for (int i=0; i<2; i++) {
Jc jc = factory.createJc();
jc.setVal(JcEnumeration.CENTER);
PPr paragraphProperty = factory.createPPr();
paragraphProperty.setJc(jc);
P paragraph = wmlTool.createPWithPicture(pictures.get(i), 3200, 1800);
paragraph.setPPr(paragraphProperty);
Tc tblCell = factory.createTc();
tblCell.getContent().add(paragraph);
Tr tblRow = factory.createTr();
tblRow.getContent().add(tblCell);
picTbl.getContent().add(tblRow);
}
wmlTool.addElement(picTbl);
wmlTool.addHeading("我是2级标题", new HeadingStyle(2, "Heading2"));
}
/**
* 第6步:写入结论部分
* @param wmlTool
* @param fileParam
* @throws JAXBException
*/
public void createConclusion(WmlTool wmlTool, FileParam fileParam) throws JAXBException{
wmlTool.addPageBreak();
wmlTool.addHeading("结论", new HeadingStyle(1, "1"));
wmlTool.addTextParagraph("我是结论的详细信息");
}
}
说明:本处main方法模拟实现的是保存到本地路径
public class Main {
public static FileService fileService= new FileService();
public static void main(String[] args){
try {
List<byte[]> pictures = new ArrayList<>();
InputStream is1 = new FileInputStream("d:/picture1.jpg");
byte[] picture1 = new byte[is1.available()];
is1.read(picture1);
is1.close();
pictures.add(picture1);
InputStream is2 = new FileInputStream("d:/picture2.jpg");
byte[] picture2 = new byte[is2.available()];
is2.read(picture2);
is2.close();
pictures.add(picture2);
FileParam fileParam = new FileParam();
fileParam.setPictures(pictures);
WordprocessingMLPackage wmlPackage = createWmlPackage(fileParam);
wmlPackage.save(new File("d:/生成文件.docx"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static WordprocessingMLPackage createWmlPackage(FileParam fileParam) throws Exception{
//第1步:加载模板文件
WmlTool wmlTool = new WmlTool("fileModel.docx");
//第2步:更新封面信息
fileService.updateCover(wmlTool, fileParam);
//第3步:生成目录信息
wmlTool.createCatalog(2);
//第4步:写入摘要信息
fileService.createAbstract(wmlTool, fileParam);
//第5步:创建图片表格和多级标题
fileService.createPictures(wmlTool, fileParam);
//第6步:写入结论部分
fileService.createConclusion(wmlTool, fileParam);
return wmlTool.getWmlPackage();
}
}
4、文件流输出到浏览器(Web项目的下载功能)
将3(3)的main方法中的wmlPackage.save()方法修改为如下即可
response.reset();
response.setHeader("Content-disposition", "attachment;filename=" + new String(fileName.getBytes("gb2312"),"ISO8859-1"));
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
SaveToZipFile saver = new SaveToZipFile(wmlPackage);
saver.save(response.getOutputStream());
注意:如果你遇到奇葩现象,请检查office版本和自定义模板文件本身,这里有一段说不清道不明的故事