Skip to content

Commit c4674ef

Browse files
authored
识别部分bug修复 (#456)
1 parent 1a68165 commit c4674ef

12 files changed

Lines changed: 9528 additions & 20 deletions

File tree

llm_web_kit/extractor/html/recognizer/code/classes.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@
44
replace_node_by_cccode
55
from llm_web_kit.extractor.html.recognizer.recognizer import CCTag
66

7+
no_code_tags = ['audio', 'td', 'span','ul', 'li', 'body', 'p', 'h1', 'h2', 'figcaption', 'figure', 'section', 'figure', 'a', 'picture', 'iframe', 'aside']
8+
79

810
def modify_tree(root: HtmlElement) -> None:
11+
912
for maybe_code_root in root.xpath('.//*[@class]'):
1013
assert isinstance(maybe_code_root, HtmlElement)
14+
1115
if not any(['code' in class_name for class_name in maybe_code_root.classes]):
1216
continue
13-
17+
# 应对list或者audio被识别为code的情况
18+
if maybe_code_root.tag in no_code_tags:
19+
continue
20+
if maybe_code_root.tag == 'div' and (any([child.tag in no_code_tags for child in maybe_code_root.iterchildren()]) or len([child for child in maybe_code_root.iterchildren()]) == 0):
21+
continue
1422
if len(maybe_code_root.xpath(f'.//{CCTag.CC_CODE}')) > 0:
1523
continue
1624

@@ -20,12 +28,15 @@ def modify_tree(root: HtmlElement) -> None:
2028
def detect(root: HtmlElement) -> bool:
2129
for maybe_code_root in root.xpath('.//*[@class]'):
2230
assert isinstance(maybe_code_root, HtmlElement)
31+
2332
if not any(['code' in class_name for class_name in maybe_code_root.classes]):
2433
continue
25-
34+
if maybe_code_root.tag in no_code_tags:
35+
continue
36+
if maybe_code_root.tag == 'div' and any([child.tag in no_code_tags for child in maybe_code_root.iterchildren()]):
37+
continue
2638
if len(maybe_code_root.xpath(f'.//{CCTag.CC_CODE}')) > 0:
2739
continue
28-
2940
return True
3041

3142
return False

llm_web_kit/extractor/html/recognizer/list.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def __extract_list_item_text_recusive(el: HtmlElement):
124124
is_sub_sup = el.tag == 'sub' or el.tag == 'sup'
125125
paragraph = []
126126
result = {}
127+
127128
if el.tag == CCTag.CC_MATH_INLINE and el.text and el.text.strip():
128129
paragraph.append({'c': f'${el.text}$', 't': ParagraphTextType.EQUATION_INLINE})
129130
elif el.tag == CCTag.CC_CODE_INLINE and el.text and el.text.strip():
@@ -146,8 +147,11 @@ def __extract_list_item_text_recusive(el: HtmlElement):
146147
'items': []
147148
}
148149
for child in el.getchildren():
149-
child_list['items'].append(__extract_list_item_text_recusive(child))
150-
result['child_list'] = child_list
150+
child_item = __extract_list_item_text_recusive(child)
151+
if len(child_item) != 0:
152+
child_list['items'].append(child_item)
153+
if child_list['items']:
154+
result['child_list'] = child_list
151155
else:
152156
if el.text and el.text.strip():
153157
paragraph.append({'c': el.text, 't': ParagraphTextType.TEXT})
@@ -160,7 +164,8 @@ def __extract_list_item_text_recusive(el: HtmlElement):
160164
result['child_list'] = p['child_list']
161165
# 添加子元素的文本内容
162166
if 'c' in p:
163-
paragraph.append({'c': p['c'], 't': p.get('t', ParagraphTextType.TEXT)})
167+
if p['c'] != '':
168+
paragraph.append({'c': p['c'], 't': p.get('t', ParagraphTextType.TEXT)})
164169
if el.tag != 'li' and el.tail and el.tail.strip():
165170
if is_sub_sup:
166171
# 如果尾部文本跟在sub/sup后面,直接附加到最后一个文本段落中
@@ -171,9 +176,10 @@ def __extract_list_item_text_recusive(el: HtmlElement):
171176
else:
172177
paragraph.append({'c': el.tail, 't': ParagraphTextType.TEXT})
173178
if paragraph:
179+
# item['c'].strip(): 会导致前面处理br标签,添加的\n\n失效
174180
result['c'] = ' '.join(normalize_text_segment(item['c'].strip()) for item in paragraph)
175181
return result
176-
list_item_tags = ('li', 'dd', 'dt')
182+
list_item_tags = ('li', 'dd', 'dt', 'ul', 'div', 'p')
177183
if child.tag in list_item_tags:
178184
paragraph = __extract_list_item_text_recusive(child)
179185
if len(paragraph) > 0:
@@ -190,6 +196,7 @@ def __get_list_content_list(self, ele: HtmlElement, list_nest_level: int) -> lis
190196
Returns:
191197
list: 包含列表项内容的列表,即items
192198
"""
199+
193200
content_list = []
194201
# 处理根元素文本
195202
if ele.text and ele.text.strip():

llm_web_kit/extractor/html/recognizer/text.py

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@
4040
'☁' # 云符号
4141
]
4242

43+
# 其他标点符
44+
other_symbols = [
45+
'“',
46+
'‘',
47+
'[',
48+
'(',
49+
'”',
50+
'’',
51+
'。',
52+
','
53+
]
54+
4355
PARAGRAPH_SEPARATOR = '\n\n'
4456

4557
# 需要保留的html实体,例如:'>' 直接在markdown中无法渲染,需要替换为html实体
@@ -50,8 +62,9 @@
5062
'a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'button', 'cite', 'code',
5163
'dfn', 'em', 'i', 'img', 'input', 'kbd', 'label', 'map', 'object', 'q',
5264
'samp', 'script', 'select', 'small', 'span', 'strong', 'sub', 'sup',
53-
'textarea', 'time', 'var', 'u', 's', 'code', 'cccode-inline', 'ccmath-inline',
54-
'marked-tail', 'marked-text','math','mspace'
65+
'textarea', 'time', 'var', 'u', 's', 'cccode-inline', 'ccmath-inline',
66+
'marked-tail', 'marked-text', 'math','mspace', 'font', 'nobr', 'bdi',
67+
'mjx-container', 'mjx-assistive-mml', 'strike', 'wbr', 'ins'
5568
}
5669

5770

@@ -93,6 +106,7 @@ def recognize(self, base_url:str, main_html_lst: List[Tuple[HtmlElement | str, H
93106
new_html_lst = []
94107
for html_element, raw_html_element in main_html_lst:
95108
# 如果是字符串则转换为 HtmlElement
109+
96110
if self.is_cc_html(html_element):
97111
new_html_lst.append((html_element, raw_html_element))
98112
else:
@@ -108,7 +122,9 @@ def __to_cctext_lst(self, lst: List[Tuple[HtmlElement | str, HtmlElement | str]]
108122
lst: List[Tuple[HtmlElement | str, HtmlElement | str]]: Element和raw_html组成的列表
109123
"""
110124
new_lst = []
125+
111126
for el, raw_html in lst:
127+
112128
# 如果是字符串则转换为 HtmlElement
113129
el_element = html_to_element(el) if isinstance(el, str) else el
114130
raw_html_element = html_to_element(raw_html) if isinstance(raw_html, str) else raw_html
@@ -120,20 +136,45 @@ def __to_cctext_lst(self, lst: List[Tuple[HtmlElement | str, HtmlElement | str]]
120136
return new_lst
121137

122138
def replace_entities(self, text, entities_map):
123-
"""使用正则表达式同时替换文本中的多个特定字符为其对应的HTML实体
139+
"""替换文本中指定字符为对应的HTML实体,但跳过HTML标签内的字符
124140
125141
:param text: 需要处理的文本。
126-
:param entities_map: 一个字典,键是需要替换的字符,值是对应的HTML实体名
142+
:param entities_map: 字典,键是要替换的字符,值是对应的HTML实体名
127143
:return: 替换后的文本。
128144
"""
129-
# 创建正则表达式模式,匹配所有需要替换的字符
130-
rx = re.compile('|'.join(re.escape(str(key)) for key in entities_map.keys()))
145+
if not entities_map:
146+
return text # 如果字典为空,直接返回原文本
147+
148+
# 构建匹配需要替换字符的正则表达式
149+
entities_pattern = '|'.join(re.escape(str(key)) for key in entities_map.keys())
150+
rx_entity = re.compile(entities_pattern)
151+
152+
# 构建匹配HTML标签的正则表达式
153+
rx_tag = re.compile(r'<[^>]*>')
131154

132-
def one_xlat(match):
133-
"""回调函数,用于将匹配到的字符替换为对应的HTML实体。"""
134-
return f'&{entities_map[match.group(0)]};'
155+
result = []
156+
last_pos = 0
135157

136-
return rx.sub(one_xlat, text)
158+
# 遍历所有HTML标签
159+
for tag_match in rx_tag.finditer(text):
160+
start, end = tag_match.start(), tag_match.end()
161+
162+
# 提取非标签部分并进行替换
163+
non_tag_part = text[last_pos:start]
164+
replaced = rx_entity.sub(lambda m: f'&{entities_map[m.group(0)]};', non_tag_part)
165+
result.append(replaced)
166+
167+
# 保留HTML标签不变
168+
result.append(text[start:end])
169+
170+
last_pos = end
171+
172+
# 处理最后剩余的非标签部分
173+
non_tag_part = text[last_pos:]
174+
replaced = rx_entity.sub(lambda m: f'&{entities_map[m.group(0)]};', non_tag_part)
175+
result.append(replaced)
176+
177+
return ''.join(result)
137178

138179
def __combine_text(self, text1:str, text2:str, lang='en') -> str:
139180
"""将两段文本合并,中间加空格.
@@ -149,7 +190,11 @@ def __combine_text(self, text1:str, text2:str, lang='en') -> str:
149190
txt = text1 + text2
150191
return self.replace_entities(txt.strip(), entities_map)
151192
else:
152-
words_sep = '' if text2[0] in string.punctuation or text2[0] in special_symbols else ' '
193+
# 根据text1的最后一个字符和text2的第一个字符判断两个text之间的连接
194+
if (text2[0] in string.punctuation) or (text2[0] in special_symbols) or (text2[0] in other_symbols) or (text1 and text1[-1] in other_symbols):
195+
words_sep = ''
196+
else :
197+
words_sep = ' '
153198
txt = text1 + words_sep + text2
154199
return self.replace_entities(txt.strip(), entities_map)
155200

@@ -169,7 +214,6 @@ def __get_paragraph_text(self, root: HtmlElement) -> List[dict]:
169214
para_text = []
170215

171216
def __get_paragraph_text_recusive(el: HtmlElement, text: str) -> str:
172-
173217
# 标记当前元素是否是sub或sup类型
174218
is_sub_sup = el.tag == 'sub' or el.tag == 'sup'
175219

@@ -187,6 +231,8 @@ def __get_paragraph_text_recusive(el: HtmlElement, text: str) -> str:
187231
text += PARAGRAPH_SEPARATOR # TODO 这个地方直接加换行是错误点做法,需要利用数据结构来保证段落。
188232
elif el.tag == 'sub' or el.tag == 'sup':
189233
text = process_sub_sup_tags(el, text, recursive=False)
234+
elif el.tag == 'audio': # 避免audio被识别为paragraph
235+
pass
190236
else:
191237
if el.text and el.text.strip():
192238
text = self.__combine_text(text, el.text.strip())

0 commit comments

Comments
 (0)