exstruct 0.2.3__tar.gz → 0.2.21__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.2.3 → exstruct-0.2.21}/PKG-INFO +39 -37
- {exstruct-0.2.3 → exstruct-0.2.21}/README.md +38 -36
- {exstruct-0.2.3 → exstruct-0.2.21}/pyproject.toml +1 -1
- exstruct-0.2.21/src/exstruct/__init__.py +122 -0
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/cli/main.py +0 -6
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/core/charts.py +2 -12
- exstruct-0.2.21/src/exstruct/core/integrate.py +140 -0
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/engine.py +135 -244
- exstruct-0.2.21/src/exstruct/io/__init__.py +187 -0
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/models/__init__.py +170 -244
- exstruct-0.2.3/src/exstruct/__init__.py +0 -215
- exstruct-0.2.3/src/exstruct/core/integrate.py +0 -252
- exstruct-0.2.3/src/exstruct/io/__init__.py +0 -418
- {exstruct-0.2.3 → exstruct-0.2.21}/LICENSE +0 -0
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/core/__init__.py +0 -0
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/core/cells.py +0 -0
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/core/shapes.py +0 -0
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/models/maps.py +0 -0
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/py.typed +0 -0
- {exstruct-0.2.3 → exstruct-0.2.21}/src/exstruct/render/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: exstruct
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.21
|
|
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
|
|
@@ -60,12 +60,12 @@ Description-Content-Type: text/markdown
|
|
|
60
60
|
|
|
61
61
|

|
|
62
62
|
|
|
63
|
-
ExStruct reads Excel workbooks and outputs structured data (
|
|
63
|
+
ExStruct reads Excel workbooks and outputs structured data (tables, shapes, charts, hyperlinks) as JSON by default, with optional YAML/TOON formats. It targets both COM/Excel environments (rich extraction) and non-COM environments (cells + table candidates), with tunable detection heuristics and multiple output modes to fit LLM/RAG pipelines.
|
|
64
64
|
|
|
65
65
|
## Features
|
|
66
66
|
|
|
67
|
-
- **Excel → Structured JSON**: cells, shapes, charts, table candidates
|
|
68
|
-
- **Output modes**: `light` (cells + table candidates
|
|
67
|
+
- **Excel → Structured JSON**: cells, shapes, charts, and table candidates per sheet.
|
|
68
|
+
- **Output modes**: `light` (cells + table candidates only), `standard` (texted shapes + arrows, charts), `verbose` (all shapes with width/height). Verbose also emits cell hyperlinks.
|
|
69
69
|
- **Formats**: JSON (compact by default, `--pretty` available), YAML, TOON (optional dependencies).
|
|
70
70
|
- **Table detection tuning**: adjust heuristics at runtime via API.
|
|
71
71
|
- **CLI rendering** (Excel required): optional PDF and per-sheet PNGs.
|
|
@@ -85,8 +85,7 @@ Optional extras:
|
|
|
85
85
|
- All extras at once: `pip install exstruct[yaml,toon,render]`
|
|
86
86
|
|
|
87
87
|
Platform note:
|
|
88
|
-
|
|
89
|
-
- Full extraction (shapes/charts) targets Windows + Excel (COM via xlwings). On other platforms, use `mode=light` to get cells + `table_candidates`.
|
|
88
|
+
- Full extraction (shapes/charts) targets Windows + Excel (COM via xlwings). On other platforms, use `mode=light` to get cells + `table_candidates` safely.
|
|
90
89
|
|
|
91
90
|
## Quick Start (CLI)
|
|
92
91
|
|
|
@@ -96,7 +95,6 @@ exstruct input.xlsx -o out.json --pretty # pretty JSON to a file
|
|
|
96
95
|
exstruct input.xlsx --format yaml # YAML (needs pyyaml)
|
|
97
96
|
exstruct input.xlsx --format toon # TOON (needs python-toon)
|
|
98
97
|
exstruct input.xlsx --sheets-dir sheets/ # split per sheet in chosen format
|
|
99
|
-
exstruct input.xlsx --print-areas-dir areas/ # split per print area (if any)
|
|
100
98
|
exstruct input.xlsx --mode light # cells + table candidates only
|
|
101
99
|
exstruct input.xlsx --pdf --image # PDF and PNGs (Excel required)
|
|
102
100
|
```
|
|
@@ -114,7 +112,7 @@ set_table_detection_params(table_score_threshold=0.3, density_min=0.04)
|
|
|
114
112
|
wb = extract("input.xlsx", mode="standard")
|
|
115
113
|
export(wb, Path("out.json"), pretty=False) # compact JSON
|
|
116
114
|
|
|
117
|
-
# Model helpers: iterate, index, and serialize directly
|
|
115
|
+
# Model helpers: iterate, index, and serialize directly from the models
|
|
118
116
|
first_sheet = wb["Sheet1"] # __getitem__ access
|
|
119
117
|
for name, sheet in wb: # __iter__ yields (name, SheetData)
|
|
120
118
|
print(name, len(sheet.rows))
|
|
@@ -122,23 +120,19 @@ wb.save("out.json", pretty=True) # WorkbookData → file (by extension)
|
|
|
122
120
|
first_sheet.save("sheet.json") # SheetData → file (by extension)
|
|
123
121
|
print(first_sheet.to_yaml()) # YAML text (requires pyyaml)
|
|
124
122
|
|
|
125
|
-
# ExStructEngine: per-instance options for extraction/output
|
|
126
|
-
from exstruct import ExStructEngine, StructOptions, OutputOptions
|
|
127
|
-
|
|
128
|
-
engine = ExStructEngine(
|
|
129
|
-
options=StructOptions(mode="verbose"), # verbose includes hyperlinks by default
|
|
130
|
-
output=OutputOptions(include_shapes=False, pretty=True),
|
|
131
|
-
)
|
|
132
|
-
wb2 = engine.extract("input.xlsx")
|
|
133
|
-
engine.export(wb2, Path("out_filtered.json")) # drops shapes via OutputOptions
|
|
134
|
-
|
|
135
|
-
# Enable hyperlinks in other modes
|
|
136
|
-
engine_links = ExStructEngine(options=StructOptions(mode="standard", include_cell_links=True))
|
|
137
|
-
with_links = engine_links.extract("input.xlsx")
|
|
138
|
-
|
|
139
|
-
# Export per print area (if print areas exist)
|
|
140
|
-
from exstruct import export_print_areas_as
|
|
141
|
-
export_print_areas_as(wb, "areas", fmt="json", pretty=True)
|
|
123
|
+
# ExStructEngine: per-instance options for extraction/output
|
|
124
|
+
from exstruct import ExStructEngine, StructOptions, OutputOptions
|
|
125
|
+
|
|
126
|
+
engine = ExStructEngine(
|
|
127
|
+
options=StructOptions(mode="verbose"), # verbose includes hyperlinks by default
|
|
128
|
+
output=OutputOptions(include_shapes=False, pretty=True),
|
|
129
|
+
)
|
|
130
|
+
wb2 = engine.extract("input.xlsx")
|
|
131
|
+
engine.export(wb2, Path("out_filtered.json")) # drops shapes via OutputOptions
|
|
132
|
+
|
|
133
|
+
# Enable hyperlinks in other modes
|
|
134
|
+
engine_links = ExStructEngine(options=StructOptions(mode="standard", include_cell_links=True))
|
|
135
|
+
with_links = engine_links.extract("input.xlsx")
|
|
142
136
|
```
|
|
143
137
|
|
|
144
138
|
**Note (non-COM environments):** If Excel COM is unavailable, extraction still runs and returns cells + `table_candidates`; `shapes`/`charts` will be empty.
|
|
@@ -158,11 +152,11 @@ set_table_detection_params(
|
|
|
158
152
|
|
|
159
153
|
Use higher thresholds to reduce false positives; lower them if true tables are missed.
|
|
160
154
|
|
|
161
|
-
## Output Modes
|
|
162
|
-
|
|
163
|
-
- **light**: cells + table candidates (no COM needed).
|
|
164
|
-
- **standard**: texted shapes + arrows, charts (COM if available), table candidates. Hyperlinks are off unless `include_cell_links=True`.
|
|
165
|
-
- **verbose**: all shapes (with width/height), charts, table candidates, and cell hyperlinks.
|
|
155
|
+
## Output Modes
|
|
156
|
+
|
|
157
|
+
- **light**: cells + table candidates (no COM needed).
|
|
158
|
+
- **standard**: texted shapes + arrows, charts (COM if available), table candidates. Hyperlinks are off unless `include_cell_links=True`.
|
|
159
|
+
- **verbose**: all shapes (with width/height), charts, table candidates, and cell hyperlinks.
|
|
166
160
|
|
|
167
161
|
## Error Handling / Fallbacks
|
|
168
162
|
|
|
@@ -191,7 +185,6 @@ To show how well exstruct can structure Excel, we parse a workbook that combines
|
|
|
191
185
|
(Screenshot below is the actual sample Excel sheet)
|
|
192
186
|

|
|
193
187
|
Sample workbook: `sample/sample.xlsx`
|
|
194
|
-
Sample workbook: `sample/sample.xlsx`
|
|
195
188
|
|
|
196
189
|
### 1. Input: Excel Sheet Overview
|
|
197
190
|
|
|
@@ -378,12 +371,6 @@ In short, **exstruct = “an engine that converts Excel into a format AI can und
|
|
|
378
371
|
- Default JSON is compact to reduce tokens; use `--pretty` or `pretty=True` when readability matters.
|
|
379
372
|
- Field `table_candidates` replaces `tables`; adjust downstream consumers accordingly.
|
|
380
373
|
|
|
381
|
-
## Print Areas (PrintArea / PrintAreaView)
|
|
382
|
-
|
|
383
|
-
- `SheetData.print_areas` holds print areas (cell coordinates) in light/standard/verbose.
|
|
384
|
-
- Use `export_print_areas_as(...)` or CLI `--print-areas-dir` to write one file per print area (nothing is written if none exist).
|
|
385
|
-
- `PrintAreaView` includes rows and table candidates inside the area, plus shapes/charts that overlap the area (size-less shapes are treated as points). `normalize=True` rebases row/col indices to the area origin.
|
|
386
|
-
|
|
387
374
|
## License
|
|
388
375
|
|
|
389
376
|
BSD-3-Clause. See `LICENSE` for details.
|
|
@@ -391,3 +378,18 @@ BSD-3-Clause. See `LICENSE` for details.
|
|
|
391
378
|
## Documentation
|
|
392
379
|
|
|
393
380
|
- API Reference (GitHub Pages): https://harumiweb.github.io/exstruct/
|
|
381
|
+
# Engine option cheat sheet
|
|
382
|
+
|
|
383
|
+
| Option class | Field | Meaning |
|
|
384
|
+
| -------------- | ------------------- | ------- |
|
|
385
|
+
| StructOptions | mode | "light"/"standard"/"verbose" |
|
|
386
|
+
| | table_params | Dict passed to `set_table_detection_params` (table_score_threshold, density_min, coverage_min, min_nonempty_cells) |
|
|
387
|
+
| | include_cell_links | Include cell hyperlinks in `rows[*].links` (None -> auto: verbose=True, others=False) |
|
|
388
|
+
| OutputOptions | fmt | Default format ("json"/"yaml"/"yml"/"toon") |
|
|
389
|
+
| | pretty / indent | Pretty-print JSON and control indent |
|
|
390
|
+
| | include_rows | Include rows (False to drop) |
|
|
391
|
+
| | include_shapes | Include shapes |
|
|
392
|
+
| | include_charts | Include charts |
|
|
393
|
+
| | include_tables | Include table_candidates |
|
|
394
|
+
| | sheets_dir | Optional directory for per-sheet exports |
|
|
395
|
+
| | stream | Default stream when output_path is None |
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
ExStruct reads Excel workbooks and outputs structured data (
|
|
7
|
+
ExStruct reads Excel workbooks and outputs structured data (tables, shapes, charts, hyperlinks) as JSON by default, with optional YAML/TOON formats. It targets both COM/Excel environments (rich extraction) and non-COM environments (cells + table candidates), with tunable detection heuristics and multiple output modes to fit LLM/RAG pipelines.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **Excel → Structured JSON**: cells, shapes, charts, table candidates
|
|
12
|
-
- **Output modes**: `light` (cells + table candidates
|
|
11
|
+
- **Excel → Structured JSON**: cells, shapes, charts, and table candidates per sheet.
|
|
12
|
+
- **Output modes**: `light` (cells + table candidates only), `standard` (texted shapes + arrows, charts), `verbose` (all shapes with width/height). Verbose also emits cell hyperlinks.
|
|
13
13
|
- **Formats**: JSON (compact by default, `--pretty` available), YAML, TOON (optional dependencies).
|
|
14
14
|
- **Table detection tuning**: adjust heuristics at runtime via API.
|
|
15
15
|
- **CLI rendering** (Excel required): optional PDF and per-sheet PNGs.
|
|
@@ -29,8 +29,7 @@ Optional extras:
|
|
|
29
29
|
- All extras at once: `pip install exstruct[yaml,toon,render]`
|
|
30
30
|
|
|
31
31
|
Platform note:
|
|
32
|
-
|
|
33
|
-
- Full extraction (shapes/charts) targets Windows + Excel (COM via xlwings). On other platforms, use `mode=light` to get cells + `table_candidates`.
|
|
32
|
+
- Full extraction (shapes/charts) targets Windows + Excel (COM via xlwings). On other platforms, use `mode=light` to get cells + `table_candidates` safely.
|
|
34
33
|
|
|
35
34
|
## Quick Start (CLI)
|
|
36
35
|
|
|
@@ -40,7 +39,6 @@ exstruct input.xlsx -o out.json --pretty # pretty JSON to a file
|
|
|
40
39
|
exstruct input.xlsx --format yaml # YAML (needs pyyaml)
|
|
41
40
|
exstruct input.xlsx --format toon # TOON (needs python-toon)
|
|
42
41
|
exstruct input.xlsx --sheets-dir sheets/ # split per sheet in chosen format
|
|
43
|
-
exstruct input.xlsx --print-areas-dir areas/ # split per print area (if any)
|
|
44
42
|
exstruct input.xlsx --mode light # cells + table candidates only
|
|
45
43
|
exstruct input.xlsx --pdf --image # PDF and PNGs (Excel required)
|
|
46
44
|
```
|
|
@@ -58,7 +56,7 @@ set_table_detection_params(table_score_threshold=0.3, density_min=0.04)
|
|
|
58
56
|
wb = extract("input.xlsx", mode="standard")
|
|
59
57
|
export(wb, Path("out.json"), pretty=False) # compact JSON
|
|
60
58
|
|
|
61
|
-
# Model helpers: iterate, index, and serialize directly
|
|
59
|
+
# Model helpers: iterate, index, and serialize directly from the models
|
|
62
60
|
first_sheet = wb["Sheet1"] # __getitem__ access
|
|
63
61
|
for name, sheet in wb: # __iter__ yields (name, SheetData)
|
|
64
62
|
print(name, len(sheet.rows))
|
|
@@ -66,23 +64,19 @@ wb.save("out.json", pretty=True) # WorkbookData → file (by extension)
|
|
|
66
64
|
first_sheet.save("sheet.json") # SheetData → file (by extension)
|
|
67
65
|
print(first_sheet.to_yaml()) # YAML text (requires pyyaml)
|
|
68
66
|
|
|
69
|
-
# ExStructEngine: per-instance options for extraction/output
|
|
70
|
-
from exstruct import ExStructEngine, StructOptions, OutputOptions
|
|
71
|
-
|
|
72
|
-
engine = ExStructEngine(
|
|
73
|
-
options=StructOptions(mode="verbose"), # verbose includes hyperlinks by default
|
|
74
|
-
output=OutputOptions(include_shapes=False, pretty=True),
|
|
75
|
-
)
|
|
76
|
-
wb2 = engine.extract("input.xlsx")
|
|
77
|
-
engine.export(wb2, Path("out_filtered.json")) # drops shapes via OutputOptions
|
|
78
|
-
|
|
79
|
-
# Enable hyperlinks in other modes
|
|
80
|
-
engine_links = ExStructEngine(options=StructOptions(mode="standard", include_cell_links=True))
|
|
81
|
-
with_links = engine_links.extract("input.xlsx")
|
|
82
|
-
|
|
83
|
-
# Export per print area (if print areas exist)
|
|
84
|
-
from exstruct import export_print_areas_as
|
|
85
|
-
export_print_areas_as(wb, "areas", fmt="json", pretty=True)
|
|
67
|
+
# ExStructEngine: per-instance options for extraction/output
|
|
68
|
+
from exstruct import ExStructEngine, StructOptions, OutputOptions
|
|
69
|
+
|
|
70
|
+
engine = ExStructEngine(
|
|
71
|
+
options=StructOptions(mode="verbose"), # verbose includes hyperlinks by default
|
|
72
|
+
output=OutputOptions(include_shapes=False, pretty=True),
|
|
73
|
+
)
|
|
74
|
+
wb2 = engine.extract("input.xlsx")
|
|
75
|
+
engine.export(wb2, Path("out_filtered.json")) # drops shapes via OutputOptions
|
|
76
|
+
|
|
77
|
+
# Enable hyperlinks in other modes
|
|
78
|
+
engine_links = ExStructEngine(options=StructOptions(mode="standard", include_cell_links=True))
|
|
79
|
+
with_links = engine_links.extract("input.xlsx")
|
|
86
80
|
```
|
|
87
81
|
|
|
88
82
|
**Note (non-COM environments):** If Excel COM is unavailable, extraction still runs and returns cells + `table_candidates`; `shapes`/`charts` will be empty.
|
|
@@ -102,11 +96,11 @@ set_table_detection_params(
|
|
|
102
96
|
|
|
103
97
|
Use higher thresholds to reduce false positives; lower them if true tables are missed.
|
|
104
98
|
|
|
105
|
-
## Output Modes
|
|
106
|
-
|
|
107
|
-
- **light**: cells + table candidates (no COM needed).
|
|
108
|
-
- **standard**: texted shapes + arrows, charts (COM if available), table candidates. Hyperlinks are off unless `include_cell_links=True`.
|
|
109
|
-
- **verbose**: all shapes (with width/height), charts, table candidates, and cell hyperlinks.
|
|
99
|
+
## Output Modes
|
|
100
|
+
|
|
101
|
+
- **light**: cells + table candidates (no COM needed).
|
|
102
|
+
- **standard**: texted shapes + arrows, charts (COM if available), table candidates. Hyperlinks are off unless `include_cell_links=True`.
|
|
103
|
+
- **verbose**: all shapes (with width/height), charts, table candidates, and cell hyperlinks.
|
|
110
104
|
|
|
111
105
|
## Error Handling / Fallbacks
|
|
112
106
|
|
|
@@ -135,7 +129,6 @@ To show how well exstruct can structure Excel, we parse a workbook that combines
|
|
|
135
129
|
(Screenshot below is the actual sample Excel sheet)
|
|
136
130
|

|
|
137
131
|
Sample workbook: `sample/sample.xlsx`
|
|
138
|
-
Sample workbook: `sample/sample.xlsx`
|
|
139
132
|
|
|
140
133
|
### 1. Input: Excel Sheet Overview
|
|
141
134
|
|
|
@@ -322,12 +315,6 @@ In short, **exstruct = “an engine that converts Excel into a format AI can und
|
|
|
322
315
|
- Default JSON is compact to reduce tokens; use `--pretty` or `pretty=True` when readability matters.
|
|
323
316
|
- Field `table_candidates` replaces `tables`; adjust downstream consumers accordingly.
|
|
324
317
|
|
|
325
|
-
## Print Areas (PrintArea / PrintAreaView)
|
|
326
|
-
|
|
327
|
-
- `SheetData.print_areas` holds print areas (cell coordinates) in light/standard/verbose.
|
|
328
|
-
- Use `export_print_areas_as(...)` or CLI `--print-areas-dir` to write one file per print area (nothing is written if none exist).
|
|
329
|
-
- `PrintAreaView` includes rows and table candidates inside the area, plus shapes/charts that overlap the area (size-less shapes are treated as points). `normalize=True` rebases row/col indices to the area origin.
|
|
330
|
-
|
|
331
318
|
## License
|
|
332
319
|
|
|
333
320
|
BSD-3-Clause. See `LICENSE` for details.
|
|
@@ -335,3 +322,18 @@ BSD-3-Clause. See `LICENSE` for details.
|
|
|
335
322
|
## Documentation
|
|
336
323
|
|
|
337
324
|
- API Reference (GitHub Pages): https://harumiweb.github.io/exstruct/
|
|
325
|
+
# Engine option cheat sheet
|
|
326
|
+
|
|
327
|
+
| Option class | Field | Meaning |
|
|
328
|
+
| -------------- | ------------------- | ------- |
|
|
329
|
+
| StructOptions | mode | "light"/"standard"/"verbose" |
|
|
330
|
+
| | table_params | Dict passed to `set_table_detection_params` (table_score_threshold, density_min, coverage_min, min_nonempty_cells) |
|
|
331
|
+
| | include_cell_links | Include cell hyperlinks in `rows[*].links` (None -> auto: verbose=True, others=False) |
|
|
332
|
+
| OutputOptions | fmt | Default format ("json"/"yaml"/"yml"/"toon") |
|
|
333
|
+
| | pretty / indent | Pretty-print JSON and control indent |
|
|
334
|
+
| | include_rows | Include rows (False to drop) |
|
|
335
|
+
| | include_shapes | Include shapes |
|
|
336
|
+
| | include_charts | Include charts |
|
|
337
|
+
| | include_tables | Include table_candidates |
|
|
338
|
+
| | sheets_dir | Optional directory for per-sheet exports |
|
|
339
|
+
| | stream | Default stream when output_path is None |
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal, Optional, TextIO
|
|
5
|
+
|
|
6
|
+
from .core.integrate import extract_workbook
|
|
7
|
+
from .core.cells import set_table_detection_params
|
|
8
|
+
from .engine import ExStructEngine, OutputOptions, StructOptions
|
|
9
|
+
from .io import save_as_json, save_as_toon, save_as_yaml, save_sheets, serialize_workbook
|
|
10
|
+
from .models import CellRow, Chart, ChartSeries, Shape, SheetData, WorkbookData
|
|
11
|
+
from .render import export_pdf, export_sheet_images
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"extract",
|
|
15
|
+
"export",
|
|
16
|
+
"export_sheets",
|
|
17
|
+
"export_pdf",
|
|
18
|
+
"export_sheet_images",
|
|
19
|
+
"process_excel",
|
|
20
|
+
"ExtractionMode",
|
|
21
|
+
"CellRow",
|
|
22
|
+
"Shape",
|
|
23
|
+
"ChartSeries",
|
|
24
|
+
"Chart",
|
|
25
|
+
"SheetData",
|
|
26
|
+
"WorkbookData",
|
|
27
|
+
"set_table_detection_params",
|
|
28
|
+
"ExStructEngine",
|
|
29
|
+
"StructOptions",
|
|
30
|
+
"OutputOptions",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
ExtractionMode = Literal["light", "standard", "verbose"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def extract(file_path: str | Path, mode: ExtractionMode = "standard") -> WorkbookData:
|
|
38
|
+
"""Extract workbook semantic structure and return WorkbookData."""
|
|
39
|
+
include_links = True if mode == "verbose" else False
|
|
40
|
+
engine = ExStructEngine(options=StructOptions(mode=mode, include_cell_links=include_links))
|
|
41
|
+
return engine.extract(file_path, mode=mode)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def export(
|
|
45
|
+
data: WorkbookData,
|
|
46
|
+
path: str | Path,
|
|
47
|
+
fmt: Optional[Literal["json", "yaml", "yml", "toon"]] = None,
|
|
48
|
+
*,
|
|
49
|
+
pretty: bool = False,
|
|
50
|
+
indent: int | None = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Export WorkbookData to supported file formats (json/yaml/toon)."""
|
|
53
|
+
dest = Path(path)
|
|
54
|
+
format_hint = (fmt or dest.suffix.lstrip(".") or "json").lower()
|
|
55
|
+
match format_hint:
|
|
56
|
+
case "json":
|
|
57
|
+
save_as_json(data, dest, pretty=pretty, indent=indent)
|
|
58
|
+
case "yaml" | "yml":
|
|
59
|
+
save_as_yaml(data, dest)
|
|
60
|
+
case "toon":
|
|
61
|
+
save_as_toon(data, dest)
|
|
62
|
+
case _:
|
|
63
|
+
raise ValueError(f"Unsupported export format: {format_hint}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def export_sheets(data: WorkbookData, dir_path: str | Path) -> dict[str, Path]:
|
|
67
|
+
"""
|
|
68
|
+
Export each sheet as a JSON file (book_name + SheetData) into a directory.
|
|
69
|
+
Returns a mapping of sheet name to written path.
|
|
70
|
+
"""
|
|
71
|
+
return save_sheets(data, Path(dir_path), fmt="json")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def export_sheets_as(
|
|
75
|
+
data: WorkbookData,
|
|
76
|
+
dir_path: str | Path,
|
|
77
|
+
fmt: Literal["json", "yaml", "yml", "toon"] = "json",
|
|
78
|
+
*,
|
|
79
|
+
pretty: bool = False,
|
|
80
|
+
indent: int | None = None,
|
|
81
|
+
) -> dict[str, Path]:
|
|
82
|
+
"""
|
|
83
|
+
Export each sheet in the given format (json/yaml/toon), including book_name and SheetData; returns sheet name → path map.
|
|
84
|
+
"""
|
|
85
|
+
return save_sheets(data, Path(dir_path), fmt=fmt, pretty=pretty, indent=indent)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def process_excel(
|
|
89
|
+
file_path: Path,
|
|
90
|
+
output_path: Path | None = None,
|
|
91
|
+
out_fmt: str = "json",
|
|
92
|
+
image: bool = False,
|
|
93
|
+
pdf: bool = False,
|
|
94
|
+
dpi: int = 72,
|
|
95
|
+
mode: ExtractionMode = "standard",
|
|
96
|
+
pretty: bool = False,
|
|
97
|
+
indent: int | None = None,
|
|
98
|
+
sheets_dir: Path | None = None,
|
|
99
|
+
stream: TextIO | None = None,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Convenience wrapper for CLI: export workbook and optionally PDF/PNG images (Excel required for rendering).
|
|
103
|
+
- If output_path is None, writes the serialized workbook to stdout (or provided stream).
|
|
104
|
+
- If sheets_dir is given, also writes per-sheet files into that directory.
|
|
105
|
+
"""
|
|
106
|
+
engine = ExStructEngine(
|
|
107
|
+
options=StructOptions(mode=mode),
|
|
108
|
+
output=OutputOptions(fmt=out_fmt, pretty=pretty, indent=indent, sheets_dir=sheets_dir, stream=stream),
|
|
109
|
+
)
|
|
110
|
+
engine.process(
|
|
111
|
+
file_path=file_path,
|
|
112
|
+
output_path=output_path,
|
|
113
|
+
out_fmt=out_fmt,
|
|
114
|
+
image=image,
|
|
115
|
+
pdf=pdf,
|
|
116
|
+
dpi=dpi,
|
|
117
|
+
mode=mode,
|
|
118
|
+
pretty=pretty,
|
|
119
|
+
indent=indent,
|
|
120
|
+
sheets_dir=sheets_dir,
|
|
121
|
+
stream=stream,
|
|
122
|
+
)
|
|
@@ -57,11 +57,6 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
57
57
|
type=Path,
|
|
58
58
|
help="Optional directory to write one file per sheet (format follows --format).",
|
|
59
59
|
)
|
|
60
|
-
parser.add_argument(
|
|
61
|
-
"--print-areas-dir",
|
|
62
|
-
type=Path,
|
|
63
|
-
help="Optional directory to write one file per print area (format follows --format).",
|
|
64
|
-
)
|
|
65
60
|
return parser
|
|
66
61
|
|
|
67
62
|
|
|
@@ -85,7 +80,6 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
85
80
|
mode=args.mode,
|
|
86
81
|
pretty=args.pretty,
|
|
87
82
|
sheets_dir=args.sheets_dir,
|
|
88
|
-
print_areas_dir=args.print_areas_dir,
|
|
89
83
|
)
|
|
90
84
|
return 0
|
|
91
85
|
except Exception as e:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Dict, List, Optional
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
5
|
|
|
6
6
|
import xlwings as xw
|
|
7
7
|
|
|
@@ -166,7 +166,7 @@ def parse_series_formula(formula: str) -> Optional[Dict[str, Optional[str]]]:
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
|
|
169
|
-
def get_charts(sheet: xw.Sheet
|
|
169
|
+
def get_charts(sheet: xw.Sheet) -> List[Chart]:
|
|
170
170
|
"""Parse charts in a sheet into Chart models; failed charts carry an error field."""
|
|
171
171
|
charts: List[Chart] = []
|
|
172
172
|
for ch in sheet.charts:
|
|
@@ -182,14 +182,6 @@ def get_charts(sheet: xw.Sheet, mode: Literal["light", "standard", "verbose"] =
|
|
|
182
182
|
chart_type_label = XL_CHART_TYPE_MAP.get(
|
|
183
183
|
chart_type_num, f"unknown_{chart_type_num}"
|
|
184
184
|
)
|
|
185
|
-
chart_width: Optional[int] = None
|
|
186
|
-
chart_height: Optional[int] = None
|
|
187
|
-
try:
|
|
188
|
-
chart_width = int(ch.width)
|
|
189
|
-
chart_height = int(ch.height)
|
|
190
|
-
except Exception:
|
|
191
|
-
chart_width = None
|
|
192
|
-
chart_height = None
|
|
193
185
|
|
|
194
186
|
for s in chart_com.SeriesCollection():
|
|
195
187
|
parsed = parse_series_formula(getattr(s, "Formula", ""))
|
|
@@ -228,8 +220,6 @@ def get_charts(sheet: xw.Sheet, mode: Literal["light", "standard", "verbose"] =
|
|
|
228
220
|
title=title,
|
|
229
221
|
y_axis_title=y_axis_title,
|
|
230
222
|
y_axis_range=y_axis_range, # type: ignore
|
|
231
|
-
w=chart_width,
|
|
232
|
-
h=chart_height,
|
|
233
223
|
series=series_list,
|
|
234
224
|
l=int(ch.left),
|
|
235
225
|
t=int(ch.top),
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, List, Literal
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
import xlwings as xw
|
|
10
|
+
|
|
11
|
+
from ..models import CellRow, Shape, SheetData, WorkbookData
|
|
12
|
+
from .cells import (
|
|
13
|
+
detect_tables,
|
|
14
|
+
detect_tables_openpyxl,
|
|
15
|
+
extract_sheet_cells,
|
|
16
|
+
extract_sheet_cells_with_links,
|
|
17
|
+
)
|
|
18
|
+
from .charts import get_charts
|
|
19
|
+
from .shapes import get_shapes_with_position
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
_ALLOWED_MODES: set[str] = {"light", "standard", "verbose"}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _find_open_workbook(file_path: Path) -> xw.Book | None:
|
|
26
|
+
"""Return an existing workbook if already open in Excel; otherwise None."""
|
|
27
|
+
try:
|
|
28
|
+
for app in xw.apps:
|
|
29
|
+
for wb in app.books:
|
|
30
|
+
try:
|
|
31
|
+
if Path(wb.fullname).resolve() == file_path.resolve():
|
|
32
|
+
return wb
|
|
33
|
+
except Exception:
|
|
34
|
+
continue
|
|
35
|
+
except Exception:
|
|
36
|
+
return None
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _open_workbook(file_path: Path) -> tuple[xw.Book, bool]:
|
|
41
|
+
"""
|
|
42
|
+
Open workbook:
|
|
43
|
+
- If already open, reuse and do not close Excel on exit.
|
|
44
|
+
- Otherwise create invisible Excel (visible=False) and close when done.
|
|
45
|
+
Returns (workbook, should_close_app).
|
|
46
|
+
"""
|
|
47
|
+
existing = _find_open_workbook(file_path)
|
|
48
|
+
if existing:
|
|
49
|
+
return existing, False
|
|
50
|
+
app = xw.App(add_book=False, visible=False)
|
|
51
|
+
wb = app.books.open(str(file_path))
|
|
52
|
+
return wb, True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def integrate_sheet_content(
|
|
56
|
+
cell_data: Dict[str, List[CellRow]],
|
|
57
|
+
shape_data: Dict[str, List[Shape]],
|
|
58
|
+
workbook: xw.Book,
|
|
59
|
+
mode: Literal["light", "standard", "verbose"] = "standard",
|
|
60
|
+
) -> Dict[str, SheetData]:
|
|
61
|
+
"""Integrate cells, shapes, charts, and tables into SheetData per sheet."""
|
|
62
|
+
result: Dict[str, SheetData] = {}
|
|
63
|
+
for sheet_name, rows in cell_data.items():
|
|
64
|
+
sheet_shapes = shape_data.get(sheet_name, [])
|
|
65
|
+
sheet = workbook.sheets[sheet_name]
|
|
66
|
+
|
|
67
|
+
sheet_model = SheetData(
|
|
68
|
+
rows=rows,
|
|
69
|
+
shapes=sheet_shapes,
|
|
70
|
+
charts=[] if mode == "light" else get_charts(sheet),
|
|
71
|
+
table_candidates=detect_tables(sheet),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
result[sheet_name] = sheet_model
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def extract_workbook(
|
|
79
|
+
file_path: Path,
|
|
80
|
+
mode: Literal["light", "standard", "verbose"] = "standard",
|
|
81
|
+
*,
|
|
82
|
+
include_cell_links: bool = False,
|
|
83
|
+
) -> WorkbookData:
|
|
84
|
+
"""Extract workbook and return WorkbookData; fallback to cells+tables if Excel COM is unavailable."""
|
|
85
|
+
if mode not in _ALLOWED_MODES:
|
|
86
|
+
raise ValueError(f"Unsupported mode: {mode}")
|
|
87
|
+
|
|
88
|
+
cell_data = extract_sheet_cells_with_links(file_path) if include_cell_links else extract_sheet_cells(file_path)
|
|
89
|
+
|
|
90
|
+
def _cells_and_tables_only(reason: str) -> WorkbookData:
|
|
91
|
+
sheets: Dict[str, SheetData] = {}
|
|
92
|
+
for sheet_name, rows in cell_data.items():
|
|
93
|
+
try:
|
|
94
|
+
tables = detect_tables_openpyxl(file_path, sheet_name)
|
|
95
|
+
except Exception:
|
|
96
|
+
tables = []
|
|
97
|
+
sheets[sheet_name] = SheetData(
|
|
98
|
+
rows=rows,
|
|
99
|
+
shapes=[],
|
|
100
|
+
charts=[],
|
|
101
|
+
table_candidates=tables,
|
|
102
|
+
)
|
|
103
|
+
logger.warning(
|
|
104
|
+
"%s Falling back to cells+tables only; shapes and charts will be empty.",
|
|
105
|
+
reason,
|
|
106
|
+
)
|
|
107
|
+
return WorkbookData(book_name=file_path.name, sheets=sheets)
|
|
108
|
+
|
|
109
|
+
if mode == "light":
|
|
110
|
+
return _cells_and_tables_only("Light mode selected.")
|
|
111
|
+
|
|
112
|
+
if os.getenv("SKIP_COM_TESTS"):
|
|
113
|
+
return _cells_and_tables_only("SKIP_COM_TESTS is set; skipping COM/xlwings access.")
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
wb, close_app = _open_workbook(file_path)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
return _cells_and_tables_only(
|
|
119
|
+
f"xlwings/Excel COM is unavailable. ({e!r})"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
try:
|
|
124
|
+
shape_data = get_shapes_with_position(wb, mode=mode)
|
|
125
|
+
merged = integrate_sheet_content(cell_data, shape_data, wb, mode=mode)
|
|
126
|
+
return WorkbookData(book_name=file_path.name, sheets=merged)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.warning("Shape extraction failed; falling back to cells+tables. (%r)", e)
|
|
129
|
+
return _cells_and_tables_only(
|
|
130
|
+
f"Shape extraction failed ({e!r})."
|
|
131
|
+
)
|
|
132
|
+
finally:
|
|
133
|
+
# Close only if we created the app to avoid shutting user sessions.
|
|
134
|
+
try:
|
|
135
|
+
if close_app:
|
|
136
|
+
app = wb.app
|
|
137
|
+
wb.close()
|
|
138
|
+
app.quit()
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|