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}}}。
*
* 只做取值与替换,不做任何表达式求值;需要计算的场景交给规则引擎。
* 取不到的路径渲染为空字符串——报告上不该出现用户看不懂的 {@code {{}}}。
*
* 与前端 {@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 listPaths(String template) {
Set 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());
}
/**
* 替换文本里的全部绑定表达式,并回报取不到值的路径。
*
* 绑定不上通常意味着数据缺口,静默渲染成空会让报告看起来「正常但缺内容」,
* 所以把缺口交给调用方,由渲染引擎汇总成可见的问题清单。
*/
public static String resolveText(String template, Object context, Consumer 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;
}
}