381 lines
14 KiB
Python
381 lines
14 KiB
Python
import re
|
||
from typing import List, Dict,Tuple
|
||
import re
|
||
from typing import List, Tuple
|
||
def extract_repairproject_tables_html1(content: str) -> Tuple[List[str], str]:
|
||
"""
|
||
提取所有维修表格,每个表格从特定表头开始,到下一个表头出现之前结束。
|
||
支持两种表头格式(含或不含 rowspan/colspan属性)。
|
||
|
||
:param content: 原始 HTML/文本内容
|
||
:return: (tables: List[str], cleaned_content: str)
|
||
- tables: 所有提取到的表格块
|
||
- cleaned_content: 仅保留第一个表格块,其余已删除
|
||
"""
|
||
|
||
# 定义表头的文本核心部分(不包含属性)
|
||
base_header = '<table><tr><td>维修项目编号</td><td>组成编码</td><td>名称</td><td>维修项目</td><td>维修间隔期</td>'
|
||
|
||
# 转义特殊字符,但为 rowspan/colspan 留出匹配空间
|
||
# 我们需要匹配: <td> 或 <td rowspan=1 colspan=1> 或 <td colspan=1 rowspan=1> 等情况
|
||
# 这里使用一个更灵活的正则:匹配 <td 后跟任意非 '>' 字符(非贪婪),直到 >
|
||
# 这种写法比硬编码两种情况更健壮,能兼容属性顺序变化或额外空格
|
||
escaped_base = re.escape('<table><tr>')
|
||
|
||
# 构建针对每个单元格的正则片段
|
||
# 匹配 "维修项目编号" 前的 <td...>
|
||
cell_pattern = r'<td[^>]*>'
|
||
header_pattern = (
|
||
escaped_base +
|
||
cell_pattern + re.escape('维修项目编号') + r'</td>' +
|
||
cell_pattern + re.escape('组成编码') + r'</td>' +
|
||
cell_pattern + re.escape('名称') + r'</td>' +
|
||
cell_pattern + re.escape('维修项目') + r'</td>' +
|
||
cell_pattern + re.escape('维修间隔期') + r'</td>'
|
||
)
|
||
|
||
# 编译正则,用于查找表格开始位置
|
||
pattern = re.compile(header_pattern, re.DOTALL)
|
||
matches = list(pattern.finditer(content))
|
||
|
||
tables = []
|
||
if not matches:
|
||
return tables, content
|
||
|
||
# 提取所有表格块
|
||
for i, match in enumerate(matches):
|
||
start_pos = match.start()
|
||
|
||
# 确定结束位置:下一个表格的开始,或者文本末尾
|
||
if i < len(matches) - 1:
|
||
end_pos = matches[i + 1].start()
|
||
else:
|
||
end_pos = len(content)
|
||
|
||
table_content = content[start_pos:end_pos]
|
||
tables.append(table_content)
|
||
|
||
# 构建 cleaned_content:只保留第一个表格块
|
||
# 即截断到第二个表格开始的位置(如果有的话)
|
||
if len(matches) > 1:
|
||
cleaned_content = content[:matches[1].start()]
|
||
else:
|
||
cleaned_content = content # 如果只有一个表格,原内容即为清理后内容(因为后面没有表格了)
|
||
|
||
return tables, cleaned_content
|
||
|
||
#提取所有维修项目表的html源码
|
||
def extract_repairproject_tables_html(content: str) -> tuple[list[str], str]:
|
||
"""
|
||
提取所有维修表格,每个表格从 start_literal 开始,到下一个 start_literal 出现之前结束。
|
||
|
||
:param content: 原始 HTML/文本内容
|
||
:return: (tables: List[str], cleaned_content: str)
|
||
- tables: 所有提取到的表格块(包含 start_literal 开头的内容)
|
||
- cleaned_content: 原文中仅保留第一个表格块,其余已删除
|
||
"""
|
||
# 构建起始模式
|
||
start_literal = (
|
||
'<table><tr>'
|
||
'<td rowspan=1 colspan=1>维修项目编号</td>'
|
||
'<td rowspan=1 colspan=1>组成编码</td>'
|
||
'<td rowspan=1 colspan=1>名称</td>'
|
||
'<td rowspan=1 colspan=1>维修项目</td>'
|
||
'<td rowspan=1 colspan=1>维修间隔期</td>'
|
||
) or (
|
||
'<table><tr>'
|
||
'<td</td>'
|
||
'<td>组成编码</td>'
|
||
'<td>名称</td>'
|
||
'<td>维修项目</td>'
|
||
'<td>维修间隔期</td>'
|
||
)
|
||
|
||
# 使用 finditer 查找所有匹配的起始位置
|
||
pattern = re.compile(re.escape(start_literal), re.DOTALL)
|
||
matches = list(pattern.finditer(content))
|
||
|
||
tables = []
|
||
if not matches:
|
||
# 没有找到表格,返回空列表和原内容
|
||
return tables, content
|
||
|
||
# 提取所有表格
|
||
for i, match in enumerate(matches):
|
||
start_pos = match.start()
|
||
# 如果不是最后一个表格,结束位置是下一个表格的开始位置
|
||
if i < len(matches) - 1:
|
||
end_pos = matches[i + 1].start()
|
||
else:
|
||
# 如果是最后一个表格,结束位置是文本末尾
|
||
end_pos = len(content)
|
||
|
||
table_content = content[start_pos:end_pos]
|
||
tables.append(table_content)
|
||
|
||
# 构建 cleaned_content:只保留第一个表格
|
||
first_table_end = len(content)
|
||
if len(matches) > 1:
|
||
first_table_end = matches[1].start()
|
||
|
||
cleaned_content = content[:first_table_end]
|
||
|
||
return tables, cleaned_content
|
||
|
||
|
||
|
||
def extract_operation_tables_html(content: str) -> Tuple[List[str], str]:
|
||
"""
|
||
提取所有以“项目编号 + 操作项目”为表头的操作表格。
|
||
每个表格的范围:从匹配的 start_pattern 开始,到下一个 start_pattern 之前(或文档结尾)。
|
||
同时生成切片:「全文开头前两行 + 前100字上下文 + 表格内容」。
|
||
cleaned_content 中仅保留第一个表格(原始形式),其余删除。
|
||
"""
|
||
# 提取全文开头的前两行(保留换行符)
|
||
lines = content.splitlines(keepends=True)
|
||
first_two_lines = ''.join(lines[:2]) if len(lines) >= 2 else ''.join(lines)
|
||
|
||
# 起始模式(修复版)
|
||
start_pattern = r'''
|
||
<table\b[^>]*> \s*
|
||
<tr\b[^>]*> \s*
|
||
<td\b[^>]*> \s* 项目编号 \s* </td> \s*
|
||
<td\b[^>]*> \s* 操作项目 \s* </td> \s*
|
||
</tr> \s*
|
||
<tr\b[^>]*>
|
||
'''
|
||
|
||
# 查找所有起始位置
|
||
matches = list(re.finditer(start_pattern, content, flags=re.DOTALL | re.VERBOSE))
|
||
|
||
if not matches:
|
||
return [], content
|
||
|
||
# 构建每个表格的完整范围:[start_i, start_{i+1}) 或 [start_i, end)
|
||
table_ranges = []
|
||
for i in range(len(matches)):
|
||
start_pos = matches[i].start()
|
||
if i + 1 < len(matches):
|
||
end_pos = matches[i + 1].start()
|
||
else:
|
||
end_pos = len(content)
|
||
table_ranges.append((start_pos, end_pos))
|
||
|
||
# 提取每个表格的 HTML 内容
|
||
tables_html = [content[start:end] for start, end in table_ranges]
|
||
|
||
# 构建 slices: 前两行 + 前100字 + 表格HTML
|
||
slices = []
|
||
for (start, _), html in zip(table_ranges, tables_html):
|
||
prefix_start = max(0, start - 100)
|
||
local_prefix = content[prefix_start:start]
|
||
full_slice = first_two_lines + local_prefix + html
|
||
|
||
# 修复标题格式(根据你的需求)
|
||
full_slice = full_slice.replace("# 操作步骤..", "操作步骤..")
|
||
full_slice = full_slice.replace("操作步骤..", "# 操作步骤")
|
||
|
||
slices.append(full_slice)
|
||
|
||
# 构建 cleaned_content:只保留第一个表格,其余删除
|
||
cleaned = content
|
||
if len(table_ranges) > 1:
|
||
# 从后往前删除第2个及之后的表格
|
||
for start, end in reversed(table_ranges[1:]):
|
||
cleaned = cleaned[:start] + cleaned[end:]
|
||
|
||
return slices, cleaned
|
||
|
||
|
||
def extract_cases(md_content):
|
||
"""
|
||
从 Markdown 内容中提取所有以 '# 数字.' 开头的案例。
|
||
每个案例从 '# X.XXX' 开始,到下一个 '# Y.YYY' 或末尾结束。
|
||
"""
|
||
# 正则表达式匹配以 "# 数字." 开头的标题
|
||
pattern = r'#\s*(\d+)\.\s*(.*?)\n(.*?)(?=(?:#\s*\d+\.\s*.*?))|$'
|
||
|
||
# 使用 re.DOTALL 让 . 匹配换行符
|
||
matches = re.findall(pattern, md_content, re.DOTALL | re.MULTILINE)
|
||
|
||
cases = []
|
||
for match in matches:
|
||
case_number = match[0] # 如 "1", "2"
|
||
title_text = match[1] # 如 "XXX 型号 A 设备 A 故障"
|
||
content = match[2] # 该案例的具体内容
|
||
|
||
# 组装完整案例
|
||
case_text = f"# {case_number}.{title_text}\n{content}"
|
||
cases.append(case_text)
|
||
|
||
return cases
|
||
|
||
def extract_fault_sections(text):
|
||
"""
|
||
从文本中提取以“故障名称:”开头的段落,去除最后一行。
|
||
"""
|
||
pattern = r'故障名称:[\s\S]*?(?=故障名称:|$)'
|
||
matches = re.findall(pattern, text, re.IGNORECASE)
|
||
|
||
results = []
|
||
for match in matches:
|
||
match = match.strip()
|
||
if match:
|
||
# 按行分割,去除最后一行,再合并
|
||
lines = match.split('\n')
|
||
if len(lines) > 1:
|
||
# 去除最后一行
|
||
modified_content = '\n'.join(lines[:-1])
|
||
results.append(modified_content.strip())
|
||
# 如果只有一行,可以根据需要决定是否保留
|
||
|
||
return results
|
||
|
||
def extract_by_unit(text):
|
||
"""
|
||
从文本中提取以“单位:”开头的段落,去除最后一行。
|
||
"""
|
||
pattern = r'(?=单位:)[\s\S]*?(?=单位:|$)'
|
||
matches = re.findall(pattern, text, re.IGNORECASE)
|
||
|
||
results = []
|
||
for match in matches:
|
||
match = match.strip()
|
||
if match:
|
||
# 按行分割,去除最后一行,再合并
|
||
lines = match.split('\n')
|
||
if len(lines) > 1:
|
||
# 去除最后一行
|
||
modified_content = '\n'.join(lines[:-1])
|
||
results.append(modified_content.strip())
|
||
|
||
return results
|
||
|
||
|
||
def extract_tables(content: str, start_len: int = 300,end_len:int = 50) -> Tuple[List[str], List[str], str]:
|
||
"""
|
||
提取所有 HTML 表格及其前后上下文(各最多 context_len 个字符),并从原文中移除表格及上下文。
|
||
|
||
:param content: 原始 HTML/文本内容
|
||
:param context_len: 上下文长度(默认 100 字符)
|
||
:return: (tables: List[str], contexts: List[str], cleaned_content: str)
|
||
其中 contexts[i] 是 tables[i] 对应的 "<前文> + <table>... + <后文>" 字符串
|
||
"""
|
||
# 构造正则:捕获最多 context_len 个任意字符(非贪婪)+ table 标签 + 最多 context_len 个任意字符
|
||
pattern = rf'(.{{0,{start_len}}}?)(<table\b[^>]*>.*?</table>)(.{{0,{end_len}}})'
|
||
|
||
matches = re.finditer(pattern, content, flags=re.DOTALL | re.IGNORECASE)
|
||
|
||
tables = []
|
||
contexts = []
|
||
cleaned_content = content
|
||
|
||
# 从后往前替换,避免位置偏移问题
|
||
for match in reversed(list(matches)):
|
||
pre_context = match.group(1)
|
||
table = match.group(2)
|
||
post_context = match.group(3)
|
||
|
||
full_context = pre_context + table + post_context
|
||
|
||
contexts.append(full_context)
|
||
tables.append(table)
|
||
|
||
# 从原文中删除整个上下文片段(包括前后文和表格)
|
||
cleaned_content = cleaned_content[:match.start()] + cleaned_content[match.end():]
|
||
|
||
# 因为是从后往前删的,所以 tables 和 contexts 顺序是反的,需要反转回来
|
||
tables.reverse()
|
||
contexts.reverse()
|
||
|
||
return tables, contexts, cleaned_content
|
||
|
||
def extract_guzhang_tables(text: str, context_length: int = 100) -> List[str]:
|
||
header1 = '<table><tr><td>序号</td><td>故障现象</td><td>故障原因</td><td>维修项目</td></tr>'
|
||
header2 = '<table><tr><td colspan="1" rowspan="1">序号</td><td colspan="1" rowspan="1">故障现象</td><td colspan="1" rowspan="1">故障原因</td><td colspan="1" rowspan="1">维修项目</td></tr>'
|
||
|
||
header1_esc = re.escape(header1)
|
||
header2_esc = re.escape(header2)
|
||
|
||
# 匹配:前文(最多 context_length) + 表头 + 表格内容 + 结尾
|
||
pattern = re.compile(
|
||
rf'(.{{0,{context_length}}}?)(?:{header1_esc}|{header2_esc})([\s\S]*?</tr></table>)',
|
||
re.DOTALL
|
||
)
|
||
|
||
results = []
|
||
for match in pattern.finditer(text):
|
||
before = match.group(1) # 前文
|
||
body_and_end = match.group(2) # 表格内容 + 结尾
|
||
|
||
# 判断是哪个表头(从原文中取)
|
||
pos = match.start(2) # 表头开始位置
|
||
if text[pos:pos + len(header1)] == header1:
|
||
table_code = header1 + body_and_end
|
||
else:
|
||
table_code = header2 + body_and_end
|
||
|
||
result_str = before + table_code
|
||
results.append(result_str)
|
||
|
||
return results
|
||
|
||
|
||
def extract_content_slices(text, keywords, context_len=100):
|
||
# 转义关键词用于正则
|
||
escaped = [re.escape(kw) for kw in keywords]
|
||
pattern = r'^\s*#?\s*(' + '|'.join(escaped) + ')'
|
||
compiled = re.compile(pattern, re.MULTILINE) # 注意:这里加了 MULTILINE 以确保 ^ 能匹配每行开头
|
||
|
||
lines = text.splitlines(keepends=True) # keepends=True 保留换行符,用于精确计算位置
|
||
if not lines:
|
||
return []
|
||
|
||
contents = []
|
||
current_content = []
|
||
in_section = False
|
||
section_start_idx = 0 # 记录当前段落在原文中的字符索引位置
|
||
|
||
# 遍历每一行
|
||
for line in lines:
|
||
if compiled.match(line):
|
||
# 遇到新关键词
|
||
if in_section and current_content:
|
||
# 1. 拼接当前内容
|
||
content_str = ''.join(current_content)
|
||
|
||
# 2. 获取前 context_len 个字符的上下文
|
||
# 确保不越界
|
||
start_context = max(0, section_start_idx - context_len)
|
||
prefix_context = text[start_context:section_start_idx]
|
||
|
||
# 3. 组合结果:上下文 + 内容
|
||
final_slice = prefix_context + content_str
|
||
contents.append(final_slice)
|
||
|
||
# 重置,准备下一段
|
||
current_content = []
|
||
|
||
# 开启新段落
|
||
in_section = True
|
||
# 更新当前段落的起始位置(即这一行在原文中的位置)
|
||
# 这里简化处理:通过查找来定位(因为直接计算索引在复杂文本中容易出错)
|
||
# find 从 section_start_idx 开始找,避免重复匹配
|
||
temp_pos = text.find(line, section_start_idx)
|
||
if temp_pos != -1:
|
||
section_start_idx = temp_pos
|
||
|
||
elif in_section:
|
||
current_content.append(line)
|
||
|
||
# 处理最后一个段落
|
||
if in_section and current_content:
|
||
content_str = ''.join(current_content)
|
||
start_context = max(0, section_start_idx - context_len)
|
||
prefix_context = text[start_context:section_start_idx]
|
||
final_slice = prefix_context + content_str
|
||
contents.append(final_slice)
|
||
|
||
return contents
|
||
|