@deepseek-ai/dsh-skill-office 0.2.1-alpha.1 → 0.2.1-alpha.3
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/README.i18n.yaml +22 -25
- package/README.md +57 -26
- package/README.zh.md +68 -37
- package/assets/office/SKILL.md +15 -0
- package/assets/office/references/sidecar.md +17 -0
- package/assets/office-docx/SKILL.md +7 -24
- package/assets/office-export-pdf/SKILL.md +18 -0
- package/assets/office-pptx/SKILL.md +6 -23
- package/assets/office-recalculate/SKILL.md +18 -0
- package/assets/office-render/SKILL.md +31 -0
- package/assets/office-xlsx/SKILL.md +7 -29
- package/lib/index.js +15 -9
- package/lib/types/index.d.ts +2 -2
- package/package.json +9 -9
- package/assets/scripts/check_office.py +0 -279
package/README.i18n.yaml
CHANGED
|
@@ -3,41 +3,38 @@
|
|
|
3
3
|
# After editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/skill/skill-office/README.md
|
|
5
5
|
/:
|
|
6
|
-
en:
|
|
7
|
-
zh:
|
|
6
|
+
en: b65118b967447a79
|
|
7
|
+
zh: 83afe7dbc1a82c44
|
|
8
8
|
/deepseek-ai-dsh-skill-office:
|
|
9
9
|
en: f855ac5e12b4e604
|
|
10
10
|
zh: b2e875d31d56cb2e
|
|
11
11
|
/deepseek-ai-dsh-skill-office/summary:
|
|
12
|
-
en:
|
|
13
|
-
zh:
|
|
12
|
+
en: 3f41e39809756048
|
|
13
|
+
zh: 5ec8623eeb617755
|
|
14
14
|
/deepseek-ai-dsh-skill-office/table-of-contents:
|
|
15
|
-
en:
|
|
16
|
-
zh:
|
|
15
|
+
en: c361195a73cf5755
|
|
16
|
+
zh: 745b04e7be0be369
|
|
17
17
|
/deepseek-ai-dsh-skill-office/use-this-package:
|
|
18
|
-
en:
|
|
19
|
-
zh:
|
|
20
|
-
/deepseek-ai-dsh-skill-office/use-this-package/
|
|
21
|
-
en:
|
|
22
|
-
zh:
|
|
23
|
-
/deepseek-ai-dsh-skill-office/use-this-package/structural-checks:
|
|
24
|
-
en: 1bb872d63bdd78b7
|
|
25
|
-
zh: 355dee19bdfb3798
|
|
18
|
+
en: bc0460f837676d8a
|
|
19
|
+
zh: be1692483d7f5daf
|
|
20
|
+
/deepseek-ai-dsh-skill-office/use-this-package/progressive-loading:
|
|
21
|
+
en: b20b58bfaab4d3f6
|
|
22
|
+
zh: 084528c543b8746e
|
|
26
23
|
/deepseek-ai-dsh-skill-office/understand-the-implementation:
|
|
27
|
-
en:
|
|
28
|
-
zh:
|
|
24
|
+
en: 6946e30f3663c72f
|
|
25
|
+
zh: 81dd5de9c7e61562
|
|
29
26
|
/deepseek-ai-dsh-skill-office/further-exploration:
|
|
30
|
-
en:
|
|
31
|
-
zh:
|
|
27
|
+
en: 23830c100287ffb8
|
|
28
|
+
zh: 7a4100e87057bc20
|
|
32
29
|
/deepseek-ai-dsh-skill-office/model-experience:
|
|
33
|
-
en:
|
|
34
|
-
zh:
|
|
30
|
+
en: bbd979fd20e7b308
|
|
31
|
+
zh: 7e6b6e04f2aedb02
|
|
35
32
|
/deepseek-ai-dsh-skill-office/model-experience/kv-cache-effect:
|
|
36
|
-
en:
|
|
37
|
-
zh:
|
|
33
|
+
en: dc9e9205d989f1d0
|
|
34
|
+
zh: 01bdbbfba743813a
|
|
38
35
|
/deepseek-ai-dsh-skill-office/known-limitations-and-deferred-work:
|
|
39
|
-
en:
|
|
40
|
-
zh:
|
|
36
|
+
en: 1f718fc3bb4352d9
|
|
37
|
+
zh: c1aa108ad2b05fdd
|
|
41
38
|
/deepseek-ai-dsh-skill-office/known-limitations-and-deferred-work/dev-note:
|
|
42
39
|
en: e67f633dc6fe7773
|
|
43
|
-
zh:
|
|
40
|
+
zh: 8ec3e73528ddb1a6
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "
|
|
2
|
+
description: "Progressive Office authoring and CLI operation skills for document workflows."
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,11 +9,12 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
The catalog lists the Office router and six authoring or operation skills. Full instructions load on demand. Authoring skills use the selected Python libraries; operation skills supply the configured Node and LibreOffice Kit CLI paths. SDK and Desktop mount the skill provider and dependency query separately.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
16
16
|
- [Use this package](#use-this-package)
|
|
17
|
+
- [Progressive loading](#progressive-loading)
|
|
17
18
|
- [Understand the implementation](#understand-the-implementation)
|
|
18
19
|
- [Further Exploration](#further-exploration)
|
|
19
20
|
- [Model Experience](#model-experience)
|
|
@@ -25,29 +26,56 @@ Agents can load Word, PowerPoint, and Excel workflows that use the bundled Pytho
|
|
|
25
26
|
<a id="use-this-package"></a>
|
|
26
27
|
## Use this package
|
|
27
28
|
|
|
28
|
-
Mount
|
|
29
|
-
|
|
30
|
-
### Minimal configuration
|
|
29
|
+
Mount the skill provider beside the skill registry and `dsh-tool-skill`. The provider supplies skill instructions; operation skills append absolute interpreter and CLI paths when loaded.
|
|
31
30
|
|
|
32
31
|
```yaml
|
|
33
32
|
- name: '@deepseek-ai/dsh-skill-office'
|
|
33
|
+
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
| Field | Default | Meaning |
|
|
37
37
|
|---|---|---|
|
|
38
|
-
| `assetRoot` |
|
|
39
|
-
| `node` |
|
|
40
|
-
| `cli` | Installed
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
| `assetRoot` | Carrier resources or packaged `assets/` | Absolute directory containing the seven skill folders. |
|
|
39
|
+
| `node` | Host Node.js | Standalone Node executable for operation skills; packaged carriers supply it explicitly. |
|
|
40
|
+
| `cli` | Installed Kit CLI | Absolute CLI entry; `false` disables operation execution. |
|
|
41
|
+
|
|
42
|
+
Invalid skill assets reject registration. Authoring instructions do not resolve renderer paths; loading operation instructions validates existing interpreter and CLI files without preparing engines. Bash or PowerShell owns approvals, sandboxing, cancellation, and output management. Resources and execution tools must refer to the same host.
|
|
43
|
+
|
|
44
|
+
<a id="progressive-loading"></a>
|
|
45
|
+
### Progressive loading
|
|
46
|
+
|
|
47
|
+
The model chooses skills from the task; the host does not inject complete instructions based on file extensions. All seven skills are listed and loadable by name through `skill` or explicit user invocation. Requested appearance changes, including spreadsheet cell fills and text alignment, require rendering even without an explicit visual-check request; an explicit request to skip rendering takes precedence. Pure operations can enter the corresponding skill directly.
|
|
48
|
+
|
|
49
|
+
```mermaid
|
|
50
|
+
flowchart TD
|
|
51
|
+
A["Initial catalog: seven Office summaries"]
|
|
52
|
+
A -->|Non-Office task| N["Continue without loading Office instructions"]
|
|
53
|
+
A -->|Office task| B["skill(office): task routing"]
|
|
54
|
+
|
|
55
|
+
B -->|Create or edit| C["skill(office-docx / office-pptx / office-xlsx)"]
|
|
56
|
+
C --> D["Author and validate content, structure and data"]
|
|
57
|
+
D -->|No additional operation needed| F["Deliver"]
|
|
58
|
+
|
|
59
|
+
B -->|Images, visual inspection, or appearance changes| R["skill(office-render)"]
|
|
60
|
+
D -->|Images, visual inspection, or appearance changes| R
|
|
61
|
+
R --> RC["Shell: render into a private workspace temporary directory"]
|
|
62
|
+
RC -->|Usable preview| V["Inspect requested images"]
|
|
63
|
+
V --> F
|
|
64
|
+
RC -->|Failed or blank preview| L["Report content checks and unfinished visual inspection separately"]
|
|
65
|
+
L --> F
|
|
66
|
+
|
|
67
|
+
B -->|Formula recalculation| X["skill(office-recalculate)"]
|
|
68
|
+
D -->|Formula recalculation| X
|
|
69
|
+
X --> XC["Shell: CLI recalculate and verify values"]
|
|
70
|
+
XC --> F
|
|
71
|
+
|
|
72
|
+
B -->|PDF export| P["skill(office-export-pdf)"]
|
|
73
|
+
D -->|PDF export| P
|
|
74
|
+
P --> PC["Shell: CLI convert"]
|
|
75
|
+
PC --> F
|
|
76
|
+
```
|
|
49
77
|
|
|
50
|
-
|
|
78
|
+
New files and content, data, or formula edits do not require rendering solely because they include incidental basic formatting. Preview images use a private, randomly named temporary directory inside the workspace and are removed after inspection. Loaded instructions remain in session history after an operation finishes. The diagram describes ordinary presets across supported clients; it does not change the shell-only definition of minimal or sdk-minimal.
|
|
51
79
|
|
|
52
80
|
-----
|
|
53
81
|
|
|
@@ -57,12 +85,15 @@ Excel data and formula tasks skip visual inspection; formatting, layout, chart a
|
|
|
57
85
|
<details>
|
|
58
86
|
<summary>Implementation internals — click to expand</summary>
|
|
59
87
|
|
|
60
|
-
The provider
|
|
88
|
+
The provider reads descriptions at registration and bodies on demand. Registry precedence, scope, and invocation permissions apply to every skill. Each loaded skill includes its resource directory for relative references. Disposal unregisters all candidates.
|
|
89
|
+
|
|
90
|
+
The provider registers no tools or activation projection. Loaded skill results remain logged through the generic skill loader. In PTC programs, print or return the full `tools.skill(...)` result so its instructions reach the model. Resume and fork use existing session history and tool declaration reconciliation. Interpreter and CLI paths are resolved only when an operation skill is loaded.
|
|
61
91
|
|
|
62
92
|
| File | Responsibility |
|
|
63
93
|
|---|---|
|
|
64
|
-
| [`src/index.ts`](src/index.ts) |
|
|
65
|
-
|
|
94
|
+
| [`src/index.ts`](src/index.ts) | Resource registration and unchanged skill bodies. |
|
|
95
|
+
|
|
96
|
+
| [`assets/`](assets/) | Router and six subskills. |
|
|
66
97
|
|
|
67
98
|
</details>
|
|
68
99
|
|
|
@@ -72,7 +103,7 @@ The provider registers three bundled candidates and reads their descriptions fro
|
|
|
72
103
|
## Further Exploration
|
|
73
104
|
|
|
74
105
|
- [Skill registry](../skill/README.md) — discovery and precedence.
|
|
75
|
-
- [Skill tool](../tool-skill/README.md) — model
|
|
106
|
+
- [Skill tool](../tool-skill/README.md) — model catalogs and loaded bodies.
|
|
76
107
|
- [File delivery](../../deliverables/tool-present/README.md) — current source-path delivery.
|
|
77
108
|
|
|
78
109
|
-----
|
|
@@ -80,19 +111,19 @@ The provider registers three bundled candidates and reads their descriptions fro
|
|
|
80
111
|
<a id="model-experience"></a>
|
|
81
112
|
## Model Experience
|
|
82
113
|
|
|
83
|
-
Indirectly, through `dsh-tool-skill`, which renders the
|
|
114
|
+
Indirectly, through `dsh-tool-skill`, which renders the initial Office summary and returns routing or selected subskill instructions from `skill(name)`.
|
|
84
115
|
|
|
85
116
|
#### KV Cache effect
|
|
86
117
|
|
|
87
|
-
|
|
118
|
+
Skill results append to history without changing tool declarations, and loaded text or images remain after an operation finishes. Character estimates use `ceil(characters / 4)`: the summary is at most 50 tokens and the router body at most 200; authoring bodies are at least 35% smaller than the combined authoring and operation guides. Skill wrappers, the generic catalog, shell declarations, and `load_workspace_dependencies` are separate costs, and these estimates are not server usage.
|
|
88
119
|
|
|
89
120
|
## Known Limitations and Deferred Work
|
|
90
121
|
|
|
91
122
|
<a id="known-limitations-and-deferred-work"></a>
|
|
92
123
|
|
|
93
|
-
- The provider
|
|
94
|
-
-
|
|
95
|
-
-
|
|
124
|
+
- The provider does not install authoring libraries.
|
|
125
|
+
- LibreOffice output can differ from native Office applications.
|
|
126
|
+
- Legacy, encrypted, and macro-enabled files need an appropriate separate workflow.
|
|
96
127
|
|
|
97
128
|
<a id="dev-note"></a>
|
|
98
129
|
### Dev Note
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "
|
|
2
|
+
description: "为文档工作流提供渐进加载的 Office 创作与 CLI 操作技能。"
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,15 +9,16 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
目录列出 Office 路由及六个创作或操作技能,完整说明按需加载。创作技能使用选定的 Python 库;操作技能提供配置的 Node 与 LibreOffice Kit CLI 路径。SDK 和 Desktop 分别挂载技能提供者和依赖查询。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
16
16
|
- [使用本包](#use-this-package)
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
17
|
+
- [渐进加载](#progressive-loading)
|
|
18
|
+
- [理解实现](#understand-the-implementation)
|
|
19
|
+
- [进一步探索](#further-exploration)
|
|
19
20
|
- [模型体验](#model-experience)
|
|
20
|
-
- [
|
|
21
|
+
- [已知限制与后续工作](#known-limitations-and-deferred-work)
|
|
21
22
|
- [开发备注](#dev-note)
|
|
22
23
|
|
|
23
24
|
-----
|
|
@@ -25,80 +26,110 @@ Agent(智能体)可以加载 Word、PowerPoint 和 Excel 工作流,默认
|
|
|
25
26
|
<a id="use-this-package"></a>
|
|
26
27
|
## 使用本包
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
### 最小配置
|
|
29
|
+
在技能注册表和 `dsh-tool-skill` 旁挂载技能提供者。提供者负责技能说明;操作技能在加载时附加解释器和 CLI 的绝对路径。
|
|
31
30
|
|
|
32
31
|
```yaml
|
|
33
32
|
- name: '@deepseek-ai/dsh-skill-office'
|
|
33
|
+
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
| 字段 | 默认值 | 含义 |
|
|
37
37
|
|---|---|---|
|
|
38
|
-
| `assetRoot` |
|
|
39
|
-
| `node` |
|
|
40
|
-
| `cli` | 已安装
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
| `assetRoot` | 载体资源或包内 `assets/` | 包含七个技能目录的绝对路径。 |
|
|
39
|
+
| `node` | 宿主 Node.js | 操作技能使用的独立 Node 可执行程序;打包载体显式提供。 |
|
|
40
|
+
| `cli` | 已安装 Kit CLI | CLI 入口的绝对路径;`false` 禁用操作执行。 |
|
|
41
|
+
|
|
42
|
+
无效技能资源会阻止注册。创作说明不解析渲染器路径;操作说明加载时校验现有解释器和 CLI 文件,不准备引擎。Bash 或 PowerShell 负责审批、沙箱、取消和输出管理。资源与执行工具必须位于同一主机。
|
|
43
|
+
|
|
44
|
+
<a id="progressive-loading"></a>
|
|
45
|
+
### 渐进加载
|
|
46
|
+
|
|
47
|
+
模型依据任务选择技能;宿主不会根据文件扩展名注入完整说明。七个技能均列入目录,可通过 `skill` 按名称加载或由用户显式调用。用户要求的外观修改,包括电子表格单元格填充和文本对齐,即使没有明确要求视觉检查,也需要渲染;用户明确要求跳过渲染时以该要求为准。纯操作任务可以直接进入对应技能。
|
|
48
|
+
|
|
49
|
+
```mermaid
|
|
50
|
+
flowchart TD
|
|
51
|
+
A["Initial catalog: seven Office summaries"]
|
|
52
|
+
A -->|Non-Office task| N["Continue without loading Office instructions"]
|
|
53
|
+
A -->|Office task| B["skill(office): task routing"]
|
|
54
|
+
|
|
55
|
+
B -->|Create or edit| C["skill(office-docx / office-pptx / office-xlsx)"]
|
|
56
|
+
C --> D["Author and validate content, structure and data"]
|
|
57
|
+
D -->|No additional operation needed| F["Deliver"]
|
|
58
|
+
|
|
59
|
+
B -->|Images, visual inspection, or appearance changes| R["skill(office-render)"]
|
|
60
|
+
D -->|Images, visual inspection, or appearance changes| R
|
|
61
|
+
R --> RC["Shell: render into a private workspace temporary directory"]
|
|
62
|
+
RC -->|Usable preview| V["Inspect requested images"]
|
|
63
|
+
V --> F
|
|
64
|
+
RC -->|Failed or blank preview| L["Report content checks and unfinished visual inspection separately"]
|
|
65
|
+
L --> F
|
|
66
|
+
|
|
67
|
+
B -->|Formula recalculation| X["skill(office-recalculate)"]
|
|
68
|
+
D -->|Formula recalculation| X
|
|
69
|
+
X --> XC["Shell: CLI recalculate and verify values"]
|
|
70
|
+
XC --> F
|
|
71
|
+
|
|
72
|
+
B -->|PDF export| P["skill(office-export-pdf)"]
|
|
73
|
+
D -->|PDF export| P
|
|
74
|
+
P --> PC["Shell: CLI convert"]
|
|
75
|
+
PC --> F
|
|
76
|
+
```
|
|
49
77
|
|
|
50
|
-
|
|
78
|
+
新建文件以及内容、数据或公式修改,不会仅因附带基础格式调整而需要渲染。预览图片保存在工作区内随机命名的私有临时目录中,并在检查后删除。操作结束后,已加载的说明仍属于会话历史。图中描述各端常规预设,不改变 minimal 或 sdk-minimal 的纯 shell 定义。
|
|
51
79
|
|
|
52
80
|
-----
|
|
53
81
|
|
|
54
82
|
<a id="understand-the-implementation"></a>
|
|
55
|
-
##
|
|
83
|
+
## 理解实现
|
|
56
84
|
|
|
57
85
|
<details>
|
|
58
|
-
<summary
|
|
86
|
+
<summary>实现细节 — 点击展开</summary>
|
|
59
87
|
|
|
60
|
-
|
|
88
|
+
提供者在注册时读取摘要,按需读取正文。注册表优先级、作用域和调用权限适用于全部技能。加载结果包含用于解析相对引用的资源目录。卸载提供者会移除全部候选技能。
|
|
89
|
+
|
|
90
|
+
提供者不注册工具或激活投影。通用技能加载器记录已加载技能的结果。PTC 程序需输出或返回完整的 `tools.skill(...)` 结果,使说明进入模型上下文。恢复和 fork 使用现有会话历史及工具声明更新机制。解释器和 CLI 路径仅在加载操作技能时解析。
|
|
61
91
|
|
|
62
92
|
| 文件 | 职责 |
|
|
63
93
|
|---|---|
|
|
64
|
-
| [`src/index.ts`](src/index.ts) |
|
|
65
|
-
|
|
94
|
+
| [`src/index.ts`](src/index.ts) | 注册资源并返回原始技能正文。 |
|
|
95
|
+
|
|
96
|
+
| [`assets/`](assets/) | 路由与六个子技能。 |
|
|
66
97
|
|
|
67
98
|
</details>
|
|
68
99
|
|
|
69
100
|
-----
|
|
70
101
|
|
|
71
102
|
<a id="further-exploration"></a>
|
|
72
|
-
##
|
|
103
|
+
## 进一步探索
|
|
73
104
|
|
|
74
|
-
- [
|
|
75
|
-
- [
|
|
76
|
-
- [文件交付](../../deliverables/tool-present/README.zh.md)
|
|
105
|
+
- [技能注册表](../skill/README.zh.md) — 发现和优先级。
|
|
106
|
+
- [技能工具](../tool-skill/README.zh.md) — 模型目录和加载正文。
|
|
107
|
+
- [文件交付](../../deliverables/tool-present/README.zh.md) — 当前源文件路径交付。
|
|
77
108
|
|
|
78
109
|
-----
|
|
79
110
|
|
|
80
111
|
<a id="model-experience"></a>
|
|
81
112
|
## 模型体验
|
|
82
113
|
|
|
83
|
-
|
|
114
|
+
间接影响:`dsh-tool-skill` 渲染初始 Office 摘要,并返回 `skill(name)` 选择的路由或子技能说明。
|
|
84
115
|
|
|
85
|
-
#### KV
|
|
116
|
+
#### KV Cache 影响
|
|
86
117
|
|
|
87
|
-
|
|
118
|
+
技能结果追加到历史中且不改变工具声明,操作完成后已加载文本或图片仍保留在历史中。字符估算采用 `ceil(字符数 / 4)`:摘要最多 50 token,路由正文最多 200 token;创作正文比合并后的创作与操作指南至少减少 35%。技能包装、通用目录、shell 声明和 `load_workspace_dependencies` 单独计费;这些估算不等同于服务端 usage。
|
|
88
119
|
|
|
89
|
-
##
|
|
120
|
+
## 已知限制与后续工作
|
|
90
121
|
|
|
91
122
|
<a id="known-limitations-and-deferred-work"></a>
|
|
92
123
|
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
124
|
+
- 提供者不安装创作库。
|
|
125
|
+
- LibreOffice 输出可能不同于原生 Office 应用。
|
|
126
|
+
- 旧格式、加密和启用宏的文件需要适用的独立工作流。
|
|
96
127
|
|
|
97
128
|
<a id="dev-note"></a>
|
|
98
129
|
### 开发备注
|
|
99
130
|
|
|
100
131
|
<details>
|
|
101
|
-
<summary
|
|
132
|
+
<summary>维护者工作上下文 — 点击展开</summary>
|
|
102
133
|
|
|
103
134
|
无。
|
|
104
135
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: office
|
|
3
|
+
description: Create, edit, inspect, or convert Office documents, spreadsheets, presentations, and PDFs. Load to choose the relevant workflow.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Office task routing
|
|
7
|
+
|
|
8
|
+
Call the `skill` tool with the format skill name before creating, reading, or editing Office files. Standalone rendering, PDF export, and recalculation need only their operation skill.
|
|
9
|
+
|
|
10
|
+
- Create, read, or edit: `office-docx` (Word), `office-pptx` (PowerPoint), or `office-xlsx` (Excel).
|
|
11
|
+
- Images, visual inspection, or tasks primarily about layout, spacing, sizing, or alignment: `office-render`, even without an explicit visual-check request.
|
|
12
|
+
- Refresh spreadsheet formula results: `office-recalculate`.
|
|
13
|
+
- Export an Office file to PDF: `office-export-pdf`.
|
|
14
|
+
|
|
15
|
+
Honor requests to skip rendering. New files, content edits, formulas, and incidental formatting such as bold headers or number formats need no render unless visual inspection is requested. Do not reload skills.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Missing Office sidecar
|
|
2
|
+
|
|
3
|
+
These instructions apply in Web, Desktop, and SDK sessions. First check whether an existing LibreOffice Kit installation can serve the task; a missing `dsoffice` command on PATH alone does not establish that the sidecar is absent. If the required sidecar is missing and the user has not already authorized its download, use the available ask-question tool to ask whether they want to download the Office conversion engine. If that tool is unavailable, ask in the conversation. Wait for explicit agreement before downloading or installing; if the user declines, continue work that does not need the engine and report the unavailable operation.
|
|
4
|
+
|
|
5
|
+
After agreement, install `@deepseek-ai/libreoffice-kit` together with the matching engine package, using the engine version declared by the main package's optional dependencies. Use an available npm or pnpm in a task-local tools directory, with `--ignore-scripts`: the packages provide prebuilt binaries and do not need installation scripts. Keep the package manager's cache or store in that writable task directory too (for npm, pass `--cache <task-tools-directory>/.npm-cache`). Select the package for the operating system and architecture of the Node.js process that will run the CLI, including when Node runs under emulation:
|
|
6
|
+
|
|
7
|
+
| Runtime platform | Engine package |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Windows x64 | `@deepseek-ai/libreoffice-kit-win32-x64` |
|
|
10
|
+
| Windows ARM64 | `@deepseek-ai/libreoffice-kit-win32-arm64` |
|
|
11
|
+
| macOS Apple Silicon | `@deepseek-ai/libreoffice-kit-darwin-arm64` |
|
|
12
|
+
| macOS Intel | `@deepseek-ai/libreoffice-kit-darwin-x64` |
|
|
13
|
+
| No matching native package, including Linux | `@deepseek-ai/libreoffice-kit-wasm` |
|
|
14
|
+
|
|
15
|
+
Prefer the matching native package; otherwise use WASM when its package supports the current platform. Check package platform restrictions before installation rather than forcing an incompatible package. The published WASM package currently declares Linux support.
|
|
16
|
+
|
|
17
|
+
Reuse the bundled Node.js and pnpm paths from `load_workspace_dependencies` when available, or the user's configured Node.js and package manager. Permission to download the Office sidecar does not include installing Python, Node.js, or a package manager. If those prerequisites are missing, explain what is needed. Run the new installation's `dsoffice` entry, or its `lib/cli.js` with the selected Node executable, by path; do not assume installation changed the session's PATH. Check `dsoffice capabilities` before continuing the requested operation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: office-docx
|
|
3
|
-
description: Create, read, edit, and
|
|
3
|
+
description: Create, read, edit, and validate Word documents (.docx).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Word documents
|
|
@@ -9,9 +9,11 @@ Use `python-docx` for DOCX creation and ordinary edits. Follow an explicit user
|
|
|
9
9
|
|
|
10
10
|
Keep source scripts, intermediate files, and final documents in the task workspace; the runtime and this skill directory are read-only resources. Use the user's requested language and preserve an existing document's design unless a redesign is requested.
|
|
11
11
|
|
|
12
|
+
Name Python scripts for their task, such as `edit_docx.py`; avoid module names such as `inspect.py`, `docx.py`, or `zipfile.py` that shadow imported libraries.
|
|
13
|
+
|
|
12
14
|
## Create and edit
|
|
13
15
|
|
|
14
|
-
For
|
|
16
|
+
For text edits, locate the target by its text or position and inspect only its runs and formatting. A title can be an ordinary paragraph without a Title style or document-property title. Update the relevant runs; assigning paragraph `.text` destroys run formatting. Save a new file unless an in-place edit is requested. Read other structures only when the edit involves them; rebuilding a document can lose unsupported features.
|
|
15
17
|
|
|
16
18
|
Use paragraph styles for headings and body text. Size tables for the section that contains them, and account for merged cells and nested tables. Chinese, Japanese, and Korean text may need an explicit `w:eastAsia` font assignment in addition to `run.font.name`; font names alone do not establish glyph availability or rendered appearance.
|
|
17
19
|
|
|
@@ -27,31 +29,12 @@ document.add_paragraph("The requested findings go here.")
|
|
|
27
29
|
document.save("report.docx")
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
python-docx does not paginate or render documents. Do not represent ordinary replacement, colored text, or comments as tracked changes. When real revisions or unsupported OOXML features matter, preserve their package parts and verify the requested operation rather than silently discarding them.
|
|
32
|
+
python-docx does not paginate or render documents. Do not represent ordinary replacement, colored text, or comments as tracked changes. When real revisions or unsupported OOXML features matter, preserve their package parts and verify the requested operation rather than silently discarding them. When writing package parts directly, create a new ZIP and copy each unchanged member once; appending a replacement member duplicates its name and corrupts the package.
|
|
31
33
|
|
|
32
34
|
## Check and deliver
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```text
|
|
37
|
-
<python> <skill-directory>/../scripts/check_office.py <document.docx> --out <checks.json>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
It checks ZIP/XML integrity and internal relationships, and reports paragraphs, logical table dimensions, and sections. Optional `--contains TEXT` arguments assert required text. A successful structural check does not verify pagination, clipping, fonts, or visual appearance. Compare the summary and reopened document with the user's request, including unchanged content that matters to an edit.
|
|
41
|
-
|
|
42
|
-
Render for a requested image/PDF deliverable or an actionable layout check. Before generating images only for inspection, establish that the current model accepts images. If image input is unavailable, finish structural and content checks and state that visual layout was not inspected; do not generate unreadable previews, retry `read_image`, or choose a second model.
|
|
43
|
-
|
|
44
|
-
Use the bundled LibreOffice binaries unless the user explicitly asks not to use them. Without that explicit opt-out, never search for or invoke system LibreOffice, `soffice`, an application-installed binary, or a downloaded replacement. If the bundled CLI is unavailable or fails, report that failure instead of substituting another executable.
|
|
45
|
-
|
|
46
|
-
Use the `libreofficeKit.node` and `libreofficeKit.cli` absolute paths supplied in the loaded skill's **Installed LibreOffice Kit** section. Run Node with the CLI entry as its first argument, then `capabilities --json` once. For the bundled runtime, these paths work from the task directory; do not search PATH, guess a package directory, use `npx`, or install a renderer. Quote each path separately; PowerShell also requires the `&` invocation operator. The examples below use `<node>` and `<cli>` for those two returned paths. Direct rendering avoids an intermediate PDF:
|
|
47
|
-
|
|
48
|
-
```sh
|
|
49
|
-
"<node>" "<cli>" render --input report.docx --output-dir preview-v1 --pages 1,3 --dpi 144
|
|
50
|
-
"<node>" "<cli>" convert --input report.docx --output report.pdf
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Choose pages relevant to the requested edit; inspect all pages when whole-document layout is required. Batch selected pages in one call and use the returned manifest's page count, image paths, and missing-font diagnostics. Output directories and converted files must be new. Reuse images for the same saved document; render again only after a source change, for an unrendered page, or to diagnose a concrete failure. Check page breaks, clipped text, headings, and table widths. Do not repeatedly export unchanged documents or infer visual overflow solely from guessed text geometry.
|
|
36
|
+
Reopen the saved document with python-docx and verify the requested content, related formatting, and unchanged content that must be preserved. Deliver when these checks pass. Investigate package-level details only for a concrete failure, ambiguous edit target, unsupported feature, or explicit user requirement.
|
|
54
37
|
|
|
55
|
-
|
|
38
|
+
Creating a file or changing content with incidental basic formatting does not require additional operations. Load `office-render` for requested images or visual inspection. When styling, font color, spacing, sizing, or text alignment is the requested edit, load `office-render` and inspect the changed page even without a separate visual-check request, unless the user explicitly skips rendering. For PDF output load `office-export-pdf`.
|
|
56
39
|
|
|
57
40
|
Call `present({"files":[{"path":"report.docx"}]})` with the actual final DOCX path. It exposes the current source file without copying or preserving its bytes, so keep that file in place and do not present temporary QA reports unless requested. If `present` is unavailable, provide the final workspace path using the session's supported file delivery method.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: office-export-pdf
|
|
3
|
+
description: Convert an Office document to a requested PDF with LibreOffice Kit.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Export an Office document to PDF
|
|
7
|
+
|
|
8
|
+
Use the installed LibreOffice Kit CLI through the shell with the absolute Node and CLI paths supplied below. In Bash, invoke `"<node>" "<cli>"`; in PowerShell, invoke `& "<node>" "<cli>"`. Prefer bundled LibreOffice unless the user requests another installation. If the Office sidecar is missing, follow [sidecar setup](../office/references/sidecar.md): ask whether the user wants to download it and install only after they agree. Do not invoke system soffice, use npx, or install a replacement to work around a failed conversion with an installed sidecar.
|
|
9
|
+
|
|
10
|
+
Convert the final saved Office file to a new PDF in the task workspace. Preserve the source. Quote each input and output path.
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
"<node>" "<cli>" convert --input <final-office-file> --output <new-file.pdf>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Check the command result, nonempty output, and `%PDF-` file signature. If a PDF reader library is already available, compare page count and extractable text with the requested content. If it is unavailable, report the extent of validation; do not generate images or install extra tools to validate a conversion. Spreadsheet PDF pages follow workbook print settings; change those settings only when requested. Conversion may differ from the native application's pagination or fonts.
|
|
17
|
+
|
|
18
|
+
If export fails, preserve the usable source and report the export limitation. Do not repeatedly convert an unchanged source or use conversion to investigate a failed preview. For PDF export alone, stop after these checks. Only a request for images or visual inspection calls for `office-render`; load it before that separate operation. Deliver the requested PDF through `present` when available, keeping the file in place.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: office-pptx
|
|
3
|
-
description: Create, read, edit, and
|
|
3
|
+
description: Create, read, edit, and validate PowerPoint presentations (.pptx).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# PowerPoint presentations
|
|
@@ -9,6 +9,8 @@ Follow an explicit user or applicable AGENTS.md requirement for an environment o
|
|
|
9
9
|
|
|
10
10
|
Keep scripts and output files in the task workspace. The runtime and skill directory contain shared read-only resources. Match the requested slide language and the supplied presentation's design when editing it.
|
|
11
11
|
|
|
12
|
+
Name Python scripts for their task, such as `edit_pptx.py`; avoid module names such as `inspect.py`, `pptx.py`, or `zipfile.py` that shadow imported libraries.
|
|
13
|
+
|
|
12
14
|
## Create and edit
|
|
13
15
|
|
|
14
16
|
Use `python-pptx` to inspect or modify an existing presentation. Inspect slide layouts, text runs, images, tables, and charts before editing. Change only the requested content, preserve mixed text formatting, and save to a new file unless the user requests an in-place edit. Rebuilding slides can discard unsupported animation, SmartArt, or other extension content.
|
|
@@ -51,31 +53,12 @@ for shape in presentation.slides[0].shapes:
|
|
|
51
53
|
presentation.save("report-edited.pptx")
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
Check that text and images fit the slide dimensions, titles form a useful sequence, and chart labels agree with source values. A native chart's embedded workbook is part of the deliverable and must contain the intended data. Library support for writing PPTX is not a rendering engine or a guarantee that every PowerPoint feature survives editing.
|
|
56
|
+
Check that text and images fit the slide dimensions, titles form a useful sequence, and chart labels agree with source values. Paragraph alignment does not move a text box: when a title must be centered on the slide, center both its paragraph and the text box against `presentation.slide_width`, then reopen to verify both. A native chart's embedded workbook is part of the deliverable and must contain the intended data. Library support for writing PPTX is not a rendering engine or a guarantee that every PowerPoint feature survives editing.
|
|
55
57
|
|
|
56
58
|
## Check and deliver
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
```text
|
|
61
|
-
<python> <skill-directory>/../scripts/check_office.py <presentation.pptx> --out <checks.json>
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
It checks ZIP/XML integrity and internal relationships and reports slide count and extracted text. Use repeated `--contains TEXT` arguments for required slide text and `--count N` for a requested slide count; `--contains` excludes chart text and speaker notes. Reopen the file to check the requested edits, chart data, and notes. Structural success does not establish text fit, alignment, readable contrast, or rendering fidelity.
|
|
65
|
-
|
|
66
|
-
Render for requested images/PDFs or an actionable layout check. Before generating images only for inspection, establish that the current model accepts images. If it does not, finish structural and content checks and state that visual layout was not inspected; do not rasterize slides the model cannot read, retry `read_image`, or choose a second model.
|
|
67
|
-
|
|
68
|
-
Use the bundled LibreOffice binaries unless the user explicitly asks not to use them. Without that explicit opt-out, never search for or invoke system LibreOffice, `soffice`, an application-installed binary, or a downloaded replacement. If the bundled CLI is unavailable or fails, report that failure instead of substituting another executable.
|
|
69
|
-
|
|
70
|
-
Use the `libreofficeKit.node` and `libreofficeKit.cli` absolute paths supplied in the loaded skill's **Installed LibreOffice Kit** section. Run Node with the CLI entry as its first argument, then `capabilities --json` once. For the bundled runtime, these paths work from the task directory; do not search PATH, guess a package directory, use `npx`, or install a renderer. Quote each path separately; PowerShell also requires the `&` invocation operator. The examples below use `<node>` and `<cli>` for those two returned paths. Render slides directly:
|
|
71
|
-
|
|
72
|
-
```sh
|
|
73
|
-
"<node>" "<cli>" render --input report.pptx --output-dir preview-v1 --pages 1,3,4 --dpi 144
|
|
74
|
-
"<node>" "<cli>" convert --input report.pptx --output report.pdf
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Batch affected slides in one call; inspect every slide for a new deck or a whole-deck design review. Read the returned manifest and selected image paths, including missing-font diagnostics. Outputs must be new directories/files. Retain and reuse previews for the same saved source; rerender only after edits, for missing slides, or to diagnose a concrete failure. Check clipping, alignment, contrast, and chart labels. Do not substitute guessed geometry for visual evidence or repeat successful exports of unchanged slides.
|
|
60
|
+
Reopen the saved presentation with python-pptx to verify the requested slide count, content, formatting, chart data, and notes, including unchanged content that must be preserved. These checks do not establish text fit, readable contrast, or rendering fidelity.
|
|
78
61
|
|
|
79
|
-
|
|
62
|
+
Deliver after the requested edits are verified. Creating a file or changing content with incidental basic formatting does not require additional operations. Load `office-render` for requested images or visual inspection and for appearance-focused styling, spacing, sizing, or alignment changes unless the user explicitly skips rendering. For PDF output load `office-export-pdf`.
|
|
80
63
|
|
|
81
64
|
Call `present({"files":[{"path":"report-edited.pptx"}]})` with the actual final PPTX path. It exposes the current source file without copying or preserving its bytes, so keep that file in place and leave intermediate images and reports out of delivery unless requested. If `present` is unavailable, use the session's supported file delivery method.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: office-recalculate
|
|
3
|
+
description: Refresh spreadsheet formula caches and verify calculated values with LibreOffice Kit.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Recalculate spreadsheet formulas
|
|
7
|
+
|
|
8
|
+
Use the installed LibreOffice Kit CLI through the shell with the absolute Node and CLI paths supplied below. In Bash, invoke `"<node>" "<cli>"`; in PowerShell, invoke `& "<node>" "<cli>"`. Prefer bundled LibreOffice unless the user requests another installation. If the Office sidecar is missing, follow [sidecar setup](../office/references/sidecar.md): ask whether the user wants to download it and install only after they agree. Do not invoke system soffice, use npx, or install a replacement to work around a failed recalculation with an installed sidecar.
|
|
9
|
+
|
|
10
|
+
Keep the original workbook and write a new file in the task workspace. Quote each input and output path.
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
"<node>" "<cli>" recalculate --input <workbook.xlsx> --output <recalculated.xlsx>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Verify the saved result with openpyxl, using `load_workspace_dependencies` for the Python path unless a project environment is required. Open it with both `data_only=False` and `data_only=True`. Check that formulas survive and cached results match the requested logic, expected totals, and cell types. Check required unchanged sheets, styles, and data. Recalculation does not validate business logic or guarantee Excel-specific feature compatibility.
|
|
17
|
+
|
|
18
|
+
If no supported recalculation engine is available or the command fails, preserve the workbook and explain that calculated values could not be refreshed. Do not replace requested formulas with constants or present stale/absent caches as fresh results. Deliver the verified output through `present` when available, keeping the file in place.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: office-render
|
|
3
|
+
description: Render requested document images or inspect visual layout with LibreOffice Kit.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Document images and visual inspection
|
|
7
|
+
|
|
8
|
+
Use the installed LibreOffice Kit CLI through the shell with the absolute Node and CLI paths supplied below. In Bash, invoke `"<node>" "<cli>"`; in PowerShell, invoke `& "<node>" "<cli>"`. Prefer bundled LibreOffice unless the user requests another installation. If the Office sidecar is missing, follow [sidecar setup](../office/references/sidecar.md): ask whether the user wants to download it and install only after they agree. Do not invoke system soffice, use npx, install a replacement renderer, or build an alternative rendering pipeline to work around a failed render with an installed sidecar.
|
|
9
|
+
|
|
10
|
+
Before generating images only for inspection, establish that the current model accepts images. Otherwise finish the applicable content and structure checks and report the requested visual check as unavailable. Do not generate unreadable previews, retry image reads, or choose a second model. Requested image exports can still be delivered.
|
|
11
|
+
|
|
12
|
+
## Render the saved deliverable
|
|
13
|
+
|
|
14
|
+
Keep the saved deliverable in the task workspace. For requested image exports, use the requested output directory and retain the images. For inspection-only previews, create a private, uniquely named temporary parent inside that workspace and pass a child path that does not yet exist as `--output-dir`; the CLI creates that child. On POSIX, use `preview_root=$(mktemp -d "./.dsh-office.XXXXXXXX")` (owner-only permissions) and `--output-dir "$preview_root/render"`. On PowerShell, create a parent named `.dsh-office-` plus `[guid]::NewGuid().ToString('N')` under the task workspace and pass an uncreated `render` child; it inherits the workspace ACL. Quote each input and output path. Retain the parent until image inspection finishes, then remove only that parent, including after a failed render. Never use a fixed directory name or remove the workspace itself.
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
"<node>" "<cli>" render --input <final-file> --output-dir <new-directory> --pages "1,3,4"
|
|
18
|
+
"<node>" "<cli>" render --input <final-workbook.xlsx> --output-dir <new-directory> --sheet <exact-name> --range <A1-range>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For DOCX, PPTX, or PDF, batch requested pages/slides in one call. In PowerShell, quote comma-separated `--pages` values so they remain one argument. Inspect all pages only for a requested whole-document review. For worksheets, choose the exact affected range, including chart/drawing regions; worksheet numbers are not PDF page numbers. Inspect the final saved file, reuse existing images for an unchanged source, and rerender only after requested source changes or for previously unrendered regions. Change print areas or scaling only when the user requests print layout.
|
|
22
|
+
|
|
23
|
+
Read the returned JSON manifest for image paths, page counts, fragment rectangles, and missing-font diagnostics. Use the session's image-reading tool to inspect the requested images. Check clipping, alignment, headings, table widths, contrast, chart labels, and number formats as relevant to the task. A successful command or image read alone does not establish a usable preview; guessed text geometry does not establish overflow.
|
|
24
|
+
|
|
25
|
+
## Failed or blank previews
|
|
26
|
+
|
|
27
|
+
For populated content, a fully transparent or uniformly blank image is a renderer failure. If a pixel check is needed, composite RGBA onto white first; raw RGB under transparent pixels can look nonblank. Check once and do not reread the same image. Stop visual checks for the task immediately after a blank result. This overrides the rerender-after-edit guidance above: do not render or read images again, even after editing or copying the source, or try another range, page, sheet, DPI, output format, diagnostic export, or print setting. Preserve the usable source and requested outputs and finish applicable content/data checks.
|
|
28
|
+
|
|
29
|
+
If content and structure checks pass, name the saved file and verified edits as needed, then include exactly one visual-status sentence: "Content and structure checks passed; visual inspection was not completed." in English, or "内容与结构检查通过;视觉检查未完成。" in Chinese. Do not add a render or preview bullet, explain why inspection stopped, or describe the image or pixels. Report content or structure failures separately; never say all checks passed or claim visual verification.
|
|
30
|
+
|
|
31
|
+
LibreOffice output can differ from Word, PowerPoint, or Excel. Images do not certify animation/media playback, refresh formula caches in the source workbook, or guarantee native application fidelity. Deliver requested files through `present` when available; keep the files in place. Do not deliver inspection images or reports unless requested.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: office-xlsx
|
|
3
|
-
description: Read, create, and
|
|
3
|
+
description: Read, create, edit, and validate Excel workbooks, data, and formulas (.xlsx).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Excel workbooks
|
|
@@ -9,6 +9,8 @@ Follow an explicit user or applicable AGENTS.md requirement for an environment o
|
|
|
9
9
|
|
|
10
10
|
Keep scripts, working files, and outputs in the task workspace. The runtime and skill directory are shared read-only resources. Save to a new workbook unless the user requests an in-place edit.
|
|
11
11
|
|
|
12
|
+
Name Python scripts for their task, such as `edit_xlsx.py`; avoid module names such as `inspect.py`, `openpyxl.py`, or `pandas.py` that shadow imported libraries.
|
|
13
|
+
|
|
12
14
|
## Read and modify
|
|
13
15
|
|
|
14
16
|
Use `openpyxl` for existing XLSX workbooks and targeted cell or style changes. Load with `data_only=False` when formulas must survive. Inspect sheet names, the affected cell types, formulas, styles, merged ranges, and referenced ranges before editing. Check the saved file by reopening it, including unchanged content that the request requires preserving.
|
|
@@ -35,40 +37,16 @@ workbook.save("report.xlsx")
|
|
|
35
37
|
|
|
36
38
|
Preserve numbers, dates, booleans, and identifiers as the intended cell types; formatting is not a type conversion. For modifications, load the existing file with `openpyxl.load_workbook("input.xlsx", data_only=False)`, change the requested ranges, and save a separate result. Avoid a DataFrame round trip when workbook features must survive.
|
|
37
39
|
|
|
38
|
-
Writing a formula does not calculate its result. openpyxl and XlsxWriter do not evaluate
|
|
40
|
+
Writing a formula does not calculate its result. openpyxl and XlsxWriter do not evaluate formulas; `data_only=True` returns cached values that may be absent or stale. When calculated results must be refreshed, load `office-recalculate`. Do not replace requested formulas with constants or report stale values as newly calculated results.
|
|
39
41
|
|
|
40
42
|
Do not rename `.xls`, `.xlsb`, encrypted files, or macro-enabled files to `.xlsx`. They need an appropriate supported operation. `keep_vba=True` can preserve VBA package content in an XLSM workflow, but does not execute or edit macros and does not guarantee every advanced workbook feature survives. Preserve the original and verify such requirements explicitly.
|
|
41
43
|
|
|
42
44
|
## Check and deliver
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```text
|
|
47
|
-
<python> <skill-directory>/../scripts/check_office.py <workbook.xlsx> --out <checks.json>
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
It checks ZIP/XML integrity and internal relationships, and reports sheet names, populated-cell counts, and formula counts. Repeated `--contains TEXT` arguments check string cells and sheet names, and `--count N` checks sheet count. `--contains` excludes numeric cells and does not validate formula results; verify those separately with openpyxl. The checker does not calculate formulas or judge workbook appearance. Compare relevant values, types, formulas, styles, and totals with the task's source data.
|
|
51
|
-
|
|
52
|
-
For data and formula tasks, deliver after structural and data checks pass. Skip preview rendering and visual inspection, and do not add an unrequested caveat that visual inspection was omitted. Basic styling such as bold headers or number formats does not itself require visual inspection. Formula recalculation is independent of rendering and remains necessary when calculated results must be refreshed.
|
|
53
|
-
|
|
54
|
-
Inspect relevant regions when the task concerns formatting, layout, chart appearance, print layout, or a known display problem. Before generating inspection images, establish that the current model accepts images. If it does not, finish structural and data checks and report the requested visual check as unavailable; do not rasterize unreadable previews, retry `read_image`, or choose a second model. Export requested images or PDFs even when visual inspection is unnecessary.
|
|
55
|
-
|
|
56
|
-
A successful command or image read does not prove that a preview contains workbook content. For a populated range, a fully transparent or uniformly blank image is a renderer failure, not evidence that the workbook needs fixing. Stop all visual QA for that workbook immediately; do not try a wider range, another sheet, a different DPI, or another output format. Check suspicious previews with the bundled Pillow library, compositing RGBA onto white before comparing pixels. Do not create diagnostic workbooks, repeatedly retry rendering, modify print settings, or convert to PDF solely to investigate a failed preview. Preserve the requested workbook changes, finish structural and data checks, and report the visual check as unavailable. Only change print areas or scaling when the user requests printed layout.
|
|
57
|
-
|
|
58
|
-
## LibreOffice operations
|
|
59
|
-
|
|
60
|
-
Use the bundled LibreOffice binaries unless the user explicitly asks not to use them. Without that explicit opt-out, never search for or invoke system LibreOffice, `soffice`, an application-installed binary, or a downloaded replacement. If the bundled CLI is unavailable or fails, report that failure instead of substituting another executable.
|
|
61
|
-
|
|
62
|
-
When recalculation, a requested export, or visual inspection is needed, use the `libreofficeKit.node` and `libreofficeKit.cli` absolute paths supplied in the loaded skill's **Installed LibreOffice Kit** section. Run Node with the CLI entry as its first argument, then `capabilities --json` once. For the bundled runtime, these paths work from the task directory; do not search PATH, guess a package directory, use `npx`, or install a renderer. Quote each path separately; PowerShell also requires the `&` invocation operator. The examples below use `<node>` and `<cli>` for those two returned paths. For worksheet rendering, select an exact sheet name and the relevant cell range:
|
|
63
|
-
|
|
64
|
-
```sh
|
|
65
|
-
"<node>" "<cli>" render --input report.xlsx --output-dir preview-v1 --sheet Summary --range A1:D20 --dpi 144
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
For a user-requested PDF export, use `"<node>" "<cli>" convert --input report.xlsx --output report.pdf`. PDF conversion is not a fallback for failed visual QA.
|
|
46
|
+
Reopen the saved workbook with openpyxl and verify the requested sheet names, values, types, formulas, styles, and totals against the source data, including unchanged content that must be preserved. These checks do not calculate formulas or judge workbook appearance.
|
|
69
47
|
|
|
70
|
-
|
|
48
|
+
Load `office-render` for requested images or visual inspection and whenever the user's primary task targets appearance, including cell fill colors, text alignment, spacing, or sizing. These appearance changes require rendering even without a separate visual-check request, unless the user explicitly skips it. New workbooks and data or formula edits do not require rendering solely because they include incidental basic formatting. In particular, bold headers and number formats accompanying data edits do not require loading `office-render` or running its commands.
|
|
71
49
|
|
|
72
|
-
|
|
50
|
+
Load `office-recalculate` when refreshed calculated values are needed and `office-export-pdf` for PDF output. Deliver after the requested data checks pass and the required operations finish.
|
|
73
51
|
|
|
74
52
|
Call `present({"files":[{"path":"report.xlsx"}]})` with the actual final workbook path. It exposes the current source file without copying or preserving its bytes, so keep that file in place and omit intermediate scripts and reports unless requested. If `present` is unavailable, use the session's supported file delivery method.
|
package/lib/index.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, isAbsolute, join } from "node:path";
|
|
3
|
+
import { dirname, isAbsolute, join, sep } from "node:path";
|
|
4
4
|
import { isSea } from "node:sea";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import z from "@deepseek-ai/schemastery";
|
|
7
7
|
import { BUNDLED_SKILL_RANK } from "@deepseek-ai/dsh-skill";
|
|
8
8
|
import { parse } from "yaml";
|
|
9
9
|
//#region lib/types/index.js
|
|
10
|
-
/** Bundled Office workflows and filesystem resources for document authoring and
|
|
10
|
+
/** Bundled Office workflows and filesystem resources for document authoring and operations. */
|
|
11
11
|
const SKILL_NAMES = [
|
|
12
|
+
"office",
|
|
12
13
|
"office-docx",
|
|
13
14
|
"office-pptx",
|
|
14
|
-
"office-xlsx"
|
|
15
|
+
"office-xlsx",
|
|
16
|
+
"office-render",
|
|
17
|
+
"office-recalculate",
|
|
18
|
+
"office-export-pdf"
|
|
15
19
|
];
|
|
16
20
|
/** Validated resource configuration. */
|
|
17
21
|
const Config = z.object({
|
|
@@ -54,8 +58,6 @@ function officeRuntime(config) {
|
|
|
54
58
|
function apply(ctx, config = {}) {
|
|
55
59
|
const assetRoot = config.assetRoot ?? fileURLToPath(new URL("../assets/", import.meta.url));
|
|
56
60
|
if (!isAbsolute(assetRoot)) throw new Error("skill-office: assetRoot must be an absolute directory");
|
|
57
|
-
if (!statSync(join(assetRoot, "scripts", "check_office.py")).isFile()) throw new Error("skill-office: assets must contain scripts/check_office.py");
|
|
58
|
-
const runtime = officeRuntime(config);
|
|
59
61
|
const candidates = SKILL_NAMES.map((skillName) => {
|
|
60
62
|
const directory = join(assetRoot, skillName);
|
|
61
63
|
const path = join(directory, "SKILL.md");
|
|
@@ -72,7 +74,7 @@ function apply(ctx, config = {}) {
|
|
|
72
74
|
rank: BUNDLED_SKILL_RANK,
|
|
73
75
|
resourceBase: {
|
|
74
76
|
kind: "directory",
|
|
75
|
-
path: directory
|
|
77
|
+
path: directory.split(sep).join("/")
|
|
76
78
|
},
|
|
77
79
|
locator: path
|
|
78
80
|
};
|
|
@@ -82,13 +84,17 @@ function apply(ctx, config = {}) {
|
|
|
82
84
|
list: () => Promise.resolve(candidates),
|
|
83
85
|
async get(candidate, options) {
|
|
84
86
|
const { rank: _rank, locator, ...summary } = candidate;
|
|
85
|
-
const
|
|
87
|
+
const content = parseSkill(await readFile(locator, {
|
|
86
88
|
encoding: "utf8",
|
|
87
89
|
signal: options.signal
|
|
88
|
-
})
|
|
90
|
+
}), locator).content + ([
|
|
91
|
+
"office-render",
|
|
92
|
+
"office-recalculate",
|
|
93
|
+
"office-export-pdf"
|
|
94
|
+
].includes(candidate.name) ? officeRuntime(config) : "");
|
|
89
95
|
return {
|
|
90
96
|
...summary,
|
|
91
|
-
content
|
|
97
|
+
content
|
|
92
98
|
};
|
|
93
99
|
}
|
|
94
100
|
};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/** Bundled Office workflows and filesystem resources for document authoring and
|
|
1
|
+
/** Bundled Office workflows and filesystem resources for document authoring and operations. */
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import z from '@deepseek-ai/schemastery';
|
|
4
4
|
/** Office skill resource location. */
|
|
5
5
|
export interface Config {
|
|
6
|
-
/** Absolute assets directory containing
|
|
6
|
+
/** Absolute assets directory containing skill folders; defaults to packaged assets. */
|
|
7
7
|
assetRoot?: string;
|
|
8
8
|
/** Standalone Node executable; defaults to the current executable outside Electron and SEA. */
|
|
9
9
|
node?: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-skill-office",
|
|
3
3
|
"description": "Bundled Word, PowerPoint, and Excel workflows and structural checks",
|
|
4
|
-
"version": "0.2.1-alpha.
|
|
4
|
+
"version": "0.2.1-alpha.3",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,18 +27,18 @@
|
|
|
27
27
|
],
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@deepseek-ai/dsh-skill": "0.2.1-alpha.
|
|
31
|
-
"@deepseek-ai/cordis": "~4.0.5-alpha.
|
|
30
|
+
"@deepseek-ai/dsh-skill": "0.2.1-alpha.3",
|
|
31
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.2"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@deepseek-ai/dsh-skill": "0.2.1-alpha.
|
|
35
|
-
"@deepseek-ai/cordis": "~4.0.5-alpha.
|
|
36
|
-
"@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.
|
|
37
|
-
"@deepseek-ai/cordis-plugin-include": "~1.0.10-alpha.
|
|
34
|
+
"@deepseek-ai/dsh-skill": "0.2.1-alpha.3",
|
|
35
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.2",
|
|
36
|
+
"@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.2",
|
|
37
|
+
"@deepseek-ai/cordis-plugin-include": "~1.0.10-alpha.2"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@deepseek-ai/libreoffice-kit": "^0.1.5",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
41
|
+
"@deepseek-ai/schemastery": "~3.18.5-alpha.2",
|
|
42
|
+
"yaml": "^2.4.2"
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Read-only OOXML checks and structural summaries; no rendering or formula evaluation.
|
|
3
|
-
|
|
4
|
-
Run with INPUT.docx, INPUT.pptx, or INPUT.xlsx. Optional --contains assertions
|
|
5
|
-
check extracted text; DOCX excludes comments, glossary text, and unreferenced parts or notes.
|
|
6
|
-
--count checks slides or sheets. JSON escapes non-ASCII
|
|
7
|
-
characters and goes to stdout and optionally --out. Exit 0 means the requested
|
|
8
|
-
structural checks passed, 1 means a document, assertion, or report write failed, and 2 means
|
|
9
|
-
invalid command-line arguments.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import argparse
|
|
15
|
-
import json
|
|
16
|
-
import posixpath
|
|
17
|
-
import sys
|
|
18
|
-
import zipfile
|
|
19
|
-
import zlib
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
from urllib.parse import unquote, urlsplit
|
|
22
|
-
from xml.etree import ElementTree as ET
|
|
23
|
-
|
|
24
|
-
W_NAMESPACES = (
|
|
25
|
-
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
26
|
-
"http://purl.oclc.org/ooxml/wordprocessingml/main",
|
|
27
|
-
)
|
|
28
|
-
A_NAMESPACES = (
|
|
29
|
-
"http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
30
|
-
"http://purl.oclc.org/ooxml/drawingml/main",
|
|
31
|
-
)
|
|
32
|
-
P_NAMESPACES = (
|
|
33
|
-
"http://schemas.openxmlformats.org/presentationml/2006/main",
|
|
34
|
-
"http://purl.oclc.org/ooxml/presentationml/main",
|
|
35
|
-
)
|
|
36
|
-
S_NAMESPACES = (
|
|
37
|
-
"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
|
38
|
-
"http://purl.oclc.org/ooxml/spreadsheetml/main",
|
|
39
|
-
)
|
|
40
|
-
R_NAMESPACES = (
|
|
41
|
-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
42
|
-
"http://purl.oclc.org/ooxml/officeDocument/relationships",
|
|
43
|
-
)
|
|
44
|
-
MAIN_PARTS = {
|
|
45
|
-
".docx": ("word/document.xml", "wordprocessingml.document.main+xml"),
|
|
46
|
-
".pptx": ("ppt/presentation.xml", "presentationml.presentation.main+xml"),
|
|
47
|
-
".xlsx": ("xl/workbook.xml", "spreadsheetml.sheet.main+xml"),
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def namespace(root: ET.Element, supported: tuple[str, ...], part: str) -> str:
|
|
52
|
-
"""Return the main XML namespace after checking its OOXML variant."""
|
|
53
|
-
uri = root.tag[1:].split("}", 1)[0] if root.tag.startswith("{") else ""
|
|
54
|
-
if uri not in supported:
|
|
55
|
-
raise ValueError(f"{part} uses unsupported XML namespace: {uri or '(none)'}")
|
|
56
|
-
return "{" + uri + "}"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def relationship_id(node: ET.Element, part: str) -> str:
|
|
60
|
-
"""Read an office-document relationship id from Transitional or Strict OOXML."""
|
|
61
|
-
for uri in R_NAMESPACES:
|
|
62
|
-
value = node.get("{" + uri + "}id")
|
|
63
|
-
if value is not None:
|
|
64
|
-
return value
|
|
65
|
-
raise ValueError(f"{part} has a reference without a relationship id")
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def relationship_types(kind: str) -> set[str]:
|
|
69
|
-
"""Return the Transitional and Strict relationship type names for one role."""
|
|
70
|
-
return {f"{uri}/{kind}" for uri in R_NAMESPACES}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def iter_namespaces(root: ET.Element, namespaces: tuple[str, ...], local_name: str):
|
|
74
|
-
"""Iterate matching elements across Transitional and Strict namespaces."""
|
|
75
|
-
for uri in namespaces:
|
|
76
|
-
yield from root.iter("{" + uri + "}" + local_name)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def relationship_target(part: str, target: str) -> str:
|
|
80
|
-
"""Resolve a package relationship without fetching external resources."""
|
|
81
|
-
path = unquote(urlsplit(target).path)
|
|
82
|
-
return posixpath.normpath(path.lstrip("/") if path.startswith("/") else posixpath.join(posixpath.dirname(part), path))
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def relationships(part: str, xml: dict[str, ET.Element], types: set[str] | None = None) -> dict[str, str]:
|
|
86
|
-
path = posixpath.join(posixpath.dirname(part), "_rels", posixpath.basename(part) + ".rels")
|
|
87
|
-
root = xml.get(path)
|
|
88
|
-
if root is None:
|
|
89
|
-
return {}
|
|
90
|
-
return {
|
|
91
|
-
rel.attrib["Id"]: relationship_target(part, rel.attrib["Target"])
|
|
92
|
-
for rel in root if rel.get("TargetMode") != "External" and (types is None or rel.get("Type") in types)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def related_xml(part: str, reference: str, links: dict[str, str], xml: dict[str, ET.Element]) -> ET.Element:
|
|
97
|
-
"""Read a related XML part with diagnostics naming its source and reference."""
|
|
98
|
-
if reference not in links:
|
|
99
|
-
raise ValueError(f"{part} references missing relationship: {reference}")
|
|
100
|
-
target = links[reference]
|
|
101
|
-
if target not in xml:
|
|
102
|
-
raise ValueError(f"{part} relationship {reference} targets a non-XML member: {target}")
|
|
103
|
-
return xml[target]
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def inspect_docx(xml: dict[str, ET.Element]) -> tuple[dict, str]:
|
|
107
|
-
part = "word/document.xml"
|
|
108
|
-
root = xml[part]
|
|
109
|
-
w = namespace(root, W_NAMESPACES, part)
|
|
110
|
-
body = root.find(f"{w}body")
|
|
111
|
-
if body is None:
|
|
112
|
-
raise ValueError("word/document.xml has no document body")
|
|
113
|
-
tables = []
|
|
114
|
-
for table in body.iter(f"{w}tbl"):
|
|
115
|
-
grid = table.findall(f"{w}tblGrid/{w}gridCol")
|
|
116
|
-
rows = table.findall(f"{w}tr")
|
|
117
|
-
# Merged cells span logical grid columns; counting physical cells loses them.
|
|
118
|
-
columns = len(grid) if grid else max((sum(
|
|
119
|
-
int(cell.find(f"{w}tcPr/{w}gridSpan").get(f"{w}val", "1"))
|
|
120
|
-
if cell.find(f"{w}tcPr/{w}gridSpan") is not None else 1
|
|
121
|
-
for cell in row.findall(f"{w}tc")
|
|
122
|
-
) for row in rows), default=0)
|
|
123
|
-
tables.append({"rows": len(rows), "columns": columns})
|
|
124
|
-
sections = []
|
|
125
|
-
for section in body.iter(f"{w}sectPr"):
|
|
126
|
-
size = section.find(f"{w}pgSz")
|
|
127
|
-
margins = section.find(f"{w}pgMar")
|
|
128
|
-
sections.append({
|
|
129
|
-
"page_twips": {} if size is None else {key.removeprefix(w): value for key, value in size.attrib.items()},
|
|
130
|
-
"margins_twips": {} if margins is None else {key.removeprefix(w): value for key, value in margins.attrib.items()},
|
|
131
|
-
})
|
|
132
|
-
text_parts = [body]
|
|
133
|
-
for kind in ("header", "footer"):
|
|
134
|
-
links = relationships(part, xml, relationship_types(kind))
|
|
135
|
-
for section in body.iter(f"{w}sectPr"):
|
|
136
|
-
for reference in section.findall(f"{w}{kind}Reference"):
|
|
137
|
-
text_parts.append(related_xml(part, relationship_id(reference, part), links, xml))
|
|
138
|
-
for kind in ("footnote", "endnote"):
|
|
139
|
-
references = {node.attrib[f"{w}id"] for node in body.iter(f"{w}{kind}Reference")}
|
|
140
|
-
if not references:
|
|
141
|
-
continue
|
|
142
|
-
links = relationships(part, xml, relationship_types(f"{kind}s"))
|
|
143
|
-
for reference in links:
|
|
144
|
-
tree = related_xml(part, reference, links, xml)
|
|
145
|
-
text_parts.extend(note for note in tree.findall(f"{w}{kind}") if note.get(f"{w}id") in references)
|
|
146
|
-
text = "\n".join("".join(node.text or "" for node in paragraph.iter(f"{w}t")) for tree in text_parts
|
|
147
|
-
for paragraph in tree.iter(f"{w}p"))
|
|
148
|
-
return {"paragraphs": len(list(body.iter(f"{w}p"))), "tables": tables, "sections": sections}, text
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def inspect_pptx(xml: dict[str, ET.Element]) -> tuple[dict, str]:
|
|
152
|
-
part = "ppt/presentation.xml"
|
|
153
|
-
links = relationships(part, xml)
|
|
154
|
-
root = xml[part]
|
|
155
|
-
p = namespace(root, P_NAMESPACES, part)
|
|
156
|
-
slides = root.findall(f"{p}sldIdLst/{p}sldId")
|
|
157
|
-
texts = []
|
|
158
|
-
for slide in slides:
|
|
159
|
-
reference = relationship_id(slide, part)
|
|
160
|
-
tree = related_xml(part, reference, links, xml)
|
|
161
|
-
texts.append("\n".join("".join(node.text or "" for node in iter_namespaces(paragraph, A_NAMESPACES, "t"))
|
|
162
|
-
for paragraph in iter_namespaces(tree, A_NAMESPACES, "p")))
|
|
163
|
-
return {"slides": len(slides)}, "\n".join(texts)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def inspect_xlsx(xml: dict[str, ET.Element]) -> tuple[dict, str]:
|
|
167
|
-
part = "xl/workbook.xml"
|
|
168
|
-
links = relationships(part, xml)
|
|
169
|
-
root = xml[part]
|
|
170
|
-
s = namespace(root, S_NAMESPACES, part)
|
|
171
|
-
sheets = []
|
|
172
|
-
texts = []
|
|
173
|
-
shared = xml.get("xl/sharedStrings.xml")
|
|
174
|
-
shared_s = s if shared is None else namespace(shared, S_NAMESPACES, "xl/sharedStrings.xml")
|
|
175
|
-
shared_strings = [] if shared is None else [
|
|
176
|
-
"".join(node.text or "" for node in item.iter(f"{shared_s}t")) for item in shared.iter(f"{shared_s}si")
|
|
177
|
-
]
|
|
178
|
-
for sheet in root.findall(f"{s}sheets/{s}sheet"):
|
|
179
|
-
reference = relationship_id(sheet, part)
|
|
180
|
-
tree = related_xml(part, reference, links, xml)
|
|
181
|
-
sheet_s = namespace(tree, S_NAMESPACES, links[reference])
|
|
182
|
-
cells = list(tree.iter(f"{sheet_s}c"))
|
|
183
|
-
formulas = sum(cell.find(f"{sheet_s}f") is not None for cell in cells)
|
|
184
|
-
sheets.append({"name": sheet.attrib["name"], "cells": len(cells), "formulas": formulas})
|
|
185
|
-
for cell in cells:
|
|
186
|
-
if cell.get("t") == "s":
|
|
187
|
-
value = cell.findtext(f"{sheet_s}v", "")
|
|
188
|
-
location = f"{links[reference]} cell {cell.get('r', '(no reference)')}"
|
|
189
|
-
try:
|
|
190
|
-
index = int(value)
|
|
191
|
-
except ValueError as error:
|
|
192
|
-
raise ValueError(f"{location}: invalid shared string index {value!r}") from error
|
|
193
|
-
if not 0 <= index < len(shared_strings):
|
|
194
|
-
raise ValueError(f"{location}: shared string index out of range: {index}")
|
|
195
|
-
texts.append(shared_strings[index])
|
|
196
|
-
texts.extend("".join(node.text or "" for node in cell.iter(f"{sheet_s}t")) for cell in cells)
|
|
197
|
-
texts.extend(cell.findtext(f"{sheet_s}v", "") for cell in cells if cell.get("t") == "str")
|
|
198
|
-
texts.append(sheet.attrib["name"])
|
|
199
|
-
return {"sheets": sheets, "formulas_evaluated": False}, "\n".join(texts)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def inspect(path: Path) -> tuple[dict, str]:
|
|
203
|
-
"""Validate package members and relationships before inspecting the main part."""
|
|
204
|
-
main, content_type = MAIN_PARTS[path.suffix.lower()]
|
|
205
|
-
with zipfile.ZipFile(path) as archive:
|
|
206
|
-
members = archive.namelist()
|
|
207
|
-
if len(members) != len(set(members)):
|
|
208
|
-
raise ValueError("ZIP contains duplicate member names")
|
|
209
|
-
corrupt = archive.testzip()
|
|
210
|
-
if corrupt is not None:
|
|
211
|
-
raise ValueError(f"ZIP member failed its CRC check: {corrupt}")
|
|
212
|
-
xml = {name: ET.fromstring(archive.read(name)) for name in members
|
|
213
|
-
if name.endswith((".xml", ".rels"))}
|
|
214
|
-
if main not in xml:
|
|
215
|
-
raise ValueError(f"missing main part: {main}")
|
|
216
|
-
types = xml.get("[Content_Types].xml")
|
|
217
|
-
if types is None or not any(node.get("PartName") == "/" + main
|
|
218
|
-
and node.get("ContentType", "").endswith(content_type) for node in types):
|
|
219
|
-
raise ValueError(f"[Content_Types].xml does not declare {main} as {path.suffix.lower()}")
|
|
220
|
-
for name, tree in xml.items():
|
|
221
|
-
if not name.endswith(".rels"):
|
|
222
|
-
continue
|
|
223
|
-
source = "" if name == "_rels/.rels" else posixpath.join(posixpath.dirname(posixpath.dirname(name)), posixpath.basename(name)[:-5])
|
|
224
|
-
for rel in tree:
|
|
225
|
-
if rel.get("TargetMode") == "External":
|
|
226
|
-
continue
|
|
227
|
-
target = relationship_target(source, rel.attrib["Target"])
|
|
228
|
-
if target not in members:
|
|
229
|
-
raise ValueError(f"{name} references missing package member: {target}")
|
|
230
|
-
if path.suffix.lower() == ".docx":
|
|
231
|
-
return inspect_docx(xml)
|
|
232
|
-
if path.suffix.lower() == ".pptx":
|
|
233
|
-
return inspect_pptx(xml)
|
|
234
|
-
return inspect_xlsx(xml)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def main() -> int:
|
|
238
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
239
|
-
parser.add_argument("input", type=Path)
|
|
240
|
-
parser.add_argument("--out", type=Path)
|
|
241
|
-
parser.add_argument("--contains", action="append", default=[], metavar="TEXT")
|
|
242
|
-
parser.add_argument("--count", type=int, help="expected slide or sheet count")
|
|
243
|
-
args = parser.parse_args()
|
|
244
|
-
if args.input.suffix.lower() not in MAIN_PARTS:
|
|
245
|
-
parser.error("input must be .docx, .pptx, or .xlsx; converting the filename does not convert its contents")
|
|
246
|
-
if args.count is not None and (args.count < 0 or args.input.suffix.lower() == ".docx"):
|
|
247
|
-
parser.error("--count must be non-negative and applies only to slides or sheets")
|
|
248
|
-
if args.out is not None and args.out.resolve() == args.input.resolve():
|
|
249
|
-
parser.error("--out must differ from the input document")
|
|
250
|
-
checks = []
|
|
251
|
-
summary = {}
|
|
252
|
-
try:
|
|
253
|
-
summary, text = inspect(args.input)
|
|
254
|
-
checks.append({"id": "package", "status": "pass"})
|
|
255
|
-
for required in args.contains:
|
|
256
|
-
checks.append({"id": "contains", "status": "pass" if required in text else "fail", "text": required})
|
|
257
|
-
if args.count is not None:
|
|
258
|
-
actual = summary.get("slides", len(summary.get("sheets", [])))
|
|
259
|
-
checks.append({"id": "count", "status": "pass" if actual == args.count else "fail", "expected": args.count, "actual": actual})
|
|
260
|
-
except (OSError, ValueError, KeyError, RuntimeError, ET.ParseError, zipfile.BadZipFile, zlib.error) as error:
|
|
261
|
-
checks.append({"id": "package", "status": "fail", "detail": str(error)})
|
|
262
|
-
failed = any(check["status"] == "fail" for check in checks)
|
|
263
|
-
report = {"format": args.input.suffix.lower()[1:], "verdict": "fail" if failed else "pass", "checks": checks, "summary": summary}
|
|
264
|
-
output = json.dumps(report, ensure_ascii=True, indent=2) + "\n"
|
|
265
|
-
if args.out is not None:
|
|
266
|
-
try:
|
|
267
|
-
args.out.parent.mkdir(parents=True, exist_ok=True)
|
|
268
|
-
args.out.write_text(output, encoding="utf-8")
|
|
269
|
-
except OSError as error:
|
|
270
|
-
failed = True
|
|
271
|
-
report["verdict"] = "fail"
|
|
272
|
-
checks.append({"id": "output", "status": "fail", "detail": str(error)})
|
|
273
|
-
output = json.dumps(report, ensure_ascii=True, indent=2) + "\n"
|
|
274
|
-
sys.stdout.write(output)
|
|
275
|
-
return 1 if failed else 0
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if __name__ == "__main__":
|
|
279
|
-
sys.exit(main())
|