feat: MergeHandler + TableExtractor(forward_fill / 表格提取)
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def forward_fill(
|
||||||
|
matrix: list[list[Any]],
|
||||||
|
merged_ranges: list[tuple[int, int, int, int]],
|
||||||
|
) -> list[list[Any]]:
|
||||||
|
"""合并单元格:用左上角主格值填充范围内全部单元格。"""
|
||||||
|
out = [list(row) for row in matrix]
|
||||||
|
for (min_row, min_col, max_row, max_col) in merged_ranges:
|
||||||
|
if not out or min_row > len(out) or min_col > len(out[min_row - 1]):
|
||||||
|
continue
|
||||||
|
main_value = out[min_row - 1][min_col - 1]
|
||||||
|
for r in range(min_row, min(max_row, len(out)) + 1):
|
||||||
|
row = out[r - 1]
|
||||||
|
for c in range(min_col, min(max_col, len(row)) + 1):
|
||||||
|
row[c - 1] = main_value
|
||||||
|
return out
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from genesis.data_models import CellValue, ExcelTable, Provenance, SheetType
|
||||||
|
|
||||||
|
|
||||||
|
def column_letter(index: int) -> str:
|
||||||
|
"""1 → A、27 → AA。"""
|
||||||
|
letters = ""
|
||||||
|
while index > 0:
|
||||||
|
index, rem = divmod(index - 1, 26)
|
||||||
|
letters = chr(65 + rem) + letters
|
||||||
|
return letters
|
||||||
|
|
||||||
|
|
||||||
|
def extract_table(
|
||||||
|
sheet_name: str,
|
||||||
|
matrix: list[list[Any]],
|
||||||
|
file_name: str,
|
||||||
|
detected_type: SheetType,
|
||||||
|
header_row: int = 0,
|
||||||
|
) -> ExcelTable:
|
||||||
|
"""从矩阵提取表格:首行视为表头,其后为数据行。"""
|
||||||
|
if not matrix:
|
||||||
|
return ExcelTable(
|
||||||
|
name=sheet_name, detected_type=detected_type,
|
||||||
|
extraction_method="openpyxl", headers=[], rows=[],
|
||||||
|
)
|
||||||
|
headers = [str(c) if c is not None else "" for c in matrix[header_row]]
|
||||||
|
rows = []
|
||||||
|
for r in range(header_row + 1, len(matrix)):
|
||||||
|
row_dict = {}
|
||||||
|
for c, h in enumerate(headers):
|
||||||
|
raw = matrix[r][c] if c < len(matrix[r]) else None
|
||||||
|
row_dict[h] = CellValue(
|
||||||
|
value=raw,
|
||||||
|
provenance=Provenance(
|
||||||
|
file_name=file_name,
|
||||||
|
sheet_name=sheet_name,
|
||||||
|
row=r - header_row, # 数据行号从 1 开始
|
||||||
|
column=column_letter(c + 1),
|
||||||
|
column_header=h,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
rows.append(row_dict)
|
||||||
|
return ExcelTable(
|
||||||
|
name=sheet_name, detected_type=detected_type,
|
||||||
|
extraction_method="openpyxl", headers=headers, rows=rows,
|
||||||
|
)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
from genesis.data_models import CellValue, ExcelTable, SheetType
|
||||||
|
from genesis.parsers.merge_fill import forward_fill
|
||||||
|
from genesis.parsers.table_extractor import column_letter, extract_table
|
||||||
|
|
||||||
|
|
||||||
|
def test_forward_fill_vertical():
|
||||||
|
matrix = [
|
||||||
|
["機能ID", "機能名", "備考"],
|
||||||
|
["A001", "", ""],
|
||||||
|
["", "", "メモ"],
|
||||||
|
]
|
||||||
|
filled = forward_fill(matrix, [(1, 2, 2, 2)]) # B1:B2 纵向合并
|
||||||
|
assert filled[1][1] == "機能名"
|
||||||
|
assert filled[2][2] == "メモ"
|
||||||
|
|
||||||
|
|
||||||
|
def test_forward_fill_horizontal():
|
||||||
|
matrix = [["A", "B"], ["x", ""]]
|
||||||
|
filled = forward_fill(matrix, [(2, 1, 2, 2)]) # A2:B2 横向合并
|
||||||
|
assert filled[1][1] == "x"
|
||||||
|
assert filled[1][0] == "x"
|
||||||
|
|
||||||
|
|
||||||
|
def test_column_letter():
|
||||||
|
assert column_letter(1) == "A"
|
||||||
|
assert column_letter(27) == "AA"
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_table_basic():
|
||||||
|
matrix = [["ID", "名前"], ["1", "田中"], ["2", "佐藤"]]
|
||||||
|
table = extract_table("社員一覧", matrix, "f.xlsx", SheetType.FUNCTION)
|
||||||
|
assert isinstance(table, ExcelTable)
|
||||||
|
assert table.headers == ["ID", "名前"]
|
||||||
|
assert table.extraction_method == "openpyxl"
|
||||||
|
assert len(table.rows) == 2
|
||||||
|
first = table.rows[0]
|
||||||
|
assert isinstance(first["ID"], CellValue)
|
||||||
|
assert first["ID"].value == "1"
|
||||||
|
assert first["ID"].provenance.sheet_name == "社員一覧"
|
||||||
|
assert first["ID"].provenance.column == "A"
|
||||||
Reference in New Issue
Block a user