"""通用 Markdown 管道表格解析器。 将 Markdown 表格文本解析为 list[dict],每个字典代表一行数据。 支持中文列名,处理列数不一致的情况。 """ import re from typing import Optional def parse_table(lines: list[str]) -> list[dict]: """解析单个 Markdown 表格。 Args: lines: 表格的文本行列表(包含表头、分隔行和数据行)。 Returns: 列表,每个元素是一行数据的字典,key 为表头文本。 返回空列表如果输入不是有效表格。 """ if not lines: return [] # 找到分隔行(包含 |---| 的行) sep_idx = -1 for i, line in enumerate(lines): if re.match(r'\s*\|[\s\-:]+\|', line): sep_idx = i break if sep_idx < 1: return [] # 没有有效的表头+分隔行 # 提取表头 header_line = lines[sep_idx - 1] headers = _split_row(header_line) if not headers: return [] # 提取数据行(分隔行之后的所有 | 开头的行) data_rows = [] for line in lines[sep_idx + 1:]: stripped = line.strip() if not stripped or not stripped.startswith('|'): break # 表格结束 cells = _split_row(stripped) data_rows.append(cells) # 构建结果 result = [] for row in data_rows: entry = {} for j, header in enumerate(headers): entry[header] = row[j] if j < len(row) else "" result.append(entry) return result def parse_all_tables(text: str) -> list[tuple[list[str], list[dict]]]: """解析文本中的所有 Markdown 表格。 Args: text: 完整的 Markdown 文本。 Returns: 列表,每个元素为 (header, rows) 元组。 header 为表头列表,rows 为 list[dict]。 """ results = [] lines = text.split('\n') i = 0 while i < len(lines): # 寻找潜在的表格起始行(包含 | 的行) if '|' in lines[i]: # 收集连续的表格行 table_lines = [] while i < len(lines) and '|' in lines[i]: table_lines.append(lines[i].strip()) i += 1 # 尝试解析 rows = parse_table(table_lines) if rows: # 提取表头 for tl in table_lines: if re.match(r'\s*\|[\s\-:]+\|', tl): sep_idx = table_lines.index(tl) if sep_idx > 0: headers = _split_row(table_lines[sep_idx - 1]) results.append((headers, rows)) break continue i += 1 return results def find_tables_in_section(text: str, section_start: int, section_end: int) -> list[list[dict]]: """在文本的指定范围内找到所有表格。 Args: text: 完整文本。 section_start: 区域起始行号。 section_end: 区域结束行号。 Returns: 表格列表,每个表格是 list[dict]。 """ lines = text.split('\n') section_lines = lines[section_start:section_end] tables = [] i = 0 while i < len(section_lines): if '|' in section_lines[i]: # 收集连续表格行 table_lines = [] while i < len(section_lines) and '|' in section_lines[i]: table_lines.append(section_lines[i]) i += 1 rows = parse_table(table_lines) if rows: tables.append(rows) continue i += 1 return tables def _split_row(line: str) -> list[str]: """将 | 分隔的行拆分为单元格列表,去除首尾空格。""" # 去除首尾空白 line = line.strip() # 按管道符分割 if not line.startswith('|'): return [] if not line.endswith('|'): # 有些行末尾可能没有 | parts = line.split('|') else: parts = line.split('|') # 去除首尾空元素(管道符分割产生的) cells = [c.strip() for c in parts] # 去掉首尾空字符串 while cells and cells[0] == '': cells.pop(0) while cells and cells[-1] == '': cells.pop() return cells def parse_location(text: str) -> tuple[float, float, float]: """解析位置字符串 '(X,Y,Z)' 为三元组。""" if not text: return (0.0, 0.0, 0.0) m = re.search(r'\(([-\d.]+)\s*[,,]\s*([-\d.]+)\s*[,,]\s*([-\d.]+)\)', text) if m: return (float(m.group(1)), float(m.group(2)), float(m.group(3))) return (0.0, 0.0, 0.0) def parse_rotation(text: str) -> tuple[float, float, float]: """解析旋转字符串 '(P,Y,R)' 为三元组(度数)。 处理箭头注释格式:'(0,0,0) → Yaw=90°' 只取括号内部分。 """ if not text: return (0.0, 0.0, 0.0) # 先尝试提取括号内的旋转值,忽略箭头后面的注释 return parse_location(text) def parse_scale(text: str) -> tuple[float, float, float]: """解析缩放字符串 '(X,Y,Z)' 为三元组。'—' 或空返回 (1,1,1)。""" if not text or text.strip() in ('—', '–', '-', '无', ''): return (1.0, 1.0, 1.0) return parse_location(text) def clean_cell(text: str) -> str: """清理单元格文本:去首尾空白、去反引号。""" if not text: return "" text = text.strip() # 去除 Markdown 反引号包裹 if text.startswith('`') and text.endswith('`'): text = text[1:-1] return text.strip()