feat: ExcelParser 基础(workbook/sheet 读取 + source_uri)
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
"""Parser Agent:输入资料解析层。"""
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from openpyxl import load_workbook
|
||||||
|
from openpyxl.worksheet.worksheet import Worksheet
|
||||||
|
|
||||||
|
|
||||||
|
def open_workbook(path: str | Path):
|
||||||
|
"""普通模式打开 .xlsx(保留公式/样式/批注),.xls 报错。"""
|
||||||
|
path = Path(path)
|
||||||
|
if path.suffix.lower() != ".xlsx":
|
||||||
|
raise ValueError(f"不支持的 Excel 格式: {path.suffix}")
|
||||||
|
return load_workbook(path)
|
||||||
|
|
||||||
|
|
||||||
|
def sheet_matrix(ws: Worksheet) -> list[list[Any]]:
|
||||||
|
"""整表矩形值(含 None),保留到 max_column。"""
|
||||||
|
return [[cell.value for cell in row] for row in ws.iter_rows()]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
def build_source_uri(file_name: str, sheet_name: str, cell_ref: str) -> str:
|
||||||
|
"""单元格来源 URI:file.xlsx#SheetName!CellRef"""
|
||||||
|
return f"{file_name}#{sheet_name}!{cell_ref}"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from openpyxl import Workbook
|
||||||
|
|
||||||
|
|
||||||
|
def new_workbook(sheets: dict[str, list[list]]) -> Workbook:
|
||||||
|
"""生成临时 Workbook:key=Sheet 名,value=grid(cell 值)。"""
|
||||||
|
wb = Workbook()
|
||||||
|
wb.remove(wb.active)
|
||||||
|
for name, grid in sheets.items():
|
||||||
|
ws = wb.create_sheet(name)
|
||||||
|
for r, row in enumerate(grid, start=1):
|
||||||
|
for c, value in enumerate(row, start=1):
|
||||||
|
ws.cell(row=r, column=c, value=value)
|
||||||
|
return wb
|
||||||
|
|
||||||
|
|
||||||
|
def save_workbook(tmp_path, wb: Workbook) -> str:
|
||||||
|
"""落盘到 tmp_path 并返回路径字符串。"""
|
||||||
|
path = tmp_path / "source.xlsx"
|
||||||
|
wb.save(path)
|
||||||
|
return str(path)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from genesis.parsers.excel_reader import open_workbook, sheet_matrix
|
||||||
|
from genesis.parsers.provenance import build_source_uri
|
||||||
|
|
||||||
|
from tests.excel_helpers import new_workbook, save_workbook
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_source_uri_format():
|
||||||
|
assert build_source_uri("要求.xlsx", "機能一覧", "A5") == "要求.xlsx#機能一覧!A5"
|
||||||
|
|
||||||
|
|
||||||
|
def test_open_workbook_and_sheet_matrix(tmp_path):
|
||||||
|
wb = new_workbook({"機能一覧": [["機能ID", "機能名"], ["F001", "社員登録"]]})
|
||||||
|
path = save_workbook(tmp_path, wb)
|
||||||
|
ws = open_workbook(path)["機能一覧"]
|
||||||
|
assert sheet_matrix(ws) == [["機能ID", "機能名"], ["F001", "社員登録"]]
|
||||||
|
|
||||||
|
|
||||||
|
def test_open_workbook_rejects_xls(tmp_path):
|
||||||
|
bad = tmp_path / "old.xls"
|
||||||
|
bad.write_bytes(b"not really xls")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
open_workbook(bad)
|
||||||
Reference in New Issue
Block a user