|
@@ -1,851 +0,0 @@
|
|
|
-package com.yaoyicloud.render;
|
|
|
-
|
|
|
-import java.awt.image.BufferedImage;
|
|
|
-import java.io.ByteArrayOutputStream;
|
|
|
-import java.io.File;
|
|
|
-import java.io.IOException;
|
|
|
-import java.io.InputStream;
|
|
|
-import java.net.URL;
|
|
|
-import java.text.DecimalFormat;
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Collection;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
-import java.util.Objects;
|
|
|
-import java.util.regex.Matcher;
|
|
|
-import java.util.regex.Pattern;
|
|
|
-import java.util.stream.Collectors;
|
|
|
-
|
|
|
-import javax.imageio.ImageIO;
|
|
|
-
|
|
|
-import org.apache.commons.collections4.CollectionUtils;
|
|
|
-import org.apache.pdfbox.Loader;
|
|
|
-import org.apache.pdfbox.pdmodel.PDDocument;
|
|
|
-import org.apache.pdfbox.rendering.PDFRenderer;
|
|
|
-import org.apache.poi.xwpf.usermodel.IBodyElement;
|
|
|
-import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFHyperlinkRun;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFParagraph;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFRun;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFTable;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFTableCell;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFTableRow;
|
|
|
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
|
|
|
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
|
|
|
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
|
|
|
-
|
|
|
-import com.deepoove.poi.XWPFTemplate;
|
|
|
-import com.deepoove.poi.config.Configure;
|
|
|
-import com.deepoove.poi.config.ConfigureBuilder;
|
|
|
-import com.deepoove.poi.data.ByteArrayPictureRenderData;
|
|
|
-import com.deepoove.poi.data.HyperlinkTextRenderData;
|
|
|
-import com.deepoove.poi.data.PictureType;
|
|
|
-import com.deepoove.poi.data.style.PictureStyle;
|
|
|
-import com.deepoove.poi.exception.RenderException;
|
|
|
-import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
|
|
|
-import com.deepoove.poi.policy.ParagraphRenderPolicy;
|
|
|
-import com.deepoove.poi.policy.PictureRenderPolicy;
|
|
|
-import com.deepoove.poi.policy.RenderPolicy;
|
|
|
-import com.deepoove.poi.policy.TextRenderPolicy;
|
|
|
-import com.deepoove.poi.template.ElementTemplate;
|
|
|
-import com.deepoove.poi.template.run.RunTemplate;
|
|
|
-import com.deepoove.poi.util.TableTools;
|
|
|
-import com.deepoove.poi.xwpf.BodyContainer;
|
|
|
-import com.deepoove.poi.xwpf.BodyContainerFactory;
|
|
|
-import com.yaoyicloud.tools.Util;
|
|
|
-
|
|
|
-import cn.hutool.core.lang.Pair;
|
|
|
-import cn.hutool.core.util.StrUtil;
|
|
|
-
|
|
|
-/**
|
|
|
- * 抽象渲染器
|
|
|
- *
|
|
|
- */
|
|
|
-public abstract class AbstractNewRender {
|
|
|
-
|
|
|
- /*
|
|
|
- * 导出文件位置
|
|
|
- */
|
|
|
- protected final String cwd;
|
|
|
-
|
|
|
- /*
|
|
|
- * Docx结果文件路径
|
|
|
- */
|
|
|
- protected String docxResultPath;
|
|
|
-
|
|
|
- /*
|
|
|
- * html结果文件路径
|
|
|
- */
|
|
|
- protected String htmlResultPath;
|
|
|
-
|
|
|
- /*
|
|
|
- * pdf结果文件路径
|
|
|
- */
|
|
|
- protected String pdfResultPath;
|
|
|
-
|
|
|
- public AbstractNewRender(String cwd) {
|
|
|
- this.cwd = cwd;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Docx 渲染
|
|
|
- *
|
|
|
- *
|
|
|
- * @param inputStream 模板内容
|
|
|
- * @return 本地文件目录
|
|
|
- * @throws IOException
|
|
|
- */
|
|
|
- @SuppressWarnings("checkstyle:ParameterNumber")
|
|
|
- public final String renderDocx(Map<String, Object> dataMap, InputStream inputStream,
|
|
|
- ConfigureBuilder builder, String relationId, String moduleType)
|
|
|
- throws IOException {
|
|
|
-
|
|
|
- // 注: 报告模板的模板变量按照json序列化的结果命名
|
|
|
- String basicPath = this.getBasicPath();
|
|
|
- String reportImagePath = this.getReportImagePath();
|
|
|
- String label = relationId + "_" + moduleType;
|
|
|
- // word导出位置
|
|
|
- String reportTempWordFile = basicPath + "/" + cwd + "/" + label + ".docx";
|
|
|
- // 新增:创建文件夹
|
|
|
- File parentDir = new File(basicPath + "/" + cwd);
|
|
|
- if (!parentDir.exists()) {
|
|
|
- boolean created = parentDir.mkdirs();
|
|
|
- if (!created) {
|
|
|
- throw new IOException("无法创建文件保存目录");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Configure config = builder.build();
|
|
|
- XWPFTemplate template =
|
|
|
- XWPFTemplate.compile(inputStream, config).render(dataMap);
|
|
|
-
|
|
|
-// ArrayList<String> templateDele = new ArrayList<>();
|
|
|
-// for (Map.Entry<String, Object> entry : specialMap.entrySet()) {
|
|
|
-// String key = entry.getKey();
|
|
|
-// Object value = entry.getValue();
|
|
|
-// if (value.equals("")) {
|
|
|
-// templateDele.add(key);
|
|
|
-// }
|
|
|
-// }
|
|
|
-// Map<String, String> keywordMap = new HashMap<>();
|
|
|
-// keywordMap.put("suggestion", "建议");
|
|
|
-// keywordMap.put("score", "分");
|
|
|
-// keywordMap.put("riskSummary", "-");
|
|
|
-// XWPFDocument doc = template.getXWPFDocument();
|
|
|
-// List<XWPFTable> tables = doc.getTables();
|
|
|
-// XWPFTable xwpfTable = tables.get(tables.size() - 1);
|
|
|
-// for (int i = xwpfTable.getNumberOfRows() - 1; i >= 0; i--) {
|
|
|
-// XWPFTableRow row = xwpfTable.getRow(i);
|
|
|
-// StringBuilder rowText = new StringBuilder();
|
|
|
-// for (XWPFTableCell cell : row.getTableCells()) {
|
|
|
-// rowText.append(cell.getText());
|
|
|
-// }
|
|
|
-// for (String codeKeyword : templateDele) {
|
|
|
-// String templateKeyword = keywordMap.get(codeKeyword);
|
|
|
-// if (rowText.toString().contains(templateKeyword)) {
|
|
|
-// xwpfTable.removeRow(i);
|
|
|
-// break;
|
|
|
-// }
|
|
|
-// }
|
|
|
-// }
|
|
|
-
|
|
|
- template.writeToFile(reportTempWordFile);
|
|
|
- template.close();
|
|
|
-
|
|
|
- this.docxResultPath = reportTempWordFile;
|
|
|
- return this.docxResultPath;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * docx转换为pdf
|
|
|
- *
|
|
|
- * @return 本地文件目录
|
|
|
- * @throws IOException
|
|
|
- */
|
|
|
- public final String fromDocxToPdf() throws IOException {
|
|
|
- // 转换 docxResultPath 为 html
|
|
|
- // 加工html
|
|
|
- // 把 html 文件转成 pdf
|
|
|
- fromDocxToHtml();
|
|
|
- this.pdfResultPath = fromHtmlToPdf();
|
|
|
- return this.pdfResultPath;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * docx转换为Html
|
|
|
- *
|
|
|
- * @return 本地文件目录
|
|
|
- * @throws IOException
|
|
|
- */
|
|
|
- public final String fromDocxToHtml() throws IOException {
|
|
|
- // 转换 docxResultPath 为 html
|
|
|
- // 加工html
|
|
|
- this.htmlResultPath = cwd + "/1.html";
|
|
|
- return this.htmlResultPath;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * html转换为pdf
|
|
|
- *
|
|
|
- * @return 本地文件目录
|
|
|
- * @throws IOException
|
|
|
- */
|
|
|
- public final String fromHtmlToPdf() throws IOException {
|
|
|
- // 转换 htmlResultPath 为 pdf
|
|
|
- this.pdfResultPath = cwd + "/1.pdf";
|
|
|
- return this.pdfResultPath;
|
|
|
- }
|
|
|
-
|
|
|
- protected abstract String getBasicPath() throws IOException;
|
|
|
-
|
|
|
- protected abstract String getReportImagePath();
|
|
|
-
|
|
|
- /**
|
|
|
- * 针对于protobuf传递来的数据处理
|
|
|
- *
|
|
|
- * @return
|
|
|
- */
|
|
|
- protected RenderPolicy indicatorsRenderPolicyToProtobuf() {
|
|
|
- return new LoopRowTableRenderPolicy() {
|
|
|
- @Override
|
|
|
- public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
|
|
|
- // 获取模板中的变量如:[name]
|
|
|
- ArrayList<String> strings = processElement(eleTemplate, data);
|
|
|
- List<Map<String, Object>> processedData = null;
|
|
|
- if (null != data && data instanceof Iterable) {
|
|
|
- processedData = (List<Map<String, Object>>) data;
|
|
|
- processedData.forEach(map -> {
|
|
|
- for (String string : strings) {
|
|
|
- Object o = map.get(string);
|
|
|
- if (null == o || "".equals(o)) {
|
|
|
- map.put(string, "-");
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- // 调用父类渲染处理后的数据
|
|
|
- super.render(eleTemplate, processedData, template);
|
|
|
- }
|
|
|
-
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("checkstyle:NestedForDepth")
|
|
|
- private ArrayList<String> processElement(ElementTemplate eleTemplate, Object data) {
|
|
|
- RunTemplate runTemplate = (RunTemplate) eleTemplate;
|
|
|
- XWPFRun run = runTemplate.getRun();
|
|
|
- XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
|
|
|
- XWPFTable table = tagCell.getTableRow().getTable();
|
|
|
- StringBuilder textBuilder = new StringBuilder();
|
|
|
-
|
|
|
- for (XWPFTableRow row : table.getRows()) {
|
|
|
- // 遍历行中的每一个单元格
|
|
|
- for (XWPFTableCell cell : row.getTableCells()) {
|
|
|
- // 提取单元格内的所有文本(包含段落、Run 等)
|
|
|
- for (XWPFParagraph para : cell.getParagraphs()) {
|
|
|
- for (XWPFRun r : para.getRuns()) {
|
|
|
- textBuilder.append(r.getText(0)); // 获取 Run 的文本
|
|
|
- }
|
|
|
- }
|
|
|
- textBuilder.append(" ");
|
|
|
- }
|
|
|
- textBuilder.append(" ");
|
|
|
- }
|
|
|
- ArrayList<String> strings = new ArrayList<>();
|
|
|
- String string = textBuilder.toString();
|
|
|
- String[] split = string.split(" ");
|
|
|
- for (String s : split) {
|
|
|
- Pattern pattern = Pattern.compile("\\[(.*?)\\]");
|
|
|
- Matcher matcher = pattern.matcher(s);
|
|
|
- while (matcher.find()) {
|
|
|
- String contentInBracket = matcher.group(1);
|
|
|
- strings.add(contentInBracket);
|
|
|
- }
|
|
|
- }
|
|
|
- return strings;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 针对数据存在
|
|
|
- *
|
|
|
- * @return
|
|
|
- */
|
|
|
- public RenderPolicy deleteLoopRowTableRenderPolicy() {
|
|
|
- return new LoopRowTableRenderPolicy() {
|
|
|
- @Override
|
|
|
- public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
|
|
|
- super.render(eleTemplate, data, template);
|
|
|
- ArrayList<Map<String, String>> data1 = (ArrayList<Map<String, String>>) data;
|
|
|
- if (CollectionUtils.isEmpty(data1)) {
|
|
|
- RunTemplate runTemplate = (RunTemplate) eleTemplate;
|
|
|
- XWPFRun run = runTemplate.getRun();
|
|
|
- XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
|
|
|
- XWPFTable table = tagCell.getTableRow().getTable();
|
|
|
- XWPFDocument doc = template.getXWPFDocument();
|
|
|
-
|
|
|
- int tablePos = -1;
|
|
|
- List<IBodyElement> bodyElements = doc.getBodyElements();
|
|
|
- for (int i = 0; i < bodyElements.size(); i++) {
|
|
|
- if (bodyElements.get(i) == table) {
|
|
|
- tablePos = i;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if (tablePos >= 0) {
|
|
|
- // 4.1 先删除前一个段落(如果存在且是段落)
|
|
|
- int paragraphsToDelete = 2; // 要删除的段落数量
|
|
|
- for (int i = 0; i < paragraphsToDelete && tablePos - 1 >= 0; i++) {
|
|
|
- IBodyElement prevElement = doc.getBodyElements().get(tablePos - 1);
|
|
|
- if (prevElement instanceof XWPFParagraph) {
|
|
|
- doc.removeBodyElement(tablePos - 1);
|
|
|
- tablePos--; // 每删除一个段落,表格位置前移
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 4.2 强制删除表格(通过操作底层 XML)
|
|
|
- doc.removeBodyElement(tablePos);
|
|
|
- doc.getTables();
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- // // 辅助方法:在XML中找到表格的实际位置
|
|
|
- // private int findTablePositionInXML(CTBody body, XWPFTable table) {
|
|
|
- // List<CTP> paragraphs = body.getPList();
|
|
|
- // List<CTTbl> tables = body.getTblList();
|
|
|
- //
|
|
|
- // for (int i = 0; i < tables.size(); i++) {
|
|
|
- // if (tables.get(i) == table.getCTTbl()) {
|
|
|
- // return i;
|
|
|
- // }
|
|
|
- // }
|
|
|
- // return -1;
|
|
|
- // }
|
|
|
-
|
|
|
- // int xmlTblPos = findTablePositionInXML(body, table);
|
|
|
- // if (xmlTblPos >= 0) {
|
|
|
- // body.removeTbl(xmlTblPos);
|
|
|
- // }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 针对于json格式全量传输数据的列表处理 将默认值null转为“-”
|
|
|
- *
|
|
|
- * @return
|
|
|
- */
|
|
|
- protected RenderPolicy indicatorsRenderPolicy() {
|
|
|
- return new LoopRowTableRenderPolicy() {
|
|
|
- @Override
|
|
|
- public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
|
|
|
- // 处理数据中的null值
|
|
|
- Object processedData = processData(data);
|
|
|
- // 调用父类渲染处理后的数据
|
|
|
- super.render(eleTemplate, processedData, template);
|
|
|
- }
|
|
|
-
|
|
|
- private Object processData(Object data) {
|
|
|
- if (data == null) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- if (data instanceof List) {
|
|
|
- // 处理List类型数据
|
|
|
- return ((List<?>) data).stream()
|
|
|
- .map(this::processItem)
|
|
|
- .collect(Collectors.toList());
|
|
|
- }
|
|
|
-
|
|
|
- return data;
|
|
|
- }
|
|
|
-
|
|
|
- private Object processItem(Object item) {
|
|
|
- if (item == null) {
|
|
|
- return "-";
|
|
|
- }
|
|
|
-
|
|
|
- // 如果元素是Map,处理Map中的null值
|
|
|
- if (item instanceof Map) {
|
|
|
- Map<?, ?> map = (Map<?, ?>) item;
|
|
|
- return map.entrySet().stream()
|
|
|
- .collect(Collectors.toMap(
|
|
|
- Map.Entry::getKey,
|
|
|
- e -> e.getValue() == null ? "-" : e.getValue()));
|
|
|
- }
|
|
|
-
|
|
|
- return item;
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 这些render policy类都应当是共享的 重要设计假设: data的类型cast都可以建立在json通用反序列化后的基本类型基础上。
|
|
|
- */
|
|
|
- protected RenderPolicy pictureRenderPolicy() {
|
|
|
- return new PictureRenderPolicy() {
|
|
|
- @Override
|
|
|
- public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- Map<String, Object> mpData = (Map<String, Object>) data;
|
|
|
- String filename = mpData.getOrDefault("fileName", "").toString().trim();
|
|
|
- String url = mpData.getOrDefault("fileUri", "").toString();
|
|
|
- float targetWidth = 456.5f;
|
|
|
-
|
|
|
- if (StrUtil.isBlank(filename)) {
|
|
|
- // uri render when no filename
|
|
|
- TextRenderPolicy.Helper.renderTextRun(((RunTemplate) eleTemplate).getRun(),
|
|
|
- new HyperlinkTextRenderData(url, url));
|
|
|
- } else if (filename.endsWith(".pdf")) {
|
|
|
- // pdf render, replace data with bytestream
|
|
|
- PDDocument document = null;
|
|
|
- try {
|
|
|
- document = Loader.loadPDF(new URL(url).openStream().readAllBytes());
|
|
|
- PDFRenderer renderer = new PDFRenderer(document);
|
|
|
-
|
|
|
- XWPFRun run = ((RunTemplate) eleTemplate).getRun();
|
|
|
- BodyContainer bodyContainer = BodyContainerFactory.getBodyContainer(run);
|
|
|
- // 每页一张图片
|
|
|
- for (int i = 0; i < document.getNumberOfPages(); i++) {
|
|
|
- // pdf 转 jpeg
|
|
|
- BufferedImage image = renderer.renderImageWithDPI(i, 150);
|
|
|
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
|
- ImageIO.write(image, "jpg", stream);
|
|
|
-
|
|
|
- // 准备docx元素
|
|
|
- if (i == 0) {
|
|
|
- run.setText("", 0);
|
|
|
- } else {
|
|
|
- run = bodyContainer.insertNewParagraph(run).createRun();
|
|
|
- }
|
|
|
-
|
|
|
- // 准备POI-TL数据
|
|
|
- ByteArrayPictureRenderData picData =
|
|
|
- new ByteArrayPictureRenderData(stream.toByteArray(), PictureType.JPEG);
|
|
|
- Pair<Integer, Integer> targetSize =
|
|
|
- calculateTargetSize(image.getWidth(), image.getHeight(), targetWidth);
|
|
|
- PictureStyle style = new PictureStyle();
|
|
|
- style.setWidth(targetSize.getKey());
|
|
|
- style.setHeight(targetSize.getValue());
|
|
|
- picData.setPictureStyle(style);
|
|
|
- Helper.renderPicture(run, picData);
|
|
|
- }
|
|
|
- } catch (Exception e) {
|
|
|
- throw new RenderException(
|
|
|
- "AttachmentRenderPolicy for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
- } finally {
|
|
|
- if (document != null) {
|
|
|
- try {
|
|
|
- document.close();
|
|
|
- } catch (IOException e) {
|
|
|
- e.printStackTrace();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- BufferedImage image = null;
|
|
|
- ByteArrayOutputStream stream = null;
|
|
|
- try {
|
|
|
- image = ImageIO.read(new URL(url));
|
|
|
- stream = new ByteArrayOutputStream();
|
|
|
- ImageIO.write(image, getFileExtension(filename), stream);
|
|
|
- } catch (IOException e) {
|
|
|
- throw new RenderException(
|
|
|
- "AttachmentRenderPolicy for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
- }
|
|
|
-
|
|
|
- ByteArrayPictureRenderData picData =
|
|
|
- new ByteArrayPictureRenderData(stream.toByteArray(), PictureType.suggestFileType(filename));
|
|
|
- Pair<Integer, Integer> targetSize =
|
|
|
- calculateTargetSize(image.getWidth(), image.getHeight(), targetWidth);
|
|
|
- PictureStyle style = new PictureStyle();
|
|
|
- style.setWidth(targetSize.getKey());
|
|
|
- style.setHeight(targetSize.getValue());
|
|
|
- picData.setPictureStyle(style);
|
|
|
- super.render(eleTemplate, picData, template);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 计算目标尺寸(提取为内部方法)
|
|
|
- */
|
|
|
- private Pair<Integer, Integer> calculateTargetSize(int originWidth, int originHeight, float targetWidth) {
|
|
|
- float targetHeight = (originHeight * targetWidth) / originWidth;
|
|
|
- if (targetHeight > 645) {
|
|
|
- targetHeight = 645;
|
|
|
- targetWidth = (originWidth * targetHeight) / originHeight;
|
|
|
- }
|
|
|
- return Pair.of(Math.round(targetWidth), Math.round(targetHeight));
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 获取文件扩展名
|
|
|
- */
|
|
|
- private String getFileExtension(String filename) {
|
|
|
- int dotIndex = filename.lastIndexOf('.');
|
|
|
- return dotIndex > 0 ? filename.substring(dotIndex + 1).toLowerCase() : "jpg";
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 超链接列表策略
|
|
|
- *
|
|
|
- * @return
|
|
|
- */
|
|
|
- protected RenderPolicy hyperlinkRenderPolicy() {
|
|
|
- return new ParagraphRenderPolicy() {
|
|
|
- @Override
|
|
|
- public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
|
|
|
- // 获取当前运行的
|
|
|
- XWPFParagraph paragraph = ((RunTemplate) eleTemplate).getRun().getParagraph();
|
|
|
- int pos = paragraph.getRuns().indexOf(((RunTemplate) eleTemplate).getRun());
|
|
|
- paragraph.removeRun(pos);
|
|
|
- paragraph.removeRun(pos);
|
|
|
- if (data instanceof String) {
|
|
|
- XWPFHyperlinkRun hyperlinkRun = paragraph.createHyperlinkRun(data.toString());
|
|
|
- hyperlinkRun.setText(data.toString());
|
|
|
- hyperlinkRun.setColor("0000FF");
|
|
|
- hyperlinkRun.setUnderline(UnderlinePatterns.SINGLE);
|
|
|
- hyperlinkRun.setFontFamily("思源黑体 Medium");
|
|
|
- hyperlinkRun.setFontSize(10.5);
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- protected RenderPolicy getScoreRenderPolicy() {
|
|
|
- RenderPolicy policy = new LoopRowTableRenderPolicy() {
|
|
|
- @SuppressWarnings("checkstyle:NestedForDepth")
|
|
|
- @Override
|
|
|
- public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
|
|
|
- // 检查数据是否为空
|
|
|
- if (data == null || (data instanceof Collection && ((Collection<?>) data).isEmpty())) {
|
|
|
- // 数据为空时,删除整个表格
|
|
|
- // removeTemplateTable(eleTemplate, template);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- super.render(eleTemplate, data, template);
|
|
|
- XWPFDocument doc = template.getXWPFDocument();
|
|
|
- for (XWPFTable table : doc.getTables()) {
|
|
|
- boolean isTargetTable = false;
|
|
|
- // 判断是否为目标表格:通过检查表格第一行是否包含货物明细相关表头关键词
|
|
|
- List<String> headerKeywords = List.of("删除");
|
|
|
- XWPFTableRow firstRow = table.getRow(0);
|
|
|
- if (firstRow != null) {
|
|
|
- for (XWPFTableCell cell : firstRow.getTableCells()) {
|
|
|
- for (XWPFParagraph para : cell.getParagraphs()) {
|
|
|
- for (XWPFRun run : para.getRuns()) {
|
|
|
- String text = run.text().trim();
|
|
|
- if (headerKeywords.contains(text)) {
|
|
|
- isTargetTable = true;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if (isTargetTable) {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if (isTargetTable) {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if (isTargetTable) {
|
|
|
- // 删除表头(假设表头是第一行)
|
|
|
- if (table.getRows().size() > 0) {
|
|
|
- table.removeRow(0);
|
|
|
- }
|
|
|
- // 合并第一列相同内容单元格并居中
|
|
|
- mergeAndCenterFirstColumn(table);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void mergeAndCenterFirstColumn(XWPFTable table) {
|
|
|
- if (table.getNumberOfRows() < 2) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- int startRow = 0;
|
|
|
- String currentValue = getCellText(table.getRow(0)).get(0);
|
|
|
-
|
|
|
- for (int i = 1; i < table.getNumberOfRows(); i++) {
|
|
|
- List<String> rowValues = getCellText(table.getRow(i));
|
|
|
- if (rowValues.isEmpty()) {
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- String firstCellValue = rowValues.get(0);
|
|
|
- if (firstCellValue.equals(currentValue)) {
|
|
|
- // 继续查找相同值的行
|
|
|
- continue;
|
|
|
- } else {
|
|
|
- // 合并从startRow到i-1行的第一列单元格
|
|
|
- if (i - 1 > startRow) {
|
|
|
- TableTools.mergeCellsVertically(table, 0, startRow, i - 1);
|
|
|
- // 设置合并后单元格内容居中
|
|
|
- setCellCenterAlignment(table.getRow(startRow).getCell(0));
|
|
|
- }
|
|
|
- startRow = i;
|
|
|
- currentValue = firstCellValue;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 处理最后一组相同值
|
|
|
- if (table.getNumberOfRows() - 1 > startRow) {
|
|
|
- TableTools.mergeCellsVertically(table, 0, startRow, table.getNumberOfRows() - 1);
|
|
|
- setCellCenterAlignment(table.getRow(startRow).getCell(0));
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
- return policy;
|
|
|
- }
|
|
|
-
|
|
|
- private List<String> getCellText(XWPFTableRow row) {
|
|
|
- List<String> texts = new ArrayList<>();
|
|
|
- if (row == null) {
|
|
|
- return texts;
|
|
|
- }
|
|
|
- for (XWPFTableCell cell : row.getTableCells()) {
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- for (XWPFParagraph para : cell.getParagraphs()) {
|
|
|
- for (XWPFRun run : para.getRuns()) {
|
|
|
- sb.append(run.text());
|
|
|
- }
|
|
|
- }
|
|
|
- texts.add(sb.toString().trim());
|
|
|
- }
|
|
|
- return texts;
|
|
|
- }
|
|
|
-
|
|
|
- private void setCellCenterAlignment(XWPFTableCell cell) {
|
|
|
- CTTc cttc = cell.getCTTc();
|
|
|
- CTTcPr ctPr = cttc.addNewTcPr();
|
|
|
- ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 这些render policy类都应当是共享的 重要设计假设: data的类型cast都可以建立在json通用反序列化后的基本类型基础上。
|
|
|
- */
|
|
|
- public class LoopColumnStaticTableRenderPolicy implements RenderPolicy {
|
|
|
-
|
|
|
- private String prefix;
|
|
|
- private String suffix;
|
|
|
- private boolean onSameLine;
|
|
|
- private boolean reverse;
|
|
|
- private int valColIndex;
|
|
|
-
|
|
|
- public LoopColumnStaticTableRenderPolicy() {
|
|
|
- this(false);
|
|
|
- }
|
|
|
-
|
|
|
- public LoopColumnStaticTableRenderPolicy(boolean onSameLine) {
|
|
|
- this("[", "]", onSameLine);
|
|
|
- }
|
|
|
-
|
|
|
- public LoopColumnStaticTableRenderPolicy(String prefix, String suffix) {
|
|
|
- this(prefix, suffix, false);
|
|
|
- }
|
|
|
-
|
|
|
- public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine) {
|
|
|
- this(prefix, suffix, onSameLine, false);
|
|
|
- }
|
|
|
-
|
|
|
- public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse) {
|
|
|
- this(prefix, suffix, onSameLine, false, 1);
|
|
|
- }
|
|
|
-
|
|
|
- public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse,
|
|
|
- int valRowIndex) {
|
|
|
- this.prefix = prefix;
|
|
|
- this.suffix = suffix;
|
|
|
- this.onSameLine = onSameLine;
|
|
|
- this.reverse = reverse;
|
|
|
- this.valColIndex = valRowIndex;
|
|
|
- }
|
|
|
-
|
|
|
- @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 templateColIndex = getTemplateColIndex(tagCell);
|
|
|
- // 模版变量列总是写在左边
|
|
|
- int minIndex = templateColIndex;
|
|
|
- int maxIndex = table.getRows().get(valColIndex).getTableCells().size() - 1;
|
|
|
- int currIndex = reverse ? maxIndex : minIndex;
|
|
|
- int indexDelta = reverse ? -1 : 1;
|
|
|
-
|
|
|
- // 目前expression当作数据Map的key来对待,将来可以当作POI-TL变量一致化的处理
|
|
|
- Map<Integer, String> idx2Expression = new HashMap<>();
|
|
|
- for (int i = 0; i < table.getRows().size(); i++) {
|
|
|
- XWPFTableCell cell = table.getRows().get(i).getCell(templateColIndex);
|
|
|
- String text = cell.getText().trim();
|
|
|
- if (text.startsWith(prefix) && text.endsWith(suffix)) {
|
|
|
- idx2Expression.put(i, text.substring(1, text.length() - 1));
|
|
|
- cell.setText("");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- int rowSize = table.getRows().size();
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
|
|
|
-
|
|
|
- for (Map<String, Object> realData : mpData) {
|
|
|
- for (int i = 0; i < rowSize; i++) {
|
|
|
- if (!idx2Expression.containsKey(i)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- XWPFTableRow row = table.getRow(i);
|
|
|
- XWPFTableCell valueCell = row.getCell(currIndex);
|
|
|
- String valStr = realData.getOrDefault(idx2Expression.get(i), "-").toString();
|
|
|
- valueCell.setText(valStr);
|
|
|
- }
|
|
|
- currIndex += indexDelta;
|
|
|
- }
|
|
|
-
|
|
|
- } catch (Exception e) {
|
|
|
- throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private int getTemplateColIndex(XWPFTableCell tagCell) {
|
|
|
- return onSameLine ? Util.getColIndexOfFirstRow(tagCell) : (Util.getColIndexOfFirstRow(tagCell) + 1);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public class LoopRowCutAndMergeFirstColTableRenderPolicy extends LoopRowTableRenderPolicy {
|
|
|
-
|
|
|
- @Override
|
|
|
- public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
|
|
|
- RunTemplate runTemplate = (RunTemplate) eleTemplate;
|
|
|
- XWPFRun run = runTemplate.getRun();
|
|
|
- XWPFTable table = null;
|
|
|
- try {
|
|
|
- if (!TableTools.isInsideTable(run)) {
|
|
|
- throw new IllegalStateException(
|
|
|
- "The template tag " + runTemplate.getSource() + " must be inside a table");
|
|
|
- }
|
|
|
- // Reserve the first two rows
|
|
|
- XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
|
|
|
- table = tagCell.getTableRow().getTable();
|
|
|
- for (int i = table.getNumberOfRows() - 1; i > 1; i--) {
|
|
|
- table.removeRow(i);
|
|
|
- }
|
|
|
- } catch (Exception e) {
|
|
|
- throw new RenderException(
|
|
|
- "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
- }
|
|
|
-
|
|
|
- // in case data not sorted by rank
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
|
|
|
- mpData.sort((a, b) -> Integer.valueOf(a.getOrDefault("rank", "0").toString())
|
|
|
- - Integer.valueOf(b.getOrDefault("rank", "0").toString()));
|
|
|
-
|
|
|
- super.render(eleTemplate, data, template);
|
|
|
-
|
|
|
- try {
|
|
|
- // merge the first column
|
|
|
- Util.mergeFirstNColSimple(table, 1, 0);
|
|
|
- } catch (Exception e) {
|
|
|
- throw new RenderException(
|
|
|
- "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 财务信息计算平均值的策略
|
|
|
- */
|
|
|
- public class LoopRowIncludeStatisticsTableRenderPolicy extends LoopRowTableRenderPolicy {
|
|
|
-
|
|
|
- private String valueTag;
|
|
|
-
|
|
|
- public LoopRowIncludeStatisticsTableRenderPolicy(String valueTag) {
|
|
|
- this.valueTag = valueTag;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
|
|
|
-
|
|
|
- RunTemplate runTemplate = (RunTemplate) eleTemplate;
|
|
|
- XWPFRun run = runTemplate.getRun();
|
|
|
-
|
|
|
- XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
|
|
|
- XWPFTable table = tagCell.getTableRow().getTable();
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
|
|
|
-
|
|
|
- for (Map<String, Object> row : mpData) {
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- List<String> values = (List<String>) row.get(valueTag);
|
|
|
-
|
|
|
- // 检查是否有百分号结尾的值
|
|
|
- boolean hasPercentage = values.stream().anyMatch(v -> v != null && v.endsWith("%"));
|
|
|
-
|
|
|
- // 处理百分号并转换为数值
|
|
|
- List<Double> processedValues = values.stream()
|
|
|
- .filter(Objects::nonNull)
|
|
|
- .map(v -> {
|
|
|
- if (v.endsWith("%")) {
|
|
|
- return v.substring(0, v.length() - 1);
|
|
|
- }
|
|
|
- return v;
|
|
|
- })
|
|
|
- .map(v -> {
|
|
|
- try {
|
|
|
- return Double.valueOf(v);
|
|
|
- } catch (NumberFormatException e) {
|
|
|
- return Double.NaN;
|
|
|
- }
|
|
|
- })
|
|
|
- .filter(v -> !Double.isNaN(v))
|
|
|
- .collect(Collectors.toList());
|
|
|
-
|
|
|
- // 计算平均值并保留两位小数
|
|
|
- double avg = processedValues.stream()
|
|
|
- .mapToDouble(Double::doubleValue)
|
|
|
- .average()
|
|
|
- .orElse(Double.NaN);
|
|
|
-
|
|
|
- // 格式化结果
|
|
|
- String formattedAvg;
|
|
|
- if (Double.isNaN(avg)) {
|
|
|
- formattedAvg = "-"; // 处理无有效数据的情况
|
|
|
- } else {
|
|
|
- DecimalFormat df = new DecimalFormat("#0.00"); // 保留两位小数
|
|
|
- formattedAvg = df.format(avg);
|
|
|
- if (hasPercentage) {
|
|
|
- formattedAvg += "%"; // 添加百分号
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- row.put("avg", formattedAvg);
|
|
|
- }
|
|
|
-
|
|
|
- super.render(eleTemplate, data, template);
|
|
|
- try {
|
|
|
- // merge the first column
|
|
|
- Util.mergeFirstNColSimple(table, 1, 0);
|
|
|
- } catch (Exception e) {
|
|
|
- throw new RenderException(
|
|
|
- "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|