import re from typing import List, Dict from bs4 import BeautifulSoup try: from .text_processor import TextProcessor except ImportError: from text_processor import TextProcessor class ContentExtractor: """从Markdown中提取表格和段落""" def __init__(self): self.text_processor = TextProcessor() def _normalize_text(self, text: str) -> str: """标准化文本:去除多余空格、回车等无效字符""" if not text: return "" # 去除多余的空白字符 text = re.sub(r'\s+', ' ', text.strip()) # 去除标点符号周围的空格 text = re.sub(r'\s*([,。:;!?、])\s*', r'\1', text) return text def _is_image_reference(self, text: str) -> bool: """判断是否为图片引用或描述""" image_keywords = [ '图', '图片', '图像', 'image', 'figure', 'fig', '照片', '截图', '示意图', '流程图', '结构图' ] # 检查是否包含图片相关关键词 for keyword in image_keywords: if keyword in text.lower(): return True # 检查是否为Markdown图片语法 if re.search(r'!\[.*?\]\(.*?\)', text): return True # 检查是否为HTML图片标签 if re.search(r']*>', text, re.IGNORECASE): return True return False def extract_structured_content(self, content: str) -> Dict: """ 提取结构化内容,返回表格和段落块 Returns: { 'tables': [ {'start_pos': int, 'end_pos': int, 'data': List[List[str]]}, ... ], 'paragraph_blocks': [ {'start_pos': int, 'end_pos': int, 'paragraphs': List[str]}, ... ] } """ # 查找所有表格的位置 table_pattern = r'.*?
' tables = [] paragraph_blocks = [] last_pos = 0 for match in re.finditer(table_pattern, content, re.DOTALL): start_pos = match.start() end_pos = match.end() # 提取表格前的段落块 if start_pos > last_pos: #[last_pos:start_pos) 左闭右开区间 before_table_content = content[last_pos:start_pos] paragraphs = self.extract_paragraphs(before_table_content) if paragraphs: paragraph_blocks.append({ 'start_pos': last_pos, 'end_pos': start_pos, 'paragraphs': paragraphs }) # 提取表格数据 table_html = match.group() table_data = self._parse_table_html(table_html) tables.append({ 'start_pos': start_pos, 'end_pos': end_pos, 'data': table_data }) last_pos = end_pos # 提取最后一个表格后的段落 if last_pos < len(content): after_table_content = content[last_pos:] paragraphs = self.extract_paragraphs(after_table_content) if paragraphs: paragraph_blocks.append({ 'start_pos': last_pos, 'end_pos': len(content), 'paragraphs': paragraphs }) return { 'tables': tables, 'paragraph_blocks': paragraph_blocks } def extract_table_data(self, content: str) -> List[List[List[str]]]: """提取所有表格数据(保持原有接口兼容)""" structured = self.extract_structured_content(content) return [t['data'] for t in structured['tables']] def _parse_table_html(self, html: str) -> List[List[str]]: """ 解析HTML表格为二维数组 Args: html: HTML表格字符串 Returns: 二维数组,每个元素为单元格文本 """ soup = BeautifulSoup(html, 'html.parser') table = soup.find('table') if not table: return [] table_data = [] rows = table.find_all('tr') for row in rows: cells = row.find_all(['td', 'th']) row_data = [] for cell in cells: cell_text = self._normalize_text(cell.get_text()) # 跳过图片内容 if not self._is_image_reference(cell_text): row_data.append(cell_text) else: row_data.append("[图片内容-忽略]") if row_data: # 只添加非空行 table_data.append(row_data) return table_data def merge_split_paragraphs(self, lines: List[str]) -> List[str]: # 合并连续的非空行作为一个段落,且过滤图片内容 merged_lines = [] current_paragraph = "" for i, line in enumerate(lines): # 跳过空行 if not line: if current_paragraph: merged_lines.append(current_paragraph) current_paragraph = "" continue # 跳过图片内容 if self._is_image_reference(line): continue # 检查是否是标题(以数字、中文数字或特殊标记开头) is_title = ( line.startswith(('一、', '二、', '三、', '四、', '五、', '六、', '七、', '八、', '九、', '十、')) or line.startswith(('1.', '2.', '3.', '4.', '5.', '6.', '7.', '8.', '9.')) or line.startswith('#') ) # 如果是标题,结束当前段落 if is_title: if current_paragraph: merged_lines.append(current_paragraph) current_paragraph = "" merged_lines.append(line) else: # 检查是否应该与前一行合并 # 如果当前段落不为空,且当前段落最后一个字符非空白字符 if current_paragraph and not current_paragraph.endswith((' ', '\t')): current_paragraph += line else: if current_paragraph: merged_lines.append(current_paragraph) current_paragraph = line # 处理最后一个段落 if current_paragraph: merged_lines.append(current_paragraph) return merged_lines def extract_paragraphs(self, content: str) -> List[str]: """提取段落内容""" # 移除HTML标签 content_no_html = re.sub(r'<[^>]+>', '', content) # 移除bbox注释 content_no_bbox = re.sub(r'', '', content_no_html) # 按换行符分割 paragraphs = [] lines = content_no_bbox.split('\n') merged_lines = self.merge_split_paragraphs(lines) for line in merged_lines: normalized = self._normalize_text(line) if normalized: paragraphs.append(normalized) else: print(f"跳过的内容无效或图片段落: {line[0:30] if line else ''}...") return paragraphs