java aspose word 模板根据数据导出pdf
支持功能:
1、字符串占位符替换。
2、占位符循环替换。
3、图片替换,包含书签替换,自定义占位符替换。
4、基础图标,折现、饼图、柱状图。
本案例运行环境:
1、aspose word21.1版本。
2、jdk 18。
话不多说直接上代码。

<!-- 图表相关 --> <dependency> <groupId>jfree</groupId> <artifactId>jfreechart</artifactId> <version>1.0.13</version> </dependency> <!-- 文档处理 --> <dependency> <groupId>com.aspose</groupId> <artifactId>aspose-words</artifactId> <version>21.1</version> <scope>system</scope> <systemPath>${project.basedir}/src/main/resources/lib/aspose-words-21.1.jar</systemPath> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency>

package com.example.demo; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.StandardChartTheme; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; import org.jfree.chart.labels.StandardPieSectionLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PiePlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.category.BarRenderer; import org.jfree.chart.renderer.category.LineAndShapeRenderer; import org.jfree.chart.renderer.category.StandardBarPainter; import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.data.general.DefaultPieDataset; import java.awt.*; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.text.DecimalFormat; import java.text.NumberFormat; /** *@author pengbenlei *@date 2024/12/12 11:37 *@desc */ public class ChartUtil { private static final Color[] BAR_COLORS = new Color[]{ new Color(79, 129, 189), new Color(192, 80, 77), new Color(155, 187, 89), }; private static final Color[] LINE_COLORS = new Color[]{ new Color(237, 123, 46), // 橙色 new Color(90, 154, 213), // 蓝色 new Color(164, 164, 164), // 灰色 new Color(255, 87, 34), // 红色(鲜艳的橙红色) new Color(76, 175, 80), // 绿色(鲜艳的绿色) new Color(33, 150, 243), // 亮蓝色 new Color(156, 39, 176), // 紫色 new Color(255, 193, 7), // 黄色 new Color(0, 188, 212), // 青色 new Color(233, 30, 99), // 粉色 new Color(0, 150, 136), // 深青色 new Color(255, 152, 0), // 橙色(亮橙色) }; private static final Shape[] SHAPES = new Shape[]{ new Ellipse2D.Double(-5, -5, 10, 10), // 圆形 new Rectangle2D.Double(-5, -5, 10, 10), // 方形 new Polygon(new int[]{0, 10, 0, -10}, new int[]{-10, 0, 10, 0}, 4), // 菱形 new Polygon(new int[]{0, 10, -10}, new int[]{10, -10, -10}, 3), // 三角形 new Polygon(new int[]{0, 10, -10, 0}, new int[]{0, 10, 10, 0}, 4) // 十字形 }; private static final Color[] PIE_COLORS = new Color[]{ new Color(75, 172, 198), new Color(128, 100, 162), new Color(155, 187, 89), new Color(192, 80, 77), new Color(79, 129, 189), new Color(44, 77, 117), new Color(247, 150, 70), new Color(165, 165, 165), }; private static StandardChartTheme initChartTheme() { StandardChartTheme currentTheme = new StandardChartTheme("JFree"); // 横轴纵轴标题文字大小 currentTheme.setLargeFont(new java.awt.Font("宋体", java.awt.Font.BOLD, 15)); // 横轴纵轴数值文字大小 currentTheme.setRegularFont(new java.awt.Font("宋体", java.awt.Font.PLAIN, 13)); currentTheme.setExtraLargeFont(new java.awt.Font("宋体", java.awt.Font.BOLD, 20)); // 背景颜色 currentTheme.setPlotBackgroundPaint(new Color(255, 255, 204, 0)); // 边框线条 currentTheme.setPlotOutlinePaint(new Color(0, 0, 0, 0)); // 网格线条 currentTheme.setRangeGridlinePaint(new Color(78, 74, 74)); return currentTheme; } /** * 线图 * * @param title 标题 * @param categoryAxisLabel 分类标签 * @param valueAxisLabel 数值标签 * @param dataset 数据集 * @return org.jfree.chart.JFreeChart * @author Hou_fx * @date 2021.8.4 10:39 */ public static JFreeChart lineChart(String title, String categoryAxisLabel, String valueAxisLabel, DefaultCategoryDataset dataset) { ChartFactory.setChartTheme(initChartTheme()); JFreeChart chart = ChartFactory.createLineChart( title, categoryAxisLabel, valueAxisLabel, dataset, PlotOrientation.VERTICAL, true, true, false ); CategoryPlot plot = chart.getCategoryPlot(); LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer(); // 折现点显示数值 renderer.setBaseItemLabelsVisible(true); renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator()); // 更改线条颜色 for (int i = 0; i < dataset.getRowKeys().size(); i++) { renderer.setSeriesPaint(i, LINE_COLORS[i]); renderer.setSeriesShapesVisible(i, true); renderer.setSeriesShape(i, SHAPES[i]); } // 获取Y轴(NumberAxis) NumberAxis yAxis = (NumberAxis) plot.getRangeAxis(); // 设置Y轴刻度间隔,例如:每隔2显示一个刻度 yAxis.setTickUnit(new org.jfree.chart.axis.NumberTickUnit(50)); return chart; } /** * 柱状图 * * @param title * @param categoryAxisLabel * @param valueAxisLabel * @param dataset 数据集 * @return org.jfree.chart.JFreeChart * @author Hou_fx * @date 2021.8.4 14:03 */ public static JFreeChart barChart(String title, String categoryAxisLabel, String valueAxisLabel, DefaultCategoryDataset dataset) { ChartFactory.setChartTheme(initChartTheme()); JFreeChart chart = ChartFactory.createBarChart( title, categoryAxisLabel, valueAxisLabel, dataset, PlotOrientation.VERTICAL, true, true, false ); CategoryPlot plot = chart.getCategoryPlot(); BarRenderer renderer = (BarRenderer) plot.getRenderer(); // 纯色显示 renderer.setBarPainter(new StandardBarPainter()); // 柱子上显示小数字 renderer.setBaseItemLabelsVisible(true); renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator()); // 设置柱子间隔 renderer.setItemMargin(0.0); // 设置柱子颜色 for (int i = 0; i < dataset.getRowKeys().size(); i++) { renderer.setSeriesPaint(i, BAR_COLORS[i]); } return chart; } /** * 饼图 * * @param title * @param dataset * @return org.jfree.chart.JFreeChart * @author Hou_fx * @date 2021.8.4 14:04 */ public static JFreeChart pieChart(String title, DefaultPieDataset dataset) { ChartFactory.setChartTheme(initChartTheme()); JFreeChart chart = ChartFactory.createPieChart( title, dataset, true, true, false ); PiePlot plot = (PiePlot) chart.getPlot(); // 设置扇区颜色 for (int i = 0; i < dataset.getKeys().size(); i++) { plot.setSectionPaint(dataset.getKey(i), PIE_COLORS[i]); } // 设置扇区的线条颜色 plot.setBaseSectionOutlinePaint(new Color(255, 255, 255)); // 设置扇区的线条大小 plot.setBaseSectionOutlineStroke(new BasicStroke(3)); // 设置标签颜色 plot.setLabelLinkPaint(new Color(255, 255, 255, 0)); // 设置标签背景色 plot.setLabelBackgroundPaint(new Color(255, 255, 255, 0)); // 设置标签线条颜色 plot.setLabelOutlinePaint(new Color(255, 255, 255, 0)); // 设置标签阴影颜色 plot.setLabelShadowPaint(new Color(255, 255, 255, 0)); // 设置饼图阴影颜色 plot.setShadowPaint(new Color(255, 255, 255, 0)); // 添加标签数字百分比显示 plot.setLabelGenerator(new StandardPieSectionLabelGenerator(("{0}{2}"), NumberFormat.getNumberInstance(), new DecimalFormat("0.00%"))); return chart; } }

package com.example.demo; import com.aspose.words.*; import org.jfree.chart.ChartUtilities; import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.data.general.DefaultPieDataset; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** *@author pengbenlei *@date 2024/12/12 15:07 *@desc */ public class AsposeUtils { /** * 根据word模板生成pdf * @param template 模板路径 * @param outPath 输出目录 * @param dataJson 业务数据json字符串 * @param sourceName 业务数据源名称 * @param wordImages 图片集合 * @throws Exception 异常信息 */ public static void getPdfByWordTemplate(String template, String outPath, String dataJson, String sourceName, List<WordImg> wordImages) throws Exception { // 验证 Assert.isTrue(StringUtils.hasLength(template), "模板文件路径不能为空!"); Assert.isTrue(StringUtils.hasLength(sourceName), "模板数据源名称!"); Assert.isTrue(StringUtils.hasLength(outPath), "pdf输出路径不能为空!"); // 将输入流转为doc // URL templateUrl = new URL(template); Document doc = new Document(template); // doc构建 DocumentBuilder builder = new DocumentBuilder(doc); // 填充数据 JsonDataLoadOptions opt = new JsonDataLoadOptions(); JsonDataSource jsonDataSource = new JsonDataSource(new ByteArrayInputStream(dataJson.getBytes()), opt); final ReportingEngine engine = new ReportingEngine(); engine.buildReport(doc, jsonDataSource, sourceName); if (!CollectionUtils.isEmpty(wordImages)) { // 图片填充不为空 // 筛查出书签占位和动态数据占位 List<WordImg> bookImages = wordImages.stream().filter(p -> p.getMergeType() == 1).collect(Collectors.toList()); bookImageInsert(bookImages, builder); List<WordImg> dynamicImages = wordImages.stream().filter(p -> p.getMergeType() == 2).collect(Collectors.toList()); dynamicImageInsert(dynamicImages, doc, builder); } // 转pdf doc.save(outPath, SaveFormat.PDF); } private static void dynamicImageInsert(List<WordImg> wordImages, Document doc, DocumentBuilder builder) throws Exception { NodeCollection paragraphs = doc.getChildNodes(NodeType.PARAGRAPH, true); for (Object node : paragraphs) { Paragraph para = (Paragraph) node; String oldPlaceholder = para.toString(SaveFormat.TEXT); String newPlaceholder = oldPlaceholder .replaceAll("[\n\r]", "") // 替换换行符 .replaceAll("[{}]", ""); // 替换大括号 if (para.toString(SaveFormat.TEXT).contains("image_placeholder")) { for (Run run : para.getRuns()) { if (run.getText().contains("image_placeholder")) { // image_placeholder_key 是自定义的图片占位符,最后的key是数据的唯一值 String[] keywords = newPlaceholder.split("_"); if (keywords.length != 3) { continue; } String key = keywords[2]; WordImg wordImg = wordImages.stream().filter(p -> p.getDynamicKey().equals(key)).findFirst().orElse(null); if (wordImg == null) { continue; } // 移动光标 builder.moveTo(run); insertImage(wordImg, builder); } // 删除原占位符文本 run.setText(""); } } } } /** * 填充书签图片 * @param bookImages * @param builder * @throws Exception */ private static void bookImageInsert(List<WordImg> bookImages, DocumentBuilder builder) throws Exception { // 填充图片 for (WordImg wordImage : bookImages) { if (!StringUtils.hasLength(wordImage.getBookmark())) { continue; } // 定位书签位置 builder.moveToBookmark(wordImage.getBookmark()); insertImage(wordImage, builder); } } /** * 插入图片 * @param wordImage * @param builder * @throws Exception */ private static void insertImage(WordImg wordImage, DocumentBuilder builder) throws Exception { if (wordImage.getType() == 1 || wordImage.getType() == 3) { // 本地图片和网络url图片 builder.insertImage(wordImage.getPath()); } else if (wordImage.getType() == 2) { // 字节码图片 builder.insertImage(wordImage.getImageBytes()); } } public static void main(String[] args) throws Exception { final String filePath = "C:\\Users\\Administrator\\Desktop\\上传整理\\新建文件夹\\tem1.docx"; final String outPath = "C:\\Users\\Administrator\\Desktop\\上传整理\\新建文件夹\\output.pdf"; String a = "注意事项\n" + "胸部DR正位片(不含胶片)\n" + "检查位置\n" + "抽血的项目 (二楼大厅 检验科)\n" + "心电图(二楼西医诊疗区左手最后一间超声办公室)\n" + "胸部X光片(一楼电梯旁 )\n" + "血压(开单科室)\n" + "超声(二楼西医诊疗区左手最后一间超声办公室)"; String jsonStr = "{\n" + " \"age\": 20,\n" + " \"birth\": \"1999-01-55\",\n" + " \"serialNumber\": \"12349864\",\n" + " \"cardNumber\": \"56456461645\",\n" + " \"items\": [\n" + " {\n" + " \"checked\": false,\n" + " \"itemName\": \"血常规\"\n" + " },\n" + " {\n" + " \"checked\": false,\n" + " \"itemName\": \"生化检验\"\n" + " },\n" + " {\n" + " \"checked\": true,\n" + " \"itemName\": \"尿常规\"\n" + " }\n" + " ],\n" + " \"name\": \"张三三\",\n" + " \"phone\": \"15445454454\",\n" + " \"sex\": \"男\",\n" + " \"hospitalName\": \"成都市温江区金马街道社区卫生服务中心\",\n" + " \"checkDate\": \"\\u200B2024-12-13\",\n" + " \"printTime\": \"\\u200B2024-12-13 14:50:22\",\n" + " \"doctorName\": \"李医生\",\n" + " \"des\": \"" + a + "\",\n" + " \"packName\": \"套餐1\"\n" + "}"; DefaultCategoryDataset lineDataset = new DefaultCategoryDataset(); lineDataset.addValue(150, "右侧高压", "2002"); lineDataset.addValue(140, "右侧高压", "2003"); lineDataset.addValue(150, "右侧高压", "2004"); lineDataset.addValue(95, "右侧低压", "2002"); lineDataset.addValue(95, "右侧低压", "2003"); lineDataset.addValue(95, "右侧低压", "2004"); lineDataset.addValue(120, "左侧高压", "2002"); lineDataset.addValue(130, "左侧高压", "2003"); lineDataset.addValue(160, "左侧高压", "2004"); lineDataset.addValue(90, "左侧低压", "2002"); lineDataset.addValue(88, "左侧低压", "2003"); lineDataset.addValue(95, "左侧低压", "2004"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ChartUtilities.writeChartAsJPEG(bos, ChartUtil.lineChart("测试折线图", "A", "B", lineDataset), 500, 500); // 折线图 // ChartUtilities.writeChartAsJPEG(bos, ChartUtil.lineChart("测试折线图", "A", "B", lineDataset), // (int) signRect.getWidth(), (int) signRect.getHeight()); // 饼图 // DefaultPieDataset defaultPieDataset = new DefaultPieDataset(); // defaultPieDataset.setValue("Category A", 40); // 类别 A 占 40% // defaultPieDataset.setValue("Category B", 30); // 类别 B 占 30% // defaultPieDataset.setValue("Category C", 20); // 类别 C 占 20% // defaultPieDataset.setValue("Category D", 10); // 类别 D 占 10% // ChartUtilities.writeChartAsJPEG(bos, ChartUtil.pieChart("测试饼图", defaultPieDataset), // 200, 200); // ChartUtilities.writeChartAsJPEG(bos, ChartUtil.barChart("测试柱状图", "A", "B", lineDataset), // 200, 200); List<WordImg> wordImages = new ArrayList<>(); byte[] imageBytes = bos.toByteArray(); WordImg wordImg = WordImg.builder() .bookmark("line") .type(2) .imageBytes(imageBytes) .build(); wordImages.add(wordImg); // 二维码 WordImg wordImg1 = WordImg.builder() .bookmark("qrCode") .type(2) .imageBytes(ZxingUtil.generateQRCode("12349864", 100, 100)) .build(); wordImages.add(wordImg1); WordImg wordImg2 = WordImg.builder() .bookmark("name") .type(2) .imageBytes(ZxingUtil.generateQRCode("张三", 100, 100)) .build(); wordImages.add(wordImg2); WordImg wordImg3 = WordImg.builder() .bookmark("sex") .type(2) .imageBytes(ZxingUtil.generateQRCode("男", 100, 100)) .build(); wordImages.add(wordImg3); WordImg wordImg4 = WordImg.builder() .bookmark("age") .type(2) .imageBytes(ZxingUtil.generateQRCode("20", 100, 100)) .build(); wordImages.add(wordImg4); WordImg wordImg5 = WordImg.builder() .bookmark("address") .type(2) .imageBytes(ZxingUtil.generateQRCode("中国", 100, 100)) .build(); wordImages.add(wordImg5); getPdfByWordTemplate(filePath, outPath, jsonStr, "data", wordImages); } }

package com.example.demo; import lombok.Builder; import lombok.Data; /** *@author pengbenlei *@date 2024/12/13 9:37 *@desc Aspose图片数据 */ @Data @Builder public class WordImg { /** * 占位模式 1-书签占位 2-动态数据占位 */ private int mergeType; /** * 动态占位key */ private String dynamicKey; /** * 占位书签名 */ private String bookmark; /** * 图片处理类型 1-本地图片 2-imageBytes 3-网络url图片 */ private int type; /** * 图片路径 */ private String path; /** * 图片字节数组 */ private byte[] imageBytes; }

void test1() throws Exception { String dataStr="{\n" + " \"hospitalName\": \"体检结果\",\n" + " \"checkItems\": [\n" + " {\n" + " \"itemName\": \"血常规\",\n" + " \"itemType\": 1,\n" + " \"itemResult\": [\n" + " {\n" + " \"itemName\": \"血清谷丙转氨酶(ALT)\",\n" + " \"result\": \"44.0\",\n" + " \"unit\": \"U/L\",\n" + " \"ckfw\": \"0-42\",\n" + " \"tip\": \"↑\",\n" + " \"resultId\":\"1\"\n" + " },\n" + " {\n" + " \"itemName\": \"血清谷草转氨酶(AST )\",\n" + " \"result\": \"39.0\",\n" + " \"unit\": \"U/L\",\n" + " \"ckfw\": \"0-42\",\n" + " \"tip\": \"\",\n" + " \"resultId\":\"2\"\n" + " },\n" + " {\n" + " \"itemName\": \"总胆红素(T-BIL )\",\n" + " \"result\": \"11.8\",\n" + " \"unit\": \"μmol/L\",\n" + " \"ckfw\": \"3.5-22\",\n" + " \"tip\": \"\"\n" + " },\n" + " {\n" + " \"itemName\": \"血尿素(REA )\",\n" + " \"result\": \"9.89\",\n" + " \"unit\": \"mmol/L\",\n" + " \"ckfw\": \"1.7-8.3\",\n" + " \"tip\": \"\",\n" + " \"resultId\":\"3\"\n" + " }\n" + " ]\n" + " },\n" + " {\n" + " \"itemName\": \"超声腹部\",\n" + " \"itemType\": 2,\n" + " \"itemResult\": [\n" + " {\n" + " \"result\": \"https://cloud.leenleda.com:3005/jinma/examination/checkFile/2412031010431774/20241203105828723.jpg\",\n" + " \"resultId\":\"4\"\n" + " }\n" + " ]\n" + " }\n" + " ]\n" + "}"; final String filePath = "C:\\Users\\Administrator\\Desktop\\上传整理\\新建文件夹\\checkresult.docx"; final String outPath = "C:\\Users\\Administrator\\Desktop\\上传整理\\新建文件夹\\output.pdf"; List<WordImg> wordImages = new ArrayList<>(); // 超声的图片 WordImg wordImg=WordImg.builder() .type(3) .path("https://img2.selfimg.com.cn/CNTgalleryLowerrightWatermark/2017/06/19/1497858683_cEh3WY.jpg") .dynamicKey("4") .mergeType(2) .build(); wordImages.add(wordImg); AsposeUtils.getPdfByWordTemplate(filePath, outPath, dataStr, "data", wordImages); }
以上就能实现大部分模板替换的业务功能,下面说下模板的设计。
1、一般使用table对word模板布局。
2、占位符和模板语法查阅官方文档,或者看我的截图。
3、图片使用书签占位。
本文来自博客园,作者:Rolay,转载请注明原文链接:https://www.cnblogs.com/rolayblog/p/18604401
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步