Coverage for src\genesis\parsers\table_extractor.py: 100%
24 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 14:20 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 14:20 +0800
1from __future__ import annotations
3from typing import Any
5from genesis.data_models import (
6 CellFormatting, CellValue, ExcelTable, ExtractionMethod, Provenance, SheetType,
7)
10def column_letter(index: int) -> str:
11 """1 → A、27 → AA。"""
12 letters = ""
13 while index > 0:
14 index, rem = divmod(index - 1, 26)
15 letters = chr(65 + rem) + letters
16 return letters
19def extract_table(
20 sheet_name: str,
21 matrix: list[list[Any]],
22 file_name: str,
23 detected_type: SheetType,
24 header_row: int = 0,
25 formatting_map: dict[tuple[int, int], CellFormatting] | None = None,
26) -> ExcelTable:
27 """从矩阵提取表格:首行视为表头,其后为数据行。
29 formatting_map:矩阵坐标 (row, col)(0 起)→ CellFormatting,用于还原取消线/背景色等样式。
30 """
31 if not matrix:
32 return ExcelTable(
33 name=sheet_name, detected_type=detected_type,
34 extraction_method=ExtractionMethod.OPENPYXL.value, headers=[], rows=[],
35 )
36 if header_row < 0 or header_row >= len(matrix):
37 header_row = 0
38 fmt_map = formatting_map or {}
39 headers = [str(c) if c is not None else "" for c in matrix[header_row]]
40 rows = []
41 for r in range(header_row + 1, len(matrix)):
42 row_dict = {}
43 for c, h in enumerate(headers):
44 raw = matrix[r][c] if c < len(matrix[r]) else None
45 row_dict[h] = CellValue(
46 value=raw,
47 provenance=Provenance(
48 file_name=file_name,
49 sheet_name=sheet_name,
50 row=r - header_row, # 数据行号从 1 开始
51 column=column_letter(c + 1),
52 column_header=h,
53 ),
54 formatting=fmt_map.get((r, c)),
55 )
56 rows.append(row_dict)
57 return ExcelTable(
58 name=sheet_name, detected_type=detected_type,
59 extraction_method=ExtractionMethod.OPENPYXL.value, headers=headers, rows=rows,
60 )