赞
踩
poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档,
支持:
1.单系列图表指的是饼图(3D饼图)、圆环图等。
2.多系列图表指的是条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、散点图等。
3.组合图表指的是由多系列图表(柱形图、折线图、面积图)组合而成的图表。
等等...
官网地址:Poi-tl Documentation
该案例使用poi-tl版本: 1.12.0
- <dependency>
- <groupId>com.deepoove</groupId>
- <artifactId>poi-tl</artifactId>
- <version>1.12.0</version>
- </dependency>
该案例引入的包
- <dependencies>
- <dependency>
- <groupId>com.deepoove</groupId>
- <artifactId>poi-tl</artifactId>
- <version>1.12.0</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.24</version>
- </dependency>
-
- <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-core</artifactId>
- <version>2.17.1</version>
- </dependency>
-
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-nop</artifactId>
- <version>1.7.2</version>
- <type>jar</type>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.28</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- <version>2.3.12.RELEASE</version>
- </dependency>
- </dependencies>
ServerTablePolicy:主要处理合并数据表格工具类
MergeRowsRenderPolicy:含图片表格合并工具类
MultiImageRenderPolicy:注册添加前缀为'&'的自定义插件:
2.1.ServerTablePolicy:主要处理合并数据表格工具类
- public class ServerTablePolicy extends DynamicTableRenderPolicy {
- @Override
- public void render(XWPFTable xwpfTable, Object tableData) throws Exception {
- if (null == tableData) {
- return;
- }
-
- // 参数数据声明
- ServerTableData serverTableData = (ServerTableData) tableData;
- List<RowRenderData> serverDataList = serverTableData.getServerDataList();
- List<Map<String, Object>> groupDataList = serverTableData.getGroupDataList();
- Integer mergeColumn = serverTableData.getMergeColumn();
-
- if (CollectionUtils.isNotEmpty(serverDataList)) {
- // 先删除一行, demo中第一行是为了调整 三线表 样式
- xwpfTable.removeRow(1);
-
- // 行从中间插入, 因此采用倒序渲染数据
- for (int i = serverDataList.size() - 1; i >= 0; i--) {
- XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
- newRow.setHeight(400);
- //每一行填充多少个单元格
- for (int j = 0; j < serverDataList.get(0).getCells().size(); j++) {
- newRow.createCell();
- }
- // 渲染一行数据
- TableRenderPolicy.Helper.renderRow(newRow, serverDataList.get(i));
- }
-
-
- // 处理合并
- for (int i = 0; i < serverDataList.size(); i++) {
- // 获取要合并的名称那一列数据 mergeColumn代表要合并的列,从0开始
- String typeNameData = serverDataList.get(i).getCells().get(mergeColumn).getParagraphs().get(0).getContents().get(0).toString();
- for (int j = 0; j < groupDataList.size(); j++) {
- String typeNameTemplate = String.valueOf(groupDataList.get(j).get("typeName"));
- int listSize = Integer.parseInt(String.valueOf(groupDataList.get(j).get("listSize")));
- //如果只有一条数据不进行合并处理
- if (listSize!=1) {
- // 若匹配上 就直接合并
- if (typeNameTemplate.equals(typeNameData)) {
- //如果合并列不为空直接合并,否则提示输入要合并的列
- if (CollectionUtils.isNotEmpty(serverTableData.getMergeColumnList())) {
- for (Integer mergeColumns : serverTableData.getMergeColumnList()) {
- TableTools.mergeCellsVertically(xwpfTable, mergeColumns, i + 1, i + listSize);
- }
- groupDataList.remove(j);
- break;
- } else {
- throw new Exception("要合并列不能为空!");
- }
- }
- }
- }
- }
- }
- }
- }
2.2.MergeRowsRenderPolicy:含图片表格合并工具类
- public class MergeRowsRenderPolicy implements RenderPolicy {
-
- private final String prefix;
- private final String suffix;
- private final boolean onSameLine;
-
- public MergeRowsRenderPolicy() {
- this(false);
- }
-
- public MergeRowsRenderPolicy(boolean onSameLine) {
- this("[", "]", onSameLine);
- }
-
- public MergeRowsRenderPolicy(String prefix, String suffix) {
- this(prefix, suffix, false);
- }
-
- public MergeRowsRenderPolicy(String prefix, String suffix, boolean onSameLine) {
- this.prefix = prefix;
- this.suffix = suffix;
- this.onSameLine = onSameLine;
- }
-
- @Override
- public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
- RunTemplate runTemplate = (RunTemplate) eleTemplate;
- XWPFRun run = runTemplate.getRun();
- try {
- if (!TableTools.isInsideTable(run)) {
- throw new IllegalStateException(
- "The template tag " + runTemplate.getSource() + " must be inside a table");
- }
- XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
- XWPFTable table = tagCell.getTableRow().getTable();
- run.setText("", 0);
-
- int templateRowIndex = getTemplateRowIndex(tagCell);
- if (data instanceof Iterable) {
- Iterator<?> iterator = ((Iterable<?>) data).iterator();
- XWPFTableRow templateRow = table.getRow(templateRowIndex);
- int insertPosition = templateRowIndex;
-
- TemplateResolver resolver = new TemplateResolver(template.getConfig().copy(prefix, suffix));
- boolean firstFlag = true;
- int index = 0;
- boolean hasNext = iterator.hasNext();
- while (hasNext) {
- Object root = iterator.next();
- hasNext = iterator.hasNext();
-
- insertPosition = templateRowIndex++;
- table.insertNewTableRow(insertPosition);
- setTableRow(table, templateRow, insertPosition);
-
- // double set row
- XmlCursor newCursor = templateRow.getCtRow().newCursor();
- newCursor.toPrevSibling();
- XmlObject object = newCursor.getObject();
- XWPFTableRow nextRow = new XWPFTableRow((CTRow) object, table);
- if (!firstFlag) {
- // update VMerge cells for non-first row
- List<XWPFTableCell> tableCells = nextRow.getTableCells();
- for (XWPFTableCell cell : tableCells) {
- CTTcPr tcPr = TableTools.getTcPr(cell);
- CTVMerge vMerge = tcPr.getVMerge();
- if (null == vMerge) {continue;}
- if (STMerge.RESTART == vMerge.getVal()) {
- vMerge.setVal(STMerge.CONTINUE);
- }
- }
- } else {
- firstFlag = false;
- }
- setTableRow(table, nextRow, insertPosition);
-
- RenderDataCompute dataCompute = template.getConfig()
- .getRenderDataComputeFactory()
- .newCompute(EnvModel.of(root, EnvIterator.makeEnv(index++, hasNext)));
- List<XWPFTableCell> cells = nextRow.getTableCells();
- cells.forEach(cell -> {
- List<MetaTemplate> templates = resolver.resolveBodyElements(cell.getBodyElements());
- new DocumentProcessor(template, resolver, dataCompute).process(templates);
- });
- }
- // 处理连续数据相同行合并
- int currPos = getTemplateRowIndex(tagCell);
- XWPFTableRow prevRow = table.getRow(currPos);
- while (currPos <= insertPosition) {
- currPos++;
- XWPFTableRow nextRow = table.getRow(currPos);
- List<XWPFTableCell> cells = nextRow.getTableCells();
- int currCellPos = 0;
- while (currCellPos < cells.size()) {
- CTTcPr tcPrPrev = TableTools.getTcPr(prevRow.getCell(currCellPos));
- CTVMerge vMergePrev = tcPrPrev.getVMerge();
- if (null == vMergePrev) {
- vMergePrev = tcPrPrev.addNewVMerge();
- vMergePrev.setVal(STMerge.RESTART);
- }
- CTTcPr tcPrNext = TableTools.getTcPr(nextRow.getCell(currCellPos));
- CTVMerge vMergeNext = tcPrNext.getVMerge();
- if (null == vMergeNext) {
- vMergeNext = tcPrNext.addNewVMerge();
- vMergeNext.setVal(STMerge.RESTART);
- }
- // 判断上下两行的字符串值是否一样,一样就合并
- if (StringUtils.equalsIgnoreCase(
- prevRow.getCell(currCellPos).getText(),
- nextRow.getCell(currCellPos).getText())) {
- vMergeNext.setVal(STMerge.CONTINUE);
- }
- currCellPos++;
- }
- prevRow = nextRow;
- }
-
- }
-
- table.removeRow(templateRowIndex);
- afterloop(table, data);
- } catch (Exception e) {
- throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
- }
- }
-
- private int getTemplateRowIndex(XWPFTableCell tagCell) {
- XWPFTableRow tagRow = tagCell.getTableRow();
- return onSameLine ? getRowIndex(tagRow) : (getRowIndex(tagRow) + 1);
- }
-
- protected void afterloop(XWPFTable table, Object data) {
- }
-
- @SuppressWarnings("unchecked")
- private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {
- List<XWPFTableRow> rows = (List<XWPFTableRow>) ReflectionUtils.getValue("tableRows", table);
- rows.set(pos, templateRow);
- table.getCTTbl().setTrArray(pos, templateRow.getCtRow());
- }
-
- private int getRowIndex(XWPFTableRow row) {
- List<XWPFTableRow> rows = row.getTable().getRows();
- return rows.indexOf(row);
- }
- }
2.3.MultiImageRenderPolicy:注册添加前缀为'&'的自定义插件:
- public class MultiImageRenderPolicy extends AbstractRenderPolicy<Collection<PictureRenderData>> {
-
- @Override
- protected void afterRender(RenderContext<Collection<PictureRenderData>> context) {
- clearPlaceholder(context, true);
- }
-
- @Override
- public void doRender(RenderContext<Collection<PictureRenderData>> context) throws Exception {
- IRunBody iRunBody = context.getRun().getParent();
- if (iRunBody instanceof XWPFParagraph) {
- XWPFParagraph p = (XWPFParagraph) iRunBody;
- Collection<PictureRenderData> data = context.getData();
- if (CollectionUtils.isNotEmpty(data)) {
- for (PictureRenderData pic : data) {
- PictureRenderPolicy.Helper.renderPicture(p.createRun(), pic);
- }
- }
- }
- }
- }
2.4. 数据表格合并实体类
- @Data
- public class ServerTableData {
-
- /**
- * 携带表格中真实数据
- */
- private List<RowRenderData> serverDataList;
-
- /**
- * 携带要分组的信息
- */
- private List<Map<String, Object>> groupDataList;
-
- /**
- * 需要合并的列,从0开始
- */
- private Integer mergeColumn;
-
- /**
- * 具体要合并的列集合
- */
- private List<Integer> mergeColumnList;
- }
- public class WordExport {
-
- public static void main(String[] args) throws IOException {
- // 获取模板文件流
- String filePath = "D:\\poi-tl\\template.docx";
- String targetPath = "D:\\poi-tl\\templateTest.docx";
- String picturePath = "D:\\poi-tl\\1_1.jpg";
- 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";
- String httpPicturePath = "http://deepoove.com/images/icecream.png";
- String httpPicturePaths = "http://deepoove.com/images/icecream.png,http://deepoove.com/images/icecream.png";
-
- //单个图片数据模板
- PictureRenderData picture = Pictures.ofStream(new FileInputStream(picturePath), PictureType.JPEG)
- .size(400, 300).create();
-
- //多张图片数据模板
-
- ArrayList<JSONObject> pictures = new ArrayList<JSONObject>() {
- {
- add(new JSONObject().fluentPut("url",picturePath));
- add(new JSONObject().fluentPut("url", httpPicturePath));
- }
- };
-
- //多张图片处理
- List<Map<String, Object>> pictureList = new ArrayList(){{
- for (int i = 0; i < 2; i++) {
- add(new HashMap<String,Object>(){{
- put("url", Pictures.ofStream(new FileInputStream(picturePath), PictureType.JPEG)
- .size(220,150).create());
- }});
- }}};
-
- //如果表格内不需要合并数据直接put("",new ArrayList<string,object>/new ArrayList<实体类>)
-
- //单元格多图片
- ArrayList<JSONObject> arrayList = getArrayList(picturePaths,httpPicturePaths);
-
- // 伪造一个表格数据
- //具体要合并的列
- List<Integer> mergeColumnList = Arrays.asList(0,3);
- ServerTableData oneTable = getServerTableData(mergeColumnList);
-
- //表格绑定
- Configure config = Configure.builder()
- //注册添加前缀为'&'的自定义插件:
- .addPlugin('&', new MultiImageRenderPolicy())
- //含图片数据表格合并同列相邻的单元格
- .bind("items", new MergeRowsRenderPolicy())
- //数据表格可指定合并列
- .bind("oneTable",new ServerTablePolicy())
- .build();
-
- XWPFTemplate template = XWPFTemplate.compile(filePath, config).render(
- new HashMap<String, Object>(){{
- //单张图片
- put("image", picture);
- //单张图片描述
- put("imageDesc","这是图片描述");
- //多张图片1
- put("pictures", pictures);
- //多张图片2
- put("pictureList", pictureList);
- //合并表格
- put("oneTable",oneTable);
- //单元格多个图片
- put("items", arrayList);
-
-
- }});
-
- //输出网络流 本地导出
- template.writeAndClose(new FileOutputStream(targetPath));
-
- //网络导出
- //exportFile(filePath,hashMap,config);
- }
-
- /**
- * 表格合并数据处理
- * @param mergeColumnList 要合并的列集合
- * @return 数据集合
- */
- private static ServerTableData getServerTableData(List<Integer> mergeColumnList) {
- ServerTableData serverTableData = new ServerTableData();
- List<RowRenderData> serverDataList = new ArrayList<>();
- for (int j = 0; j < 5; j++) {
- String typeName;
- if (j > 1) {
- typeName = "张三";
- serverDataList.add(Rows.of(typeName, "男", "3", "喝酒").center().create());
- }else {
- typeName = "李四";
- serverDataList.add(Rows.of(typeName, "女", "4", "逛街").center().create());
- }
- }
-
- List<Map<String, Object>> groupDataList = new ArrayList<>();
- Map<String, Object> groupData1 = new HashMap<>();
- //typeName是张三的数据有两条
- groupData1.put("typeName", "张三");
- groupData1.put("listSize", "3");
- Map<String, Object> groupData2 = new HashMap<>();
- //typeName是李四的数据有三条
- groupData2.put("typeName", "李四");
- groupData2.put("listSize", "2");
- groupDataList.add(groupData1);
- groupDataList.add(groupData2);
-
- //具体合并的列
- serverTableData.setMergeColumnList(mergeColumnList);
- //表格中数据
- serverTableData.setServerDataList(serverDataList);
- //分组的信息
- serverTableData.setGroupDataList(groupDataList);
- //从哪列开始合并
- serverTableData.setMergeColumn(0);
- return serverTableData;
- }
-
- /**
- * 含图片表格数据处理, 可单独抽成工具类,官网支持多种不同图片处理
- * @param picturePaths java图片路径
- * @param httpPicturePaths 网络图片路径
- * @return 数据集合
- * @throws IOException 抛出io流异常
- */
- public static ArrayList<JSONObject> getArrayList(String picturePaths,String httpPicturePaths) throws IOException {
-
- String[] picturePathArr = picturePaths.split(",");
- String[] httpPicturePathArr = httpPicturePaths.split(",");
-
- word模板中使用 {{&images}}来插入多张图片了
- ArrayList<JSONObject> arrayList = new ArrayList<JSONObject>() {{
- //网络图片
- add(new JSONObject().fluentPut("title", "第一组图片")
- .fluentPut("detail", "这是第1.1组图片描述")
- .fluentPut("images", new ArrayList<PictureRenderData>() {{
- for (String httpPicturePath : httpPicturePathArr) {
- add(Pictures.ofUrl(httpPicturePath).create());
- }
- }}));
- //网络图片
- add(new JSONObject().fluentPut("title", "第一组图片")
- .fluentPut("detail", "这是第1.2组图片描述")
- .fluentPut("images", new ArrayList<PictureRenderData>() {{
- for (String httpPicturePath : httpPicturePathArr) {
- add(Pictures.ofUrl(httpPicturePath).create());
- }
- }}));
-
- // java图片
- add(new JSONObject().fluentPut("title", "第二组图片")
- .fluentPut("detail", "这是第2.1组图片描述")
- .fluentPut("images", new ArrayList<PictureRenderData>() {{
- for (String picturePath : picturePathArr) {
- BufferedImage bufferedImage = ImageIO.read(new FileInputStream(picturePath));
- add(Pictures.ofBufferedImage(bufferedImage, PictureType.PNG)
- .size(205, 205).create());
- }
- }}));
- }};
- return arrayList;
- }
-
- /**
- * 浏览器导出
- * @param fileName 文件名称
- * @param map 数据集合
- * @param configure 配置信息
- */
- public static void exportFile(String fileName, Map<String, Object> map,Configure configure) {
- try {
-
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- HttpServletResponse response = attributes.getResponse();
-
- // 获取Word模板,模板存放路径在项目的resources目录下
- //PoiController就是方法所在的类
- XWPFTemplate template = XWPFTemplate.compile(fileName,configure).render(map);
-
- //处理文件名乱码
- String attachName = new String(("测试.docx").getBytes(), "ISO-8859-1");
-
- // 浏览器端下载
- response.setContentType("application/x-download;charset=" + "utf-8");
- response.addHeader("Content-Disposition", "attachment;filename=" + attachName);
-
- OutputStream out = response.getOutputStream();
- BufferedOutputStream bos = new BufferedOutputStream(out);
- template.write(bos);
- bos.flush();
- out.flush();
- PoitlIOUtils.closeQuietlyMulti(template, bos, out);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- }
如果出现出现与项目其他技术不兼容,可单独拉出一个服务,但此时涉及到不同服务之间通过feign实现Response传递可参考: https://mp.csdn.net/mp_blog/creation/editor/129615552
针对于未接触的技术,先了解官方文档,然后百度,谷歌,嘎嘎一顿乱造,想要的结果也就差不多了,
有疑问欢迎交流沟通!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。