Skip to content

Commit ff427b8

Browse files
author
TinySnow
committed
refactor: 扫描器替换部分正则并补充中文注释
将空格规则、拉丁标点转换、百分号补空格从高级正则实现改为逐字符扫描器实现,减少对 lookbehind 与 Script 正则能力的依赖。 新增 chars.ts 统一字符分类与空白处理函数,避免重复逻辑分散在多个模块。 为扫描器关键路径补充详尽中文注释,说明上下文判定、边界处理与序号恢复逻辑,降低后续维护成本。
1 parent 1b168a1 commit ff427b8

5 files changed

Lines changed: 436 additions & 135 deletions

File tree

src/utils/chars.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* 字符分类工具:
3+
* 1) 统一处理文本扫描器里“字符类型判断”的口径,避免每个规则各写一套正则。
4+
* 2) 这些函数全部是 O(1) 判定,适合在逐字符扫描中高频调用。
5+
*/
6+
7+
/**
8+
* 需要当作“中文标点”处理的一组字符。
9+
* 这里的集合对应项目现有规则范围,而不是完整 Unicode 标点全集。
10+
*/
11+
const CN_PUNC_SET = new Set<string>([
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+
* 判断是否属于常用 CJK 汉字区段。
38+
* 与旧版 fallback 正则范围保持一致:
39+
* - CJK Ext A: 3400-4DBF
40+
* - CJK Unified: 4E00-9FFF
41+
* - CJK Compatibility Ideographs: F900-FAFF
42+
*/
43+
function isHan(ch: string | null): boolean {
44+
if (!ch || ch.length !== 1) {
45+
return false;
46+
}
47+
48+
const code = ch.charCodeAt(0);
49+
return (
50+
(code >= 0x3400 && code <= 0x4dbf) ||
51+
(code >= 0x4e00 && code <= 0x9fff) ||
52+
(code >= 0xf900 && code <= 0xfaff)
53+
);
54+
}
55+
56+
/**
57+
* 判断是否是 ASCII 词字符:A-Z / a-z / 0-9 / _。
58+
* 用于“中英文边界插空格”等规则,保持与历史实现一致。
59+
*/
60+
function isAsciiWord(ch: string | null): boolean {
61+
if (!ch || ch.length !== 1) {
62+
return false;
63+
}
64+
65+
const code = ch.charCodeAt(0);
66+
return (
67+
(code >= 48 && code <= 57) ||
68+
(code >= 65 && code <= 90) ||
69+
(code >= 97 && code <= 122) ||
70+
code === 95
71+
);
72+
}
73+
74+
/** 判断是否属于项目定义的中文标点集合。 */
75+
function isCnPunc(ch: string | null): boolean {
76+
if (!ch || ch.length !== 1) {
77+
return false;
78+
}
79+
return CN_PUNC_SET.has(ch);
80+
}
81+
82+
/**
83+
* 空白字符判定。
84+
* 特别包含了全角空格(U+3000)和 NBSP(U+00A0),
85+
* 以覆盖中文文本常见输入场景。
86+
*/
87+
function isWs(ch: string | null): boolean {
88+
if (!ch || ch.length !== 1) {
89+
return false;
90+
}
91+
92+
return (
93+
ch === " " ||
94+
ch === "\t" ||
95+
ch === "\n" ||
96+
ch === "\r" ||
97+
ch === "\v" ||
98+
ch === "\f" ||
99+
ch === "\u00A0" ||
100+
ch === "\u3000"
101+
);
102+
}
103+
104+
/**
105+
* 从下标 from 开始,返回第一个非空白字符;找不到返回 null。
106+
* 注意:这里只返回字符,不返回下标,适合做上下文判定。
107+
*/
108+
function nextNonWs(text: string, from: number): string | null {
109+
for (let i = from; i < text.length; i += 1) {
110+
if (!isWs(text[i])) {
111+
return text[i];
112+
}
113+
}
114+
return null;
115+
}
116+
117+
/**
118+
* 从下标 from 开始跳过连续空白,返回跳过后的下标。
119+
* 常用于“替换标点后吞掉其后多余空白”的场景。
120+
*/
121+
function skipWs(text: string, from: number): number {
122+
let i = from;
123+
while (i < text.length && isWs(text[i])) {
124+
i += 1;
125+
}
126+
return i;
127+
}
128+
129+
/** 判断是否十进制数字字符。 */
130+
function isDigit(ch: string | null): boolean {
131+
if (!ch || ch.length !== 1) {
132+
return false;
133+
}
134+
135+
const code = ch.charCodeAt(0);
136+
return code >= 48 && code <= 57;
137+
}
138+
139+
export { isHan, isAsciiWord, isCnPunc, isWs, nextNonWs, skipWs, isDigit };

src/utils/optional.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { supportsAdvancedRegex, HAN_CLASS } from "./regex-support";
2-
3-
const useAdvancedRegex = supportsAdvancedRegex();
4-
const RE_SPACE_AFTER_PERCENT_ADV = useAdvancedRegex
5-
? new RegExp("(?<=%)(?=\\p{Script=Han})", "gu")
6-
: null;
7-
const RE_SPACE_AFTER_PERCENT_FB = new RegExp(`%(${HAN_CLASS})`, "g");
1+
import { isHan } from "./chars";
82

3+
/**
4+
* 可选规则:在百分号后补一个空格。
5+
* 行为保持与历史版本一致:仅当下一个字符是汉字时补空格。
6+
*/
97
function insertSpaceAfterPercentSign(
108
enabled: boolean,
119
origin: (string | null | undefined)[]
@@ -17,13 +15,38 @@ function insertSpaceAfterPercentSign(
1715
for (let i = 0; i < origin.length; i += 1) {
1816
const s = origin[i];
1917
if (s != null) {
20-
origin[i] = RE_SPACE_AFTER_PERCENT_ADV
21-
? s.replaceAll(RE_SPACE_AFTER_PERCENT_ADV, " ")
22-
: s.replace(RE_SPACE_AFTER_PERCENT_FB, "% $1");
18+
origin[i] = insertSpaceAfterPctInLine(s);
2319
}
2420
}
2521

2622
return origin;
2723
}
2824

25+
/**
26+
* 单行处理:扫描到 "%" 时,若下一个字符是汉字则插入半角空格。
27+
* 例如:"上涨5%利润" -> "上涨5% 利润"
28+
*/
29+
function insertSpaceAfterPctInLine(line: string): string {
30+
if (!line.includes("%")) {
31+
return line;
32+
}
33+
34+
let out = "";
35+
for (let i = 0; i < line.length; i += 1) {
36+
const ch = line[i];
37+
out += ch;
38+
39+
if (ch !== "%") {
40+
continue;
41+
}
42+
43+
const next = i + 1 < line.length ? line[i + 1] : null;
44+
if (isHan(next)) {
45+
out += " ";
46+
}
47+
}
48+
49+
return out;
50+
}
51+
2952
export { insertSpaceAfterPercentSign };

0 commit comments

Comments
 (0)