在文本处理的实际需求中,我们经常需要对文本里的特定关键词进行高亮展示,同时记录下这些关键词在原文本中的起始和结束区间,并且要求匹配时不区分大小写。比如在处理用户搜索结果的展示时,就需要把搜索词在结果文本中标记出来,不管用户输入的搜索词是大写还是小写,都能准确匹配到对应的内容。

基础实现思路
核心逻辑可以分为三个步骤:首先把原文本和待匹配的关键词都转换为统一的大小写形式,找到所有匹配的位置区间;然后根据这些区间对原文本进行加工,插入高亮标记;最后返回处理后的高亮文本和对应的区间列表。需要注意的是,插入高亮标记后,原文本的长度会发生变化,后续的区间计算要基于原文本的长度,不能受高亮标记的影响。
无重叠匹配的实现
当关键词之间没有重叠的时候,实现起来相对简单,我们可以使用正则表达式来完成匹配,正则的re.IGNORECASE标志可以直接实现不区分大小写的匹配。下面的代码实现了无重叠场景下的文本高亮和区间标记:
import re
def highlight_text_no_overlap(text, keyword):
# 存储匹配到的区间,格式为(起始索引, 结束索引)
intervals = []
# 正则匹配,不区分大小写
pattern = re.compile(re.escape(keyword), re.IGNORECASE)
# 遍历所有匹配结果
for match in pattern.finditer(text):
start = match.start()
end = match.end()
intervals.append((start, end))
# 对区间按起始位置排序,避免后续插入标记时顺序混乱
intervals.sort(key=lambda x: x[0])
# 拼接高亮文本,这里用strong标签做高亮标记
highlighted_parts = []
last_end = 0
for start, end in intervals:
# 添加匹配区间前的内容
highlighted_parts.append(text[last_end:start])
# 添加高亮后的匹配内容
highlighted_parts.append(f"<strong>{text[start:end]}</strong>")
last_end = end
# 添加最后剩余的内容
highlighted_parts.append(text[last_end:])
highlighted_text = "".join(highlighted_parts)
return highlighted_text, intervals
# 测试示例
text = "Hello Python, hello python, HELLO PYTHON"
keyword = "python"
result_text, result_intervals = highlight_text_no_overlap(text, keyword)
print("高亮文本:", result_text)
print("匹配区间:", result_intervals)
上面的代码首先通过正则的finditer方法找到所有不区分大小写的匹配位置,记录下原始的区间,然后按照区间顺序拼接文本,在匹配到的内容前后插入<strong>标签实现高亮。运行后可以看到所有大小写的python都被高亮,同时返回了正确的原始区间。
存在重叠匹配的处理
如果关键词存在重叠的情况,比如原文本是"ababa",关键词是"aba",直接匹配会得到两个重叠的区间(0,3)和(2,5),这时候如果直接插入高亮标记会导致内容重复。我们需要先对重叠的区间进行合并,只保留最大的覆盖区间。下面的代码实现了重叠区间的合并逻辑:
def merge_intervals(intervals):
if not intervals:
return []
# 先按起始位置排序
sorted_intervals = sorted(intervals, key=lambda x: x[0])
merged = [sorted_intervals[0]]
for current in sorted_intervals[1:]:
last = merged[-1]
# 如果当前区间的起始位置小于等于上一个区间的结束位置,说明重叠
if current[0] <= last[1]:
# 合并区间,结束位置取两者的最大值
merged[-1] = (last[0], max(last[1], current[1]))
else:
merged.append(current)
return merged
def highlight_text_with_overlap(text, keyword):
intervals = []
pattern = re.compile(re.escape(keyword), re.IGNORECASE)
for match in pattern.finditer(text):
intervals.append((match.start(), match.end()))
# 合并重叠的区间
merged_intervals = merge_intervals(intervals)
# 拼接高亮文本
highlighted_parts = []
last_end = 0
for start, end in merged_intervals:
highlighted_parts.append(text[last_end:start])
highlighted_parts.append(f"<strong>{text[start:end]}</strong>")
last_end = end
highlighted_parts.append(text[last_end:])
highlighted_text = "".join(highlighted_parts)
return highlighted_text, merged_intervals
# 测试重叠场景
text = "ababa"
keyword = "aba"
result_text, result_intervals = highlight_text_with_overlap(text, keyword)
print("高亮文本:", result_text)
print("合并后区间:", result_intervals)
这里的merge_intervals函数会先把所有区间按起始位置排序,然后遍历区间,如果当前区间和上一个区间重叠就合并,最终得到没有重叠的区间列表,再进行高亮处理就可以避免内容重复的问题。
注意事项
- 正则匹配时要用
re.escape处理关键词,避免关键词中包含正则特殊字符导致匹配错误。 - 区间记录一定要基于原文本的长度,不能基于插入高亮标记后的文本长度,否则区间会完全错误。
- 如果高亮标记不是
<strong>而是其他自定义标签,需要保证标签的语法正确,且不会和原文本中的内容冲突。 - 如果需要同时高亮多个不同的关键词,可以先收集所有关键词的匹配区间,合并之后再统一处理高亮,避免多次插入标记导致结构混乱。
总结
实现不区分大小写的文本高亮与区间标记,核心是利用正则的re.IGNORECASE标志完成大小写不敏感的匹配,准确记录原文本中的匹配区间,再根据区间处理高亮逻辑。如果存在重叠匹配的场景,需要先对区间进行合并再处理。上面的代码可以直接复用在大部分文本处理场景中,开发者也可以根据自己的需求调整高亮标记的样式和区间的处理逻辑。