Archived
refactor: 用直接XML预处理替换poi-tl模板引擎 — 合并Run+替换占位符在ZIP层完成,减少依赖
This commit is contained in:
@@ -11,9 +11,9 @@ import java.util.zip.ZipEntry;
|
|||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.deepoove.poi.XWPFTemplate;
|
|
||||||
import com.gmp.mock.template.TemplateProvider;
|
import com.gmp.mock.template.TemplateProvider;
|
||||||
import com.gmp.mock.template.TemplateRegistry;
|
import com.gmp.mock.template.TemplateRegistry;
|
||||||
|
|
||||||
@@ -38,22 +38,17 @@ public class DocxService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Pre-process: merge split runs
|
// 1. Pre-process: merge split runs + replace all placeholders in XML
|
||||||
byte[] preprocessed = mergeSplitPlaceholders(provider.getTemplateResource());
|
byte[] processed = preprocessTemplate(provider.getTemplateResource(), provider.getDataModel(), businessData);
|
||||||
|
|
||||||
// 2. Build data model
|
// 2. Load as XWPFDocument for post-processing
|
||||||
Map<String, Object> data = new java.util.HashMap<>(provider.getDataModel());
|
XWPFDocument document = new XWPFDocument(new ByteArrayInputStream(processed));
|
||||||
if (businessData != null) {
|
provider.postProcess(document);
|
||||||
data.putAll(businessData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Render with poi-tl
|
// 3. Write to output
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
XWPFTemplate template = XWPFTemplate.compile(new ByteArrayInputStream(preprocessed));
|
document.write(baos);
|
||||||
template.render(data);
|
document.close();
|
||||||
provider.postProcess(template.getXWPFDocument());
|
|
||||||
template.write(baos);
|
|
||||||
template.close();
|
|
||||||
|
|
||||||
// 4. Cache to disk
|
// 4. Cache to disk
|
||||||
String key = fileCache.store(baos.toByteArray());
|
String key = fileCache.store(baos.toByteArray());
|
||||||
@@ -67,13 +62,21 @@ public class DocxService {
|
|||||||
|
|
||||||
public record CachedResult(String key, String fileName) {}
|
public record CachedResult(String key, String fileName) {}
|
||||||
|
|
||||||
// ====== Run merging (unchanged) ======
|
/**
|
||||||
|
* 读取模板 docx,合并拆分占位符,替换全部 {{placeholder}},返回处理后的 docx 字节。
|
||||||
|
*/
|
||||||
|
private byte[] preprocessTemplate(InputStream templateStream, Map<String, Object> dataModel, Map<String, Object> businessData) throws IOException {
|
||||||
|
// Merge data: businessData overrides dataModel
|
||||||
|
Map<String, Object> allData = new java.util.HashMap<>(dataModel);
|
||||||
|
if (businessData != null) {
|
||||||
|
allData.putAll(businessData);
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] mergeSplitPlaceholders(InputStream templateStream) throws IOException {
|
|
||||||
byte[] docxBytes;
|
byte[] docxBytes;
|
||||||
try (InputStream is = templateStream) {
|
try (InputStream is = templateStream) {
|
||||||
docxBytes = is.readAllBytes();
|
docxBytes = is.readAllBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(docxBytes));
|
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(docxBytes));
|
||||||
ZipOutputStream zos = new ZipOutputStream(out)) {
|
ZipOutputStream zos = new ZipOutputStream(out)) {
|
||||||
@@ -83,7 +86,13 @@ public class DocxService {
|
|||||||
byte[] content = zis.readAllBytes();
|
byte[] content = zis.readAllBytes();
|
||||||
if ("word/document.xml".equals(entry.getName())) {
|
if ("word/document.xml".equals(entry.getName())) {
|
||||||
String xml = new String(content, "UTF-8");
|
String xml = new String(content, "UTF-8");
|
||||||
|
// 1. Merge split runs (e.g. {{ year }} → {{year}})
|
||||||
xml = mergeRunsInXml(xml);
|
xml = mergeRunsInXml(xml);
|
||||||
|
// 2. Replace all {{placeholders}} with actual values
|
||||||
|
for (var kv : allData.entrySet()) {
|
||||||
|
String val = kv.getValue() != null ? kv.getValue().toString() : "";
|
||||||
|
xml = xml.replace("{{" + kv.getKey() + "}}", val);
|
||||||
|
}
|
||||||
content = xml.getBytes("UTF-8");
|
content = xml.getBytes("UTF-8");
|
||||||
}
|
}
|
||||||
zos.write(content);
|
zos.write(content);
|
||||||
@@ -93,6 +102,8 @@ public class DocxService {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====== Run merging (unchanged) ======
|
||||||
|
|
||||||
private String mergeRunsInXml(String xml) {
|
private String mergeRunsInXml(String xml) {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
java.util.regex.Matcher m = Pattern.compile(
|
java.util.regex.Matcher m = Pattern.compile(
|
||||||
|
|||||||
@@ -91,17 +91,26 @@ public class AnnualReportProvider implements TemplateProvider {
|
|||||||
int yearA = 2024;
|
int yearA = 2024;
|
||||||
int yearB = 2025;
|
int yearB = 2025;
|
||||||
int yearC = 2026;
|
int yearC = 2026;
|
||||||
return Map.of(
|
Map<String, Object> model = new java.util.HashMap<>();
|
||||||
"title", "2025 年度工作报告",
|
model.put("title", "2025 年度工作报告");
|
||||||
"companyName", "示例科技有限公司",
|
model.put("companyName", "示例科技有限公司");
|
||||||
"reportPeriod", "2025年1月 - 2025年12月",
|
model.put("reportPeriod", "2025年1月 - 2025年12月");
|
||||||
"generatedDate", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")),
|
model.put("generatedDate", LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
|
||||||
"yearA", String.valueOf(yearA),
|
model.put("yearA", String.valueOf(yearA));
|
||||||
"yearB", String.valueOf(yearB),
|
model.put("yearB", String.valueOf(yearB));
|
||||||
"yearC", String.valueOf(yearC),
|
model.put("yearC", String.valueOf(yearC));
|
||||||
"snhj", "300",
|
model.put("snhj", "300");
|
||||||
"dwjjtz", "200"
|
model.put("dwjjtz", "200");
|
||||||
);
|
model.put("dwjjbl", "66.7");
|
||||||
|
model.put("xxjjtz", "30");
|
||||||
|
model.put("xxjjzb", "10.0");
|
||||||
|
model.put("jgtz", "50");
|
||||||
|
model.put("jgzb", "16.7");
|
||||||
|
model.put("kjtz", "10");
|
||||||
|
model.put("kjzb", "3.3");
|
||||||
|
model.put("xxhtz", "10");
|
||||||
|
model.put("xxhzb", "3.3");
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user