8 小时以前 35722562e9e13f0504acc15b740d042ecb810199
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
 * 数据绑定:模板文本里的 {{path}}
 * <p>
 * 只做取值与替换,不做任何表达式求值;需要计算的场景交给规则引擎。
 * 取不到的路径渲染为空字符串——报告上不该出现用户看不懂的 {{}}。
 */
import { formatValue, readPath } from './path';
 
/** 绑定表达式:{{ report.reportNo }} */
const BINDING_PATTERN = /\{\{\s*([^{}]+?)\s*\}\}/g;
 
/** 提取文本里出现的所有绑定路径(去重),用于渲染前的数据依赖检查 */
export function listBindingPaths(template: string): string[] {
  const paths = new Set<string>();
  for (const match of template.matchAll(BINDING_PATTERN)) {
    const path = match[1]?.trim();
    if (path) {
      paths.add(path);
    }
  }
  return [...paths];
}
 
/** 解析单个绑定路径,取不到返回 undefined */
export function resolveBinding(path: string, context: unknown): unknown {
  return readPath(context, path.trim());
}
 
/**
 * 替换文本里的全部绑定表达式,并回报取不到值的路径。
 * <p>
 * 渲染时绑定不上通常意味着数据缺口,静默渲染成空会让报告看起来"正常但缺内容",
 * 所以把缺口交给调用方,由渲染引擎汇总成可见的问题清单。
 */
export function resolveTextWithReport(
  template: string,
  context: unknown,
  onMissing: (path: string) => void,
): string {
  return template.replaceAll(BINDING_PATTERN, (_, rawPath: string) => {
    const path = rawPath.trim();
    const value = readPath(context, path);
    if (value === undefined || value === null) {
      onMissing(path);
    }
    return formatValue(value);
  });
}
 
/** 替换文本里的全部绑定表达式;未绑定的路径渲染为空 */
export function resolveText(template: string, context: unknown): string {
  return resolveTextWithReport(template, context, () => {});
}
 
/** 判断一段文本是否是纯绑定表达式({{path}} 单占一段),是则返回路径 */
export function asSingleBinding(template: string): string | undefined {
  const trimmed = template.trim();
  const match = /^\{\{\s*([^{}]+?)\s*\}\}$/.exec(trimmed);
  return match?.[1]?.trim();
}