freemarker动态生成word并将生成的word转为PDF,openoffice转换word乱码

之前项目有个需求,需要先动态生成word内容,然后再预览生成word的内容(不能修改).整理一下,方便以后使用.

网上参考了好多大神的博客.具体也忘了参考谁的了

思路一:

将目标word文件另存为xml文件,将里面的需要动态生成的内容用freemarker的表达式${}替换.

用freemarker生成word的工具类,动态生成word. 这样生成的word实际上是xml文件,用办公软件能正常打开使用.

但是转PDF的时候发现转不成功.转过之后的PDF显示的不是word的格式字符,而是像xml文件的标签及字符,失败!

思路二:

word的docx文件其实属于zip的一种. 这里只需要对它的核心内容部分进行操作.其他数据不动.具体做法为:

1.用办公软件(wps/office)打开模板文件,将需要修改的内容,用freemarker的表达式${}替换.

(注意:需要循环展示的内容还需要在xml文件中处理)如下:

 

freemarker ftl模板生成pdf数字换行了 freemarker word转pdf_xml

 

2.将模板docx文件重命名为.zip的压缩文件.

3.用解压工具打开,取出word/document.xml 文件.

 

freemarker ftl模板生成pdf数字换行了 freemarker word转pdf_数据_02

 

4.此时用文本工具打开document.xml,内容不太好看,将文件格式化一下.(我这里没找到好的格式化工具,使用notepad没格好,最后用idea还行).格式化后如下.

 

freemarker ftl模板生成pdf数字换行了 freemarker word转pdf_java_03

 

5.在xml中需要循环的内容前增加如下标签:

 

freemarker ftl模板生成pdf数字换行了 freemarker word转pdf_数据_04

 

6.说明

word中要填充的数据为map格式,${}中为map的key.如果还需要循环填充可以如下操作:

map1   map2   list
map1.put("userName",name);
list.add(map1);
map2.put("list",list);
map2.put("title",title);

map2即为要填充的所有数据.这样给list一个别名listKey 后,${}中如下填写即可.

 

freemarker ftl模板生成pdf数字换行了 freemarker word转pdf_数据_05

 

7.将模板文件与xml文件保存到一个固定位置.我这里保存到了项目中:

 

freemarker ftl模板生成pdf数字换行了 freemarker word转pdf_xml_06

 

8.准备工作完成,生成word工具类如下:

import freemarker.template.TemplateException;

import java.io.*;
import java.util.Enumeration;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * 其实docx属于zip的一种,这里只需要操作word/document.xml中的数据,其他的数据不用动
 *
 * @author
 *
 */
public class XmlToDocx {

    /**
     *
     *  @param xmlTemplate xml的文件名
     *  @param docxTemplate docx的路径和文件名
     * @param xmlTemp  填充完数据的临时xml
     * @param toFilePath  目标文件名
     * @param map  需要动态传入的数据
     * @throws IOException
     * @throws TemplateException
     */
    public static void toDocx(String xmlTemplate,String docxTemplate,String xmlTemp ,String  toFilePath,Map map)  {
        try {
            // 1.map是动态传入的数据
            // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
            Writer w1 = new OutputStreamWriter(new FileOutputStream(xmlTemp), "gb2312");
            // 2.把map中的数据动态由freemarker传给xml
            XmlTplUtil.process(xmlTemplate, map, w1);
            // 3.把填充完成的xml写入到docx中
            XmlToDocx xtd = new XmlToDocx();
            xtd.outDocx(new File(xmlTemp), docxTemplate, toFilePath);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     *
     * @param documentFile 动态生成数据的docunment.xml文件
     * @param docxTemplate docx的模板
     * @param toFilePath  需要导出的文件路径
     * @throws ZipException
     * @throws IOException
     */

    public void outDocx(File documentFile, String docxTemplate, String toFilePath) throws ZipException, IOException {

        try {
            File docxFile = new File(docxTemplate);
            ZipFile zipFile = new ZipFile(docxFile);
            Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
            ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(toFilePath));
            int len = -1;
            byte[] buffer = new byte[1024];
            while (zipEntrys.hasMoreElements()) {
                ZipEntry next = zipEntrys.nextElement();
                InputStream is = zipFile.getInputStream(next);
                // 把输入流的文件传到输出流中 如果是word/document.xml由我们输入
                zipout.putNextEntry(new ZipEntry(next.toString()));
                if ("word/document.xml".equals(next.toString())) {
                    InputStream in = new FileInputStream(documentFile);
                    while ((len = in.read(buffer)) != -1) {
                        zipout.write(buffer, 0, len);
                    }
                    in.close();
                } else {
                    while ((len = is.read(buffer)) != -1) {
                        zipout.write(buffer, 0, len);
                    }
                    is.close();
                }
            }
            zipout.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


9.生成PDF工具类


import com.artofsolving.jodconverter.DocumentConverter;
import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter;
import org.apache.poi.xwpf.converter.pdf.PdfConverter;
import org.apache.poi.xwpf.converter.pdf.PdfOptions;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

import java.io.*;


public class XMlToDoc {

    /**
     * 生成pdf
     */
    public static String makePdfByXcode(String docx) {
        String filename = null;
        File outFile = null;
        try {

            //    document.setParagraph(new Pa );
            if (docx.contains(".docx")) {
                XWPFDocument document=new XWPFDocument(new FileInputStream(new File(docx)));
                outFile=new File(docx.replace(".docx",".pdf"));
                filename=docx.replace(".docx",".pdf");

                outFile.getParentFile().mkdirs();
                OutputStream out=new FileOutputStream(outFile);
                //    IFontProvider fontProvider = new AbstractFontRegistry();
                PdfOptions options= PdfOptions.create();  //gb2312
                PdfConverter.getInstance().convert(document,out,options);

            } else {
                File inputFile = new File(docx);
                outFile = new File(docx.replace(".doc", ".pdf"));
                filename = docx.replace(".doc", ".pdf");
                outFile.getParentFile().mkdirs();

                OpenOfficeConnection connection = new SocketOpenOfficeConnection(8100);
                connection.connect();

                // convert
                DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
                converter.convert(inputFile, outFile);

                // close the connection
                connection.disconnect();
            }


        }catch (IllegalArgumentException e){
            System.err.println("未知文件格式");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return filename;

    }


}
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;

public class XmlTplUtil {

    private static XmlTplUtil tplm = null;
    private Configuration cfg = null;

    private XmlTplUtil() {
        cfg = new Configuration();
        try {
            // 注册tmlplate的load路径
            // 这的路径是xml的路径
            String pathName = XmlTplUtil.class.getClassLoader().getResource("").getPath();
            String path = pathName.substring(1, pathName.lastIndexOf("/"));
            String parentPath1 = new File(path).getParent();//获取项目的上一级目录
            String parentPath2 = new File(parentPath1).getParent();//获取项目的上一级目录
            String xmlPath = parentPath2 + "/static/excelModel";
            cfg.setDirectoryForTemplateLoading(new File(xmlPath));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static Template getTemplate(String name) throws IOException {
        if (tplm == null) {
            tplm = new XmlTplUtil();
        }
       Template template =  tplm.cfg.getTemplate(name);
        return template;
    }

    /**
     *
     * @param templatefile 模板文件
     * @param param 需要填充的内容
     * @param out 填充完成输出的文件
     * @throws IOException
     * @throws TemplateException
     */
    public static void process(String templatefile, Map param, Writer out) throws IOException, TemplateException {
        // 获取模板
        Template template = XmlTplUtil.getTemplate(templatefile);
        template.setOutputEncoding("GBK");
        // 合并数据
        template.process(param, out);
        if (out != null) {
            out.close();
        }
    }
}


注意:生成PDF需要安装openoffice 软件,安装完成后,

cd openoffice目录下有个OpenOffice 4\program

然后输入命令

soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard

就ok了.

10.用到的maven包

<dependency>
      <groupId>com.artofsolving</groupId>
      <artifactId>jodconverter</artifactId>
      <version>2.2.1</version>
    </dependency>
<!--openoffice-->
    <dependency>
      <groupId>org.openoffice</groupId>
      <artifactId>jurt</artifactId>
      <version>3.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.openoffice</groupId>
      <artifactId>ridl</artifactId>
      <version>3.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.openoffice</groupId>
      <artifactId>juh</artifactId>
      <version>3.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.openoffice</groupId>
      <artifactId>unoil</artifactId>
      <version>3.0.1</version>
    </dependency>
<dependency>
      <groupId>fr.opensagres.xdocreport</groupId>
      <artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
      <version>1.0.6</version>
    </dependency>
 <dependency>
      <groupId>org.freemarker</groupId>
      <artifactId>freemarker</artifactId>
      <version>2.3.22</version>
    </dependency>


---------------------
作者:菜鸟-也-想飞

注意:springboot打成jar无法放入webapp下生成,