a3c38aa92a
- asset_mapping: 38种BSP资产实测锚点+pivot_offset_xy自动补偿 - scene_builder: Rotator改关键字参数修正旋转轴映射 - scene_builder: spawn前XY锚点补偿(支持Yaw旋转) - scene_builder: 新增ExponentialHeightFog+大气效果后处理 - build_cmdline: 构建后自动添加雾+GameMode+DirectionalLight大气 - build_guide_parser: 去重+全局地面+旋转列名兼容+边界段范围修正 - BarrierWall实际尺寸修正(60×210非20×100) - 搭建指南: 全局地面中心坐标+西边界旋转统一+院门改双开门
193 lines
5.5 KiB
Python
193 lines
5.5 KiB
Python
"""通用 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()
|