当前位置:   article > 正文

Poi实现根据word模板导出-图表篇_poi word模板

poi word模板

往期系列传送门:

Poi实现根据word模板导出-文本段落篇

(需要完整代码的直接看最后位置!!!)

前言:

补充Word中图表的知识:

每个图表在word中都有一个内置的Excel,用于操作数据。

内置Excel有类别、系列、值三个概念:

poi可以获取word中的图表对象,通过这个图表对象来操作Excel的系列、类别、值。这样是不是思路很清晰了,直接看代码:

  1. public void exportWord() throws Exception {
  2. //获取word模板
  3. InputStream is = WordController.class.getResourceAsStream("/template/wordChartTemplate.docx");
  4. try {
  5. ZipSecureFile.setMinInflateRatio(-1.0d);
  6. // 获取docx解析对象
  7. XWPFDocument document = new XWPFDocument(is);
  8. // 解析替换第一个图表数据,根据具体业务封装数据
  9. List<Number[]> singleBarValues = new ArrayList<>();
  10. // 第一个系列的值
  11. singleBarValues.add(new Number[]{3232, 5222, 2424, 2346, 3123});
  12. // 第二个系列的值
  13. singleBarValues.add(new Number[]{42.2, 24.41, 31.73, 74.83, 53.28});
  14. // x轴的值
  15. String[] xValues = new String[]{"山东省", "河南省", "北京市", "天津市", "陕西省"};
  16. changeChart1(document, singleBarValues, xValues);
  17. // 输出新文件
  18. FileOutputStream fos = new FileOutputStream("D:\\test\\test.docx");
  19. document.write(fos);
  20. document.close();
  21. fos.close();
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. private static XWPFChart changeChart1(XWPFDocument document, List<Number[]> singleBarValues, String[] xValues) {
  27. //获取word中所有图表对象
  28. List<XWPFChart> charts = document.getCharts();
  29. //获取第一个单系列柱状图
  30. XWPFChart docChart = charts.get(0);
  31. //系列信息
  32. String[] seriesNames = {"出生人口数","出生率"};
  33. //分类信息
  34. String[] cats = xValues;
  35. // 业务数据
  36. singleBarValues.add(singleBarValues.get(0));
  37. singleBarValues.add(singleBarValues.get(1));
  38. //获取图表数据对象
  39. XDDFChartData chartData = null;
  40. //word图表均对应一个内置的excel,用于保存图表对应的数据
  41. //excel中 第一列第二行开始的数据为分类信息
  42. //CellRangeAddress(1, categories.size(), 0, 0) 四个参数依次为 起始行 截止行 起始列 截止列。
  43. //excel中分类信息的范围
  44. String catDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, 0, 0));
  45. //根据分类信息的范围创建分类信息的数据源
  46. XDDFDataSource<?> catDataSource = XDDFDataSourcesFactory.fromArray(cats, catDataRange, 0);
  47. //更新数据
  48. for (int i = 0; i < seriesNames.length; i++) {
  49. chartData = docChart.getChartSeries().get(i);
  50. //excel中各系列对应的数据的范围
  51. String valDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, i + 1, i + 1));
  52. //根据数据的范围创建值的数据源
  53. Number[] val = singleBarValues.get(i);
  54. XDDFNumericalDataSource<Number> valDataSource = XDDFDataSourcesFactory.fromArray(val, valDataRange, i + 1);
  55. //获取图表系列的数据对象
  56. XDDFChartData.Series series = chartData.getSeries(0);
  57. //替换系列数据对象中的分类和值
  58. series.replaceData(catDataSource, valDataSource);
  59. //修改系列数据对象中的标题
  60. // CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
  61. // series.setTitle(seriesNames[i], cellReference);
  62. //更新图表数据对象
  63. docChart.plot(chartData);
  64. }
  65. //图表整体的标题 传空值则不替换标题
  66. // if (!Strings.isNullOrEmpty(title)) {
  67. // docChart.setTitleText(title);
  68. // docChart.setTitleOverlay(false);
  69. // }
  70. return docChart;
  71. }

导出效果:

重点!重点!重点!

看下面这个图表用上述方法能成功导出吗?

像这种x轴带有分类的通过上述方法已经不行了,原因是在用

chartData = docChart.getChartSeries().get(i);

这个方法获取系列对象时报空指针异常。可能是poi不支持这个格式的x轴。

那我们只能换个角度来处理了,之前说过Word中每个图表都是一个内置Excel控制,那Poi操作Excel我可太会了,不熟悉的可以看之前Poi处理Excel的文章。

果然,我们可以通过图表对象拿到XSSFWorkbook

  1. //获取word中所有图表对象
  2. List<XWPFChart> charts = doc.getCharts();
  3. //获取第一个单系列柱状图
  4. XWPFChart singleBarChar = charts.get(1);
  5. XSSFWorkbook workbook = singleBarChar.getWorkbook();
  6. XSSFSheet sheet = workbook.getSheetAt(0);
  7. int lastRowNum = sheet.getLastRowNum();
  8. // 更新内置excel数据
  9. for (int i = 1; i <= lastRowNum; i++) {
  10. XSSFRow row = sheet.getRow(i);
  11. XSSFCell cell = row.getCell(2);
  12. cell.setCellValue(Double.parseDouble(n1[i - 1].toString()));
  13. XSSFCell cell1 = row.getCell(3);
  14. cell1.setCellValue(Double.parseDouble(n2[i - 1].toString()));
  15. }

处理完后,发现导出的Word虽然内置的Excel数据替换成我们的数据了,页面显示的还是之前模板数据,必须点击编辑后页面数据才显示Excel中的值。

现在问题成了,怎么刷新内置Excel数据,简单图表通过docChart.plot(chartData);方法直接刷新,现在拿不到了怎么办?

上一篇(Poi实现根据word模板导出-文本段落篇)说到,poi是将word解析成xml操作的,那Word页面显示的图表也应该是xml来生成的,我们只需把对应标签数据也改成我们的数据,那页面数据和内置Excel数据不就保持一致了。

通过Debug看下一图表对象

可以发现果然有标签是控制值显示的,下面我们只需按照标签顺序找到位置替换值就可以了。

  1. // 内置excel数据不会更新到页面,需要刷新页面数据
  2. CTChart ctChart = singleBarChar.getCTChart();
  3. CTPlotArea plotArea = ctChart.getPlotArea();
  4. // 第一列数据
  5. CTBarChart barChartArray = plotArea.getBarChartArray(0);
  6. List<CTBarSer> serList = barChartArray.getSerList();
  7. CTBarSer ctBarSer = serList.get(0);
  8. List<CTNumVal> ptList = ctBarSer.getVal().getNumRef().getNumCache().getPtList();
  9. for (int i = 0; i < ptList.size(); i++) {
  10. ptList.get(i).setV(String.valueOf(n1[i]));
  11. }
  12. // 第二列数据
  13. CTLineChart lineChartArray = plotArea.getLineChartArray(0);
  14. List<CTLineSer> lineSers = lineChartArray.getSerList();
  15. CTLineSer ctLineSer = lineSers.get(0);
  16. List<CTNumVal> ptList1 = ctLineSer.getVal().getNumRef().getNumCache().getPtList();
  17. for (int i = 0; i < ptList1.size(); i++) {
  18. ptList1.get(i).setV(String.valueOf(n2[i]));
  19. }

导出效果:

完整代码:

  1. package com.javacoding.controller;
  2. import cn.hutool.core.util.RandomUtil;
  3. import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
  4. import org.apache.poi.openxml4j.util.ZipSecureFile;
  5. import org.apache.poi.ss.util.CellRangeAddress;
  6. import org.apache.poi.xddf.usermodel.chart.*;
  7. import org.apache.poi.xssf.usermodel.XSSFCell;
  8. import org.apache.poi.xssf.usermodel.XSSFRow;
  9. import org.apache.poi.xssf.usermodel.XSSFSheet;
  10. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  11. import org.apache.poi.xwpf.usermodel.XWPFChart;
  12. import org.apache.poi.xwpf.usermodel.XWPFDocument;
  13. import org.apache.poi.xwpf.usermodel.XWPFParagraph;
  14. import org.apache.poi.xwpf.usermodel.XWPFRun;
  15. import org.openxmlformats.schemas.drawingml.x2006.chart.*;
  16. import org.springframework.web.bind.annotation.RequestMapping;
  17. import org.springframework.web.bind.annotation.RestController;
  18. import java.io.*;
  19. import java.util.*;
  20. @RestController
  21. @RequestMapping("/word")
  22. public class WordController {
  23. @RequestMapping("/export")
  24. public void exportWord() throws Exception {
  25. //获取word模板
  26. InputStream is = WordController.class.getResourceAsStream("/template/wordChartTemplate.docx");
  27. try {
  28. ZipSecureFile.setMinInflateRatio(-1.0d);
  29. // 获取docx解析对象
  30. XWPFDocument document = new XWPFDocument(is);
  31. // 解析替换文本段落对象
  32. // 替换word模板中占位符数据,根据业务自行封装
  33. Map<String, String> params = new HashMap<>();
  34. params.put("area1", "山东省");
  35. params.put("area2", "河南省");
  36. params.put("area3", "北京市");
  37. params.put("area4", "天津市");
  38. params.put("area5", "陕西省");
  39. params.put("areaRate1", "42.15");
  40. params.put("areaRate2", "22.35");
  41. params.put("areaRate3", "42.35");
  42. params.put("areaRate4", "23.11");
  43. params.put("areaRate5", "15.34");
  44. changeText(document, params);
  45. // 解析替换第一个图表数据,根据具体业务封装数据
  46. List<Number[]> singleBarValues = new ArrayList<>();
  47. // 第一个系列的值
  48. singleBarValues.add(new Number[]{3232, 5222, 2424, 2346, 3123});
  49. // 第二个系列的值
  50. singleBarValues.add(new Number[]{42.2, 24.41, 31.73, 74.83, 53.28});
  51. // x轴的值
  52. String[] xValues = new String[]{"山东省", "河南省", "北京市", "天津市", "陕西省"};
  53. changeChart1(document, singleBarValues, xValues);
  54. // 解析替换第二个图表数据,根据具体业务封装数据
  55. List<Number[]> singleBarValues2 = new ArrayList<>();
  56. Number[] n1 = new Number[32];
  57. Number[] n2 = new Number[32];
  58. for (int i = 0; i < 32; i++) {
  59. n1[i] = RandomUtil.randomInt(1000000);
  60. n2[i] = RandomUtil.randomDouble(1);
  61. }
  62. singleBarValues2.add(n1);
  63. singleBarValues2.add(n2);
  64. changeChart2(document, singleBarValues2);
  65. // 输出新文件
  66. FileOutputStream fos = new FileOutputStream("D:\\test\\test.docx");
  67. document.write(fos);
  68. fos.close();
  69. } catch (Exception e) {
  70. e.printStackTrace();
  71. }
  72. }
  73. private static void changeChart2(XWPFDocument doc, List<Number[]> singleBarValues2) throws IOException, InvalidFormatException {
  74. // 业务数据
  75. Number[] n1 = singleBarValues2.get(0);
  76. Number[] n2 = singleBarValues2.get(1);
  77. //获取word中所有图表对象
  78. List<XWPFChart> charts = doc.getCharts();
  79. //获取第一个单系列柱状图
  80. XWPFChart singleBarChar = charts.get(1);
  81. XSSFWorkbook workbook = singleBarChar.getWorkbook();
  82. XSSFSheet sheet = workbook.getSheetAt(0);
  83. int lastRowNum = sheet.getLastRowNum();
  84. // 更新内置excel数据
  85. for (int i = 1; i <= lastRowNum; i++) {
  86. XSSFRow row = sheet.getRow(i);
  87. XSSFCell cell = row.getCell(2);
  88. cell.setCellValue(Double.parseDouble(n1[i - 1].toString()));
  89. XSSFCell cell1 = row.getCell(3);
  90. cell1.setCellValue(Double.parseDouble(n2[i - 1].toString()));
  91. }
  92. // 内置excel数据不会更新到页面,需要刷新页面数据
  93. CTChart ctChart = singleBarChar.getCTChart();
  94. CTPlotArea plotArea = ctChart.getPlotArea();
  95. // 第一列数据
  96. CTBarChart barChartArray = plotArea.getBarChartArray(0);
  97. List<CTBarSer> serList = barChartArray.getSerList();
  98. CTBarSer ctBarSer = serList.get(0);
  99. List<CTNumVal> ptList = ctBarSer.getVal().getNumRef().getNumCache().getPtList();
  100. for (int i = 0; i < ptList.size(); i++) {
  101. ptList.get(i).setV(String.valueOf(n1[i]));
  102. }
  103. // 第二列数据
  104. CTLineChart lineChartArray = plotArea.getLineChartArray(0);
  105. List<CTLineSer> lineSers = lineChartArray.getSerList();
  106. CTLineSer ctLineSer = lineSers.get(0);
  107. List<CTNumVal> ptList1 = ctLineSer.getVal().getNumRef().getNumCache().getPtList();
  108. for (int i = 0; i < ptList1.size(); i++) {
  109. ptList1.get(i).setV(String.valueOf(n2[i]));
  110. }
  111. }
  112. private static XWPFChart changeChart1(XWPFDocument document, List<Number[]> singleBarValues, String[] xValues) {
  113. //获取word中所有图表对象
  114. List<XWPFChart> charts = document.getCharts();
  115. //获取第一个单系列柱状图
  116. XWPFChart docChart = charts.get(0);
  117. //系列信息
  118. String[] seriesNames = {"出生人口数","出生率"};
  119. //分类信息
  120. String[] cats = xValues;
  121. // 业务数据
  122. singleBarValues.add(singleBarValues.get(0));
  123. singleBarValues.add(singleBarValues.get(1));
  124. //获取图表数据对象
  125. XDDFChartData chartData = null;
  126. //word图表均对应一个内置的excel,用于保存图表对应的数据
  127. //excel中 第一列第二行开始的数据为分类信息
  128. //CellRangeAddress(1, categories.size(), 0, 0) 四个参数依次为 起始行 截止行 起始列 截止列。
  129. //excel中分类信息的范围
  130. String catDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, 0, 0));
  131. //根据分类信息的范围创建分类信息的数据源
  132. XDDFDataSource<?> catDataSource = XDDFDataSourcesFactory.fromArray(cats, catDataRange, 0);
  133. //更新数据
  134. for (int i = 0; i < seriesNames.length; i++) {
  135. chartData = docChart.getChartSeries().get(i);
  136. //excel中各系列对应的数据的范围
  137. String valDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, i + 1, i + 1));
  138. //根据数据的范围创建值的数据源
  139. Number[] val = singleBarValues.get(i);
  140. XDDFNumericalDataSource<Number> valDataSource = XDDFDataSourcesFactory.fromArray(val, valDataRange, i + 1);
  141. //获取图表系列的数据对象
  142. XDDFChartData.Series series = chartData.getSeries(0);
  143. //替换系列数据对象中的分类和值
  144. series.replaceData(catDataSource, valDataSource);
  145. //修改系列数据对象中的标题
  146. // CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
  147. // series.setTitle(seriesNames[i], cellReference);
  148. //更新图表数据对象
  149. docChart.plot(chartData);
  150. }
  151. //图表整体的标题 传空值则不替换标题
  152. // if (!Strings.isNullOrEmpty(title)) {
  153. // docChart.setTitleText(title);
  154. // docChart.setTitleOverlay(false);
  155. // }
  156. return docChart;
  157. }
  158. private void changeText(XWPFDocument document, Map<String, String> params) {
  159. //获取段落集合
  160. List<XWPFParagraph> paragraphs = document.getParagraphs();
  161. for (XWPFParagraph paragraph : paragraphs) {
  162. //判断此段落时候需要进行替换
  163. String text = paragraph.getText();
  164. if(checkText(text)){
  165. List<XWPFRun> runs = paragraph.getRuns();
  166. for (XWPFRun run : runs) {
  167. //替换模板原来位置
  168. String value = changeValue(run.toString(), params);
  169. if (Objects.nonNull(value)) {
  170. run.setText(value, 0);
  171. }
  172. }
  173. }
  174. }
  175. }
  176. /**
  177. * 判断文本中时候包含$
  178. * @param text 文本
  179. * @return 包含返回true,不包含返回false
  180. */
  181. public static boolean checkText(String text){
  182. boolean check = false;
  183. if(text.indexOf("$")!= -1){
  184. check = true;
  185. }
  186. return check;
  187. }
  188. /**
  189. * 匹配传入信息集合与模板
  190. * @param value 模板需要替换的区域
  191. * @param textMap 传入信息集合
  192. * @return 模板需要替换区域信息集合对应值
  193. */
  194. public static String changeValue(String value, Map<String, String> textMap){
  195. Set<Map.Entry<String, String>> textSets = textMap.entrySet();
  196. for (Map.Entry<String, String> textSet : textSets) {
  197. //匹配模板与替换值 格式${key}
  198. String key = "${"+textSet.getKey()+"}";
  199. if(value.indexOf(key)!= -1){
  200. value = value.replace(key, textSet.getValue());
  201. }
  202. }
  203. //模板未匹配到区域替换为空
  204. if(checkText(value)){
  205. value = "";
  206. }
  207. return value;
  208. }
  209. }

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

闽ICP备14008679号