exstruct 0.3.6__tar.gz → 0.3.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {exstruct-0.3.6 → exstruct-0.3.7}/PKG-INFO +3 -2
- {exstruct-0.3.6 → exstruct-0.3.7}/README.md +2 -1
- {exstruct-0.3.6 → exstruct-0.3.7}/pyproject.toml +16 -20
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/__init__.py +7 -19
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/backends/base.py +9 -1
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/backends/com_backend.py +33 -8
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/backends/openpyxl_backend.py +22 -4
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/cells.py +181 -23
- exstruct-0.3.7/src/exstruct/core/integrate.py +62 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/modeling.py +3 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/pipeline.py +201 -56
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/workbook.py +9 -6
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/engine.py +30 -8
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/models/__init__.py +7 -0
- exstruct-0.3.7/src/exstruct/render/__init__.py +636 -0
- exstruct-0.3.6/src/exstruct/core/integrate.py +0 -58
- exstruct-0.3.6/src/exstruct/render/__init__.py +0 -239
- {exstruct-0.3.6 → exstruct-0.3.7}/LICENSE +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/cli/availability.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/cli/main.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/__init__.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/backends/__init__.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/charts.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/logging_utils.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/ranges.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/core/shapes.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/errors.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/io/__init__.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/io/serialize.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/models/maps.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/models/types.py +0 -0
- {exstruct-0.3.6 → exstruct-0.3.7}/src/exstruct/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: exstruct
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Excel to structured JSON (tables, shapes, charts) for LLM/RAG pipelines
|
|
5
5
|
Keywords: excel,structure,data,exstruct
|
|
6
6
|
Author: harumiWeb
|
|
@@ -66,7 +66,8 @@ ExStruct reads Excel workbooks and outputs structured data (cells, table candida
|
|
|
66
66
|
## Features
|
|
67
67
|
|
|
68
68
|
- **Excel → Structured JSON**: cells, shapes, charts, smartart, table candidates, print areas/views, and auto page-break areas per sheet.
|
|
69
|
-
- **Output modes**: `light` (cells + table candidates + print areas; no COM, shapes/charts empty), `standard` (texted shapes + arrows, charts, smartart, merged cell ranges, print areas), `verbose` (all shapes with width/height, charts with size, merged cell ranges, print areas). Verbose also emits cell hyperlinks and `colors_map`. Size output is flag-controlled.
|
|
69
|
+
- **Output modes**: `light` (cells + table candidates + print areas; no COM, shapes/charts empty), `standard` (texted shapes + arrows, charts, smartart, merged cell ranges, print areas), `verbose` (all shapes with width/height, charts with size, merged cell ranges, print areas). Verbose also emits cell hyperlinks and `colors_map`. Size output is flag-controlled.
|
|
70
|
+
- **Formula map extraction**: emits `formulas_map` (formula string -> cell coordinates) via openpyxl/COM; enabled by default in `verbose` or via `include_formulas_map`.
|
|
70
71
|
- **Auto page-break export (COM only)**: capture Excel-computed auto page breaks and write per-area JSON/YAML/TOON when requested (CLI option appears only when COM is available).
|
|
71
72
|
- **Formats**: JSON (compact by default, `--pretty` available), YAML, TOON (optional dependencies).
|
|
72
73
|
- **Table detection tuning**: adjust heuristics at runtime via API.
|
|
@@ -11,7 +11,8 @@ ExStruct reads Excel workbooks and outputs structured data (cells, table candida
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- **Excel → Structured JSON**: cells, shapes, charts, smartart, table candidates, print areas/views, and auto page-break areas per sheet.
|
|
14
|
-
- **Output modes**: `light` (cells + table candidates + print areas; no COM, shapes/charts empty), `standard` (texted shapes + arrows, charts, smartart, merged cell ranges, print areas), `verbose` (all shapes with width/height, charts with size, merged cell ranges, print areas). Verbose also emits cell hyperlinks and `colors_map`. Size output is flag-controlled.
|
|
14
|
+
- **Output modes**: `light` (cells + table candidates + print areas; no COM, shapes/charts empty), `standard` (texted shapes + arrows, charts, smartart, merged cell ranges, print areas), `verbose` (all shapes with width/height, charts with size, merged cell ranges, print areas). Verbose also emits cell hyperlinks and `colors_map`. Size output is flag-controlled.
|
|
15
|
+
- **Formula map extraction**: emits `formulas_map` (formula string -> cell coordinates) via openpyxl/COM; enabled by default in `verbose` or via `include_formulas_map`.
|
|
15
16
|
- **Auto page-break export (COM only)**: capture Excel-computed auto page breaks and write per-area JSON/YAML/TOON when requested (CLI option appears only when COM is available).
|
|
16
17
|
- **Formats**: JSON (compact by default, `--pretty` available), YAML, TOON (optional dependencies).
|
|
17
18
|
- **Table detection tuning**: adjust heuristics at runtime via API.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "exstruct"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.7"
|
|
4
4
|
description = "Excel to structured JSON (tables, shapes, charts) for LLM/RAG pipelines"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -60,7 +60,10 @@ omit = [
|
|
|
60
60
|
[tool.ruff]
|
|
61
61
|
target-version = "py311"
|
|
62
62
|
src = ["exstruct"]
|
|
63
|
+
fix = true
|
|
63
64
|
|
|
65
|
+
# 静的解析ルール
|
|
66
|
+
[tool.ruff.lint]
|
|
64
67
|
select = [
|
|
65
68
|
"E", # pycodestyle errors
|
|
66
69
|
"W", # pycodestyle warnings
|
|
@@ -75,43 +78,36 @@ select = [
|
|
|
75
78
|
]
|
|
76
79
|
|
|
77
80
|
ignore = [
|
|
78
|
-
"E501", #
|
|
79
|
-
"B008", # Pydantic の default_factory
|
|
80
|
-
"ANN101", # self
|
|
81
|
-
"ANN102", # cls
|
|
81
|
+
"E501", # 長い行は許容(Excel JSON は長くなりがち)
|
|
82
|
+
"B008", # Pydantic の default_factory を使用するため
|
|
83
|
+
"ANN101", # self の型注釈は省略可能
|
|
84
|
+
"ANN102", # cls の型注釈は省略可能
|
|
82
85
|
]
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# 型ヒントのスタイル
|
|
87
|
-
[tool.ruff.lint]
|
|
88
|
-
extend-select = ["ANN"]
|
|
89
|
-
|
|
90
|
-
# import の並び替え設定
|
|
91
|
-
[tool.ruff.isort]
|
|
87
|
+
# import の並び順
|
|
88
|
+
[tool.ruff.lint.isort]
|
|
92
89
|
combine-as-imports = true
|
|
93
90
|
known-first-party = ["exstruct"]
|
|
94
91
|
force-sort-within-sections = true
|
|
95
92
|
|
|
96
|
-
#
|
|
97
|
-
[tool.ruff.mccabe]
|
|
93
|
+
# 複雑度の最大値
|
|
94
|
+
[tool.ruff.lint.mccabe]
|
|
98
95
|
max-complexity = 12
|
|
99
96
|
|
|
100
|
-
[tool.ruff.per-file-ignores]
|
|
97
|
+
[tool.ruff.lint.per-file-ignores]
|
|
101
98
|
"tests/**/*.py" = ["N802", "N803", "N806"]
|
|
102
99
|
|
|
103
|
-
|
|
104
100
|
[tool.mypy]
|
|
105
101
|
packages = ["exstruct"]
|
|
106
102
|
python_version = "3.11"
|
|
107
103
|
|
|
108
|
-
#
|
|
104
|
+
# 外部ライブラリの型情報がない場合は無視
|
|
109
105
|
ignore_missing_imports = true
|
|
110
106
|
|
|
111
|
-
#
|
|
107
|
+
# 厳格モードを有効化
|
|
112
108
|
strict = true
|
|
113
109
|
|
|
114
|
-
# Pydantic v2
|
|
110
|
+
# Pydantic v2 対応
|
|
115
111
|
plugins = ["pydantic.mypy"]
|
|
116
112
|
|
|
117
113
|
[tool.pytest.ini_options]
|
|
@@ -90,36 +90,24 @@ ExtractionMode = Literal["light", "standard", "verbose"]
|
|
|
90
90
|
|
|
91
91
|
def extract(file_path: str | Path, mode: ExtractionMode = "standard") -> WorkbookData:
|
|
92
92
|
"""
|
|
93
|
-
|
|
93
|
+
Extracts an Excel workbook into a WorkbookData structure.
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
file_path: Path to .xlsx
|
|
97
|
-
mode: "light" / "standard"
|
|
98
|
-
- light: cells + table detection only (no COM, shapes/charts empty). Print areas via openpyxl.
|
|
99
|
-
- standard: texted shapes + arrows + charts (COM if available), print areas included. Shape/chart size is kept but hidden by default in output.
|
|
100
|
-
- verbose: all shapes (including textless) with size, charts with size, and colors_map.
|
|
95
|
+
Parameters:
|
|
96
|
+
file_path (str | Path): Path to the workbook file (.xlsx, .xlsm, .xls).
|
|
97
|
+
mode (ExtractionMode): Extraction detail level. "light" includes cells and table detection only (no COM, shapes/charts empty; print areas via openpyxl). "standard" includes texted shapes, arrows, charts (COM if available) and print areas. "verbose" also includes shape/chart sizes, cell link map, colors map, and formulas map.
|
|
101
98
|
|
|
102
99
|
Returns:
|
|
103
|
-
WorkbookData containing sheets, rows, shapes, charts, and print areas.
|
|
104
|
-
|
|
105
|
-
Raises:
|
|
106
|
-
ValueError: If an invalid mode is provided.
|
|
107
|
-
|
|
108
|
-
Examples:
|
|
109
|
-
Extract with hyperlinks (verbose) and inspect table candidates:
|
|
110
|
-
|
|
111
|
-
>>> from exstruct import extract
|
|
112
|
-
>>> wb = extract("input.xlsx", mode="verbose")
|
|
113
|
-
>>> wb.sheets["Sheet1"].table_candidates
|
|
114
|
-
['A1:B5']
|
|
100
|
+
WorkbookData: Parsed workbook representation containing sheets, rows, shapes, charts, and print areas.
|
|
115
101
|
"""
|
|
116
102
|
include_links = True if mode == "verbose" else False
|
|
117
103
|
include_colors_map = True if mode == "verbose" else None
|
|
104
|
+
include_formulas_map = True if mode == "verbose" else None
|
|
118
105
|
engine = ExStructEngine(
|
|
119
106
|
options=StructOptions(
|
|
120
107
|
mode=mode,
|
|
121
108
|
include_cell_links=include_links,
|
|
122
109
|
include_colors_map=include_colors_map,
|
|
110
|
+
include_formulas_map=include_formulas_map,
|
|
123
111
|
)
|
|
124
112
|
)
|
|
125
113
|
return engine.extract(file_path, mode=mode)
|
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|
|
4
4
|
from typing import Protocol
|
|
5
5
|
|
|
6
6
|
from ...models import CellRow, PrintArea
|
|
7
|
-
from ..cells import MergedCellRange, WorkbookColorsMap
|
|
7
|
+
from ..cells import MergedCellRange, WorkbookColorsMap, WorkbookFormulasMap
|
|
8
8
|
|
|
9
9
|
CellData = dict[str, list[CellRow]]
|
|
10
10
|
PrintAreaData = dict[str, list[PrintArea]]
|
|
@@ -40,3 +40,11 @@ class Backend(Protocol):
|
|
|
40
40
|
|
|
41
41
|
def extract_merged_cells(self) -> MergedCellData:
|
|
42
42
|
"""Extract merged cell ranges from the workbook."""
|
|
43
|
+
|
|
44
|
+
def extract_formulas_map(self) -> WorkbookFormulasMap | None:
|
|
45
|
+
"""
|
|
46
|
+
Retrieve the workbook's formulas organized by worksheet.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
WorkbookFormulasMap | None: A mapping of worksheet identifiers to their formulas, or `None` if the backend cannot provide a formulas map.
|
|
50
|
+
"""
|
|
@@ -9,7 +9,12 @@ from typing import Any, cast
|
|
|
9
9
|
import xlwings as xw
|
|
10
10
|
|
|
11
11
|
from ...models import PrintArea
|
|
12
|
-
from ..cells import
|
|
12
|
+
from ..cells import (
|
|
13
|
+
WorkbookColorsMap,
|
|
14
|
+
WorkbookFormulasMap,
|
|
15
|
+
extract_sheet_colors_map_com,
|
|
16
|
+
extract_sheet_formulas_map_com,
|
|
17
|
+
)
|
|
13
18
|
from ..ranges import parse_range_zero_based
|
|
14
19
|
from .base import MergedCellData, PrintAreaData
|
|
15
20
|
|
|
@@ -58,14 +63,15 @@ class ComBackend:
|
|
|
58
63
|
def extract_colors_map(
|
|
59
64
|
self, *, include_default_background: bool, ignore_colors: set[str] | None
|
|
60
65
|
) -> WorkbookColorsMap | None:
|
|
61
|
-
"""
|
|
66
|
+
"""
|
|
67
|
+
Extract a workbook colors map using the Excel COM API.
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
include_default_background:
|
|
65
|
-
ignore_colors: Optional set of color keys to
|
|
69
|
+
Parameters:
|
|
70
|
+
include_default_background (bool): Include the workbook's default background color in the resulting map.
|
|
71
|
+
ignore_colors (set[str] | None): Optional set of color keys to exclude from the map.
|
|
66
72
|
|
|
67
73
|
Returns:
|
|
68
|
-
WorkbookColorsMap
|
|
74
|
+
WorkbookColorsMap | None: A mapping of workbook color definitions when extraction succeeds, or `None` if COM extraction fails.
|
|
69
75
|
"""
|
|
70
76
|
try:
|
|
71
77
|
return extract_sheet_colors_map_com(
|
|
@@ -80,11 +86,30 @@ class ComBackend:
|
|
|
80
86
|
)
|
|
81
87
|
return None
|
|
82
88
|
|
|
89
|
+
def extract_formulas_map(self) -> WorkbookFormulasMap | None:
|
|
90
|
+
"""
|
|
91
|
+
Extracts the workbook's formulas map using COM.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
WorkbookFormulasMap or None: The extracted formulas map, or `None` if extraction failed.
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
return extract_sheet_formulas_map_com(self.workbook)
|
|
98
|
+
except Exception as exc:
|
|
99
|
+
logger.warning(
|
|
100
|
+
"COM formula map extraction failed; skipping formulas_map. (%r)",
|
|
101
|
+
exc,
|
|
102
|
+
)
|
|
103
|
+
return None
|
|
104
|
+
|
|
83
105
|
def extract_auto_page_breaks(self) -> PrintAreaData:
|
|
84
|
-
"""
|
|
106
|
+
"""
|
|
107
|
+
Compute auto page-break rectangles for each worksheet using Excel COM.
|
|
108
|
+
|
|
109
|
+
For each sheet, determine the sheet's print area (PageSetup.PrintArea or the used range) and split it into sub-rectangles along Excel's horizontal and vertical page breaks; parts that reference a different sheet are ignored. If extraction for a sheet fails, the sheet is skipped and a warning is logged.
|
|
85
110
|
|
|
86
111
|
Returns:
|
|
87
|
-
Mapping
|
|
112
|
+
Mapping from sheet name to a list of PrintArea entries. Each PrintArea describes a rectangular region with `r1` and `r2` as 1-based row indices and `c1` and `c2` as 0-based column indices.
|
|
88
113
|
"""
|
|
89
114
|
results: PrintAreaData = {}
|
|
90
115
|
for sheet in self.workbook.sheets:
|
|
@@ -9,10 +9,12 @@ from pathlib import Path
|
|
|
9
9
|
from ...models import PrintArea
|
|
10
10
|
from ..cells import (
|
|
11
11
|
WorkbookColorsMap,
|
|
12
|
+
WorkbookFormulasMap,
|
|
12
13
|
detect_tables_openpyxl,
|
|
13
14
|
extract_sheet_cells,
|
|
14
15
|
extract_sheet_cells_with_links,
|
|
15
16
|
extract_sheet_colors_map,
|
|
17
|
+
extract_sheet_formulas_map,
|
|
16
18
|
extract_sheet_merged_cells,
|
|
17
19
|
)
|
|
18
20
|
from ..ranges import parse_range_zero_based
|
|
@@ -99,14 +101,30 @@ class OpenpyxlBackend:
|
|
|
99
101
|
except Exception:
|
|
100
102
|
return {}
|
|
101
103
|
|
|
104
|
+
def extract_formulas_map(self) -> WorkbookFormulasMap | None:
|
|
105
|
+
"""
|
|
106
|
+
Extract a mapping of workbook formulas for each sheet.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
WorkbookFormulasMap | None: A mapping from sheet name to its formulas, or `None` if extraction fails.
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
return extract_sheet_formulas_map(self.file_path)
|
|
113
|
+
except Exception as exc:
|
|
114
|
+
logger.warning(
|
|
115
|
+
"Formula map extraction failed; skipping formulas_map. (%r)", exc
|
|
116
|
+
)
|
|
117
|
+
return None
|
|
118
|
+
|
|
102
119
|
def detect_tables(self, sheet_name: str) -> list[str]:
|
|
103
|
-
"""
|
|
120
|
+
"""
|
|
121
|
+
Detects table candidate ranges within the specified worksheet.
|
|
104
122
|
|
|
105
|
-
|
|
106
|
-
sheet_name:
|
|
123
|
+
Parameters:
|
|
124
|
+
sheet_name (str): Name of the worksheet to analyze for table candidates.
|
|
107
125
|
|
|
108
126
|
Returns:
|
|
109
|
-
|
|
127
|
+
list[str]: Detected table candidate ranges as A1-style range strings; empty list if none are found or detection fails.
|
|
110
128
|
"""
|
|
111
129
|
try:
|
|
112
130
|
return detect_tables_openpyxl(self.file_path, sheet_name)
|
|
@@ -56,13 +56,41 @@ class WorkbookColorsMap:
|
|
|
56
56
|
sheets: dict[str, SheetColorsMap]
|
|
57
57
|
|
|
58
58
|
def get_sheet(self, sheet_name: str) -> SheetColorsMap | None:
|
|
59
|
-
"""
|
|
59
|
+
"""
|
|
60
|
+
Retrieve the SheetColorsMap for a worksheet by name.
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
sheet_name:
|
|
62
|
+
Parameters:
|
|
63
|
+
sheet_name (str): Name of the worksheet to retrieve.
|
|
63
64
|
|
|
64
65
|
Returns:
|
|
65
|
-
SheetColorsMap
|
|
66
|
+
SheetColorsMap | None: The sheet's color map if present, `None` otherwise.
|
|
67
|
+
"""
|
|
68
|
+
return self.sheets.get(sheet_name)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True)
|
|
72
|
+
class SheetFormulasMap:
|
|
73
|
+
"""Formula map for a single worksheet."""
|
|
74
|
+
|
|
75
|
+
sheet_name: str
|
|
76
|
+
formulas_map: dict[str, list[tuple[int, int]]]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True)
|
|
80
|
+
class WorkbookFormulasMap:
|
|
81
|
+
"""Formula maps for all worksheets in a workbook."""
|
|
82
|
+
|
|
83
|
+
sheets: dict[str, SheetFormulasMap]
|
|
84
|
+
|
|
85
|
+
def get_sheet(self, sheet_name: str) -> SheetFormulasMap | None:
|
|
86
|
+
"""
|
|
87
|
+
Retrieve the formulas map for a worksheet.
|
|
88
|
+
|
|
89
|
+
Parameters:
|
|
90
|
+
sheet_name (str): Name of the worksheet to look up.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
SheetFormulasMap | None: The sheet's formulas map if present, `None` if the worksheet is not found.
|
|
66
94
|
"""
|
|
67
95
|
return self.sheets.get(sheet_name)
|
|
68
96
|
|
|
@@ -102,22 +130,79 @@ def extract_sheet_colors_map(
|
|
|
102
130
|
return WorkbookColorsMap(sheets=sheets)
|
|
103
131
|
|
|
104
132
|
|
|
133
|
+
def extract_sheet_formulas_map(file_path: Path) -> WorkbookFormulasMap:
|
|
134
|
+
"""
|
|
135
|
+
Extract normalized formula strings from every worksheet in the workbook.
|
|
136
|
+
|
|
137
|
+
Parameters:
|
|
138
|
+
file_path (Path): Path to the Excel workbook to read.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
WorkbookFormulasMap: Mapping of sheet names to SheetFormulasMap objects. Each SheetFormulasMap contains a mapping from normalized formula strings (each beginning with "=") to a list of cell coordinates (row, column) where that formula occurs.
|
|
142
|
+
"""
|
|
143
|
+
sheets: dict[str, SheetFormulasMap] = {}
|
|
144
|
+
with openpyxl_workbook(file_path, data_only=False, read_only=False) as wb:
|
|
145
|
+
for ws in wb.worksheets:
|
|
146
|
+
sheet_map = _extract_sheet_formulas(ws)
|
|
147
|
+
sheets[ws.title] = sheet_map
|
|
148
|
+
return WorkbookFormulasMap(sheets=sheets)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def extract_sheet_formulas_map_com(workbook: xw.Book) -> WorkbookFormulasMap:
|
|
152
|
+
"""
|
|
153
|
+
Collects and normalizes formulas from every worksheet in an xlwings workbook into per-sheet mappings.
|
|
154
|
+
|
|
155
|
+
Parameters:
|
|
156
|
+
workbook: xlwings Book instance whose sheets will be scanned for formulas.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
WorkbookFormulasMap: maps sheet names to SheetFormulasMap objects. Each SheetFormulasMap.formulas_map maps a normalized formula string (consistent representation, e.g., beginning with "=") to a list of (row, column) tuples where row is 1-based and column is 0-based.
|
|
160
|
+
"""
|
|
161
|
+
sheets: dict[str, SheetFormulasMap] = {}
|
|
162
|
+
for sheet in workbook.sheets:
|
|
163
|
+
formulas_map: dict[str, list[tuple[int, int]]] = {}
|
|
164
|
+
used = sheet.used_range
|
|
165
|
+
start_row = int(getattr(used, "row", 1))
|
|
166
|
+
start_col = int(getattr(used, "column", 1))
|
|
167
|
+
max_row = used.last_cell.row
|
|
168
|
+
max_col = used.last_cell.column
|
|
169
|
+
if max_row <= 0 or max_col <= 0:
|
|
170
|
+
sheets[sheet.name] = SheetFormulasMap(
|
|
171
|
+
sheet_name=sheet.name, formulas_map=formulas_map
|
|
172
|
+
)
|
|
173
|
+
continue
|
|
174
|
+
rng = sheet.range((start_row, start_col), (max_row, max_col))
|
|
175
|
+
matrix = _normalize_matrix(rng.formula)
|
|
176
|
+
for r_offset, row in enumerate(matrix):
|
|
177
|
+
for c_offset, value in enumerate(row):
|
|
178
|
+
normalized = _normalize_formula_from_com(value)
|
|
179
|
+
if normalized is None:
|
|
180
|
+
continue
|
|
181
|
+
row_index = start_row + r_offset
|
|
182
|
+
col_index = start_col + c_offset - 1
|
|
183
|
+
formulas_map.setdefault(normalized, []).append((row_index, col_index))
|
|
184
|
+
sheets[sheet.name] = SheetFormulasMap(
|
|
185
|
+
sheet_name=sheet.name, formulas_map=formulas_map
|
|
186
|
+
)
|
|
187
|
+
return WorkbookFormulasMap(sheets=sheets)
|
|
188
|
+
|
|
189
|
+
|
|
105
190
|
def extract_sheet_colors_map_com(
|
|
106
191
|
workbook: xw.Book,
|
|
107
192
|
*,
|
|
108
193
|
include_default_background: bool,
|
|
109
194
|
ignore_colors: set[str] | None,
|
|
110
195
|
) -> WorkbookColorsMap:
|
|
111
|
-
"""
|
|
196
|
+
"""
|
|
197
|
+
Extract per-sheet background color maps using the workbook's COM/display-format interfaces.
|
|
112
198
|
|
|
113
|
-
|
|
114
|
-
workbook: xlwings workbook
|
|
115
|
-
include_default_background:
|
|
116
|
-
|
|
117
|
-
ignore_colors: Optional set of color keys to ignore.
|
|
199
|
+
Parameters:
|
|
200
|
+
workbook (xw.Book): xlwings workbook whose sheets will be inspected.
|
|
201
|
+
include_default_background (bool): If true, include default background colors (e.g., white) for cells inside each sheet's used range.
|
|
202
|
+
ignore_colors (set[str] | None): Optional set of normalized color keys to exclude from results.
|
|
118
203
|
|
|
119
204
|
Returns:
|
|
120
|
-
WorkbookColorsMap
|
|
205
|
+
WorkbookColorsMap: Mapping of sheet names to SheetColorsMap containing detected background color positions for each worksheet.
|
|
121
206
|
"""
|
|
122
207
|
_prepare_workbook_for_display_format(workbook)
|
|
123
208
|
sheets: dict[str, SheetColorsMap] = {}
|
|
@@ -133,15 +218,16 @@ def extract_sheet_colors_map_com(
|
|
|
133
218
|
def _extract_sheet_colors(
|
|
134
219
|
ws: Worksheet, include_default_background: bool, ignore_colors: set[str] | None
|
|
135
220
|
) -> SheetColorsMap:
|
|
136
|
-
"""
|
|
221
|
+
"""
|
|
222
|
+
Extract the background color locations present on a single worksheet.
|
|
137
223
|
|
|
138
|
-
|
|
139
|
-
ws:
|
|
140
|
-
include_default_background:
|
|
141
|
-
ignore_colors: Optional set of color keys to ignore.
|
|
224
|
+
Parameters:
|
|
225
|
+
ws (Worksheet): Worksheet to scan.
|
|
226
|
+
include_default_background (bool): If true, treat cells with the workbook default/background color as having a color key.
|
|
227
|
+
ignore_colors (set[str] | None): Optional set of color keys to ignore (keys are normalized before comparison).
|
|
142
228
|
|
|
143
229
|
Returns:
|
|
144
|
-
SheetColorsMap
|
|
230
|
+
SheetColorsMap: Mapping from normalized color key to a list of cell coordinates where that color appears. Coordinates are tuples (row, col) where `row` is 1-based and `col` is 0-based.
|
|
145
231
|
"""
|
|
146
232
|
min_row, min_col, max_row, max_col = _get_used_range_bounds(ws)
|
|
147
233
|
colors_map: dict[str, list[tuple[int, int]]] = {}
|
|
@@ -165,18 +251,90 @@ def _extract_sheet_colors(
|
|
|
165
251
|
return SheetColorsMap(sheet_name=ws.title, colors_map=colors_map)
|
|
166
252
|
|
|
167
253
|
|
|
254
|
+
def _extract_sheet_formulas(ws: Worksheet) -> SheetFormulasMap:
|
|
255
|
+
"""
|
|
256
|
+
Collect normalized formula strings from a worksheet and group their cell coordinates.
|
|
257
|
+
|
|
258
|
+
Parameters:
|
|
259
|
+
ws (Worksheet): Worksheet to scan for formulas.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
SheetFormulasMap: container with the sheet's name and a mapping from each normalized formula string (prefixed with "=") to a list of cell coordinates as (row, zero-based-column).
|
|
263
|
+
"""
|
|
264
|
+
min_row, min_col, max_row, max_col = _get_used_range_bounds(ws)
|
|
265
|
+
formulas_map: dict[str, list[tuple[int, int]]] = {}
|
|
266
|
+
if min_row > max_row or min_col > max_col:
|
|
267
|
+
return SheetFormulasMap(sheet_name=ws.title, formulas_map=formulas_map)
|
|
268
|
+
|
|
269
|
+
for row in ws.iter_rows(
|
|
270
|
+
min_row=min_row, max_row=max_row, min_col=min_col, max_col=max_col
|
|
271
|
+
):
|
|
272
|
+
for cell in row:
|
|
273
|
+
if getattr(cell, "data_type", None) != "f":
|
|
274
|
+
continue
|
|
275
|
+
normalized = _normalize_formula_value(getattr(cell, "value", None))
|
|
276
|
+
if normalized is None:
|
|
277
|
+
continue
|
|
278
|
+
formulas_map.setdefault(normalized, []).append((cell.row, cell.col_idx - 1))
|
|
279
|
+
return SheetFormulasMap(sheet_name=ws.title, formulas_map=formulas_map)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _normalize_formula_value(value: object) -> str | None:
|
|
283
|
+
"""Normalize a formula string for openpyxl cells.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
value: Raw cell value.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Formula string with leading "=", or None when empty.
|
|
290
|
+
"""
|
|
291
|
+
if value is None:
|
|
292
|
+
return None
|
|
293
|
+
array_text = getattr(value, "text", None)
|
|
294
|
+
if array_text is not None:
|
|
295
|
+
text = str(array_text)
|
|
296
|
+
else:
|
|
297
|
+
text = str(value)
|
|
298
|
+
if text == "":
|
|
299
|
+
return None
|
|
300
|
+
if not text.startswith("="):
|
|
301
|
+
return f"={text}"
|
|
302
|
+
return text
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _normalize_formula_from_com(value: object) -> str | None:
|
|
306
|
+
"""
|
|
307
|
+
Normalize a COM-returned cell formula into a string that begins with '='.
|
|
308
|
+
|
|
309
|
+
Parameters:
|
|
310
|
+
value (object): Raw value returned from COM for a cell's formula.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
str | None: The input string if it is non-empty and starts with '=', `None` otherwise.
|
|
314
|
+
"""
|
|
315
|
+
if value is None or not isinstance(value, str):
|
|
316
|
+
return None
|
|
317
|
+
text = value
|
|
318
|
+
if text == "":
|
|
319
|
+
return None
|
|
320
|
+
if not text.startswith("="):
|
|
321
|
+
return None
|
|
322
|
+
return text
|
|
323
|
+
|
|
324
|
+
|
|
168
325
|
def _extract_sheet_colors_com(
|
|
169
326
|
sheet: xw.Sheet, include_default_background: bool, ignore_colors: set[str] | None
|
|
170
327
|
) -> SheetColorsMap:
|
|
171
|
-
"""
|
|
328
|
+
"""
|
|
329
|
+
Extract per-sheet background color mapping using COM/DisplayFormat.
|
|
172
330
|
|
|
173
|
-
|
|
174
|
-
sheet:
|
|
175
|
-
include_default_background:
|
|
176
|
-
ignore_colors: Optional set of color keys to
|
|
331
|
+
Parameters:
|
|
332
|
+
sheet (xw.Sheet): xlwings sheet object to inspect.
|
|
333
|
+
include_default_background (bool): If True, include cells whose background is the workbook default color.
|
|
334
|
+
ignore_colors (set[str] | None): Optional set of normalized color keys to exclude from the result.
|
|
177
335
|
|
|
178
336
|
Returns:
|
|
179
|
-
SheetColorsMap
|
|
337
|
+
SheetColorsMap: Mapping from normalized color key (hex/theme/index canonical form) to a list of cell coordinates where that color appears. Each coordinate is a tuple (row, col) where `row` is the worksheet row number (1-based) and `col` is the zero-based column index.
|
|
180
338
|
"""
|
|
181
339
|
colors_map: dict[str, list[tuple[int, int]]] = {}
|
|
182
340
|
used = sheet.used_range
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from ..models import WorkbookData
|
|
7
|
+
from .pipeline import resolve_extraction_inputs, run_extraction_pipeline
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def extract_workbook( # noqa: C901
|
|
11
|
+
file_path: str | Path,
|
|
12
|
+
mode: Literal["light", "standard", "verbose"] = "standard",
|
|
13
|
+
*,
|
|
14
|
+
include_cell_links: bool | None = None,
|
|
15
|
+
include_print_areas: bool | None = None,
|
|
16
|
+
include_auto_page_breaks: bool = False,
|
|
17
|
+
include_colors_map: bool | None = None,
|
|
18
|
+
include_default_background: bool = False,
|
|
19
|
+
ignore_colors: set[str] | None = None,
|
|
20
|
+
include_formulas_map: bool | None = None,
|
|
21
|
+
include_merged_cells: bool | None = None,
|
|
22
|
+
include_merged_values_in_rows: bool = True,
|
|
23
|
+
) -> WorkbookData:
|
|
24
|
+
"""
|
|
25
|
+
Extract a workbook into a structured WorkbookData representation.
|
|
26
|
+
|
|
27
|
+
May fall back to cells+tables extraction if Excel COM automation is unavailable.
|
|
28
|
+
|
|
29
|
+
Parameters:
|
|
30
|
+
file_path (str | Path): Path to the workbook file.
|
|
31
|
+
mode (Literal['light', 'standard', 'verbose']): Extraction mode that controls detail level.
|
|
32
|
+
include_cell_links (bool | None): Include cell hyperlinks; `None` uses mode defaults.
|
|
33
|
+
include_print_areas (bool | None): Include print areas; `None` defaults to True.
|
|
34
|
+
include_auto_page_breaks (bool): Include automatic page break information.
|
|
35
|
+
include_colors_map (bool | None): Include a colors map; `None` uses mode defaults.
|
|
36
|
+
include_default_background (bool): Include default background color when present.
|
|
37
|
+
ignore_colors (set[str] | None): Set of color keys to ignore during color mapping.
|
|
38
|
+
include_formulas_map (bool | None): Include a map of cell formulas; `None` uses mode defaults.
|
|
39
|
+
include_merged_cells (bool | None): Include merged cell ranges; `None` uses mode defaults.
|
|
40
|
+
include_merged_values_in_rows (bool): Preserve merged cell values in row-wise output.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
WorkbookData: The extracted workbook representation.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If `mode` is not one of "light", "standard", or "verbose".
|
|
47
|
+
"""
|
|
48
|
+
inputs = resolve_extraction_inputs(
|
|
49
|
+
file_path,
|
|
50
|
+
mode=mode,
|
|
51
|
+
include_cell_links=include_cell_links,
|
|
52
|
+
include_print_areas=include_print_areas,
|
|
53
|
+
include_auto_page_breaks=include_auto_page_breaks,
|
|
54
|
+
include_colors_map=include_colors_map,
|
|
55
|
+
include_default_background=include_default_background,
|
|
56
|
+
ignore_colors=ignore_colors,
|
|
57
|
+
include_formulas_map=include_formulas_map,
|
|
58
|
+
include_merged_cells=include_merged_cells,
|
|
59
|
+
include_merged_values_in_rows=include_merged_values_in_rows,
|
|
60
|
+
)
|
|
61
|
+
result = run_extraction_pipeline(inputs)
|
|
62
|
+
return result.workbook
|
|
@@ -27,6 +27,7 @@ class SheetRawData:
|
|
|
27
27
|
table_candidates: Detected table ranges.
|
|
28
28
|
print_areas: Extracted print areas.
|
|
29
29
|
auto_print_areas: Extracted auto page-break areas.
|
|
30
|
+
formulas_map: Mapping of formula strings to (row, column) positions.
|
|
30
31
|
colors_map: Mapping of color keys to (row, column) positions.
|
|
31
32
|
merged_cells: Extracted merged cell ranges.
|
|
32
33
|
"""
|
|
@@ -37,6 +38,7 @@ class SheetRawData:
|
|
|
37
38
|
table_candidates: list[str]
|
|
38
39
|
print_areas: list[PrintArea]
|
|
39
40
|
auto_print_areas: list[PrintArea]
|
|
41
|
+
formulas_map: dict[str, list[tuple[int, int]]]
|
|
40
42
|
colors_map: dict[str, list[tuple[int, int]]]
|
|
41
43
|
merged_cells: list[MergedCellRange]
|
|
42
44
|
|
|
@@ -70,6 +72,7 @@ def build_sheet_data(raw: SheetRawData) -> SheetData:
|
|
|
70
72
|
table_candidates=raw.table_candidates,
|
|
71
73
|
print_areas=raw.print_areas,
|
|
72
74
|
auto_print_areas=raw.auto_print_areas,
|
|
75
|
+
formulas_map=raw.formulas_map,
|
|
73
76
|
colors_map=raw.colors_map,
|
|
74
77
|
merged_cells=_build_merged_cells(raw.merged_cells),
|
|
75
78
|
)
|