tablewise 1.1.0__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.
Files changed (31) hide show
  1. tablewise-1.1.0/LICENSE +21 -0
  2. tablewise-1.1.0/PKG-INFO +217 -0
  3. tablewise-1.1.0/README.md +169 -0
  4. tablewise-1.1.0/motor_docling.py +80 -0
  5. tablewise-1.1.0/motor_excel.py +500 -0
  6. tablewise-1.1.0/preconversion.py +93 -0
  7. tablewise-1.1.0/pyproject.toml +37 -0
  8. tablewise-1.1.0/setup.cfg +4 -0
  9. tablewise-1.1.0/tablewise.egg-info/PKG-INFO +217 -0
  10. tablewise-1.1.0/tablewise.egg-info/SOURCES.txt +29 -0
  11. tablewise-1.1.0/tablewise.egg-info/dependency_links.txt +1 -0
  12. tablewise-1.1.0/tablewise.egg-info/entry_points.txt +2 -0
  13. tablewise-1.1.0/tablewise.egg-info/requires.txt +11 -0
  14. tablewise-1.1.0/tablewise.egg-info/top_level.txt +6 -0
  15. tablewise-1.1.0/tablewise_app.py +361 -0
  16. tablewise-1.1.0/tablewise_cli.py +185 -0
  17. tablewise-1.1.0/tablewise_core.py +394 -0
  18. tablewise-1.1.0/tests/test_app_convertir.py +341 -0
  19. tablewise-1.1.0/tests/test_cli.py +149 -0
  20. tablewise-1.1.0/tests/test_cli_motores.py +93 -0
  21. tablewise-1.1.0/tests/test_core.py +649 -0
  22. tablewise-1.1.0/tests/test_docs.py +69 -0
  23. tablewise-1.1.0/tests/test_entradas_externas.py +128 -0
  24. tablewise-1.1.0/tests/test_motor_docling.py +50 -0
  25. tablewise-1.1.0/tests/test_motor_docling_cli.py +131 -0
  26. tablewise-1.1.0/tests/test_motor_docling_convertir.py +115 -0
  27. tablewise-1.1.0/tests/test_motor_excel.py +578 -0
  28. tablewise-1.1.0/tests/test_preconversion.py +113 -0
  29. tablewise-1.1.0/tests/test_preconversion_ejecutables_falsos.py +147 -0
  30. tablewise-1.1.0/tests/test_preconversion_errores.py +167 -0
  31. tablewise-1.1.0/tests/test_red_sintaxis.py +69 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luis Francisco Palomares Gamarra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: tablewise
3
+ Version: 1.1.0
4
+ Summary: Convierte PDF/Word/Excel/PowerPoint a Markdown con cabecera de procedencia, listo para un RAG
5
+ Author: Luis Francisco Palomares Gamarra
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Luis Francisco Palomares Gamarra
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Keywords: markdown,rag,pdf,docx,xlsx,markitdown,docling
29
+ Classifier: License :: OSI Approved :: MIT License
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Programming Language :: Python :: 3.11
32
+ Classifier: Programming Language :: Python :: 3.12
33
+ Classifier: Programming Language :: Python :: 3.13
34
+ Classifier: Environment :: MacOS X
35
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
36
+ Requires-Python: >=3.11
37
+ Description-Content-Type: text/markdown
38
+ License-File: LICENSE
39
+ Provides-Extra: excel
40
+ Requires-Dist: openpyxl>=3.1; extra == "excel"
41
+ Provides-Extra: docling
42
+ Requires-Dist: docling; extra == "docling"
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest>=8; extra == "dev"
45
+ Requires-Dist: openpyxl>=3.1; extra == "dev"
46
+ Requires-Dist: PyYAML>=6; extra == "dev"
47
+ Dynamic: license-file
48
+
49
+ # Tablewise
50
+
51
+ **Spreadsheet-grade document conversion to Markdown, with provenance.**
52
+ PDF, Word, Excel and PowerPoint in; clean, citable Markdown out — built for RAG,
53
+ LLM ingestion and audit trails.
54
+
55
+ Every output file starts with a header that tells your retrieval layer where the
56
+ text came from:
57
+
58
+ ```markdown
59
+ ---
60
+ source: "2026-budget.xlsx"
61
+ engine: "excel"
62
+ converted: 2026-10-22
63
+ ---
64
+ ```
65
+
66
+ Three front ends, one decision engine:
67
+
68
+ - **`tablewise` CLI** — one document in, one `.md` out. Scripts, agents, batches.
69
+ - **Desktop app** (Tkinter) — for people who do not live in a terminal.
70
+ - **Hosted API** — `POST /v1/convert`, per-month plans, no install. See
71
+ [PRICING.md](PRICING.md).
72
+
73
+ ## The problem it solves
74
+
75
+ Most converters flatten a financial table into something no model can use. A
76
+ three-level header (`Hotel · 2026 · Real/Budget · Jan..Dec`) comes out as 70
77
+ columns named `Unnamed: 12`, formulas vanish, and a percentage becomes `0.8769`.
78
+ A RAG built on that answers with the wrong number and no way to prove it.
79
+
80
+ Tablewise routes spreadsheets to an engine that reads the sheet as a sheet:
81
+
82
+ | Hotel | 2026 | 2026 | 2026 |
83
+ | | Real | Budget | Last year |
84
+ | | Jan | Feb | Jan |
85
+ | ADR | 120.50 | 110 | 105.25 |
86
+ | Occupancy | 87.69% | | |
87
+
88
+ - Merged cells and multi-level headers preserved as a real Markdown table.
89
+ - Dates as `YYYY-MM-DD`, percentages with their format, `-0.00` never appears.
90
+ - Hidden sheets kept but labelled `(hidden sheet)`.
91
+ - A formula that was never recalculated says `(formula not calculated)` instead
92
+ of an empty cell that a model reads as zero.
93
+ - Deterministic: no AI, no network, same bytes in → same Markdown out.
94
+
95
+ ## Quickstart
96
+
97
+ ```bash
98
+ pip install -e . # installs the `tablewise` command
99
+ tablewise report.pdf # -> ~/md-repo/report.md
100
+ tablewise budget.xlsx -o ~/md-repo -n "2026 budget"
101
+ tablewise contract.docx --dry-run # print the engine command, do not run it
102
+ ```
103
+
104
+ The desktop app:
105
+
106
+ ```bash
107
+ python tablewise_app.py
108
+ ```
109
+
110
+ The API:
111
+
112
+ ```bash
113
+ curl -X POST https://api.tablewise.dev/v1/convert \
114
+ -H "Authorization: Bearer $TABLEWISE_API_KEY" \
115
+ -F "file=@budget.xlsx"
116
+ ```
117
+
118
+ ## Why not just MarkItDown / Docling?
119
+
120
+ They are good engines and Tablewise uses both. The difference is the routing,
121
+ the table fidelity and the provenance — not a new parser.
122
+
123
+ | | MarkItDown | Docling | Tablewise |
124
+ |---|---|---|---|
125
+ | Excel with merged / multi-level header | flattened | partial | read as a sheet |
126
+ | Formula never recalculated | empty cell | empty cell | labelled |
127
+ | Provenance header on every file | no | no | yes |
128
+ | Deterministic, no AI, no network | yes | yes (heavy) | yes |
129
+ | Desktop GUI for non-technical users | no | no | yes |
130
+ | Hosted API with per-month plans and batch | no | no | yes |
131
+
132
+ ## Engine routing
133
+
134
+ Each extension has exactly one owner:
135
+
136
+ - `.xlsx` `.xlsm` `.xltx` `.xltm` → `motor_excel.py` (openpyxl, local, no AI)
137
+ - `.pdf` `.docx` `.pptx` → `motor_docling.py` (Docling, local, no AI)
138
+ - `.doc` `.rtf` `.odt` → `textutil` (macOS) → HTML → `motor_docling.py`
139
+ - `.xls` `.ppt` (Office 97-2003) → LibreOffice → `.xlsx`/`.pptx` → `motor_excel.py` / `motor_docling.py`
140
+ - everything else (CSV, HTML, EPUB, …) → MarkItDown
141
+
142
+ The two own engines are `motor_excel.py` and `motor_docling.py`. A format
143
+ without an available engine is refused with an explicit message, never silently
144
+ degraded.
145
+
146
+ ## CLI reference
147
+
148
+ ```
149
+ tablewise DOCUMENT [-o DIR] [-n NAME] [--python PATH] [--markitdown PATH]
150
+ [--force] [--dry-run] [-q] [--version]
151
+ ```
152
+
153
+ - Never overwrites an existing `.md` without `--force`.
154
+ - Exit codes: `0` ok · `1` the engine produced no file · `2` invalid input.
155
+ - `--python` / `TABLEWISE_PYTHON`: interpreter that carries docling/openpyxl.
156
+ - `--markitdown` / `TABLEWISE_MARKITDOWN`: MarkItDown executable.
157
+
158
+ ## Hosted API
159
+
160
+ The API is the same engine behind HTTP, for teams that cannot install anything:
161
+
162
+ | Plan | Units / month | Price |
163
+ |---|---|---|
164
+ | Free | 50 | $0 |
165
+ | Starter | 1,000 | $19 |
166
+ | Pro | 5,000 | $49 |
167
+ | Business | 25,000 | $149 |
168
+
169
+ One unit = one PDF page, one spreadsheet sheet, one slide, or one document.
170
+ Overage $0.015/unit. Annual −20%. The full contract (quota, DPA, refunds) is in
171
+ [PRICING.md](PRICING.md) and on the website.
172
+
173
+ ## Development
174
+
175
+ Runner: `pytest`, using the engines virtualenv (it carries openpyxl and PyYAML):
176
+
177
+ ```bash
178
+ .venv/bin/python3 -m pytest -q # 432 tests
179
+ .venv/bin/python3 -m pytest server/tests -q # 46 API tests
180
+ ```
181
+
182
+ | File | What it covers |
183
+ |---|---|
184
+ | `tests/test_core.py` | `tablewise_core.py`: extension routing, safe names, engine command (`shlex`, `~`), YAML frontmatter, atomic write, free name |
185
+ | `tests/test_motor_excel.py` | `motor_excel.py` on synthetic books: merged cells, unevaluated formulas, empty rows/columns, dates, hidden sheets, safety ceiling |
186
+ | `tests/test_cli_motores.py` | engine CLI contract by subprocess (exit code + message, never a traceback) |
187
+ | `tests/test_motor_docling.py` | `.doc`/`.rtf`/`.odt` preconversion to HTML with `textutil` (no Docling import; skipped off macOS) |
188
+ | `tests/test_preconversion.py` | LibreOffice (`soffice`): lookup outside PATH and `.xls` → `.xlsx` keeping merges (skipped without LibreOffice) |
189
+ | `tests/test_preconversion_errores.py` | `preconversion.py` with simulated `subprocess.run`: timeout, tool that cannot start, exit 0 without file, error code, own profile |
190
+ | `tests/test_motor_docling_convertir.py` | `motor_docling.convertir` with an injected fake Docling: empty output, temp cleanup, `.ppt` routing, uppercase extensions |
191
+ | `tests/test_entradas_externas.py` | `.xls` path, malformed array formulas, backslashes in the YAML header, non-UTF-8 output, BOM and CRLF |
192
+ | `tests/test_preconversion_ejecutables_falsos.py` | textutil/LibreOffice boundary with fake executables |
193
+ | `tests/test_app_convertir.py` | `App.convert()` with fake tkinter and engine: existing `.md` (cancel / numbered copy / overwrite), failing engine, non-UTF-8 output, partial writes |
194
+ | `tests/test_motor_docling_cli.py` | `motor_docling` CLI with a fake `docling` package |
195
+ | `tests/test_red_sintaxis.py` | regression net: every application file is valid Python and the app imports without opening a window |
196
+ | `tests/test_cli.py` | `tablewise` CLI: same decisions as the app, `--dry-run`, no overwrite without `--force`, exit codes |
197
+ | `tests/test_docs.py` | drift net: this README lists every test file and every routed extension |
198
+
199
+ No test uses real documents.
200
+
201
+ ## Limits and security
202
+
203
+ - Upload limit 25 MB and 2,000 units per request (configurable).
204
+ - Engines run as subprocesses with a timeout; a hung converter cannot take the
205
+ worker down.
206
+ - API keys are stored as SHA-256 hashes and shown once.
207
+ - No document content is logged. See `web/privacy.html` and `web/dpa.html`.
208
+ - macOS-only pieces: `textutil` (`.doc`/`.rtf`/`.odt`) and the Tkinter app.
209
+ LibreOffice is needed for `.xls`/`.ppt`.
210
+
211
+ ## License
212
+
213
+ MIT. See [LICENSE](LICENSE).
214
+
215
+ ---
216
+
217
+ Spanish engineering log, kept for continuity: [README.es.md](README.es.md).
@@ -0,0 +1,169 @@
1
+ # Tablewise
2
+
3
+ **Spreadsheet-grade document conversion to Markdown, with provenance.**
4
+ PDF, Word, Excel and PowerPoint in; clean, citable Markdown out — built for RAG,
5
+ LLM ingestion and audit trails.
6
+
7
+ Every output file starts with a header that tells your retrieval layer where the
8
+ text came from:
9
+
10
+ ```markdown
11
+ ---
12
+ source: "2026-budget.xlsx"
13
+ engine: "excel"
14
+ converted: 2026-10-22
15
+ ---
16
+ ```
17
+
18
+ Three front ends, one decision engine:
19
+
20
+ - **`tablewise` CLI** — one document in, one `.md` out. Scripts, agents, batches.
21
+ - **Desktop app** (Tkinter) — for people who do not live in a terminal.
22
+ - **Hosted API** — `POST /v1/convert`, per-month plans, no install. See
23
+ [PRICING.md](PRICING.md).
24
+
25
+ ## The problem it solves
26
+
27
+ Most converters flatten a financial table into something no model can use. A
28
+ three-level header (`Hotel · 2026 · Real/Budget · Jan..Dec`) comes out as 70
29
+ columns named `Unnamed: 12`, formulas vanish, and a percentage becomes `0.8769`.
30
+ A RAG built on that answers with the wrong number and no way to prove it.
31
+
32
+ Tablewise routes spreadsheets to an engine that reads the sheet as a sheet:
33
+
34
+ | Hotel | 2026 | 2026 | 2026 |
35
+ | | Real | Budget | Last year |
36
+ | | Jan | Feb | Jan |
37
+ | ADR | 120.50 | 110 | 105.25 |
38
+ | Occupancy | 87.69% | | |
39
+
40
+ - Merged cells and multi-level headers preserved as a real Markdown table.
41
+ - Dates as `YYYY-MM-DD`, percentages with their format, `-0.00` never appears.
42
+ - Hidden sheets kept but labelled `(hidden sheet)`.
43
+ - A formula that was never recalculated says `(formula not calculated)` instead
44
+ of an empty cell that a model reads as zero.
45
+ - Deterministic: no AI, no network, same bytes in → same Markdown out.
46
+
47
+ ## Quickstart
48
+
49
+ ```bash
50
+ pip install -e . # installs the `tablewise` command
51
+ tablewise report.pdf # -> ~/md-repo/report.md
52
+ tablewise budget.xlsx -o ~/md-repo -n "2026 budget"
53
+ tablewise contract.docx --dry-run # print the engine command, do not run it
54
+ ```
55
+
56
+ The desktop app:
57
+
58
+ ```bash
59
+ python tablewise_app.py
60
+ ```
61
+
62
+ The API:
63
+
64
+ ```bash
65
+ curl -X POST https://api.tablewise.dev/v1/convert \
66
+ -H "Authorization: Bearer $TABLEWISE_API_KEY" \
67
+ -F "file=@budget.xlsx"
68
+ ```
69
+
70
+ ## Why not just MarkItDown / Docling?
71
+
72
+ They are good engines and Tablewise uses both. The difference is the routing,
73
+ the table fidelity and the provenance — not a new parser.
74
+
75
+ | | MarkItDown | Docling | Tablewise |
76
+ |---|---|---|---|
77
+ | Excel with merged / multi-level header | flattened | partial | read as a sheet |
78
+ | Formula never recalculated | empty cell | empty cell | labelled |
79
+ | Provenance header on every file | no | no | yes |
80
+ | Deterministic, no AI, no network | yes | yes (heavy) | yes |
81
+ | Desktop GUI for non-technical users | no | no | yes |
82
+ | Hosted API with per-month plans and batch | no | no | yes |
83
+
84
+ ## Engine routing
85
+
86
+ Each extension has exactly one owner:
87
+
88
+ - `.xlsx` `.xlsm` `.xltx` `.xltm` → `motor_excel.py` (openpyxl, local, no AI)
89
+ - `.pdf` `.docx` `.pptx` → `motor_docling.py` (Docling, local, no AI)
90
+ - `.doc` `.rtf` `.odt` → `textutil` (macOS) → HTML → `motor_docling.py`
91
+ - `.xls` `.ppt` (Office 97-2003) → LibreOffice → `.xlsx`/`.pptx` → `motor_excel.py` / `motor_docling.py`
92
+ - everything else (CSV, HTML, EPUB, …) → MarkItDown
93
+
94
+ The two own engines are `motor_excel.py` and `motor_docling.py`. A format
95
+ without an available engine is refused with an explicit message, never silently
96
+ degraded.
97
+
98
+ ## CLI reference
99
+
100
+ ```
101
+ tablewise DOCUMENT [-o DIR] [-n NAME] [--python PATH] [--markitdown PATH]
102
+ [--force] [--dry-run] [-q] [--version]
103
+ ```
104
+
105
+ - Never overwrites an existing `.md` without `--force`.
106
+ - Exit codes: `0` ok · `1` the engine produced no file · `2` invalid input.
107
+ - `--python` / `TABLEWISE_PYTHON`: interpreter that carries docling/openpyxl.
108
+ - `--markitdown` / `TABLEWISE_MARKITDOWN`: MarkItDown executable.
109
+
110
+ ## Hosted API
111
+
112
+ The API is the same engine behind HTTP, for teams that cannot install anything:
113
+
114
+ | Plan | Units / month | Price |
115
+ |---|---|---|
116
+ | Free | 50 | $0 |
117
+ | Starter | 1,000 | $19 |
118
+ | Pro | 5,000 | $49 |
119
+ | Business | 25,000 | $149 |
120
+
121
+ One unit = one PDF page, one spreadsheet sheet, one slide, or one document.
122
+ Overage $0.015/unit. Annual −20%. The full contract (quota, DPA, refunds) is in
123
+ [PRICING.md](PRICING.md) and on the website.
124
+
125
+ ## Development
126
+
127
+ Runner: `pytest`, using the engines virtualenv (it carries openpyxl and PyYAML):
128
+
129
+ ```bash
130
+ .venv/bin/python3 -m pytest -q # 432 tests
131
+ .venv/bin/python3 -m pytest server/tests -q # 46 API tests
132
+ ```
133
+
134
+ | File | What it covers |
135
+ |---|---|
136
+ | `tests/test_core.py` | `tablewise_core.py`: extension routing, safe names, engine command (`shlex`, `~`), YAML frontmatter, atomic write, free name |
137
+ | `tests/test_motor_excel.py` | `motor_excel.py` on synthetic books: merged cells, unevaluated formulas, empty rows/columns, dates, hidden sheets, safety ceiling |
138
+ | `tests/test_cli_motores.py` | engine CLI contract by subprocess (exit code + message, never a traceback) |
139
+ | `tests/test_motor_docling.py` | `.doc`/`.rtf`/`.odt` preconversion to HTML with `textutil` (no Docling import; skipped off macOS) |
140
+ | `tests/test_preconversion.py` | LibreOffice (`soffice`): lookup outside PATH and `.xls` → `.xlsx` keeping merges (skipped without LibreOffice) |
141
+ | `tests/test_preconversion_errores.py` | `preconversion.py` with simulated `subprocess.run`: timeout, tool that cannot start, exit 0 without file, error code, own profile |
142
+ | `tests/test_motor_docling_convertir.py` | `motor_docling.convertir` with an injected fake Docling: empty output, temp cleanup, `.ppt` routing, uppercase extensions |
143
+ | `tests/test_entradas_externas.py` | `.xls` path, malformed array formulas, backslashes in the YAML header, non-UTF-8 output, BOM and CRLF |
144
+ | `tests/test_preconversion_ejecutables_falsos.py` | textutil/LibreOffice boundary with fake executables |
145
+ | `tests/test_app_convertir.py` | `App.convert()` with fake tkinter and engine: existing `.md` (cancel / numbered copy / overwrite), failing engine, non-UTF-8 output, partial writes |
146
+ | `tests/test_motor_docling_cli.py` | `motor_docling` CLI with a fake `docling` package |
147
+ | `tests/test_red_sintaxis.py` | regression net: every application file is valid Python and the app imports without opening a window |
148
+ | `tests/test_cli.py` | `tablewise` CLI: same decisions as the app, `--dry-run`, no overwrite without `--force`, exit codes |
149
+ | `tests/test_docs.py` | drift net: this README lists every test file and every routed extension |
150
+
151
+ No test uses real documents.
152
+
153
+ ## Limits and security
154
+
155
+ - Upload limit 25 MB and 2,000 units per request (configurable).
156
+ - Engines run as subprocesses with a timeout; a hung converter cannot take the
157
+ worker down.
158
+ - API keys are stored as SHA-256 hashes and shown once.
159
+ - No document content is logged. See `web/privacy.html` and `web/dpa.html`.
160
+ - macOS-only pieces: `textutil` (`.doc`/`.rtf`/`.odt`) and the Tkinter app.
161
+ LibreOffice is needed for `.xls`/`.ppt`.
162
+
163
+ ## License
164
+
165
+ MIT. See [LICENSE](LICENSE).
166
+
167
+ ---
168
+
169
+ Spanish engineering log, kept for continuity: [README.es.md](README.es.md).
@@ -0,0 +1,80 @@
1
+ """
2
+ Conversor de PDF/Word/PowerPoint (y escaneados) a Markdown con Docling
3
+ (IBM, open source, 100% local, gratis) -- sustituye a MarkItDown para estos
4
+ formatos porque reconstruye tablas complejas con celdas combinadas en vez
5
+ de volcarlas en crudo (ver README.md, "Piloto de alternativas gratis",
6
+ 23/08/2026).
7
+
8
+ Import diferido: docling carga modelos pesados (layout/tabla) la primera
9
+ vez que se usa. Se importa dentro de convertir(), no al importar el
10
+ módulo, para que cargar este fichero sea barato -- solo paga el coste
11
+ quien de verdad convierte un documento (invocado siempre como subprocess
12
+ en un venv aparte, nunca dentro del proceso de la GUI Tkinter).
13
+
14
+ CLI: python motor_docling.py <origen> <destino.md>
15
+ Librería: from motor_docling import convertir
16
+ """
17
+ import sys
18
+ import tempfile
19
+ from pathlib import Path
20
+
21
+ from tablewise_core import EXTENSIONES_TEXTUTIL, escribir_atomico, misma_ruta
22
+ from preconversion import con_soffice, con_textutil
23
+
24
+
25
+ class ConversionVacia(Exception):
26
+ """Docling no produjo ningún contenido."""
27
+
28
+
29
+ def preconvertir(origen: Path, carpeta: Path) -> Path:
30
+ """Lo que Docling no lee bien, a un formato que sí:
31
+ - .doc/.rtf/.odt → HTML con textutil (macOS). HTML y no .docx: el
32
+ .docx de textutil aplana las tablas, y la ruta nativa de Docling para
33
+ .doc también (pasa por LibreOffice y saca "ConceptoImporte...").
34
+ - .ppt → .pptx con LibreOffice.
35
+ Lo demás se devuelve tal cual."""
36
+ ext = origen.suffix.lower()
37
+ if ext in EXTENSIONES_TEXTUTIL:
38
+ return con_textutil(origen, carpeta)
39
+ if ext == ".ppt":
40
+ return con_soffice(origen, "pptx", carpeta)
41
+ return origen
42
+
43
+
44
+ def convertir(origen: Path) -> str:
45
+ from docling.document_converter import DocumentConverter
46
+
47
+ converter = DocumentConverter()
48
+ with tempfile.TemporaryDirectory(prefix="tablewise-") as tmp:
49
+ resultado = converter.convert(str(preconvertir(origen, Path(tmp))))
50
+ md = resultado.document.export_to_markdown()
51
+ if not md.strip():
52
+ raise ConversionVacia(f"Docling no produjo contenido para {origen.name}.")
53
+ return md
54
+
55
+
56
+ if __name__ == "__main__":
57
+ if len(sys.argv) < 3:
58
+ sys.exit("Uso: python motor_docling.py <origen> <destino.md>")
59
+ origen = Path(sys.argv[1]).expanduser()
60
+ destino = Path(sys.argv[2]).expanduser()
61
+ if not origen.exists():
62
+ sys.exit(f"No existe: {origen}")
63
+ if misma_ruta(origen, destino):
64
+ sys.exit(f"El destino es el propio documento de origen ({origen.name}): "
65
+ "no se sobrescribe.")
66
+
67
+ try:
68
+ md = convertir(origen)
69
+ except ConversionVacia as e:
70
+ sys.exit(str(e))
71
+ except Exception as e:
72
+ sys.exit(f"motor_docling falló con {origen.name}: {e}")
73
+
74
+ try:
75
+ escribir_atomico(destino, md)
76
+ except (OSError, UnicodeError) as e:
77
+ # Sin esto, un destino no escribible acababa en traceback crudo,
78
+ # que la app enseña tal cual en el diálogo de error.
79
+ sys.exit(f"No se pudo escribir {destino}: {e}")
80
+ print(f"Guardado en {destino} ({len(md)} caracteres)")