dsh-office-kit 0.2.0 → 0.3.1
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.
- package/CAPABILITY.md +147 -68
- package/README.md +3 -1
- package/assets/skills/excel-live-control/SKILL.md +234 -0
- package/assets/skills/excel-live-control/container_tools/live_excel.py +1589 -0
- package/assets/skills/excel-live-control/domain_guidance/financial_models.md +86 -0
- package/assets/skills/excel-live-control/domain_guidance/healthcare.md +58 -0
- package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +53 -0
- package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +53 -0
- package/assets/skills/excel-live-control/references/office-automation.md +207 -0
- package/assets/skills/excel-live-control/references/read_only_qna.md +59 -0
- package/assets/skills/excel-live-control/style_guidelines.md +87 -0
- package/assets/skills/excel-live-control/workflows/create_workflows.md +78 -0
- package/assets/skills/excel-live-control/workflows/edit_workflows.md +72 -0
- package/assets/skills/office-documents/SKILL.md +90 -77
- package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +86 -0
- package/assets/skills/office-documents/ooxml/comments.md +40 -0
- package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +66 -0
- package/assets/skills/office-documents/ooxml/rels_and_content_types.md +62 -0
- package/assets/skills/office-documents/ooxml/tracked_changes.md +53 -0
- package/assets/skills/office-documents/references/docx-structure.md +83 -0
- package/assets/skills/office-documents/references/template-elicitation.md +49 -0
- package/assets/skills/office-documents/references/writing-quality.md +49 -0
- package/assets/skills/office-documents/render_docx.py +99 -0
- package/assets/skills/office-documents/scripts/a11y_audit.py +201 -0
- package/assets/skills/office-documents/scripts/accept_tracked_changes.py +158 -0
- package/assets/skills/office-documents/scripts/add_tracked_replacements.py +155 -0
- package/assets/skills/office-documents/scripts/append_blocks.py +115 -0
- package/assets/skills/office-documents/scripts/apply_template_styles.py +111 -0
- package/assets/skills/office-documents/scripts/captions_and_crossrefs.py +109 -0
- package/assets/skills/office-documents/scripts/comments_add.py +143 -0
- package/assets/skills/office-documents/scripts/comments_apply_patch.py +121 -0
- package/assets/skills/office-documents/scripts/comments_extract.py +92 -0
- package/assets/skills/office-documents/scripts/comments_strip.py +81 -0
- package/assets/skills/office-documents/scripts/content_controls.py +199 -0
- package/assets/skills/office-documents/scripts/core_properties.py +131 -0
- package/assets/skills/office-documents/scripts/docx_kit.py +594 -0
- package/assets/skills/office-documents/scripts/docx_ooxml_patch.py +232 -0
- package/assets/skills/office-documents/scripts/docx_table_to_csv.py +106 -0
- package/assets/skills/office-documents/scripts/fields_materialize.py +73 -0
- package/assets/skills/office-documents/scripts/fields_report.py +66 -0
- package/assets/skills/office-documents/scripts/flatten_ref_fields.py +69 -0
- package/assets/skills/office-documents/scripts/footnotes_report.py +84 -0
- package/assets/skills/office-documents/scripts/google_docs_title_sanitize.py +103 -0
- package/assets/skills/office-documents/scripts/heading_audit.py +88 -0
- package/assets/skills/office-documents/scripts/images_audit.py +87 -0
- package/assets/skills/office-documents/scripts/insert_note.py +133 -0
- package/assets/skills/office-documents/scripts/insert_ref_fields.py +97 -0
- package/assets/skills/office-documents/scripts/insert_toc.py +91 -0
- package/assets/skills/office-documents/scripts/internal_nav.py +153 -0
- package/assets/skills/office-documents/scripts/make_fixtures.py +176 -0
- package/assets/skills/office-documents/scripts/merge_docx_append.py +246 -0
- package/assets/skills/office-documents/scripts/privacy_scrub.py +138 -0
- package/assets/skills/office-documents/scripts/redact_docx.py +120 -0
- package/assets/skills/office-documents/scripts/render_and_diff.py +108 -0
- package/assets/skills/office-documents/scripts/replace_text.py +161 -0
- package/assets/skills/office-documents/scripts/section_audit.py +98 -0
- package/assets/skills/office-documents/scripts/set_protection.py +99 -0
- package/assets/skills/office-documents/scripts/style_lint.py +118 -0
- package/assets/skills/office-documents/scripts/style_normalize.py +110 -0
- package/assets/skills/office-documents/scripts/table_geometry.py +174 -0
- package/assets/skills/office-documents/scripts/watermark_add.py +55 -0
- package/assets/skills/office-documents/scripts/watermark_audit_remove.py +74 -0
- package/assets/skills/office-documents/scripts/xlsx_to_docx_table.py +124 -0
- package/assets/skills/office-documents/tasks/accessibility_a11y.md +45 -0
- package/assets/skills/office-documents/tasks/captions_crossrefs.md +49 -0
- package/assets/skills/office-documents/tasks/clean_tracked_changes.md +49 -0
- package/assets/skills/office-documents/tasks/comments_manage.md +59 -0
- package/assets/skills/office-documents/tasks/compare_diff.md +40 -0
- package/assets/skills/office-documents/tasks/create_edit.md +107 -0
- package/assets/skills/office-documents/tasks/equations.md +59 -0
- package/assets/skills/office-documents/tasks/fields_update.md +44 -0
- package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +50 -0
- package/assets/skills/office-documents/tasks/footnotes_endnotes.md +38 -0
- package/assets/skills/office-documents/tasks/forms_content_controls.md +48 -0
- package/assets/skills/office-documents/tasks/headings_numbering.md +55 -0
- package/assets/skills/office-documents/tasks/images_figures.md +63 -0
- package/assets/skills/office-documents/tasks/multi_doc_merge.md +43 -0
- package/assets/skills/office-documents/tasks/navigation_internal_links.md +38 -0
- package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +45 -0
- package/assets/skills/office-documents/tasks/protection_restrict_editing.md +41 -0
- package/assets/skills/office-documents/tasks/read_review.md +50 -0
- package/assets/skills/office-documents/tasks/redaction_anonymization.md +42 -0
- package/assets/skills/office-documents/tasks/sections_layout.md +54 -0
- package/assets/skills/office-documents/tasks/style_lint_normalize.md +44 -0
- package/assets/skills/office-documents/tasks/tables_spreadsheets.md +64 -0
- package/assets/skills/office-documents/tasks/templates_style_packs.md +53 -0
- package/assets/skills/office-documents/tasks/toc_workflow.md +39 -0
- package/assets/skills/office-documents/tasks/verify_render.md +50 -0
- package/assets/skills/office-documents/tasks/watermarks_background.md +35 -0
- package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +47 -0
- package/assets/skills/office-documents/troubleshooting/run_splitting.md +36 -0
- package/assets/skills/office-pdf/SKILL.md +73 -56
- package/assets/skills/office-pdf/references/form-fields.md +56 -0
- package/assets/skills/office-pdf/references/geometry.md +44 -0
- package/assets/skills/office-pdf/references/layout-quality.md +41 -0
- package/assets/skills/office-pdf/references/limits.md +58 -0
- package/assets/skills/office-pdf/scripts/extract_text.py +172 -0
- package/assets/skills/office-pdf/scripts/forms.py +556 -0
- package/assets/skills/office-pdf/scripts/metadata.py +161 -0
- package/assets/skills/office-pdf/scripts/page_ops.py +198 -0
- package/assets/skills/office-pdf/scripts/redact_region.py +347 -0
- package/assets/skills/office-pdf/scripts/render_pages.py +207 -0
- package/assets/skills/office-pdf/scripts/split_pages.py +155 -0
- package/assets/skills/office-pdf/scripts/stamp_overlay.py +199 -0
- package/assets/skills/office-pdf/tasks/annotations.md +98 -0
- package/assets/skills/office-pdf/tasks/create.md +75 -0
- package/assets/skills/office-pdf/tasks/edit-pages.md +71 -0
- package/assets/skills/office-pdf/tasks/forms.md +67 -0
- package/assets/skills/office-pdf/tasks/read.md +62 -0
- package/assets/skills/office-pdf/tasks/redact.md +72 -0
- package/assets/skills/office-pdf/tasks/stamp.md +60 -0
- package/assets/skills/office-presentations/SKILL.md +141 -87
- package/assets/skills/office-presentations/container_tools/add_slides.py +143 -0
- package/assets/skills/office-presentations/container_tools/core_properties.py +127 -0
- package/assets/skills/office-presentations/container_tools/create_montage.py +133 -0
- package/assets/skills/office-presentations/container_tools/inspect_presentation_layout_geometry.py +292 -0
- package/assets/skills/office-presentations/container_tools/inspect_presentation_package_integrity.py +164 -0
- package/assets/skills/office-presentations/container_tools/native_chart_title_gate.py +151 -0
- package/assets/skills/office-presentations/container_tools/native_quantitative_chart_gate.py +206 -0
- package/assets/skills/office-presentations/container_tools/native_table_arithmetic_gate.py +219 -0
- package/assets/skills/office-presentations/container_tools/ooxml_helpers.py +352 -0
- package/assets/skills/office-presentations/container_tools/preview_render.py +382 -0
- package/assets/skills/office-presentations/container_tools/render_slides.py +146 -0
- package/assets/skills/office-presentations/container_tools/replace_text.py +175 -0
- package/assets/skills/office-presentations/container_tools/template_source_fidelity_gate.py +236 -0
- package/assets/skills/office-presentations/domain_guidance/finance.md +66 -0
- package/assets/skills/office-presentations/recipes/README.md +71 -0
- package/assets/skills/office-presentations/recipes/autolayout.md +88 -0
- package/assets/skills/office-presentations/recipes/connectors.md +83 -0
- package/assets/skills/office-presentations/recipes/images.md +80 -0
- package/assets/skills/office-presentations/recipes/master-and-layouts.md +127 -0
- package/assets/skills/office-presentations/recipes/native-charts.md +87 -0
- package/assets/skills/office-presentations/recipes/rich-text-runs.md +86 -0
- package/assets/skills/office-presentations/recipes/speaker-notes.md +65 -0
- package/assets/skills/office-presentations/recipes/tables.md +88 -0
- package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +71 -0
- package/assets/skills/office-presentations/recipes/theme-tokens.md +87 -0
- package/assets/skills/office-presentations/references/authoring-workflow.md +65 -0
- package/assets/skills/office-presentations/references/finalization.md +63 -0
- package/assets/skills/office-presentations/references/native-bullets.md +78 -0
- package/assets/skills/office-presentations/references/native-evidence.md +67 -0
- package/assets/skills/office-presentations/references/style-guidelines.md +66 -0
- package/assets/skills/office-presentations/references/templates.md +76 -0
- package/assets/skills/office-presentations/routing/google_slides.md +45 -0
- package/assets/skills/office-presentations/tasks/apply-annotations.md +83 -0
- package/assets/skills/office-presentations/tasks/charts-tables-notes.md +79 -0
- package/assets/skills/office-presentations/tasks/create-deck.md +106 -0
- package/assets/skills/office-presentations/tasks/edit-deck.md +93 -0
- package/assets/skills/office-presentations/tasks/inspect-and-verify.md +107 -0
- package/assets/skills/office-presentations/tasks/template-following.md +132 -0
- package/assets/skills/office-presentations/template_following_scripts/check_template_fidelity.py +183 -0
- package/assets/skills/office-presentations/template_following_scripts/inspect_template_deck.py +161 -0
- package/assets/skills/office-presentations/template_following_scripts/make_contact_sheet.py +118 -0
- package/assets/skills/office-presentations/template_following_scripts/prepare_template_starter_deck.py +246 -0
- package/assets/skills/office-presentations/template_following_scripts/validate_template_plan.py +192 -0
- package/assets/skills/office-spreadsheets/SKILL.md +105 -101
- package/assets/skills/office-spreadsheets/container_tools/add_chart.py +243 -0
- package/assets/skills/office-spreadsheets/container_tools/add_image.py +114 -0
- package/assets/skills/office-spreadsheets/container_tools/append_rows.py +129 -0
- package/assets/skills/office-spreadsheets/container_tools/diff_workbooks.py +313 -0
- package/assets/skills/office-spreadsheets/container_tools/inspect_workbook.py +405 -0
- package/assets/skills/office-spreadsheets/container_tools/recalc_gate.py +287 -0
- package/assets/skills/office-spreadsheets/container_tools/set_cells.py +142 -0
- package/assets/skills/office-spreadsheets/container_tools/sheet_ops.py +141 -0
- package/assets/skills/office-spreadsheets/container_tools/style_cells.py +140 -0
- package/assets/skills/office-spreadsheets/recipes/charts.md +66 -0
- package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +69 -0
- package/assets/skills/office-spreadsheets/recipes/data-tables.md +58 -0
- package/assets/skills/office-spreadsheets/recipes/named-ranges.md +41 -0
- package/assets/skills/office-spreadsheets/recipes/number-formats.md +72 -0
- package/assets/skills/office-spreadsheets/recipes/sparklines.md +43 -0
- package/assets/skills/office-spreadsheets/references/charts.md +63 -0
- package/assets/skills/office-spreadsheets/references/domain/financial-models.md +40 -0
- package/assets/skills/office-spreadsheets/references/domain/healthcare.md +22 -0
- package/assets/skills/office-spreadsheets/references/domain/marketing.md +28 -0
- package/assets/skills/office-spreadsheets/references/domain/research.md +22 -0
- package/assets/skills/office-spreadsheets/references/formula-patterns.md +78 -0
- package/assets/skills/office-spreadsheets/references/style-guidelines.md +69 -0
- package/assets/skills/office-spreadsheets/references/workbook-structure.md +60 -0
- package/assets/skills/office-spreadsheets/routing/google-sheets.md +36 -0
- package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +37 -0
- package/assets/skills/office-spreadsheets/tasks/annotations.md +51 -0
- package/assets/skills/office-spreadsheets/tasks/create-workbook.md +66 -0
- package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +38 -0
- package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +69 -0
- package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +38 -0
- package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +57 -0
- package/assets/skills/shared/annotation-recipes.md +252 -0
- package/assets/skills/shared/check_runtime.py +260 -0
- package/assets/skills/shared/common-workflow.md +139 -0
- package/assets/skills/shared/scripts/ensure_raster_image.py +187 -0
- package/dist/client.js +4 -4
- package/dist/client.js.map +1 -1
- package/dist/index.js +246 -89
- package/package.json +1 -1
- package/postinstall.mjs +92 -82
- package/python/office_cli.py +213 -254
package/python/office_cli.py
CHANGED
|
@@ -21,7 +21,18 @@ import tempfile
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Any, Iterable
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
PACKAGE_ROOT = Path(__file__).resolve().parents[1]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _package_version() -> str:
|
|
28
|
+
try:
|
|
29
|
+
data = json.loads((PACKAGE_ROOT / "package.json").read_text(encoding="utf-8"))
|
|
30
|
+
return str(data["version"])
|
|
31
|
+
except (OSError, ValueError, KeyError, TypeError):
|
|
32
|
+
return "0.0.0"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
VERSION = _package_version()
|
|
25
36
|
|
|
26
37
|
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
27
38
|
for _stream in (sys.stdout, sys.stderr):
|
|
@@ -93,53 +104,121 @@ def find_soffice() -> str | None:
|
|
|
93
104
|
return found
|
|
94
105
|
|
|
95
106
|
|
|
96
|
-
|
|
107
|
+
#: Automation entry points per suite and format: (executable, COM ProgID).
|
|
108
|
+
RENDER_ENGINES: dict[str, dict[str, tuple[str, str]]] = {
|
|
109
|
+
"microsoft": {
|
|
110
|
+
"docx": ("WINWORD.EXE", "Word.Application"),
|
|
111
|
+
"pptx": ("POWERPNT.EXE", "PowerPoint.Application"),
|
|
112
|
+
"xlsx": ("EXCEL.EXE", "Excel.Application"),
|
|
113
|
+
},
|
|
114
|
+
"kingsoft": {
|
|
115
|
+
"docx": ("wps.exe", "KWps.Application"),
|
|
116
|
+
"pptx": ("wpp.exe", "KWpp.Application"),
|
|
117
|
+
"xlsx": ("et.exe", "Ket.Application"),
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _kingsoft_dirs() -> list[Path]:
|
|
123
|
+
"""WPS Office hides its binaries under a per-version directory."""
|
|
124
|
+
bases = [
|
|
125
|
+
os.environ.get("WPS_OFFICE_HOME"),
|
|
126
|
+
os.path.join(os.environ.get("ProgramFiles", r"C:\Program Files"), "Kingsoft", "WPS Office"),
|
|
127
|
+
os.path.join(os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)"), "Kingsoft", "WPS Office"),
|
|
128
|
+
os.path.join(os.environ.get("LOCALAPPDATA", ""), "Kingsoft", "WPS Office"),
|
|
129
|
+
]
|
|
130
|
+
found: list[Path] = []
|
|
131
|
+
for base in bases:
|
|
132
|
+
if not base:
|
|
133
|
+
continue
|
|
134
|
+
root = Path(base)
|
|
135
|
+
if (root / "office6").is_dir():
|
|
136
|
+
found.append(root / "office6")
|
|
137
|
+
continue
|
|
138
|
+
if not root.is_dir():
|
|
139
|
+
continue
|
|
140
|
+
versions = [child for child in root.iterdir() if (child / "office6").is_dir()]
|
|
141
|
+
versions.sort(key=lambda child: [int(part) if part.isdigit() else part for part in child.name.replace("-", ".").split(".")], reverse=True)
|
|
142
|
+
for child in versions:
|
|
143
|
+
found.append(child / "office6")
|
|
144
|
+
return found
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def office_engine(kind: str, family: str | None = None) -> tuple[str, str] | None:
|
|
148
|
+
"""Return (suite, executable) for an installed Office-compatible application."""
|
|
97
149
|
if sys.platform != "win32":
|
|
98
150
|
return None
|
|
99
|
-
|
|
100
|
-
name
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
151
|
+
families = [family] if family else ["microsoft", "kingsoft"]
|
|
152
|
+
for name in families:
|
|
153
|
+
executable = RENDER_ENGINES[name][kind][0]
|
|
154
|
+
if name == "microsoft":
|
|
155
|
+
roots = [
|
|
156
|
+
r"C:\Program Files\Microsoft Office\root\Office16",
|
|
157
|
+
r"C:\Program Files (x86)\Microsoft Office\root\Office16",
|
|
158
|
+
r"C:\Program Files\Microsoft Office\Office16",
|
|
159
|
+
r"C:\Program Files (x86)\Microsoft Office\Office16",
|
|
160
|
+
]
|
|
161
|
+
for root in roots:
|
|
162
|
+
candidate = Path(root) / executable
|
|
163
|
+
if candidate.is_file():
|
|
164
|
+
return name, str(candidate)
|
|
165
|
+
continue
|
|
166
|
+
for directory in _kingsoft_dirs():
|
|
167
|
+
candidate = directory / executable
|
|
168
|
+
if candidate.is_file():
|
|
169
|
+
return name, str(candidate)
|
|
111
170
|
return None
|
|
112
171
|
|
|
113
172
|
|
|
173
|
+
def office_binary(kind: str) -> str | None:
|
|
174
|
+
engine = office_engine(kind)
|
|
175
|
+
return engine[1] if engine else None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _engine_path(kind: str, family: str) -> str | None:
|
|
179
|
+
engine = office_engine(kind, family)
|
|
180
|
+
return engine[1] if engine else None
|
|
181
|
+
|
|
182
|
+
|
|
114
183
|
def _powershell() -> str | None:
|
|
115
184
|
return shutil.which("powershell") or shutil.which("pwsh")
|
|
116
185
|
|
|
117
186
|
|
|
118
|
-
def _office_script(kind: str) -> str:
|
|
187
|
+
def _office_script(kind: str, family: str) -> str:
|
|
188
|
+
progid = RENDER_ENGINES[family][kind][1]
|
|
119
189
|
if kind == "docx":
|
|
120
|
-
|
|
190
|
+
if family == "kingsoft":
|
|
191
|
+
return rf"""
|
|
192
|
+
$ErrorActionPreference = 'Stop'
|
|
193
|
+
$app = New-Object -ComObject {progid}
|
|
194
|
+
$app.Visible = $false
|
|
195
|
+
$app.DisplayAlerts = 0
|
|
196
|
+
$doc = $app.Documents.Open($env:DSH_INPUT, $false, $true)
|
|
197
|
+
try {{ $doc.SaveAs([ref]$env:DSH_PDF, [ref]17) }} finally {{ $doc.Close(0); $app.Quit() }}
|
|
198
|
+
"""
|
|
199
|
+
return rf"""
|
|
121
200
|
$ErrorActionPreference = 'Stop'
|
|
122
|
-
$app = New-Object -ComObject
|
|
201
|
+
$app = New-Object -ComObject {progid}
|
|
123
202
|
$app.Visible = $false
|
|
124
203
|
$app.DisplayAlerts = 0
|
|
125
204
|
$doc = $app.Documents.Open($env:DSH_INPUT, $false, $true)
|
|
126
|
-
try { $doc.ExportAsFixedFormat($env:DSH_PDF, 17) } finally { $doc.Close(0); $app.Quit() }
|
|
205
|
+
try {{ $doc.ExportAsFixedFormat($env:DSH_PDF, 17) }} finally {{ $doc.Close(0); $app.Quit() }}
|
|
127
206
|
"""
|
|
128
207
|
if kind == "pptx":
|
|
129
|
-
return
|
|
208
|
+
return rf"""
|
|
130
209
|
$ErrorActionPreference = 'Stop'
|
|
131
|
-
$app = New-Object -ComObject
|
|
210
|
+
$app = New-Object -ComObject {progid}
|
|
132
211
|
$app.DisplayAlerts = 1
|
|
133
212
|
$pres = $app.Presentations.Open($env:DSH_INPUT, $true, $false, $false)
|
|
134
|
-
try { $pres.SaveAs($env:DSH_PDF, 32) } finally { $pres.Close(); $app.Quit() }
|
|
213
|
+
try {{ $pres.SaveAs($env:DSH_PDF, 32) }} finally {{ $pres.Close(); $app.Quit() }}
|
|
135
214
|
"""
|
|
136
|
-
return
|
|
215
|
+
return rf"""
|
|
137
216
|
$ErrorActionPreference = 'Stop'
|
|
138
|
-
$app = New-Object -ComObject
|
|
217
|
+
$app = New-Object -ComObject {progid}
|
|
139
218
|
$app.Visible = $false
|
|
140
219
|
$app.DisplayAlerts = $false
|
|
141
220
|
$wb = $app.Workbooks.Open($env:DSH_INPUT)
|
|
142
|
-
try { $wb.ExportAsFixedFormat(0, $env:DSH_PDF) } finally { $wb.Close($false); $app.Quit() }
|
|
221
|
+
try {{ $wb.ExportAsFixedFormat(0, $env:DSH_PDF) }} finally {{ $wb.Close($false); $app.Quit() }}
|
|
143
222
|
"""
|
|
144
223
|
|
|
145
224
|
|
|
@@ -151,28 +230,33 @@ def export_pdf(input_path: Path, kind: str, out_dir: Path) -> tuple[str, str]:
|
|
|
151
230
|
pdf.unlink()
|
|
152
231
|
|
|
153
232
|
power_shell = _powershell()
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
233
|
+
failures: list[str] = []
|
|
234
|
+
if power_shell:
|
|
235
|
+
for family, renderer in (("microsoft", "microsoft-office"), ("kingsoft", "kingsoft-wps")):
|
|
236
|
+
engine = office_engine(kind, family)
|
|
237
|
+
if engine is None:
|
|
238
|
+
continue
|
|
239
|
+
script = Path(tempfile.gettempdir()) / f"dsh-office-{kind}-{os.getpid()}.ps1"
|
|
240
|
+
script.write_text(_office_script(kind, family), encoding="utf-8")
|
|
241
|
+
env = dict(os.environ)
|
|
242
|
+
env["DSH_INPUT"] = str(input_path.resolve())
|
|
243
|
+
env["DSH_PDF"] = str(pdf.resolve())
|
|
244
|
+
try:
|
|
245
|
+
completed = subprocess.run(
|
|
246
|
+
[power_shell, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", str(script)],
|
|
247
|
+
capture_output=True,
|
|
248
|
+
text=True,
|
|
249
|
+
timeout=240,
|
|
250
|
+
env=env,
|
|
251
|
+
)
|
|
252
|
+
except subprocess.TimeoutExpired:
|
|
253
|
+
failures.append(f"{family}: timed out")
|
|
254
|
+
continue
|
|
255
|
+
finally:
|
|
256
|
+
script.unlink(missing_ok=True)
|
|
257
|
+
if completed.returncode == 0 and pdf.is_file():
|
|
258
|
+
return renderer, str(pdf)
|
|
259
|
+
failures.append(f"{family}: " + ((completed.stderr or completed.stdout).strip() or "no PDF produced"))
|
|
176
260
|
|
|
177
261
|
soffice = find_soffice()
|
|
178
262
|
if soffice:
|
|
@@ -187,7 +271,10 @@ def export_pdf(input_path: Path, kind: str, out_dir: Path) -> tuple[str, str]:
|
|
|
187
271
|
fail(f"LibreOffice export failed for {input_path.name}", detail=detail or None)
|
|
188
272
|
return "libreoffice", str(pdf)
|
|
189
273
|
|
|
190
|
-
fail(
|
|
274
|
+
fail(
|
|
275
|
+
"no document renderer found: install LibreOffice, Microsoft Office, or WPS Office",
|
|
276
|
+
detail="; ".join(failures) or None,
|
|
277
|
+
)
|
|
191
278
|
|
|
192
279
|
|
|
193
280
|
def rasterize_pdf(pdf: str, out_dir: Path, dpi: int = 110) -> list[str]:
|
|
@@ -263,6 +350,16 @@ def _docx_add_equation(paragraph, latex: str) -> bool:
|
|
|
263
350
|
return False
|
|
264
351
|
|
|
265
352
|
|
|
353
|
+
def _docx_mark_header_row(row) -> None:
|
|
354
|
+
"""Repeat this row across pages and mark it as a header for assistive tech."""
|
|
355
|
+
from docx.oxml.ns import qn
|
|
356
|
+
|
|
357
|
+
properties = row._tr.get_or_add_trPr()
|
|
358
|
+
if properties.find(qn("w:tblHeader")) is None:
|
|
359
|
+
header = properties.makeelement(qn("w:tblHeader"), {})
|
|
360
|
+
properties.append(header)
|
|
361
|
+
|
|
362
|
+
|
|
266
363
|
def _docx_add_block(document, block: dict[str, Any], counters: dict[str, int]) -> None:
|
|
267
364
|
from docx.shared import Inches, Pt
|
|
268
365
|
|
|
@@ -315,6 +412,7 @@ def _docx_add_block(document, block: dict[str, Any], counters: dict[str, int]) -
|
|
|
315
412
|
for paragraph in cells[index].paragraphs:
|
|
316
413
|
for run in paragraph.runs:
|
|
317
414
|
run.bold = True
|
|
415
|
+
_docx_mark_header_row(table.rows[0])
|
|
318
416
|
for row in rows:
|
|
319
417
|
cells = table.add_row().cells
|
|
320
418
|
for index, value in enumerate(row):
|
|
@@ -383,6 +481,10 @@ def docx_create(spec: dict[str, Any], out: Path) -> dict[str, Any]:
|
|
|
383
481
|
if spec.get("author") or spec.get("title"):
|
|
384
482
|
document.core_properties.title = str(spec.get("title", ""))
|
|
385
483
|
document.core_properties.author = str(spec.get("author", ""))
|
|
484
|
+
# A fresh python-docx document carries the library name in comments; a
|
|
485
|
+
# delivered file should not advertise the tool that wrote it.
|
|
486
|
+
document.core_properties.comments = str(spec.get("comments", ""))
|
|
487
|
+
document.core_properties.last_modified_by = str(spec.get("author", ""))
|
|
386
488
|
if spec.get("header"):
|
|
387
489
|
document.sections[0].header.paragraphs[0].text = str(spec["header"])
|
|
388
490
|
if spec.get("footer"):
|
|
@@ -424,38 +526,6 @@ def _replace_in_paragraph(paragraph, find: str, replacement: str) -> int:
|
|
|
424
526
|
return count
|
|
425
527
|
|
|
426
528
|
|
|
427
|
-
def docx_edit(spec: dict[str, Any], input_path: Path, out: Path) -> dict[str, Any]:
|
|
428
|
-
from docx import Document
|
|
429
|
-
|
|
430
|
-
document = Document(str(input_path))
|
|
431
|
-
replace_count = 0
|
|
432
|
-
for item in spec.get("replace", []):
|
|
433
|
-
find = str(item.get("find", ""))
|
|
434
|
-
replacement = str(item.get("replace", ""))
|
|
435
|
-
if not find:
|
|
436
|
-
continue
|
|
437
|
-
for paragraph in _docx_iter_paragraphs(document):
|
|
438
|
-
replace_count += _replace_in_paragraph(paragraph, find, replacement)
|
|
439
|
-
counters = {key: 0 for key in ("headings", "paragraphs", "list_items", "tables", "images", "equations", "equation_fallbacks")}
|
|
440
|
-
_docx_add_blocks(document, spec.get("blocks", []), counters)
|
|
441
|
-
core = spec.get("core")
|
|
442
|
-
if isinstance(core, dict):
|
|
443
|
-
for key in ("title", "author", "subject", "keywords", "comments"):
|
|
444
|
-
if key in core:
|
|
445
|
-
setattr(document.core_properties, key, str(core[key]))
|
|
446
|
-
ensure_parent(str(out))
|
|
447
|
-
document.save(str(out))
|
|
448
|
-
return {
|
|
449
|
-
"ok": True,
|
|
450
|
-
"kind": "docx",
|
|
451
|
-
"command": "edit",
|
|
452
|
-
"input": str(input_path),
|
|
453
|
-
"output": str(out),
|
|
454
|
-
"replacements": replace_count,
|
|
455
|
-
"counts": counters,
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
|
|
459
529
|
def _docx_has_equation(paragraph) -> bool:
|
|
460
530
|
tag = "{http://schemas.openxmlformats.org/officeDocument/2006/math}oMath"
|
|
461
531
|
return bool(paragraph._p.findall(f".//{tag}"))
|
|
@@ -615,17 +685,21 @@ def pdf_create(spec: dict[str, Any], out: Path) -> dict[str, Any]:
|
|
|
615
685
|
author=str(spec.get("author", "")),
|
|
616
686
|
)
|
|
617
687
|
counters = {key: 0 for key in ("headings", "paragraphs", "list_items", "tables", "images")}
|
|
688
|
+
following: list[dict[str, Any]] = []
|
|
689
|
+
for page in spec.get("pages", []):
|
|
690
|
+
if isinstance(page, dict):
|
|
691
|
+
following.extend(page.get("blocks", []))
|
|
692
|
+
if page.get("break", True):
|
|
693
|
+
following.append({"type": "page_break"})
|
|
694
|
+
following.extend(spec.get("blocks", []))
|
|
695
|
+
kinds = {str(block.get("type", "")).lower() for block in following if isinstance(block, dict)}
|
|
618
696
|
blocks: list[dict[str, Any]] = []
|
|
619
|
-
|
|
697
|
+
# `title` is metadata; only draw it when the blocks do not already carry one.
|
|
698
|
+
if spec.get("title") and "title" not in kinds:
|
|
620
699
|
blocks.append({"type": "title", "text": spec["title"]})
|
|
621
700
|
if spec.get("subtitle"):
|
|
622
701
|
blocks.append({"type": "paragraph", "text": spec["subtitle"]})
|
|
623
|
-
|
|
624
|
-
if isinstance(page, dict):
|
|
625
|
-
blocks.extend(page.get("blocks", []))
|
|
626
|
-
if page.get("break", True):
|
|
627
|
-
blocks.append({"type": "page_break"})
|
|
628
|
-
blocks.extend(spec.get("blocks", []))
|
|
702
|
+
blocks.extend(following)
|
|
629
703
|
document.build(_pdf_flowables(blocks, font, counters))
|
|
630
704
|
return {"ok": True, "kind": "pdf", "command": "create", "output": str(out), "font": font, "counts": counters}
|
|
631
705
|
|
|
@@ -667,45 +741,6 @@ def pdf_read(input_path: Path) -> dict[str, Any]:
|
|
|
667
741
|
}
|
|
668
742
|
|
|
669
743
|
|
|
670
|
-
def pdf_edit(spec: dict[str, Any], input_path: Path, out: Path) -> dict[str, Any]:
|
|
671
|
-
from pypdf import PdfReader, PdfWriter
|
|
672
|
-
|
|
673
|
-
writer = PdfWriter()
|
|
674
|
-
reader = PdfReader(str(input_path))
|
|
675
|
-
for page in reader.pages:
|
|
676
|
-
writer.add_page(page)
|
|
677
|
-
rotate = int(spec.get("rotate", 0))
|
|
678
|
-
targets = spec.get("pages")
|
|
679
|
-
if rotate:
|
|
680
|
-
for index, page in enumerate(writer.pages):
|
|
681
|
-
if targets is None or index in targets:
|
|
682
|
-
page.rotate(rotate)
|
|
683
|
-
for extra in spec.get("merge", []):
|
|
684
|
-
extra_reader = PdfReader(str(require_file(str(extra), "PDF to merge")))
|
|
685
|
-
for page in extra_reader.pages:
|
|
686
|
-
writer.add_page(page)
|
|
687
|
-
if spec.get("remove_pages"):
|
|
688
|
-
remove = set(int(value) for value in spec["remove_pages"])
|
|
689
|
-
writer = PdfWriter()
|
|
690
|
-
for index, page in enumerate(reader.pages):
|
|
691
|
-
if index not in remove:
|
|
692
|
-
writer.add_page(page)
|
|
693
|
-
for extra in spec.get("merge", []):
|
|
694
|
-
extra_reader = PdfReader(str(require_file(str(extra), "PDF to merge")))
|
|
695
|
-
for page in extra_reader.pages:
|
|
696
|
-
writer.add_page(page)
|
|
697
|
-
if isinstance(spec.get("metadata"), dict):
|
|
698
|
-
metadata = {}
|
|
699
|
-
for key, value in spec["metadata"].items():
|
|
700
|
-
name = str(key)
|
|
701
|
-
metadata[name if name.startswith("/") else f"/{name}"] = str(value)
|
|
702
|
-
writer.add_metadata(metadata)
|
|
703
|
-
ensure_parent(str(out))
|
|
704
|
-
with open(out, "wb") as handle:
|
|
705
|
-
writer.write(handle)
|
|
706
|
-
return {"ok": True, "kind": "pdf", "command": "edit", "input": str(input_path), "output": str(out), "page_count": len(writer.pages)}
|
|
707
|
-
|
|
708
|
-
|
|
709
744
|
def pdf_render(input_path: Path, out_dir: Path, dpi: int) -> dict[str, Any]:
|
|
710
745
|
pages = rasterize_pdf(str(input_path), out_dir, dpi)
|
|
711
746
|
return {"ok": True, "kind": "pdf", "command": "render", "input": str(input_path), "page_count": len(pages), "pages": pages}
|
|
@@ -844,6 +879,10 @@ def pptx_create(spec: dict[str, Any], out: Path) -> dict[str, Any]:
|
|
|
844
879
|
prs.slide_height = int(7.5 * 914400)
|
|
845
880
|
prs.core_properties.title = str(spec.get("title", ""))
|
|
846
881
|
prs.core_properties.author = str(spec.get("author", "DSH Office Suite"))
|
|
882
|
+
# python-pptx leaves its own name in the properties; a delivered deck should
|
|
883
|
+
# carry the author's, not the library's.
|
|
884
|
+
prs.core_properties.comments = str(spec.get("comments", ""))
|
|
885
|
+
prs.core_properties.last_modified_by = str(spec.get("author", "DSH Office Suite"))
|
|
847
886
|
counters = {key: 0 for key in ("slides", "bullets", "tables", "charts", "images")}
|
|
848
887
|
for slide_spec in spec.get("slides", []):
|
|
849
888
|
if isinstance(slide_spec, dict):
|
|
@@ -912,38 +951,6 @@ def pptx_read(input_path: Path) -> dict[str, Any]:
|
|
|
912
951
|
}
|
|
913
952
|
|
|
914
953
|
|
|
915
|
-
def pptx_edit(spec: dict[str, Any], input_path: Path, out: Path) -> dict[str, Any]:
|
|
916
|
-
from pptx import Presentation
|
|
917
|
-
|
|
918
|
-
prs = Presentation(str(input_path))
|
|
919
|
-
replacements = 0
|
|
920
|
-
for item in spec.get("replace", []):
|
|
921
|
-
find = str(item.get("find", ""))
|
|
922
|
-
replacement = str(item.get("replace", ""))
|
|
923
|
-
if not find:
|
|
924
|
-
continue
|
|
925
|
-
for slide in prs.slides:
|
|
926
|
-
for shape in slide.shapes:
|
|
927
|
-
if not shape.has_text_frame:
|
|
928
|
-
continue
|
|
929
|
-
for paragraph in shape.text_frame.paragraphs:
|
|
930
|
-
for run in paragraph.runs:
|
|
931
|
-
if find in run.text:
|
|
932
|
-
run.text = run.text.replace(find, replacement)
|
|
933
|
-
replacements += 1
|
|
934
|
-
counters = {key: 0 for key in ("slides", "bullets", "tables", "charts", "images")}
|
|
935
|
-
for slide_spec in spec.get("slides", []):
|
|
936
|
-
if isinstance(slide_spec, dict):
|
|
937
|
-
_pptx_add_slide(prs, slide_spec, counters)
|
|
938
|
-
if isinstance(spec.get("core"), dict):
|
|
939
|
-
for key in ("title", "author", "subject", "keywords", "comments"):
|
|
940
|
-
if key in spec["core"]:
|
|
941
|
-
setattr(prs.core_properties, key, str(spec["core"][key]))
|
|
942
|
-
ensure_parent(str(out))
|
|
943
|
-
prs.save(str(out))
|
|
944
|
-
return {"ok": True, "kind": "pptx", "command": "edit", "input": str(input_path), "output": str(out), "replacements": replacements, "added": counters}
|
|
945
|
-
|
|
946
|
-
|
|
947
954
|
# ---------------------------------------------------------------------------
|
|
948
955
|
# XLSX
|
|
949
956
|
# ---------------------------------------------------------------------------
|
|
@@ -1113,86 +1120,26 @@ def xlsx_read(input_path: Path, data_only: bool = False) -> dict[str, Any]:
|
|
|
1113
1120
|
}
|
|
1114
1121
|
|
|
1115
1122
|
|
|
1116
|
-
def xlsx_edit(spec: dict[str, Any], input_path: Path, out: Path) -> dict[str, Any]:
|
|
1117
|
-
from openpyxl import load_workbook
|
|
1118
|
-
from openpyxl.drawing.image import Image as XLImage
|
|
1119
|
-
|
|
1120
|
-
workbook = load_workbook(str(input_path))
|
|
1121
|
-
changed = 0
|
|
1122
|
-
for operation in spec.get("operations", []):
|
|
1123
|
-
if not isinstance(operation, dict):
|
|
1124
|
-
continue
|
|
1125
|
-
action = str(operation.get("action", "set"))
|
|
1126
|
-
if action == "add_sheet":
|
|
1127
|
-
title = str(operation.get("name", "Sheet"))
|
|
1128
|
-
worksheet = workbook.create_sheet(title)
|
|
1129
|
-
elif action == "delete_sheet":
|
|
1130
|
-
name = str(operation.get("name", ""))
|
|
1131
|
-
if name not in workbook.sheetnames:
|
|
1132
|
-
fail(f"sheet not found: {name}")
|
|
1133
|
-
workbook.remove(workbook[name])
|
|
1134
|
-
continue
|
|
1135
|
-
elif action == "rename_sheet":
|
|
1136
|
-
old = str(operation.get("from", ""))
|
|
1137
|
-
new = str(operation.get("to", ""))
|
|
1138
|
-
if old not in workbook.sheetnames:
|
|
1139
|
-
fail(f"sheet not found: {old}")
|
|
1140
|
-
workbook[old].title = new
|
|
1141
|
-
continue
|
|
1142
|
-
else:
|
|
1143
|
-
name = str(operation.get("sheet", workbook.sheetnames[0]))
|
|
1144
|
-
if name not in workbook.sheetnames:
|
|
1145
|
-
fail(f"sheet not found: {name}")
|
|
1146
|
-
worksheet = workbook[name]
|
|
1147
|
-
if action == "set_cells":
|
|
1148
|
-
for address, value in (operation.get("cells") or {}).items():
|
|
1149
|
-
worksheet[str(address)] = _xlsx_value(value)
|
|
1150
|
-
changed += 1
|
|
1151
|
-
elif action == "append_rows":
|
|
1152
|
-
for row in operation.get("rows", []):
|
|
1153
|
-
worksheet.append([_xlsx_value(value) for value in row])
|
|
1154
|
-
changed += 1
|
|
1155
|
-
elif action == "style":
|
|
1156
|
-
for address, style in (operation.get("cells") or {}).items():
|
|
1157
|
-
if isinstance(style, dict):
|
|
1158
|
-
for cell in _xlsx_cells(worksheet, str(address)):
|
|
1159
|
-
_xlsx_apply_style(cell, style)
|
|
1160
|
-
changed += 1
|
|
1161
|
-
elif action == "chart":
|
|
1162
|
-
_xlsx_add_chart(worksheet, operation)
|
|
1163
|
-
changed += 1
|
|
1164
|
-
elif action == "image":
|
|
1165
|
-
path = require_file(str(operation.get("path", "")), "image")
|
|
1166
|
-
worksheet.add_image(XLImage(str(path)), str(operation.get("anchor", "A1")))
|
|
1167
|
-
changed += 1
|
|
1168
|
-
elif action == "rows":
|
|
1169
|
-
for row in operation.get("rows", []):
|
|
1170
|
-
worksheet.append([_xlsx_value(value) for value in row])
|
|
1171
|
-
changed += 1
|
|
1172
|
-
ensure_parent(str(out))
|
|
1173
|
-
workbook.save(str(out))
|
|
1174
|
-
workbook.close()
|
|
1175
|
-
return {"ok": True, "kind": "xlsx", "command": "edit", "input": str(input_path), "output": str(out), "changes": changed}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
1123
|
def xlsx_recalc(input_path: Path, out: Path) -> dict[str, Any]:
|
|
1179
|
-
"""Force
|
|
1180
|
-
if sys.platform
|
|
1181
|
-
|
|
1124
|
+
"""Force a spreadsheet application to recalculate and store cached formula values."""
|
|
1125
|
+
engine = office_engine("xlsx") if sys.platform == "win32" else None
|
|
1126
|
+
if engine is None or _powershell() is None:
|
|
1127
|
+
fail("formula recalculation requires Microsoft Excel or WPS Spreadsheets on Windows; openpyxl cannot evaluate formulas")
|
|
1128
|
+
family, executable = engine
|
|
1129
|
+
progid = RENDER_ENGINES[family]["xlsx"][1]
|
|
1130
|
+
ensure_parent(str(out))
|
|
1131
|
+
if out.resolve() != input_path.resolve():
|
|
1132
|
+
shutil.copyfile(input_path, out)
|
|
1182
1133
|
script = Path(tempfile.gettempdir()) / f"dsh-office-recalc-{os.getpid()}.ps1"
|
|
1183
|
-
script.write_text(
|
|
1134
|
+
script.write_text(rf"""
|
|
1184
1135
|
$ErrorActionPreference = 'Stop'
|
|
1185
|
-
$app = New-Object -ComObject
|
|
1136
|
+
$app = New-Object -ComObject {progid}
|
|
1186
1137
|
$app.Visible = $false
|
|
1187
1138
|
$app.DisplayAlerts = $false
|
|
1188
|
-
$wb = $app.Workbooks.Open($env:
|
|
1189
|
-
try {
|
|
1190
|
-
$app.CalculateFull()
|
|
1191
|
-
$wb.SaveAs($env:DSH_OUT, 51)
|
|
1192
|
-
} finally { $wb.Close($false); $app.Quit() }
|
|
1139
|
+
$wb = $app.Workbooks.Open($env:DSH_OUT)
|
|
1140
|
+
try {{ $app.CalculateFull(); $wb.Save() }} finally {{ $wb.Close($true); $app.Quit() }}
|
|
1193
1141
|
""", encoding="utf-8")
|
|
1194
1142
|
env = dict(os.environ)
|
|
1195
|
-
env["DSH_INPUT"] = str(input_path.resolve())
|
|
1196
1143
|
env["DSH_OUT"] = str(out.resolve())
|
|
1197
1144
|
try:
|
|
1198
1145
|
completed = subprocess.run(
|
|
@@ -1205,8 +1152,28 @@ try {
|
|
|
1205
1152
|
finally:
|
|
1206
1153
|
script.unlink(missing_ok=True)
|
|
1207
1154
|
if completed.returncode != 0 or not out.is_file():
|
|
1208
|
-
fail("
|
|
1209
|
-
|
|
1155
|
+
fail("formula recalculation failed", detail=(completed.stderr or completed.stdout).strip() or None)
|
|
1156
|
+
cached = 0
|
|
1157
|
+
try:
|
|
1158
|
+
import openpyxl
|
|
1159
|
+
|
|
1160
|
+
workbook = openpyxl.load_workbook(str(out), data_only=True)
|
|
1161
|
+
for sheet in workbook.worksheets:
|
|
1162
|
+
for row in sheet.iter_rows():
|
|
1163
|
+
cached += sum(1 for cell in row if cell.value is not None)
|
|
1164
|
+
workbook.close()
|
|
1165
|
+
except Exception:
|
|
1166
|
+
cached = 0
|
|
1167
|
+
return {
|
|
1168
|
+
"ok": True,
|
|
1169
|
+
"kind": "xlsx",
|
|
1170
|
+
"command": "recalc",
|
|
1171
|
+
"input": str(input_path),
|
|
1172
|
+
"output": str(out),
|
|
1173
|
+
"renderer": f"{family}-office" if family == "microsoft" else "kingsoft-wps",
|
|
1174
|
+
"executable": executable,
|
|
1175
|
+
"cached_values": cached,
|
|
1176
|
+
}
|
|
1210
1177
|
|
|
1211
1178
|
|
|
1212
1179
|
# ---------------------------------------------------------------------------
|
|
@@ -1246,9 +1213,20 @@ def status() -> dict[str, Any]:
|
|
|
1246
1213
|
"renderers": {
|
|
1247
1214
|
"libreoffice": find_soffice(),
|
|
1248
1215
|
"microsoft_office": {
|
|
1249
|
-
"word":
|
|
1250
|
-
"powerpoint":
|
|
1251
|
-
"excel":
|
|
1216
|
+
"word": _engine_path("docx", "microsoft"),
|
|
1217
|
+
"powerpoint": _engine_path("pptx", "microsoft"),
|
|
1218
|
+
"excel": _engine_path("xlsx", "microsoft"),
|
|
1219
|
+
},
|
|
1220
|
+
"kingsoft_wps": {
|
|
1221
|
+
"writer": _engine_path("docx", "kingsoft"),
|
|
1222
|
+
"presentation": _engine_path("pptx", "kingsoft"),
|
|
1223
|
+
"spreadsheet": _engine_path("xlsx", "kingsoft"),
|
|
1224
|
+
},
|
|
1225
|
+
"can_render": {
|
|
1226
|
+
"docx": bool(office_engine("docx") or find_soffice()),
|
|
1227
|
+
"pptx": bool(office_engine("pptx") or find_soffice()),
|
|
1228
|
+
"xlsx": bool(office_engine("xlsx") or find_soffice()),
|
|
1229
|
+
"pdf": True,
|
|
1252
1230
|
},
|
|
1253
1231
|
},
|
|
1254
1232
|
}
|
|
@@ -1280,13 +1258,6 @@ def main() -> None:
|
|
|
1280
1258
|
render.add_argument("--dpi", type=int, default=110)
|
|
1281
1259
|
render.add_argument("--json-out")
|
|
1282
1260
|
|
|
1283
|
-
if kind in {"docx", "pdf", "pptx", "xlsx"}:
|
|
1284
|
-
edit = commands.add_parser("edit")
|
|
1285
|
-
edit.add_argument("--input", required=True)
|
|
1286
|
-
edit.add_argument("--spec", required=True)
|
|
1287
|
-
edit.add_argument("--out", required=True)
|
|
1288
|
-
edit.add_argument("--json-out")
|
|
1289
|
-
|
|
1290
1261
|
if kind == "xlsx":
|
|
1291
1262
|
recalc = commands.add_parser("recalc")
|
|
1292
1263
|
recalc.add_argument("--input", required=True)
|
|
@@ -1321,18 +1292,6 @@ def main() -> None:
|
|
|
1321
1292
|
result = pptx_read(input_path)
|
|
1322
1293
|
else:
|
|
1323
1294
|
result = xlsx_read(input_path, data_only=bool(getattr(args, "data_only", False)))
|
|
1324
|
-
elif args.command == "edit":
|
|
1325
|
-
input_path = require_file(args.input)
|
|
1326
|
-
spec = load_json(args.spec)
|
|
1327
|
-
out = Path(args.out)
|
|
1328
|
-
if args.format == "docx":
|
|
1329
|
-
result = docx_edit(spec, input_path, out)
|
|
1330
|
-
elif args.format == "pdf":
|
|
1331
|
-
result = pdf_edit(spec, input_path, out)
|
|
1332
|
-
elif args.format == "pptx":
|
|
1333
|
-
result = pptx_edit(spec, input_path, out)
|
|
1334
|
-
else:
|
|
1335
|
-
result = xlsx_edit(spec, input_path, out)
|
|
1336
1295
|
elif args.command == "render":
|
|
1337
1296
|
input_path = require_file(args.input)
|
|
1338
1297
|
out_dir = Path(args.out_dir)
|