基于FreeMarker+aspose的Word模板制作及打印

目录

一、前言介绍:

二、word制作

2.1 基本格式

2.2 图表的相关制作

三、ftl编辑

3.1 普通文本渲染

3.2 普通list数据渲染

3.3 横向list数据渲染

3.4 图片插入

3.5图表ftl制作

3.6 循环情况下图表编辑

3.7 freemaker相关语法说明

四、相关工具类

4.1 WordToPdfUtils

4.2 ExcelPdfUtil

4.3 工具类简单调用

五、实际显示效果

5.1.图例渲染效果

5.2 其它渲染效果显现


一、前言介绍:

        最近的项目要求前端自定义制作模板及打印,因技术能力及时间成本有限,模板相关数据由后端写死,无法动态修改。前端效果是由后端传递数据及相关样式,动态渲染成一个类似word的效果展示(也可以前端写死),打印及下载功能则由FreeMarker 制作成模板后,通过aspose转换成pdf前端预览打印,虽难FreeMarker维护难度大,但是可实现效果更多,而转pdf试过几个组件,都有不同程度的样式失效,故使用了这一套方案

-- 相关jar

      <!--导出word-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>

        <!--word转pdf -->
        <dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-words</artifactId>
            <version>19.5</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/lib/aspose-words-19.5-jdk.jar</systemPath>
        </dependency>
        <!-- excel转pdf -->
        <dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-cells</artifactId>
            <version>18.8</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/lib/aspose-cells-18.8.jar</systemPath>
        </dependency>

-- aspose相关jar下载 百度网盘  ,提取码:s53u

二、word制作

2.1 基本格式

        按照实际需求制作好word,然后填充好相关占位符,建议只写一个1占位,便于修改。这里列举了可能会用到的6种常用数据渲染,注:如果图表/图片在表格内,需要修改布局选项-改为文字环绕,否则pdf打印会丢失。

         多选框及其他特殊字符可以通过 插入--符号 进行选择

2.2 图表的相关制作

通过插入-->图表-->选择差不多的图表示例,然后修改样式,标记及线条(标记可以插入自定义图片),修改数据,调整成相差不多的效果,点击图表线条出现右边图标可以修改样式及数据等。

注:1.选中纵坐标后右键可以固定坐标轴刻度。2.隐藏单元格处可以使图表非相邻的两个点连接起来。3.如果有数据,可以选择图标后通过右侧标记选项隐藏图标,模仿无数据,效果见下图的黑线。4.标记可以插入自定义图片,选择内置标记为正方形然后边框设置无,插入图片的大小跟随tu'biao

三、ftl编辑

        编辑好的word然后再通过文件-另存为--选择Word XML(*.xml),然后修改后缀为.ftl,然后通过编辑器进行修改,变量格式为${name},因为转成ftl后会可能拆分成多个字符,需要手动修改,编辑好的xml直接修改后缀为ftl,然后通过编辑器格式化,找到 <w:body> 的地方往下这是word正式内容,然后找到 ${name}的地方把生成的多余的标签删除

注:1.修改前的ftl最好备份一个,方便后期修改/维护。2.选择的.xml选项不建议选择2003版的,虽难它更简洁。3.如果需要同时显示多个相同的word(批量打印等),可以把内容体用一个list包含起来

3.1 普通文本渲染

3.2 普通list数据渲染

3.3 横向list数据渲染

找到需要渲染的单元格,单独渲染单元格,方法和普通list基本一致

3.4 图片插入

      图片则需要先在图片使用处找到对应的标识embed="rId*"  word/_rels/document.xml.rels 标签处找到对应的 图片具体的标签 /media/image**.png,替换内容体为对应的Base64字符串即可

图片引用处

图片存放地点

3.5图表ftl制作

        单个图表会比较简单,只需要找到 word/charts/chart**.xml 标签里面的信息,找到相应的数据进行修改,对应标签分别为<c:cat> 和<c:val>。

        如果图表隐藏了横坐标,制作的PDF内的图表会拉伸(变形),此时在加入一列隐藏数据即可,即统计数据的第一列数据默认全为0,并隐藏图标

         然后替换内置的excel表格数据。注:1.要把图表内的数据对应格式生成一个后缀为.xlsx的表格,并转换成base64的字符串。2.表格内数据渲染需要类型为数字。3.sheet 页名 为 Sheet1。 4.如果出现问题,可以把word内的表格保存一份,然后用自己生成的表格进行对比

3.6 循环情况下图表处理

        为保证生成的word可打开,需要把chart**.xml.rels 相关的除了静态资源(图片)外的其他数据全部遍历一遍。图表实际显示的地方,通过id进行关联

编辑图表对应的标签,在 /word/_rels/document.xml.rels 内,循环创建

图表相关标签的循环,搜索并修改 chart.xml 和 chart.xml.rels 名称 及chart.xml.rels内引用的名称

折线图有数据时不显示图标,用来保证分页的情况下上一页最后几项无数据,导致无法与下一页的图标连接起来

3.7 freemaker相关语法说明

循环 :<#list dataList as data></#list> 
是否最后一条数据:data_has_next 
当前list的下标:nibpd_index 
数据不存在则取!后面的数据:${data.name!}
if判断:<#if data.name?? &&  data.name=='1'><#else></#if> if判断
word分页符号: <w:br w:type="page"/> 
三元表达式:${(data_index>9)?string(data_index,'0'+data_index)}
特殊字符处理 ${data?html}
替换 ${data?replace("\n","aa")}
<>特殊字符处理 ${data?replace("<","&lt;")?replace(">","&gt;")}

四、相关工具类

4.1 WordToPdfUtils

请注意版本,文中使用不需校验,大多数情况下,需要校验license,这时候请把注释代码放开

import com.aspose.words.*;
import freemarker.template.*;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import sun.misc.BASE64Encoder;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * Word转PDF工具类
 * @author pw
 *
 */
@Component
public class WordToPdfUtils {
    /**
     *  word模板存放地址
     */
    private static String templatePath ="ftl";

    @Value("${template.path:ftl}")
    public void setTemplatePath(String templatePath) {
        String os = System.getProperty("os.name");
        //Linux操作系统
        if (os != null && os.toLowerCase().startsWith("linux")) {
            WordToPdfUtils.templatePath = templatePath;
        }
    }
//使用19.5版本不需要校验许可
    private static final Configuration CONFIGURATION;
//    private static boolean license;
    static {
        CONFIGURATION = new Configuration(Configuration.VERSION_2_3_30);
        CONFIGURATION.setDefaultEncoding("utf-8");
        CONFIGURATION.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_30));
//        try(InputStream is = ParamUtil.class.getClassLoader().getResourceAsStream("license.xml")) {
//            License aposeLic = new License();
//            aposeLic.setLicense(is);
//            license = true;
//        } catch (Exception e) {
//            e.printStackTrace();
//            license = false;
//        }
    }
    /**
     * 用freemaker模板生成word文档
     * @param dataMap 要填充的数据
     * @param templateName 模板名称
     * @param fileName 要输出的文件名称
     */
    public static void createWord(Map<String, Object> dataMap,String templateName, String fileName,HttpServletResponse response) throws IOException {
        setResponse(response,fileName+".doc");
        try (Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8))){
            CONFIGURATION.setDirectoryForTemplateLoading(new File(templatePath));
            Template template  = CONFIGURATION.getTemplate(templateName,"UTF-8");
            //创建一个Word文档输出流
            template.process(dataMap, out);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 用freemaker模板生成word文档
     * @param dataMap 要填充的数据
     * @param templateName 模板名称
     * @param fileName 要输出的文件名称
     */
    public static void createPdf(Map<String, Object> dataMap,String templateName, String fileName,HttpServletResponse response) throws IOException {
        ///临时路径
        String wordPath =templatePath+"/short/"+System.currentTimeMillis();
        setResponse(response,fileName+".pdf");
        File f = new File(wordPath+"/"+fileName+".doc");
        //判断文件夹是否存在,不存在,则新建一个文件夹
        if (!f.getParentFile().exists()) {
            f.getParentFile().mkdirs();
        }
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8))){
            CONFIGURATION.setDirectoryForTemplateLoading(new File(templatePath));
            Template template  = CONFIGURATION.getTemplate(templateName,"UTF-8");
            //创建一个Word文档输出流
            template.process(dataMap, out);
            //word文档转PDF
            ServletOutputStream outputStream = response.getOutputStream();
            FileInputStream fio = new FileInputStream(f);
            wordOrPdf(fio, outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            deleteFolder(f.getParentFile());
        }
    }

    private static void deleteFolder(File folder) {
        if (!folder.exists()) {
            return;
        }
        File[] files = folder.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    //递归直到目录下没有文件
                    deleteFolder(file);
                } else {
                    //删除
                    file.delete();
                }
            }
        }
        //删除
        folder.delete();
    }

    /**
     * 合并多个word文件
     */
    public void twoDocToOneDoc(List<String> files, String newFile) throws Exception {
        Document doc3 = new Document();
        for(String s:files){
            doc3.appendDocument( new Document(s), ImportFormatMode.USE_DESTINATION_STYLES );
        }
        doc3.save(newFile);
    }

    /**
     * 使用aspose.word把word文档转为pdf文档
     */
    public static void wordOrPdf(InputStream in, OutputStream os) throws Exception {
        try {
            Document doc = new Document(in);
            doc.save(os, SaveFormat.PDF);
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("生成PDF文档失败!");
        }finally {
            os.flush();
            os.close();
            in.close();
        }
    }

    /**
     * pdf预览、下载
     */
    public static void setResponse(HttpServletResponse response,String fileName) throws UnsupportedEncodingException {
        response.setContentType("application/octet-stream; charset=UTF-8");
        response.setCharacterEncoding("utf-8");
        //Content-Disposition属性名 (attachment表示以附件的方式下载;inline表示在页面内打开)
        response.setHeader("content-disposition", "attachment; filename="+ URLEncoder.encode(fileName, "UTF-8"));
    }


    /**
     * 获取网络图片
     */
    public static String getImageFromNetByUrl(String strUrl) {
        InputStream inStream =null;
        try {
            URL url = new URL(strUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5 * 1000);
            inStream = conn.getInputStream();// 通过输入流获取图片数据
            return readInputStream(inStream);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(null!=inStream){
                try {
                    inStream.close();
                }catch (Exception e1){
                    e1.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * 从输入流中获取数据
     * @param inputStream
     *            输入流
     * @return
     */
    public static String readInputStream(InputStream inputStream){
        try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()){
            byte[] buffer = new byte[10240];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outStream.write(buffer, 0, len);
            }
            outStream.close();
            return Base64.encodeBase64String(outStream.toByteArray());
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 图片转码工具类
     * @param imgFile 图片地址
     */
    public static String getImgFileToBase64(String imgFile){
        byte[] buffer = null;
        try(InputStream inputStream = new FileInputStream(imgFile)) {
           return readInputStream(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 流转base64
     */
    public static String getByteToBase64(byte[] buffer) {
        return Base64.encodeBase64String(buffer);
    }
}

4.2 ExcelPdfUtil

excel转pdf使用比较少,可供参考

import com.alibaba.nacos.client.utils.ParamUtil;
import com.aspose.cells.License;
import com.aspose.cells.PdfSaveOptions;
import com.aspose.cells.Workbook;
import com.aspose.cells.Worksheet;
import org.springframework.beans.factory.annotation.Value;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;


/**
 * @author Administrator
 */
public class ExcelPdfUtil {

    /**
     *  word模板存放地址
     */
    private static String templatePath ="ftl";

    @Value("${template.path:ftl}")
    public void setTemplatePath(String templatePath) {
        //Linux操作系统
        if (IS_LINUX) {
            ExcelPdfUtil.templatePath = templatePath;
        }
    }

    private static boolean license;
    private static final Boolean IS_LINUX;

    static {
        String os = System.getProperty("os.name");
        IS_LINUX = os!=null && os.toLowerCase().startsWith("linux");
        try {
            InputStream is = ParamUtil.class.getClassLoader().getResourceAsStream("license-18.8.xml");
            License aposeLic = new License();
            aposeLic.setLicense(is);
            license = true;
        } catch (Exception e) {
            license = false;
            e.printStackTrace();
        }
    }

    /**
     * pdf预览、下载
     */
    public static void setResponse(HttpServletResponse response,String fileName) throws UnsupportedEncodingException {
        response.setContentType("application/octet-stream; charset=UTF-8");
        response.setCharacterEncoding("utf-8");
        //content-disposition属性名 (attachment表示以附件的方式下载;inline表示在页面内打开)
        response.setHeader("Content-Disposition", "attachment; filename*=utf-8'zh_cn'"+URLEncoder.encode(fileName+".pdf", "UTF-8"));
    }



    /**
     * 将excel转为pdf
     * @param in 需要转换的word
     * @param os 保存pdf文件
     */
    public static void excel2pdf(InputStream in, OutputStream os) {
        if (!license) {
            return;
        }
        try {
            Workbook wb = new Workbook(in); // Address是将要被转化的excel文档
            PdfSaveOptions pdfSaveOptions = new PdfSaveOptions();
            pdfSaveOptions.setOnePagePerSheet(true);
            int sheetCount = wb.getWorksheets().getCount();
            for (int i = 0; i < sheetCount; i++) {
                Worksheet worksheet = wb.getWorksheets().get(i);
                //单元格自动高度
                worksheet.autoFitRows();
                //自动拉伸比例
                worksheet.getHorizontalPageBreaks().clear();
                worksheet.getVerticalPageBreaks().clear();
            }
            wb.save(os, pdfSaveOptions);
            os.flush();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.3 工具类简单调用

 @Override
    public void test(HttpServletResponse response) throws IOException {
        Map<String, Object> dataMap = new HashMap<>(8);
        dataMap.put("name","测试");
        dataMap.put("isSelect","0");
        dataMap.put("isSelect2","1");
        //普通list
        List<Map<String,String>> list1 = new ArrayList<>();
        Map<String,String> map = new HashMap<>();
        map.put("id","1");
        map.put("name","张三");
        map.put("sex","男");
        map.put("post","测试");
        list1.add(map);
        Map<String,String> map2 = new HashMap<>();
        map2.put("id","2");
        map2.put("name","李四");
        map2.put("sex","女");
        map2.put("post","主管");
        list1.add(map2);
        dataMap.put("list1",list1);
        //横向数据
        Map<String,List<String>> list2 = new HashMap<>();
        list2.put("name", Arrays.asList("张三","李四","王五"));
        list2.put("sex", Arrays.asList("男","女","--"));
        list2.put("post", Arrays.asList("测试","主管","运维"));
        dataMap.put("list2",list2);
        //图片
        dataMap.put("img",WordToPdfUtils.getImgFileToBase64("F:\\1668418379310.jpg"));
        //图表数据组装
        String[] headMap = {"","血压1","血压2","体温"};
        String[] dataStrMap = {"nibps","nibpd","animalHeat"};
        List<String> time= Arrays.asList("05","06","07","08","09","10","11");
        Map<String, List<String>> chartDate = new HashMap<>();
        chartDate.put("nibps",Arrays.asList("83","68","72","80","72","88","95"));
        chartDate.put("nibpd",Arrays.asList("54","35","43","56","60","81","61"));
        chartDate.put("animalHeat",Arrays.asList("38","34","36","40","39","35","36"));
        //生成excel
        dataMap.put("charExcel", WordToPdfUtils.getWordExcel(headMap, dataStrMap,time, chartDate));
        //图表数据
        dataMap.put("chart", chartDate);
        dataMap.put("timeList", time);

        WordToPdfUtils.createWord(dataMap,"测试.ftl",String.valueOf(System.currentTimeMillis()),response);
    }

五、实际显示效果

5.1.图例渲染效果

5.2 其它渲染效果显现

注:展示的HTML为前端动态渲染(因能力和时间有限数据,样式需要手动在数据库录入/修改,维护难度较大),pdf为FreeMarker模板渲染,如需与打印效果一致/或需前端直接打印网页,需要比较详细的配置每个单元格的样式及打印样式,使用的单位最好使用cm

左边为html渲染,右边为生成的pdf

普通图表word 效果:

 层叠图表word 效果:

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用Freemarker和POI结合生成带水印的Word文档。下面是一个简单的示例: 1. 首先,需要准备一个模板Word文档,其中可以包含一些占位符,例如`${title}`和`${content}`,表示需要根据数据填充的部分。 2. 使用POI读取模板Word文档,并将其转换为`XWPFDocument`对象。 3. 使用Freemarker生成需要填充的数据,并将其保存在一个Map中。 4. 使用Freemarker将数据填充到模板中,生成一个新的Word文档。 5. 在新生成的Word文档中添加水印。 下面是示例代码: ```java // 读取模板Word文档 XWPFDocument doc = new XWPFDocument(new FileInputStream("template.docx")); // 准备数据 Map<String, Object> dataMap = new HashMap<>(); dataMap.put("title", "这是标题"); dataMap.put("content", "这是内容"); // 使用Freemarker填充数据 Configuration configuration = new Configuration(Configuration.VERSION_2_3_30); configuration.setDefaultEncoding("UTF-8"); Template template = configuration.getTemplate("template.ftl"); StringWriter writer = new StringWriter(); template.process(dataMap, writer); // 将填充后的内容写入新的Word文档 XWPFDocument newDoc = new XWPFDocument(new ByteArrayInputStream(writer.toString().getBytes())); FileOutputStream out = new FileOutputStream("output.docx"); newDoc.write(out); out.close(); // 添加水印 XWPFParagraph waterMarkParagraph = doc.createParagraph(); waterMarkParagraph.createRun().setText("水印内容"); waterMarkParagraph.setSpacingAfter(0); waterMarkParagraph.setSpacingBefore(0); waterMarkParagraph.setAlignment(ParagraphAlignment.CENTER); waterMarkParagraph.setVerticalAlignment(TextAlignment.CENTER); CTTcPr tcPr = waterMarkParagraph.getCTP().addNewR().addNewRPr().addNewTcPr(); tcPr.addNewTcW().setW(BigInteger.valueOf(10000)); tcPr.addNewVAlign().setVal(STVerticalJc.CENTER); waterMarkParagraph.getCTP().addNewPPr().addNewJc().setVal(STJc.CENTER); for (XWPFParagraph paragraph : newDoc.getParagraphs()) { for (XWPFRun run : paragraph.getRuns()) { run.getCTR().addNewRPr().addNewNoProof(); } } newDoc.write(out); out.close(); ``` 注意,这只是一个简单的示例,实际上需要根据具体需求进行修改和完善。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值