package com.yaoyicloud.render; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.yaoyicloud.config.FilerepoProperties; import com.yaoyicloud.message.FxyProtos; import lombok.extern.slf4j.Slf4j; 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 com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; import com.deepoove.poi.config.ConfigureBuilder; import com.deepoove.poi.exception.RenderException; import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; import com.deepoove.poi.policy.RenderPolicy; import com.deepoove.poi.template.ElementTemplate; import com.deepoove.poi.template.run.RunTemplate; import com.deepoove.poi.util.TableTools; import com.google.protobuf.util.JsonFormat; import com.yaoyicloud.message.FxyProtos.FinancialInfo; import com.yaoyicloud.tools.Util; /** * FinancialInfo渲染器 * */ @Slf4j public final class FinancialInfoRender extends AbstractRender { private final FilerepoProperties filerepoProperties; public FinancialInfoRender(String cwd, FilerepoProperties filerepoProperties, FilerepoProperties filerepoProperties1) { super(cwd); this.filerepoProperties = filerepoProperties1; } @Override protected String getBasicPath() throws IOException { return filerepoProperties.getBasePath(); } @Override protected String getReportImagePath() { return filerepoProperties.getReportImagePath(); } /** * Docx 渲染 * * @param info 数据 * @param templateFileContent 模板内容 * @return 本地文件目录 * @throws IOException */ public String renderDocx(String info, Map addtionalMap, byte[] templateFileContent, String relationId) throws IOException { log.info("开始渲染附件模块,relationId: {}", relationId); // 配置POI-TL渲染器 ConfigureBuilder builder = Configure.builder(); RenderPolicy indicatorsRenderPolicy = this.indicatorsRenderPolicy(); builder.bind("basicInfoChecks", indicatorsRenderPolicy); FxyProtos.BasicInfo.Builder basicInfoBuilder = FxyProtos.BasicInfo.newBuilder(); JsonFormat.parser().merge(info, basicInfoBuilder); FxyProtos.BasicInfo defaultInstance = FxyProtos.BasicInfo.getDefaultInstance(); FxyProtos.BasicInfo mergedProto = defaultInstance.toBuilder() .mergeFrom(basicInfoBuilder.build()) .build(); String completeJson = JsonFormat.printer() .includingDefaultValueFields() .print(mergedProto); ObjectMapper objectMapper = new ObjectMapper(); Map data = objectMapper.readValue(completeJson, new TypeReference>() {}); data.replaceAll((k, v) -> v.equals("") ? "-" : v); if (addtionalMap != null) { data.putAll(addtionalMap); } fillBasicDefaultValues(data); try { // 渲染文档 String resultPath = this.renderDocx(data, templateFileContent, builder, relationId); log.info("渲染附件模块成功,文件路径: {}", resultPath); return resultPath; } catch (Exception e) { log.error("渲染附件模块失败,relationId: {}", relationId, e); throw new IOException("文档渲染失败", e); } } /** * 填充默认值,确保所有必要字段都存在 */ private void fillBasicDefaultValues(Map data) { Map basicInfoSummary = (Map) data.get("basicInfoSummary"); basicInfoSummary.replaceAll((k, v) -> v.equals("") ? "-" : v); Map platformExt = (Map) data.get("platformExt"); platformExt.replaceAll((k, v) -> v.equals("") ? "-" : v); Object certReceived = data.get("certReceived"); if (certReceived.equals("-")) { data.put("certReceived", "否"); } Object bankLicense = data.get("bankLicense"); if (bankLicense.equals("-")) { data.put("bankLicense", "否"); } } // public String renderDocx(FinancialInfo info, Map addtionalMap, byte[] templateFileContent) throws IOException { // // // 不需要定制展示逻辑的时候,使用protobuf的转json方法 // String jsonStr = JsonFormat.printer().print(info); // // // 注: 报告模板的模板变量按照json序列化的结果命名 // // 注: 目前的实现假设:一个session对应一个cwd目录 // ConfigureBuilder builder = Configure.builder(); // builder.bind("indicators", new LoopRowIncludeStatisticsTableRenderPolicy("values")); // builder.bind("financialDataSeq", new LoopColumnStaticTableRenderPolicy("[", "]", false, true, 2)); // builder.bind("financialCheckDetails", new LoopRowCutAndMergeFirstColTableRenderPolicy()); // // 注意使用了SpringEL之后,每个模板变量都要设置值,不然会报错 // builder.useSpringEL(); // // this.docxResultPath = this.renderDocx(jsonStr, addtionalMap, templateFileContent, builder, // Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString()); // return this.docxResultPath; // } /** * 这些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 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> mpData = (List>) data; for (Map 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> mpData = (List>) 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) { @SuppressWarnings("unchecked") List> mpData = (List>) data; for (Map row : mpData) { @SuppressWarnings("unchecked") List values = (List) row.get(valueTag); row.put("avg", values.stream().mapToLong(Long::valueOf).average().orElse(Double.NaN)); } super.render(eleTemplate, data, template); } } }