
本文介绍一种健壮的方案:先移除字符串中的显示空格进行正则匹配,再基于原始匹配位置在带空格格式化后的字符串中精准插入高亮标签,避免正则转义与空格干扰问题。
本文介绍一种健壮的方案:先移除字符串中的显示空格进行正则匹配,再基于原始匹配位置在带空格格式化后的字符串中精准插入高亮标签,避免正则转义与空格干扰问题。
在前端开发中,常需对长文本(如DNA序列、日志流或十六进制数据)按固定宽度(如每10字符加一空格)格式化显示,并支持任意正则模式的高亮搜索。但直接在含空格的字符串上执行正则匹配会导致失败——因为用户输入的 LST 实际在 DOM 中表现为 LS T 或 L ST,而 LS.+P 等含量词的模式更会因空格位置不确定而完全失配。
你当前的 insertSpaces(pattern, true) 方案试图将每个字符后追加 s* 来“容忍空格”,但这存在两个根本缺陷:
- 正则元字符未转义:如 L[ST]P 中的 [、]、.、+、{ 等若未经 RegExp.escape() 处理,会被当作语法结构而非字面量;
- 空格插入逻辑不可逆:insertSpaces(str, true) 每10字符加空格是确定性操作,但 insertSpaces(pattern, false) 为每个字符加 s* 后,正则引擎可能跨多个空格匹配,导致高亮范围错位(例如匹配到 L␣S␣T 却只应高亮 LST 对应的无空格片段)。
✅ 推荐采用分离关注点策略:
- 匹配阶段:在纯净无空格的原始字符串上执行严格正则匹配,获取所有匹配的起始索引和长度;
- 渲染阶段:将原始字符串格式化为带空格的展示串,再根据匹配位置计算其在新串中的偏移,插入 标签。
以下是完整 TypeScript 实现:
public highlight(str: string, pattern: string): string {
// 步骤1:移除所有空格用于匹配(保留原始语义)
const cleanStr = str.replace(/s/g, '');
// 步骤2:安全转义用户输入的正则字符串(防止注入)
const escapedPattern = pattern.replace(/[.*+?^${}()|[]\]/g, '\$&');
// 步骤3:构造全局正则(支持 flags 如 i, g)
const regex = new RegExp(escapedPattern, 'g');
// 步骤4:收集所有匹配的位置信息
const matches: Array = [];
let match;
while ((match = regex.exec(cleanStr)) !== null) {
matches.push({ index: match.index, length: match[0].length });
}
// 步骤5:生成带空格的展示字符串
const spacedStr = this.insertSpaces(str, true); // 每10字符后加空格
// 步骤6:从后往前插入高亮标签(避免索引偏移)
let result = spacedStr;
for (let i = matches.length - 1; i >= 0; i--) {
const { index, length } = matches[i];
// 计算原始匹配子串在 spacedStr 中的实际起始位置:
// 每10字符插入1空格 → 前 index 个字符会引入 Math.floor(index / 10) 个空格
const spacedIndex = index + Math.floor(index / 10);
// 同理,匹配长度不变,但后续空格数影响结束位置
const spacedEnd = spacedIndex + length + Math.floor((index + length) / 10) - Math.floor(index / 10);
result =
result.substring(0, spacedIndex) +
'<span class="highlightText">' +
result.substring(spacedIndex, spacedEnd) +
'</span>' +
result.substring(spacedEnd);
}
return result;
}
private insertSpaces(seq: string, isDisplay: boolean): string {
if (!isDisplay) return seq;
return seq.replace(/(.{10})/g, '$1 ');
}
⚠️ 注意事项:
- 该方案天然支持任意正则语法(LS.+P、L[ST]P、LS.{2,4}P),无需手动构造 s* 模式;
- 使用 RegExp.escape()(通过正则替换模拟)确保用户输入不破坏正则结构;
- 必须从后往前插入标签,否则前面插入的 HTML 字符会改变后续匹配位置的偏移量;
- 若需支持大小写不敏感等 flag,可扩展 highlight() 参数,如 highlight(str, pattern, flags = 'g');
- innerHTML 渲染需确保 Angular 的 DomSanitizer 已正确处理(在组件中注入并调用 this.sanitizer.bypassSecurityTrustHtml(...))。
此方法兼顾准确性、安全性与可维护性,是处理“格式化显示 + 灵活搜索”场景的最佳实践。











