package cn.iocoder.yudao.module.qcreport.engine;
|
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Set;
|
import java.util.function.Consumer;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
|
/**
|
* 数据绑定:模板文本里的 {@code {{path}}}。
|
* <p>
|
* 只做取值与替换,不做任何表达式求值;需要计算的场景交给规则引擎。
|
* 取不到的路径渲染为空字符串——报告上不该出现用户看不懂的 {@code {{}}}。
|
* <p>
|
* 与前端 {@code engine/binding.ts} 一一对应,两边必须同语义。
|
*/
|
public final class Bindings {
|
|
/** 绑定表达式:{{ report.reportNo }} */
|
private static final Pattern BINDING_PATTERN = Pattern.compile("\\{\\{\\s*([^{}]+?)\\s*\\}\\}");
|
|
/** 整段就是一个绑定表达式 */
|
private static final Pattern SINGLE_BINDING_PATTERN = Pattern.compile("^\\{\\{\\s*([^{}]+?)\\s*\\}\\}$");
|
|
private Bindings() {
|
}
|
|
/** 提取文本里出现的所有绑定路径(去重、保持出现顺序),用于渲染前的数据依赖检查 */
|
public static List<String> listPaths(String template) {
|
Set<String> paths = new LinkedHashSet<>();
|
if (template == null) {
|
return List.of();
|
}
|
Matcher matcher = BINDING_PATTERN.matcher(template);
|
while (matcher.find()) {
|
String path = matcher.group(1).trim();
|
if (!path.isEmpty()) {
|
paths.add(path);
|
}
|
}
|
return List.copyOf(paths);
|
}
|
|
/** 解析单个绑定路径,取不到返回 null */
|
public static Object resolve(String path, Object context) {
|
return path == null ? null : Paths.readPath(context, path.trim());
|
}
|
|
/**
|
* 替换文本里的全部绑定表达式,并回报取不到值的路径。
|
* <p>
|
* 绑定不上通常意味着数据缺口,静默渲染成空会让报告看起来「正常但缺内容」,
|
* 所以把缺口交给调用方,由渲染引擎汇总成可见的问题清单。
|
*/
|
public static String resolveText(String template, Object context, Consumer<String> onMissing) {
|
if (template == null) {
|
return "";
|
}
|
Matcher matcher = BINDING_PATTERN.matcher(template);
|
StringBuilder result = new StringBuilder();
|
while (matcher.find()) {
|
String path = matcher.group(1).trim();
|
Object value = Paths.readPath(context, path);
|
if (value == null && onMissing != null) {
|
onMissing.accept(path);
|
}
|
matcher.appendReplacement(result, Matcher.quoteReplacement(Paths.formatValue(value)));
|
}
|
matcher.appendTail(result);
|
return result.toString();
|
}
|
|
/** 替换文本里的全部绑定表达式;未绑定的路径渲染为空 */
|
public static String resolveText(String template, Object context) {
|
return resolveText(template, context, null);
|
}
|
|
/** 判断一段文本是否是纯绑定表达式({{path}} 单占一段),是则返回路径,否则返回 null */
|
public static String asSingleBinding(String template) {
|
if (template == null) {
|
return null;
|
}
|
Matcher matcher = SINGLE_BINDING_PATTERN.matcher(template.trim());
|
return matcher.matches() ? matcher.group(1).trim() : null;
|
}
|
|
}
|