|
@@ -13,6 +13,8 @@ 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 cn.hutool.core.lang.Pair;
|
|
@@ -87,7 +89,7 @@ public abstract class AbstractRender {
|
|
|
/**
|
|
|
* Docx 渲染
|
|
|
*
|
|
|
-
|
|
|
+ *
|
|
|
* @param templateFileContent 模板内容
|
|
|
* @return 本地文件目录
|
|
|
* @throws IOException
|
|
@@ -99,7 +101,7 @@ public abstract class AbstractRender {
|
|
|
String basicPath = this.getBasicPath();
|
|
|
String reportImagePath = this.getReportImagePath();
|
|
|
String label = relationId + "_" + moduleType;
|
|
|
- //word导出位置
|
|
|
+ // word导出位置
|
|
|
String reportTempWordFile = basicPath + "/" + cwd + "/" + label + ".docx";
|
|
|
// 新增:创建文件夹
|
|
|
File parentDir = new File(basicPath + "/" + cwd);
|
|
@@ -159,8 +161,78 @@ public abstract class AbstractRender {
|
|
|
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);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 针对于json格式全量传输数据的列表处理 将默认值null转为“-”
|
|
|
+ * @return
|
|
|
+ */
|
|
|
protected RenderPolicy indicatorsRenderPolicy() {
|
|
|
return new LoopRowTableRenderPolicy() {
|
|
|
@Override
|
|
@@ -179,8 +251,8 @@ public abstract class AbstractRender {
|
|
|
if (data instanceof List) {
|
|
|
// 处理List类型数据
|
|
|
return ((List<?>) data).stream()
|
|
|
- .map(this::processItem)
|
|
|
- .collect(Collectors.toList());
|
|
|
+ .map(this::processItem)
|
|
|
+ .collect(Collectors.toList());
|
|
|
}
|
|
|
|
|
|
return data;
|
|
@@ -195,16 +267,16 @@ public abstract class AbstractRender {
|
|
|
if (item instanceof Map) {
|
|
|
Map<?, ?> map = (Map<?, ?>) item;
|
|
|
return map.entrySet().stream()
|
|
|
- .collect(Collectors.toMap(
|
|
|
- Map.Entry::getKey,
|
|
|
- e -> e.getValue() == null ? "-" : e.getValue()
|
|
|
- ));
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ Map.Entry::getKey,
|
|
|
+ e -> e.getValue() == null ? "-" : e.getValue()));
|
|
|
}
|
|
|
|
|
|
return item;
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
+
|
|
|
/**
|
|
|
* 这些render policy类都应当是共享的 重要设计假设: data的类型cast都可以建立在json通用反序列化后的基本类型基础上。
|
|
|
*/
|
|
@@ -221,7 +293,7 @@ public abstract class AbstractRender {
|
|
|
if (StrUtil.isBlank(filename)) {
|
|
|
// uri render when no filename
|
|
|
TextRenderPolicy.Helper.renderTextRun(((RunTemplate) eleTemplate).getRun(),
|
|
|
- new HyperlinkTextRenderData(url, url));
|
|
|
+ new HyperlinkTextRenderData(url, url));
|
|
|
} else if (filename.endsWith(".pdf")) {
|
|
|
// pdf render, replace data with bytestream
|
|
|
PDDocument document = null;
|
|
@@ -247,9 +319,9 @@ public abstract class AbstractRender {
|
|
|
|
|
|
// 准备POI-TL数据
|
|
|
ByteArrayPictureRenderData picData =
|
|
|
- new ByteArrayPictureRenderData(stream.toByteArray(), PictureType.JPEG);
|
|
|
+ new ByteArrayPictureRenderData(stream.toByteArray(), PictureType.JPEG);
|
|
|
Pair<Integer, Integer> targetSize =
|
|
|
- calculateTargetSize(image.getWidth(), image.getHeight(), targetWidth);
|
|
|
+ calculateTargetSize(image.getWidth(), image.getHeight(), targetWidth);
|
|
|
PictureStyle style = new PictureStyle();
|
|
|
style.setWidth(targetSize.getKey());
|
|
|
style.setHeight(targetSize.getValue());
|
|
@@ -258,7 +330,7 @@ public abstract class AbstractRender {
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
throw new RenderException(
|
|
|
- "AttachmentRenderPolicy for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
+ "AttachmentRenderPolicy for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
} finally {
|
|
|
if (document != null) {
|
|
|
try {
|
|
@@ -277,12 +349,13 @@ public abstract class AbstractRender {
|
|
|
ImageIO.write(image, getFileExtension(filename), stream);
|
|
|
} catch (IOException e) {
|
|
|
throw new RenderException(
|
|
|
- "AttachmentRenderPolicy for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
+ "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);
|
|
|
+ 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());
|
|
@@ -313,7 +386,7 @@ public abstract class AbstractRender {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- protected RenderPolicy hyperlinkRenderPolicy() {
|
|
|
+ protected RenderPolicy hyperlinkRenderPolicy() {
|
|
|
return new ParagraphRenderPolicy() {
|
|
|
@Override
|
|
|
public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
|
|
@@ -335,7 +408,6 @@ public abstract class AbstractRender {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
-
|
|
|
protected RenderPolicy getScoreRenderPolicy() {
|
|
|
RenderPolicy policy = new LoopRowTableRenderPolicy() {
|
|
|
@SuppressWarnings("checkstyle:NestedForDepth")
|
|
@@ -344,7 +416,7 @@ public abstract class AbstractRender {
|
|
|
// 检查数据是否为空
|
|
|
if (data == null || (data instanceof Collection && ((Collection<?>) data).isEmpty())) {
|
|
|
// 数据为空时,删除整个表格
|
|
|
- // removeTemplateTable(eleTemplate, template);
|
|
|
+ // removeTemplateTable(eleTemplate, template);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -424,6 +496,7 @@ public abstract class AbstractRender {
|
|
|
};
|
|
|
return policy;
|
|
|
}
|
|
|
+
|
|
|
private List<String> getCellText(XWPFTableRow row) {
|
|
|
List<String> texts = new ArrayList<>();
|
|
|
if (row == null) {
|
|
@@ -446,6 +519,7 @@ public abstract class AbstractRender {
|
|
|
CTTcPr ctPr = cttc.addNewTcPr();
|
|
|
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
|
|
|
}
|
|
|
+
|
|
|
/**
|
|
|
* 这些render policy类都应当是共享的 重要设计假设: data的类型cast都可以建立在json通用反序列化后的基本类型基础上。
|
|
|
*/
|
|
@@ -478,7 +552,7 @@ public abstract class AbstractRender {
|
|
|
}
|
|
|
|
|
|
public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse,
|
|
|
- int valRowIndex) {
|
|
|
+ int valRowIndex) {
|
|
|
this.prefix = prefix;
|
|
|
this.suffix = suffix;
|
|
|
this.onSameLine = onSameLine;
|
|
@@ -493,7 +567,7 @@ public abstract class AbstractRender {
|
|
|
try {
|
|
|
if (!TableTools.isInsideTable(run)) {
|
|
|
throw new IllegalStateException(
|
|
|
- "The template tag " + runTemplate.getSource() + " must be inside a table");
|
|
|
+ "The template tag " + runTemplate.getSource() + " must be inside a table");
|
|
|
}
|
|
|
XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
|
|
|
XWPFTable table = tagCell.getTableRow().getTable();
|
|
@@ -554,7 +628,7 @@ public abstract class AbstractRender {
|
|
|
try {
|
|
|
if (!TableTools.isInsideTable(run)) {
|
|
|
throw new IllegalStateException(
|
|
|
- "The template tag " + runTemplate.getSource() + " must be inside a table");
|
|
|
+ "The template tag " + runTemplate.getSource() + " must be inside a table");
|
|
|
}
|
|
|
// Reserve the first two rows
|
|
|
XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
|
|
@@ -564,14 +638,14 @@ public abstract class AbstractRender {
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
throw new RenderException(
|
|
|
- "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
+ "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()));
|
|
|
+ - Integer.valueOf(b.getOrDefault("rank", "0").toString()));
|
|
|
|
|
|
super.render(eleTemplate, data, template);
|
|
|
|
|
@@ -580,7 +654,7 @@ public abstract class AbstractRender {
|
|
|
Util.mergeFirstNColSimple(table, 1, 0);
|
|
|
} catch (Exception e) {
|
|
|
throw new RenderException(
|
|
|
- "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
+ "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -607,28 +681,28 @@ public abstract class AbstractRender {
|
|
|
|
|
|
// 处理百分号并转换为数值
|
|
|
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());
|
|
|
+ .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);
|
|
|
+ .mapToDouble(Double::doubleValue)
|
|
|
+ .average()
|
|
|
+ .orElse(Double.NaN);
|
|
|
|
|
|
// 格式化结果
|
|
|
String formattedAvg;
|