使用doc4j生成word文档

docx4j 因为方法实现过于底层,相关文档说明特别少,而很少被人熟知

当需要使用 docx4j 创建office文档时,往往要自己实现一些常用的基本功能,这带来了一定的开发难度和不必要的精力开销

在经历一轮 docx4j 的学习和开发工作后,总结了一些基本方法,大家可以拿去参考


这其中难免有些错误和纰漏,烦请批评和指正


先看下导出文件的效果吧:

------------- page1

page1

------------- page2

page2

------------- page3

page3

------------- page4

page4

------------- page5

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("我是结论的详细信息");
    }
    
}


(3)业务相关Controller类

说明:本处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版本和自定义模板文件本身,这里有一段说不清道不明的故事






评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值