exstruct 0.2.11__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: exstruct
3
- Version: 0.2.11
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
@@ -56,16 +56,16 @@ Description-Content-Type: text/markdown
56
56
 
57
57
  # ExStruct — Excel Structured Extraction Engine
58
58
 
59
- [![PyPI version](https://badge.fury.io/py/exstruct.svg)](https://pypi.org/project/exstruct/) [![PyPI Downloads](https://static.pepy.tech/personalized-badge/exstruct?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/exstruct) ![Licence: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue?style=flat-square) [![CI](https://github.com/harumiWeb/exstruct/actions/workflows/ci.yml/badge.svg)](https://github.com/harumiWeb/exstruct/actions/workflows/ci.yml)
59
+ [![PyPI version](https://badge.fury.io/py/exstruct.svg)](https://pypi.org/project/exstruct/) [![PyPI Downloads](https://static.pepy.tech/personalized-badge/exstruct?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/exstruct) ![Licence: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue?style=flat-square) [![pytest](https://github.com/harumiWeb/exstruct/actions/workflows/pytest.yml/badge.svg)](https://github.com/harumiWeb/exstruct/actions/workflows/pytest.yml)
60
60
 
61
61
  ![ExStruct Image](/docs/assets/icon.webp)
62
62
 
63
- ExStruct reads Excel workbooks and outputs structured data (tables, shapes, charts) 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.
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
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).
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.
@@ -77,17 +77,17 @@ ExStruct reads Excel workbooks and outputs structured data (tables, shapes, char
77
77
  pip install exstruct
78
78
  ```
79
79
 
80
- Optional extras:
81
-
82
- - YAML: `pip install pyyaml`
83
- - TOON: `pip install python-toon`
84
- - Rendering (PDF/PNG): Excel + `pip install pypdfium2 pillow`
85
- - All extras at once: `pip install exstruct[yaml,toon,render]`
86
-
87
- Platform note:
88
- - Full extraction (shapes/charts) targets Windows + Excel (COM via xlwings). On other platforms, use `mode=light` to get cells + `table_candidates` safely.
89
-
90
- ## Quick Start (CLI)
80
+ Optional extras:
81
+
82
+ - YAML: `pip install pyyaml`
83
+ - TOON: `pip install python-toon`
84
+ - Rendering (PDF/PNG): Excel + `pip install pypdfium2 pillow`
85
+ - All extras at once: `pip install exstruct[yaml,toon,render]`
86
+
87
+ Platform note:
88
+ - Full extraction (shapes/charts) targets Windows + Excel (COM via xlwings). On other platforms, use `mode=light` to get cells + `table_candidates` safely.
89
+
90
+ ## Quick Start (CLI)
91
91
 
92
92
  ```bash
93
93
  exstruct input.xlsx > output.json # compact JSON to stdout (default)
@@ -120,15 +120,19 @@ wb.save("out.json", pretty=True) # WorkbookData → file (by extension)
120
120
  first_sheet.save("sheet.json") # SheetData → file (by extension)
121
121
  print(first_sheet.to_yaml()) # YAML text (requires pyyaml)
122
122
 
123
- # ExStructEngine: per-instance options for extraction/output
124
- from exstruct import ExStructEngine, StructOptions, OutputOptions
125
-
126
- engine = ExStructEngine(
127
- options=StructOptions(mode="standard"),
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
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")
132
136
  ```
133
137
 
134
138
  **Note (non-COM environments):** If Excel COM is unavailable, extraction still runs and returns cells + `table_candidates`; `shapes`/`charts` will be empty.
@@ -148,11 +152,11 @@ set_table_detection_params(
148
152
 
149
153
  Use higher thresholds to reduce false positives; lower them if true tables are missed.
150
154
 
151
- ## Output Modes
152
-
153
- - **light**: cells + table candidates (no COM needed).
154
- - **standard**: texted shapes + arrows, charts (COM if available), table candidates.
155
- - **verbose**: all shapes (with width/height), charts, table candidates.
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.
156
160
 
157
161
  ## Error Handling / Fallbacks
158
162
 
@@ -376,15 +380,16 @@ BSD-3-Clause. See `LICENSE` for details.
376
380
  - API Reference (GitHub Pages): https://harumiweb.github.io/exstruct/
377
381
  # Engine option cheat sheet
378
382
 
379
- | Option class | Field | Meaning |
380
- | -------------- | ------------------- | ------- |
381
- | StructOptions | mode | "light"/"standard"/"verbose" |
382
- | | table_params | Dict passed to `set_table_detection_params` (table_score_threshold, density_min, coverage_min, min_nonempty_cells) |
383
- | OutputOptions | fmt | Default format ("json"/"yaml"/"yml"/"toon") |
384
- | | pretty / indent | Pretty-print JSON and control indent |
385
- | | include_rows | Include rows (False to drop) |
386
- | | include_shapes | Include shapes |
387
- | | include_charts | Include charts |
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 |
388
393
  | | include_tables | Include table_candidates |
389
394
  | | sheets_dir | Optional directory for per-sheet exports |
390
395
  | | stream | Default stream when output_path is None |
@@ -1,15 +1,15 @@
1
1
  # ExStruct — Excel Structured Extraction Engine
2
2
 
3
- [![PyPI version](https://badge.fury.io/py/exstruct.svg)](https://pypi.org/project/exstruct/) [![PyPI Downloads](https://static.pepy.tech/personalized-badge/exstruct?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/exstruct) ![Licence: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue?style=flat-square) [![CI](https://github.com/harumiWeb/exstruct/actions/workflows/ci.yml/badge.svg)](https://github.com/harumiWeb/exstruct/actions/workflows/ci.yml)
3
+ [![PyPI version](https://badge.fury.io/py/exstruct.svg)](https://pypi.org/project/exstruct/) [![PyPI Downloads](https://static.pepy.tech/personalized-badge/exstruct?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/exstruct) ![Licence: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue?style=flat-square) [![pytest](https://github.com/harumiWeb/exstruct/actions/workflows/pytest.yml/badge.svg)](https://github.com/harumiWeb/exstruct/actions/workflows/pytest.yml)
4
4
 
5
5
  ![ExStruct Image](/docs/assets/icon.webp)
6
6
 
7
- ExStruct reads Excel workbooks and outputs structured data (tables, shapes, charts) 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.
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
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).
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.
@@ -21,17 +21,17 @@ ExStruct reads Excel workbooks and outputs structured data (tables, shapes, char
21
21
  pip install exstruct
22
22
  ```
23
23
 
24
- Optional extras:
25
-
26
- - YAML: `pip install pyyaml`
27
- - TOON: `pip install python-toon`
28
- - Rendering (PDF/PNG): Excel + `pip install pypdfium2 pillow`
29
- - All extras at once: `pip install exstruct[yaml,toon,render]`
30
-
31
- Platform note:
32
- - Full extraction (shapes/charts) targets Windows + Excel (COM via xlwings). On other platforms, use `mode=light` to get cells + `table_candidates` safely.
33
-
34
- ## Quick Start (CLI)
24
+ Optional extras:
25
+
26
+ - YAML: `pip install pyyaml`
27
+ - TOON: `pip install python-toon`
28
+ - Rendering (PDF/PNG): Excel + `pip install pypdfium2 pillow`
29
+ - All extras at once: `pip install exstruct[yaml,toon,render]`
30
+
31
+ Platform note:
32
+ - Full extraction (shapes/charts) targets Windows + Excel (COM via xlwings). On other platforms, use `mode=light` to get cells + `table_candidates` safely.
33
+
34
+ ## Quick Start (CLI)
35
35
 
36
36
  ```bash
37
37
  exstruct input.xlsx > output.json # compact JSON to stdout (default)
@@ -64,15 +64,19 @@ wb.save("out.json", pretty=True) # WorkbookData → file (by extension)
64
64
  first_sheet.save("sheet.json") # SheetData → file (by extension)
65
65
  print(first_sheet.to_yaml()) # YAML text (requires pyyaml)
66
66
 
67
- # ExStructEngine: per-instance options for extraction/output
68
- from exstruct import ExStructEngine, StructOptions, OutputOptions
69
-
70
- engine = ExStructEngine(
71
- options=StructOptions(mode="standard"),
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
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")
76
80
  ```
77
81
 
78
82
  **Note (non-COM environments):** If Excel COM is unavailable, extraction still runs and returns cells + `table_candidates`; `shapes`/`charts` will be empty.
@@ -92,11 +96,11 @@ set_table_detection_params(
92
96
 
93
97
  Use higher thresholds to reduce false positives; lower them if true tables are missed.
94
98
 
95
- ## Output Modes
96
-
97
- - **light**: cells + table candidates (no COM needed).
98
- - **standard**: texted shapes + arrows, charts (COM if available), table candidates.
99
- - **verbose**: all shapes (with width/height), charts, table candidates.
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.
100
104
 
101
105
  ## Error Handling / Fallbacks
102
106
 
@@ -320,15 +324,16 @@ BSD-3-Clause. See `LICENSE` for details.
320
324
  - API Reference (GitHub Pages): https://harumiweb.github.io/exstruct/
321
325
  # Engine option cheat sheet
322
326
 
323
- | Option class | Field | Meaning |
324
- | -------------- | ------------------- | ------- |
325
- | StructOptions | mode | "light"/"standard"/"verbose" |
326
- | | table_params | Dict passed to `set_table_detection_params` (table_score_threshold, density_min, coverage_min, min_nonempty_cells) |
327
- | OutputOptions | fmt | Default format ("json"/"yaml"/"yml"/"toon") |
328
- | | pretty / indent | Pretty-print JSON and control indent |
329
- | | include_rows | Include rows (False to drop) |
330
- | | include_shapes | Include shapes |
331
- | | include_charts | Include charts |
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 |
332
337
  | | include_tables | Include table_candidates |
333
338
  | | sheets_dir | Optional directory for per-sheet exports |
334
339
  | | stream | Default stream when output_path is None |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "exstruct"
3
- version = "0.2.11"
3
+ version = "0.2.21"
4
4
  description = "Excel to structured JSON (tables, shapes, charts) for LLM/RAG pipelines"
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
@@ -36,7 +36,8 @@ ExtractionMode = Literal["light", "standard", "verbose"]
36
36
 
37
37
  def extract(file_path: str | Path, mode: ExtractionMode = "standard") -> WorkbookData:
38
38
  """Extract workbook semantic structure and return WorkbookData."""
39
- engine = ExStructEngine(options=StructOptions(mode=mode))
39
+ include_links = True if mode == "verbose" else False
40
+ engine = ExStructEngine(options=StructOptions(mode=mode, include_cell_links=include_links))
40
41
  return engine.extract(file_path, mode=mode)
41
42
 
42
43
 
@@ -33,25 +33,64 @@ def warn_once(key: str, message: str) -> None:
33
33
  _warned_keys.add(key)
34
34
 
35
35
 
36
- def extract_sheet_cells(file_path: Path) -> Dict[str, List[CellRow]]:
37
- """Read all sheets via pandas and convert to CellRow list while skipping empty cells."""
38
- dfs = pd.read_excel(file_path, header=None, sheet_name=None, dtype=str)
39
- result: Dict[str, List[CellRow]] = {}
40
- for sheet_name, df in dfs.items():
41
- df = df.fillna("")
42
- rows: List[CellRow] = []
43
- for excel_row, row in enumerate(df.itertuples(index=False, name=None), start=1):
44
- filtered: Dict[str, int | float | str] = {}
45
- for j, v in enumerate(row):
46
- s = "" if v is None else str(v)
47
- if s.strip() == "":
48
- continue
49
- filtered[str(j)] = _coerce_numeric_preserve_format(s)
50
- if not filtered:
51
- continue
52
- rows.append(CellRow(r=excel_row, c=filtered))
53
- result[sheet_name] = rows
54
- return result
36
+ def extract_sheet_cells(file_path: Path) -> Dict[str, List[CellRow]]:
37
+ """Read all sheets via pandas and convert to CellRow list while skipping empty cells."""
38
+ dfs = pd.read_excel(file_path, header=None, sheet_name=None, dtype=str)
39
+ result: Dict[str, List[CellRow]] = {}
40
+ for sheet_name, df in dfs.items():
41
+ df = df.fillna("")
42
+ rows: List[CellRow] = []
43
+ for excel_row, row in enumerate(df.itertuples(index=False, name=None), start=1):
44
+ filtered: Dict[str, int | float | str] = {}
45
+ for j, v in enumerate(row):
46
+ s = "" if v is None else str(v)
47
+ if s.strip() == "":
48
+ continue
49
+ filtered[str(j)] = _coerce_numeric_preserve_format(s)
50
+ if not filtered:
51
+ continue
52
+ rows.append(CellRow(r=excel_row, c=filtered))
53
+ result[sheet_name] = rows
54
+ return result
55
+
56
+
57
+ def extract_sheet_cells_with_links(file_path: Path) -> Dict[str, List[CellRow]]:
58
+ """
59
+ Extract cells and hyperlinks per sheet.
60
+
61
+ Returns:
62
+ {sheet_name: [CellRow(r=..., c=..., links={"col_index": url, ...}), ...]}
63
+
64
+ Notes:
65
+ - Uses pandas extraction for values (same filtering as extract_sheet_cells).
66
+ - Collects hyperlinks via openpyxl (requires read_only=False because border maps/hyperlinks need full objects).
67
+ - Links are mapped by column index string (e.g., "0") to hyperlink.target.
68
+ """
69
+ cell_rows = extract_sheet_cells(file_path)
70
+ wb = load_workbook(file_path, data_only=True, read_only=False)
71
+ links_by_sheet: Dict[str, Dict[int, Dict[str, str]]] = {}
72
+ for ws in wb.worksheets:
73
+ sheet_links: Dict[int, Dict[str, str]] = {}
74
+ for row in ws.iter_rows():
75
+ for cell in row:
76
+ link = getattr(cell, "hyperlink", None)
77
+ target = getattr(link, "target", None) if link else None
78
+ if not target:
79
+ continue
80
+ col_str = str(cell.col_idx - 1) # zero-based to align with extract_sheet_cells
81
+ sheet_links.setdefault(cell.row, {})[col_str] = target
82
+ links_by_sheet[ws.title] = sheet_links
83
+
84
+ merged: Dict[str, List[CellRow]] = {}
85
+ for sheet_name, rows in cell_rows.items():
86
+ sheet_links = links_by_sheet.get(sheet_name, {})
87
+ merged_rows: List[CellRow] = []
88
+ for row in rows:
89
+ links = sheet_links.get(row.r, {})
90
+ merged_rows.append(CellRow(r=row.r, c=row.c, links=links or None))
91
+ merged[sheet_name] = merged_rows
92
+ wb.close()
93
+ return merged
55
94
 
56
95
 
57
96
  def shrink_to_content(
@@ -4,15 +4,17 @@ from pathlib import Path
4
4
  from typing import Dict, List, Literal
5
5
 
6
6
  import logging
7
+ import os
7
8
 
8
9
  import xlwings as xw
9
10
 
10
11
  from ..models import CellRow, Shape, SheetData, WorkbookData
11
- from .cells import (
12
- detect_tables,
13
- detect_tables_openpyxl,
14
- extract_sheet_cells,
15
- )
12
+ from .cells import (
13
+ detect_tables,
14
+ detect_tables_openpyxl,
15
+ extract_sheet_cells,
16
+ extract_sheet_cells_with_links,
17
+ )
16
18
  from .charts import get_charts
17
19
  from .shapes import get_shapes_with_position
18
20
 
@@ -74,13 +76,16 @@ def integrate_sheet_content(
74
76
 
75
77
 
76
78
  def extract_workbook(
77
- file_path: Path, mode: Literal["light", "standard", "verbose"] = "standard"
79
+ file_path: Path,
80
+ mode: Literal["light", "standard", "verbose"] = "standard",
81
+ *,
82
+ include_cell_links: bool = False,
78
83
  ) -> WorkbookData:
79
84
  """Extract workbook and return WorkbookData; fallback to cells+tables if Excel COM is unavailable."""
80
85
  if mode not in _ALLOWED_MODES:
81
86
  raise ValueError(f"Unsupported mode: {mode}")
82
87
 
83
- cell_data = extract_sheet_cells(file_path)
88
+ cell_data = extract_sheet_cells_with_links(file_path) if include_cell_links else extract_sheet_cells(file_path)
84
89
 
85
90
  def _cells_and_tables_only(reason: str) -> WorkbookData:
86
91
  sheets: Dict[str, SheetData] = {}
@@ -104,6 +109,9 @@ def extract_workbook(
104
109
  if mode == "light":
105
110
  return _cells_and_tables_only("Light mode selected.")
106
111
 
112
+ if os.getenv("SKIP_COM_TESTS"):
113
+ return _cells_and_tables_only("SKIP_COM_TESTS is set; skipping COM/xlwings access.")
114
+
107
115
  try:
108
116
  wb, close_app = _open_workbook(file_path)
109
117
  except Exception as e:
@@ -32,6 +32,7 @@ class StructOptions:
32
32
 
33
33
  mode: ExtractionMode = "standard"
34
34
  table_params: Optional[dict] = None # forwarded to set_table_detection_params if provided
35
+ include_cell_links: Optional[bool] = None # None → auto: verbose=True, others=False
35
36
 
36
37
 
37
38
  @dataclass(frozen=True)
@@ -133,8 +134,13 @@ class ExStructEngine:
133
134
  chosen_mode = mode or self.options.mode
134
135
  if chosen_mode not in ("light", "standard", "verbose"):
135
136
  raise ValueError(f"Unsupported mode: {chosen_mode}")
137
+ include_links = (
138
+ self.options.include_cell_links
139
+ if self.options.include_cell_links is not None
140
+ else chosen_mode == "verbose"
141
+ )
136
142
  with self._table_params_scope():
137
- return extract_workbook(Path(file_path), mode=chosen_mode)
143
+ return extract_workbook(Path(file_path), mode=chosen_mode, include_cell_links=include_links)
138
144
 
139
145
  def serialize(
140
146
  self,
@@ -23,6 +23,7 @@ class Shape(BaseModel):
23
23
  class CellRow(BaseModel):
24
24
  r: int
25
25
  c: Dict[str, int | float | str]
26
+ links: Optional[Dict[str, str]] = None
26
27
 
27
28
 
28
29
  class ChartSeries(BaseModel):
File without changes