当前位置:   article > 正文

poi-tl导出word, 含表格单元格合并,表格单元格多图合并_poi-tl合并单元格

poi-tl合并单元格

poi-tl是干嘛的?

poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档,

支持:

        1.单系列图表指的是饼图(3D饼图)、圆环图等。

        2.多系列图表指的是条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、散点图等。

        3.组合图表指的是由多系列图表(柱形图、折线图、面积图)组合而成的图表。

等等...

官网地址:Poi-tl Documentation

展示


一、效果展示

二、模板

 三使用步骤

1.引包

该案例使用poi-tl版本: 1.12.0

  1. <dependency>
  2. <groupId>com.deepoove</groupId>
  3. <artifactId>poi-tl</artifactId>
  4. <version>1.12.0</version>
  5. </dependency>

该案例引入的包

  1. <dependencies>
  2. <dependency>
  3. <groupId>com.deepoove</groupId>
  4. <artifactId>poi-tl</artifactId>
  5. <version>1.12.0</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.projectlombok</groupId>
  9. <artifactId>lombok</artifactId>
  10. <version>1.18.24</version>
  11. </dependency>
  12. <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
  13. <dependency>
  14. <groupId>org.apache.logging.log4j</groupId>
  15. <artifactId>log4j-core</artifactId>
  16. <version>2.17.1</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.slf4j</groupId>
  20. <artifactId>slf4j-nop</artifactId>
  21. <version>1.7.2</version>
  22. <type>jar</type>
  23. </dependency>
  24. <dependency>
  25. <groupId>com.alibaba</groupId>
  26. <artifactId>fastjson</artifactId>
  27. <version>1.2.28</version>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-web</artifactId>
  32. <version>2.3.12.RELEASE</version>
  33. </dependency>
  34. </dependencies>

2.代码

ServerTablePolicy:主要处理合并数据表格工具类

MergeRowsRenderPolicy:含图片表格合并工具类

MultiImageRenderPolicy:注册添加前缀为'&'的自定义插件:

2.1.ServerTablePolicy:主要处理合并数据表格工具类

  1. public class ServerTablePolicy extends DynamicTableRenderPolicy {
  2. @Override
  3. public void render(XWPFTable xwpfTable, Object tableData) throws Exception {
  4. if (null == tableData) {
  5. return;
  6. }
  7. // 参数数据声明
  8. ServerTableData serverTableData = (ServerTableData) tableData;
  9. List<RowRenderData> serverDataList = serverTableData.getServerDataList();
  10. List<Map<String, Object>> groupDataList = serverTableData.getGroupDataList();
  11. Integer mergeColumn = serverTableData.getMergeColumn();
  12. if (CollectionUtils.isNotEmpty(serverDataList)) {
  13. // 先删除一行, demo中第一行是为了调整 三线表 样式
  14. xwpfTable.removeRow(1);
  15. // 行从中间插入, 因此采用倒序渲染数据
  16. for (int i = serverDataList.size() - 1; i >= 0; i--) {
  17. XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
  18. newRow.setHeight(400);
  19. //每一行填充多少个单元格
  20. for (int j = 0; j < serverDataList.get(0).getCells().size(); j++) {
  21. newRow.createCell();
  22. }
  23. // 渲染一行数据
  24. TableRenderPolicy.Helper.renderRow(newRow, serverDataList.get(i));
  25. }
  26. // 处理合并
  27. for (int i = 0; i < serverDataList.size(); i++) {
  28. // 获取要合并的名称那一列数据 mergeColumn代表要合并的列,从0开始
  29. String typeNameData = serverDataList.get(i).getCells().get(mergeColumn).getParagraphs().get(0).getContents().get(0).toString();
  30. for (int j = 0; j < groupDataList.size(); j++) {
  31. String typeNameTemplate = String.valueOf(groupDataList.get(j).get("typeName"));
  32. int listSize = Integer.parseInt(String.valueOf(groupDataList.get(j).get("listSize")));
  33. //如果只有一条数据不进行合并处理
  34. if (listSize!=1) {
  35. // 若匹配上 就直接合并
  36. if (typeNameTemplate.equals(typeNameData)) {
  37. //如果合并列不为空直接合并,否则提示输入要合并的列
  38. if (CollectionUtils.isNotEmpty(serverTableData.getMergeColumnList())) {
  39. for (Integer mergeColumns : serverTableData.getMergeColumnList()) {
  40. TableTools.mergeCellsVertically(xwpfTable, mergeColumns, i + 1, i + listSize);
  41. }
  42. groupDataList.remove(j);
  43. break;
  44. } else {
  45. throw new Exception("要合并列不能为空!");
  46. }
  47. }
  48. }
  49. }
  50. }
  51. }
  52. }
  53. }

2.2.MergeRowsRenderPolicy:含图片表格合并工具类

  1. public class MergeRowsRenderPolicy implements RenderPolicy {
  2. private final String prefix;
  3. private final String suffix;
  4. private final boolean onSameLine;
  5. public MergeRowsRenderPolicy() {
  6. this(false);
  7. }
  8. public MergeRowsRenderPolicy(boolean onSameLine) {
  9. this("[", "]", onSameLine);
  10. }
  11. public MergeRowsRenderPolicy(String prefix, String suffix) {
  12. this(prefix, suffix, false);
  13. }
  14. public MergeRowsRenderPolicy(String prefix, String suffix, boolean onSameLine) {
  15. this.prefix = prefix;
  16. this.suffix = suffix;
  17. this.onSameLine = onSameLine;
  18. }
  19. @Override
  20. public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
  21. RunTemplate runTemplate = (RunTemplate) eleTemplate;
  22. XWPFRun run = runTemplate.getRun();
  23. try {
  24. if (!TableTools.isInsideTable(run)) {
  25. throw new IllegalStateException(
  26. "The template tag " + runTemplate.getSource() + " must be inside a table");
  27. }
  28. XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
  29. XWPFTable table = tagCell.getTableRow().getTable();
  30. run.setText("", 0);
  31. int templateRowIndex = getTemplateRowIndex(tagCell);
  32. if (data instanceof Iterable) {
  33. Iterator<?> iterator = ((Iterable<?>) data).iterator();
  34. XWPFTableRow templateRow = table.getRow(templateRowIndex);
  35. int insertPosition = templateRowIndex;
  36. TemplateResolver resolver = new TemplateResolver(template.getConfig().copy(prefix, suffix));
  37. boolean firstFlag = true;
  38. int index = 0;
  39. boolean hasNext = iterator.hasNext();
  40. while (hasNext) {
  41. Object root = iterator.next();
  42. hasNext = iterator.hasNext();
  43. insertPosition = templateRowIndex++;
  44. table.insertNewTableRow(insertPosition);
  45. setTableRow(table, templateRow, insertPosition);
  46. // double set row
  47. XmlCursor newCursor = templateRow.getCtRow().newCursor();
  48. newCursor.toPrevSibling();
  49. XmlObject object = newCursor.getObject();
  50. XWPFTableRow nextRow = new XWPFTableRow((CTRow) object, table);
  51. if (!firstFlag) {
  52. // update VMerge cells for non-first row
  53. List<XWPFTableCell> tableCells = nextRow.getTableCells();
  54. for (XWPFTableCell cell : tableCells) {
  55. CTTcPr tcPr = TableTools.getTcPr(cell);
  56. CTVMerge vMerge = tcPr.getVMerge();
  57. if (null == vMerge) {continue;}
  58. if (STMerge.RESTART == vMerge.getVal()) {
  59. vMerge.setVal(STMerge.CONTINUE);
  60. }
  61. }
  62. } else {
  63. firstFlag = false;
  64. }
  65. setTableRow(table, nextRow, insertPosition);
  66. RenderDataCompute dataCompute = template.getConfig()
  67. .getRenderDataComputeFactory()
  68. .newCompute(EnvModel.of(root, EnvIterator.makeEnv(index++, hasNext)));
  69. List<XWPFTableCell> cells = nextRow.getTableCells();
  70. cells.forEach(cell -> {
  71. List<MetaTemplate> templates = resolver.resolveBodyElements(cell.getBodyElements());
  72. new DocumentProcessor(template, resolver, dataCompute).process(templates);
  73. });
  74. }
  75. // 处理连续数据相同行合并
  76. int currPos = getTemplateRowIndex(tagCell);
  77. XWPFTableRow prevRow = table.getRow(currPos);
  78. while (currPos <= insertPosition) {
  79. currPos++;
  80. XWPFTableRow nextRow = table.getRow(currPos);
  81. List<XWPFTableCell> cells = nextRow.getTableCells();
  82. int currCellPos = 0;
  83. while (currCellPos < cells.size()) {
  84. CTTcPr tcPrPrev = TableTools.getTcPr(prevRow.getCell(currCellPos));
  85. CTVMerge vMergePrev = tcPrPrev.getVMerge();
  86. if (null == vMergePrev) {
  87. vMergePrev = tcPrPrev.addNewVMerge();
  88. vMergePrev.setVal(STMerge.RESTART);
  89. }
  90. CTTcPr tcPrNext = TableTools.getTcPr(nextRow.getCell(currCellPos));
  91. CTVMerge vMergeNext = tcPrNext.getVMerge();
  92. if (null == vMergeNext) {
  93. vMergeNext = tcPrNext.addNewVMerge();
  94. vMergeNext.setVal(STMerge.RESTART);
  95. }
  96. // 判断上下两行的字符串值是否一样,一样就合并
  97. if (StringUtils.equalsIgnoreCase(
  98. prevRow.getCell(currCellPos).getText(),
  99. nextRow.getCell(currCellPos).getText())) {
  100. vMergeNext.setVal(STMerge.CONTINUE);
  101. }
  102. currCellPos++;
  103. }
  104. prevRow = nextRow;
  105. }
  106. }
  107. table.removeRow(templateRowIndex);
  108. afterloop(table, data);
  109. } catch (Exception e) {
  110. throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
  111. }
  112. }
  113. private int getTemplateRowIndex(XWPFTableCell tagCell) {
  114. XWPFTableRow tagRow = tagCell.getTableRow();
  115. return onSameLine ? getRowIndex(tagRow) : (getRowIndex(tagRow) + 1);
  116. }
  117. protected void afterloop(XWPFTable table, Object data) {
  118. }
  119. @SuppressWarnings("unchecked")
  120. private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {
  121. List<XWPFTableRow> rows = (List<XWPFTableRow>) ReflectionUtils.getValue("tableRows", table);
  122. rows.set(pos, templateRow);
  123. table.getCTTbl().setTrArray(pos, templateRow.getCtRow());
  124. }
  125. private int getRowIndex(XWPFTableRow row) {
  126. List<XWPFTableRow> rows = row.getTable().getRows();
  127. return rows.indexOf(row);
  128. }
  129. }

2.3.MultiImageRenderPolicy:注册添加前缀为'&'的自定义插件:

  1. public class MultiImageRenderPolicy extends AbstractRenderPolicy<Collection<PictureRenderData>> {
  2. @Override
  3. protected void afterRender(RenderContext<Collection<PictureRenderData>> context) {
  4. clearPlaceholder(context, true);
  5. }
  6. @Override
  7. public void doRender(RenderContext<Collection<PictureRenderData>> context) throws Exception {
  8. IRunBody iRunBody = context.getRun().getParent();
  9. if (iRunBody instanceof XWPFParagraph) {
  10. XWPFParagraph p = (XWPFParagraph) iRunBody;
  11. Collection<PictureRenderData> data = context.getData();
  12. if (CollectionUtils.isNotEmpty(data)) {
  13. for (PictureRenderData pic : data) {
  14. PictureRenderPolicy.Helper.renderPicture(p.createRun(), pic);
  15. }
  16. }
  17. }
  18. }
  19. }

2.4. 数据表格合并实体类

  1. @Data
  2. public class ServerTableData {
  3. /**
  4. * 携带表格中真实数据
  5. */
  6. private List<RowRenderData> serverDataList;
  7. /**
  8. * 携带要分组的信息
  9. */
  10. private List<Map<String, Object>> groupDataList;
  11. /**
  12. * 需要合并的列,从0开始
  13. */
  14. private Integer mergeColumn;
  15. /**
  16. * 具体要合并的列集合
  17. */
  18. private List<Integer> mergeColumnList;
  19. }

四.测试及数据处理

  1. public class WordExport {
  2. public static void main(String[] args) throws IOException {
  3. // 获取模板文件流
  4. String filePath = "D:\\poi-tl\\template.docx";
  5. String targetPath = "D:\\poi-tl\\templateTest.docx";
  6. String picturePath = "D:\\poi-tl\\1_1.jpg";
  7. String picturePaths="D:\\poi-tl\\1_1.jpg,D:\\poi-tl\\2_2.jpg,D:\\poi-tl\\3_3.jpg,D:\\poi-tl\\4_4.jpg";
  8. String httpPicturePath = "http://deepoove.com/images/icecream.png";
  9. String httpPicturePaths = "http://deepoove.com/images/icecream.png,http://deepoove.com/images/icecream.png";
  10. //单个图片数据模板
  11. PictureRenderData picture = Pictures.ofStream(new FileInputStream(picturePath), PictureType.JPEG)
  12. .size(400, 300).create();
  13. //多张图片数据模板
  14. ArrayList<JSONObject> pictures = new ArrayList<JSONObject>() {
  15. {
  16. add(new JSONObject().fluentPut("url",picturePath));
  17. add(new JSONObject().fluentPut("url", httpPicturePath));
  18. }
  19. };
  20. //多张图片处理
  21. List<Map<String, Object>> pictureList = new ArrayList(){{
  22. for (int i = 0; i < 2; i++) {
  23. add(new HashMap<String,Object>(){{
  24. put("url", Pictures.ofStream(new FileInputStream(picturePath), PictureType.JPEG)
  25. .size(220,150).create());
  26. }});
  27. }}};
  28. //如果表格内不需要合并数据直接put("",new ArrayList<string,object>/new ArrayList<实体类>)
  29. //单元格多图片
  30. ArrayList<JSONObject> arrayList = getArrayList(picturePaths,httpPicturePaths);
  31. // 伪造一个表格数据
  32. //具体要合并的列
  33. List<Integer> mergeColumnList = Arrays.asList(0,3);
  34. ServerTableData oneTable = getServerTableData(mergeColumnList);
  35. //表格绑定
  36. Configure config = Configure.builder()
  37. //注册添加前缀为'&'的自定义插件:
  38. .addPlugin('&', new MultiImageRenderPolicy())
  39. //含图片数据表格合并同列相邻的单元格
  40. .bind("items", new MergeRowsRenderPolicy())
  41. //数据表格可指定合并列
  42. .bind("oneTable",new ServerTablePolicy())
  43. .build();
  44. XWPFTemplate template = XWPFTemplate.compile(filePath, config).render(
  45. new HashMap<String, Object>(){{
  46. //单张图片
  47. put("image", picture);
  48. //单张图片描述
  49. put("imageDesc","这是图片描述");
  50. //多张图片1
  51. put("pictures", pictures);
  52. //多张图片2
  53. put("pictureList", pictureList);
  54. //合并表格
  55. put("oneTable",oneTable);
  56. //单元格多个图片
  57. put("items", arrayList);
  58. }});
  59. //输出网络流 本地导出
  60. template.writeAndClose(new FileOutputStream(targetPath));
  61. //网络导出
  62. //exportFile(filePath,hashMap,config);
  63. }
  64. /**
  65. * 表格合并数据处理
  66. * @param mergeColumnList 要合并的列集合
  67. * @return 数据集合
  68. */
  69. private static ServerTableData getServerTableData(List<Integer> mergeColumnList) {
  70. ServerTableData serverTableData = new ServerTableData();
  71. List<RowRenderData> serverDataList = new ArrayList<>();
  72. for (int j = 0; j < 5; j++) {
  73. String typeName;
  74. if (j > 1) {
  75. typeName = "张三";
  76. serverDataList.add(Rows.of(typeName, "男", "3", "喝酒").center().create());
  77. }else {
  78. typeName = "李四";
  79. serverDataList.add(Rows.of(typeName, "女", "4", "逛街").center().create());
  80. }
  81. }
  82. List<Map<String, Object>> groupDataList = new ArrayList<>();
  83. Map<String, Object> groupData1 = new HashMap<>();
  84. //typeName是张三的数据有两条
  85. groupData1.put("typeName", "张三");
  86. groupData1.put("listSize", "3");
  87. Map<String, Object> groupData2 = new HashMap<>();
  88. //typeName是李四的数据有三条
  89. groupData2.put("typeName", "李四");
  90. groupData2.put("listSize", "2");
  91. groupDataList.add(groupData1);
  92. groupDataList.add(groupData2);
  93. //具体合并的列
  94. serverTableData.setMergeColumnList(mergeColumnList);
  95. //表格中数据
  96. serverTableData.setServerDataList(serverDataList);
  97. //分组的信息
  98. serverTableData.setGroupDataList(groupDataList);
  99. //从哪列开始合并
  100. serverTableData.setMergeColumn(0);
  101. return serverTableData;
  102. }
  103. /**
  104. * 含图片表格数据处理, 可单独抽成工具类,官网支持多种不同图片处理
  105. * @param picturePaths java图片路径
  106. * @param httpPicturePaths 网络图片路径
  107. * @return 数据集合
  108. * @throws IOException 抛出io流异常
  109. */
  110. public static ArrayList<JSONObject> getArrayList(String picturePaths,String httpPicturePaths) throws IOException {
  111. String[] picturePathArr = picturePaths.split(",");
  112. String[] httpPicturePathArr = httpPicturePaths.split(",");
  113. word模板中使用 {{&images}}来插入多张图片了
  114. ArrayList<JSONObject> arrayList = new ArrayList<JSONObject>() {{
  115. //网络图片
  116. add(new JSONObject().fluentPut("title", "第一组图片")
  117. .fluentPut("detail", "这是第1.1组图片描述")
  118. .fluentPut("images", new ArrayList<PictureRenderData>() {{
  119. for (String httpPicturePath : httpPicturePathArr) {
  120. add(Pictures.ofUrl(httpPicturePath).create());
  121. }
  122. }}));
  123. //网络图片
  124. add(new JSONObject().fluentPut("title", "第一组图片")
  125. .fluentPut("detail", "这是第1.2组图片描述")
  126. .fluentPut("images", new ArrayList<PictureRenderData>() {{
  127. for (String httpPicturePath : httpPicturePathArr) {
  128. add(Pictures.ofUrl(httpPicturePath).create());
  129. }
  130. }}));
  131. // java图片
  132. add(new JSONObject().fluentPut("title", "第二组图片")
  133. .fluentPut("detail", "这是第2.1组图片描述")
  134. .fluentPut("images", new ArrayList<PictureRenderData>() {{
  135. for (String picturePath : picturePathArr) {
  136. BufferedImage bufferedImage = ImageIO.read(new FileInputStream(picturePath));
  137. add(Pictures.ofBufferedImage(bufferedImage, PictureType.PNG)
  138. .size(205, 205).create());
  139. }
  140. }}));
  141. }};
  142. return arrayList;
  143. }
  144. /**
  145. * 浏览器导出
  146. * @param fileName 文件名称
  147. * @param map 数据集合
  148. * @param configure 配置信息
  149. */
  150. public static void exportFile(String fileName, Map<String, Object> map,Configure configure) {
  151. try {
  152. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  153. HttpServletResponse response = attributes.getResponse();
  154. // 获取Word模板,模板存放路径在项目的resources目录下
  155. //PoiController就是方法所在的类
  156. XWPFTemplate template = XWPFTemplate.compile(fileName,configure).render(map);
  157. //处理文件名乱码
  158. String attachName = new String(("测试.docx").getBytes(), "ISO-8859-1");
  159. // 浏览器端下载
  160. response.setContentType("application/x-download;charset=" + "utf-8");
  161. response.addHeader("Content-Disposition", "attachment;filename=" + attachName);
  162. OutputStream out = response.getOutputStream();
  163. BufferedOutputStream bos = new BufferedOutputStream(out);
  164. template.write(bos);
  165. bos.flush();
  166. out.flush();
  167. PoitlIOUtils.closeQuietlyMulti(template, bos, out);
  168. } catch (Exception e) {
  169. e.printStackTrace();
  170. }
  171. }
  172. }

五.总结

如果出现出现与项目其他技术不兼容,可单独拉出一个服务,但此时涉及到不同服务之间通过feign实现Response传递可参考https://mp.csdn.net/mp_blog/creation/editor/129615552

针对于未接触的技术,先了解官方文档,然后百度,谷歌,嘎嘎一顿乱造,想要的结果也就差不多了,

有疑问欢迎交流沟通!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/606771
推荐阅读
相关标签
  

闽ICP备14008679号