java使用poi-tl导出word及转换PDF后的合并导出pdf

1、背景

为某单位开发的一款项目申报审批系统,用户需求在申报阶段填写的信息资料能够导出PDF。且项目申报的报告正文为用户上传,所以需要合并导出。

2、问题

在项目初期阶段使用的是PDF的预设模板导出,因为以前使用过,比较熟悉。所以优先选择此方法,但项目测试阶段发现问题,因为某些项目的某些资料是动态的,不能确定有多少,PDF预设模板方式不够灵活,而且某些表格内容长度也是不确定的,导出效果很差。

3、解决

总体解决思路为导出word,因为有许多开源方法支持,且导出内容更灵活。满足用户数据内容长度不确定的要求。再将word转换PDF与用户上传的报告正文合并导出。

一、easypoi导出word

第一想法是想到easypoi导出word的方式。easypoi是对poi的二次封装,使得poi的多数功能得以简单实现,让许多没有接触过poi的开发者也能实现对Excel,word的导出。通过简单的注解和表达式语法实现导出功能。
引入依赖
GItHub地址
教程地址

引入依赖
		<!-- easyPOi-->
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-spring-boot-starter</artifactId>
            <version>4.2.0</version>
        </dependency>
使用表达式创建模板
指令作用
{{test ? obj:obj2}}三元运算
n:表示 这个cell是数值类型 {{n:}}
le:代表长度{{le:()}} 在if/else 运用{{le:() > 8 ? obj1 : obj2}}
fd:格式化时间 {{fd:(obj;yyyy-MM-dd)}}
fn:格式化数字 {{fn:(obj;###.00)}}
fe:遍历数据,创建row
!fe:遍历数据不创建row
$fe:下移插入,把当前行,下面的行全部下移.size()行,然后插入
#fe:横向遍历
v_fe:横向遍历值
!if:删除当前列 {{!if:(test)}}
‘’单引号表示常量值 ‘’ 比如’1’ 那么输出的就是 1
&NULL&空格
]]换行符 多行遍历导出
sum:统计数据

创建模板
在这里插入图片描述注意模板中合租单位信息栏处,此处有坑,后续会讲到

模板上传与代码编写

模板设置好后,将文件放在项目能访问点的地方,可以是resources文件夹下。如下
在这里插入图片描述

我系统内是将文件上传oss文件服务,通过系统配置获取文件访问URL的方式,避免修改一次导出模板就得打包一次项目。

代码编写
1、数据准备
  		dataMap.put("birth", PdfUtils.getDateMonth(vo.getSysUser().getBirthday()));//申请者出生年月
        dataMap.put("applicationPhone", vo.getSysUser().getPhone() == null ? "" : vo.getSysUser().getPhone());//申请者手机号码
        dataMap.put("applicationEmail", vo.getSysUser().getEmail() == null ? "" : vo.getSysUser().getEmail());//申请者电子邮件
        dataMap.put("companyName", vo.getDepart().getDepartName() == null ? "" : vo.getDepart().getDepartName());//申请单位--名称
        dataMap.put("companyPeople", vo.getDepart().getPeople() == null ? "" : vo.getDepart().getPeople());//申请单位-联系人
        dataMap.put("companyPhone", vo.getDepart().getMobile() == null ? "" : vo.getDepart().getMobile());//申请单位--手机
        dataMap.put("companyEmail", vo.getDepart().getEmail() == null ? "" : vo.getDepart().getEmail());//申请单位--电子邮件

        if (vo.getUnits().size() > 0) {
             dataMap.put("coorList",vo.getUnits());
        }
2、代码实现

这里的word生成是用了一个临时文件夹进行保存。使用easypoi工具类下的方法exportWord07()实现word的数据填充,方法前一个参数为模板URL,后一个为map的数据内容。因为我这里使用的oss文件存储,直接使用URL会获取不到文件。所以使用了工具类通过URL获取File。代码贴下面。

        //临时文件夹路径
        String filename = (String) dataMap.get("fileName");
        //word导出模板
        String url = wordUrl;

        File templateFile = UrlFilesToZip.Url2File(url);
        //获取模板文档
      //File templateFile = new File(url);

        //2.映射为模板
        XWPFDocument xwpfDocument = null;
        xwpfDocument = WordExportUtil.exportWord07(templateFile.getPath(), dataMap);
        //删除
        File file = new File(filename);
        for (File listFile : file.listFiles()) {
            listFile.delete();
        }
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        FileOutputStream outputStream = new FileOutputStream(filename + "/test.docx");
        xwpfDocument.write(outputStream);
通过URL获取网络文件
public static File Url2File(String url) throws Exception {
        //对本地文件命名,可以从链接截取,可以自己手写,看需求
        String fileName = "fileName";
        // String fileName = url.substring(url.lastIndexOf("."));
        File file = null;
        URL urlfile;
        InputStream inStream = null;
        OutputStream os = null;
        file = File.createTempFile("net_url", fileName);
        //下载
        urlfile = new URL(url);
        inStream = urlfile.openStream();
        os = new FileOutputStream(file);

        int bytesRead = 0;
        byte[] buffer = new byte[8192];
        while ((bytesRead = inStream.read(buffer, 0, 8192)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
        return file;
    }
方法不足

这里还没进行word转PDF的代码编写,就发现此方法的不足之处。在上面提到过合作单位信息栏。如果表格的第一列是固定的不进行遍历,而在第二列开始遍历插入。easypoi的原始方法是不满足需求的。查询后有方式可以通过修改源码实现,但没尝试,感觉麻烦,且easypoi对于富文本内容导出的处理不够完美,只能通过自己找方法实现处理。尝试过后仍然有部分样式无法保留。所以弃用此方式。

二、poi-tl导出word

通过查找,发现poi-tl的开源类库,也是基于poi开发,且对word导出的支持更好,对于导出的方式与easypoi相同,减少了关于数据准备阶段的代码修改。使用方法在参考文档中也有很多例子。

开发参考文档

依赖引入

注意版本对应,不然会出问题

<!-- POI -->		
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml-schemas</artifactId>
    <version>4.1.2</version>
</dependency>

<!-- poi-tl -->
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.10.0</version>
</dependency>
<!--poi-tl 富文本插件-->
<dependency>
   <groupId>io.github.draco1023</groupId>
   <artifactId>poi-tl-ext</artifactId>
   <version>0.4.2</version>
</dependency>
创建模板

与easypoi类似,只是在列表遍历处不一样
在这里插入图片描述

代码编写
1、数据准备

此处的数据格式与之前的数据格式一致,无需修改

2、代码编写
public byte[] exportWordByPOi_tl(Map dataMap, String wordPath)throws Exception{
        long start = System.currentTimeMillis();
//        wordPath = "D:/Test/poi-tl/青年科学基金项目申请书导出模板.docx";
//        wordPath = "https://guizhou-keyan-oss.oss-cn-hangzhou.aliyuncs.com/temp/博士基金项目申请书导出模板_1676885802664.docx";

        //绑定行循环
        LoopRowTableRenderPolicy policy=new LoopRowTableRenderPolicy();
        //富文本插件
        HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy();
        Configure configure  = Configure.builder().bind("coorList", policy).bind("peopleList", policy).bind("peopleSingList", policy)
                .bind("fileList", policy).bind("allotList",policy).bind("equipmentList",policy).bind("budget_state",htmlRenderPolicy).build();

        //通过url获取网络文件输入流
        InputStream inputStream = POICacheManager.getFile(wordPath);

        XWPFTemplate render = XWPFTemplate.compile(inputStream,configure).render(dataMap);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        render.write(byteArrayOutputStream);
        log.info("导出word消耗时间" + (System.currentTimeMillis() - start) + "毫秒");
        render.close();
        byteArrayOutputStream.close();
        return byteArrayOutputStream.toByteArray();
    }

行循环与富文本内容处理都使用了插件,所以代码中添加了二者的写法。poi-tl类库中方法XWPFTemplate.compile不支持模板的网络url使用,所以使用了easypoi中获取网络文件输入流方法,也可以自己写一个工具实现。此处便于下一步的word转PDF,所以返回了byte数组。如果不需要下一步的处理,也可以像后文直接进行输出流返回给前端。

三、word转PDF的合并导出

此处先使用了poi的word转PDF,虽然此方法使用率很高,但是效果实在不怎么样。后来看到一个横向对比的文章
Java开发中Word转PDF文件5种方案横向评测
对比中aspose与spire的转换效果最好,本着互联网精神,最后选择aspose的方式。

依赖引入
<!--aspose 破解 word转pdf-->
        <dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-words</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/aspose-words-16.8.0-jdk16.jar</systemPath>
        </dependency>

在这里插入图片描述

这里的jar包是下载后放置在系统resources/lib文件夹,因为做一些其他操作,所以仓库是没有这个jar包。

链接:https://pan.baidu.com/s/1k4qEQBHf-t8rco6PSWwpiQ
提取码:1446

代码编写
1、word转PDF

使用aspose转换需要进行验证

public byte[] asposeWord2Pdf(InputStream inputStream) {

        if (!getLicense()) { // 验证License 若不验证则转化出的pdf文档会有水印产生
            return null;
        }
        if (inputStream.equals(null)) {
            log.info("word为null");
            return null;
        }

        try {
            long old = System.currentTimeMillis();
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            com.aspose.words.Document doc = new com.aspose.words.Document(inputStream); //Address是将要被转化的word文档
            doc.save(os, SaveFormat.PDF);//全面支持DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF, EPUB, XPS, SWF 相互转换
            os.close();
            log.info("word2pdf消耗" + (System.currentTimeMillis() - old) + "毫秒");
            return os.toByteArray();
        } catch (Exception e) {
            log.info("doc转pdf文件失败,", e);
            return null;
        }
    }

license验证

public boolean getLicense() {
        boolean result = false;
        try {
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("wordPath/license.xml");
            License aposeLic = new License();
            aposeLic.setLicense(resourceAsStream);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

license内容,放置于resources/wordPath

<License>
    <Data>
        <Products>
            <Product>Aspose.Total for Java</Product>
            <Product>Aspose.Words for Java</Product>
        </Products>
        <EditionType>Enterprise</EditionType>
        <SubscriptionExpiry>20991231</SubscriptionExpiry>
        <LicenseExpiry>20991231</LicenseExpiry>
        <SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber>
    </Data>
    <Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature>
</License>
2、查找位置合并PDF

这里的合并我理解的是一页一页的拼接在上一页后面,不知道还有没有其他方式,至于PDF查找那一块现在我有点困,不想写了。还有俩小时下班,有人需要再补吧。合并后通过Response给前端。

/**
     * 合并pdf 关键词查找合并的位置
     * @Author CoCo
     * @Date 2023/2/16
     * @params
     * @return
     */
    public void mergePdf(HttpServletResponse response, byte[] pdfByte, String textUrl,String keyWord) {

        response.reset();
        response.setContentType("application/pdf");
        //response.setContentType("content-type:octet-stream;charset=UTF-8");

        try {
            response.setHeader("Content-Disposition", "attachment;filename=" + new String(("filename").getBytes(), "iso-8859-1"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        PdfUtils pdfUtils=new PdfUtils();
        com.itextpdf.text.Document document = null;
        PdfCopy copy = null;
        OutputStream os = null;
        boolean empty = textUrl != null;
        try {
            os = response.getOutputStream();
//            File file = new File("D:/Test/aspose.pdf"); //新建一个pdf文档
//            FileOutputStream oss = new FileOutputStream(file);

            document = new com.itextpdf.text.Document(new PdfReader(pdfByte).getPageSize(1));
            copy = new PdfCopy(document, os);
            document.open();
            PdfReader pdfReader = new PdfReader(pdfByte);
            PdfReader textReader = null;
            for (int i = 1; i <= pdfReader.getNumberOfPages(); i++) {
                PdfImportedPage pdfPage = copy.getImportedPage(pdfReader, i);
                //根据关键字查询页数及其他信息
                List list = pdfUtils.matchPage(pdfReader, i, keyWord);
                //如果PDF关键字查找有返回数据且正文url不为空,拼接正文pdf
                if (!list.isEmpty()&&empty){
                    textReader = new PdfReader(UrlFilesToZip.getFileFromURL(textUrl));
                    for (int k = 1; k <= textReader.getNumberOfPages(); k++) {
                        PdfImportedPage textPage = copy.getImportedPage(textReader,k);
                        copy.addPage(textPage);
                    }
                }
                copy.addPage(pdfPage);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("合并失败");
        }
        finally {
            if (copy != null) {
                try {
                    copy.close();
                } catch (Exception ex) {
                    /* ignore */
                }
            }
            if (document != null) {
                try {
                    document.close();
                } catch (Exception ex) {
                    /* ignore */
                }
            }
            if (os != null) {
                try {
                    os.close();
                } catch (Exception ex) {
                    /* ignore */
                }
            }
        }

    }
  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Java使用POI可以将Word文档转换PDF格式。具体步骤如下: 1. 导入POI库 在Java项目中,需要导入POI库,以便使用POI提供的API。 2. 读取Word文档 使用POI的XWPFDocument类可以读取Word文档。代码如下: FileInputStream fis = new FileInputStream("test.docx"); XWPFDocument document = new XWPFDocument(fis); 3. 创建PDF文档 使用iText库可以创建PDF文档。代码如下: PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("test.pdf")); document.open(); 4. 将Word文档内容写入PDF文档 使用iText的PdfContentByte类可以将Word文档内容写入PDF文档。代码如下: PdfContentByte cb = writer.getDirectContent(); List<XWPFParagraph> paragraphs = document.getParagraphs(); for (XWPFParagraph paragraph : paragraphs) { List<XWPFRun> runs = paragraph.getRuns(); for (XWPFRun run : runs) { String text = run.getText(); cb.showText(text); } } 5. 关闭文档 最后需要关闭文档。代码如下: document.close(); 这样就可以将Word文档转换PDF格式了。 ### 回答2: Java是一种广泛使用的编程语言,而POIJava语言中的一个开源项目,主要用于读取、创建和编辑Microsoft Office格式的文档,包括Word、Excel、PowerPoint等。在Java中,我们可以使用POI来将一个Word文档转换PDF格式的文档。具体的做法如下: 1. 首先我们需要将POI的相关依赖加入到我们的项目中,通常可以使用Maven或Gradle来进行依赖管理。 2. 然后我们需要使用POI打开需要转换Word文档,可以使用XWPFDocument类来实现。 3. 接下来,我们需要使用Apache FOP来生成PDF文档。FOP是一个Apache开源项目,用于将XML和XSL-FO文档转换PDF、PostScript、PCL等格式的文档。在Java中,我们可以通过Maven或Gradle将其引入到项目中。 4. 然后,我们创建一个XSL-FO模板文件,它描述了我们需要生成的PDF文档的样式和内容。我们可以使用Apache FOP提供的示例文件作为起点。这个过程需要一定的XSLT编程知识。 5. 接下来,我们使用Java代码来生成PDF文档。我们可以使用Apache FOP提供的API来生成PDF文档。我们需要将XWPFDocument对象转换成XSL-FO对象,再使用Apache FOP将XSL-FO对象转换PDF文档。这个过程需要一定的Java编程知识。 6. 最后,我们将生成的PDF文档保存到本地或上传到云端。我们可以使用Java提供的IO类来实现这个过程。 总之,通过使用POI和Apache FOP,我们可以轻松地将Word文档转换PDF格式的文档。这对于一些需要在Web应用中生成和展示PDF文档的业务场景非常有用。 ### 回答3: Java使用POI可以很方便地将Word文档转换PDF文件。POI是一个开源的Java API,它支持用Java操作Microsoft Office文档,包括Word、Excel、PowerPoint等。 POI的操作非常简单,主要的操作步骤如下: 1. 加载Word文档 首先需要使用POI加载Word文档。可以使用HWPFDocument类加载.doc格式的Word文档,或者使用XWPFDocument类加载.docx格式的Word文档。 2. 设置PDF输出路径和文件名 在将Word文档转换PDF文件时,需要指定输出路径和文件名。可以使用Java的File类来设置输出路径和文件名。 3. 将Word文档写入PDF文件 使用iText库将Word文档写入PDF文件。可以使用iText的PdfWriter类打开PDF文件并写入Word文档。 4. 保存PDF文件 最后,在将Word文档写入PDF文件后,需要使用Java的FileOutputStream类来将PDF文件保存到指定的输出路径和文件名。 上述步骤的代码实现可以参考以下示例: ```java import java.io.*; import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFDocument; import com.itextpdf.text.Document; import com.itextpdf.text.pdf.PdfWriter; public class WordToPDF { public static void convertToPDF(File wordFile, File pdfFile) { try { InputStream in = new FileInputStream(wordFile); OutputStream out = new FileOutputStream(pdfFile); if (wordFile.getName().endsWith(".doc")) { HWPFDocument doc = new HWPFDocument(in); Document pdfDoc = new Document(); PdfWriter.getInstance(pdfDoc, out); pdfDoc.open(); pdfDoc.addAuthor(doc.getSummaryInformation().getAuthor()); pdfDoc.addCreator(doc.getSummaryInformation().getAuthor()); pdfDoc.addTitle(doc.getSummaryInformation().getTitle()); pdfDoc.addSubject(doc.getSummaryInformation().getSubject()); pdfDoc.addKeywords(doc.getSummaryInformation().getKeywords()); pdfDoc.addCreationDate(); pdfDoc.addProducer(); pdfDoc.newPage(); pdfDoc.close(); } else if (wordFile.getName().endsWith(".docx")) { XWPFDocument doc = new XWPFDocument(in); PdfWriter.getInstance(new Document(), out); new PDFWordConverter().convert(doc, out); } in.close(); out.close(); System.out.println("转换成功!"); } catch (Exception e) { System.out.println("转换失败: " + e.getMessage()); } } public static void main(String[] args) { File wordFile = new File("word.doc"); File pdfFile = new File("pdf.pdf"); convertToPDF(wordFile, pdfFile); } } ``` 上述示例可以将.doc或.docx格式的Word文档转换PDF文件,并输出转换结果。有了POI和iText库,Java可以很方便地实现Word文档转换PDF文件的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值