@v1hz/md2docx 2.2.0 → 2.3.0
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.md +52 -15
- package/config/style-config.json +21 -0
- package/config/style-config.schema.json +71 -0
- package/config/style.json +14 -14
- package/dist/index.js +263 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,18 +17,18 @@
|
|
|
17
17
|
|
|
18
18
|
## 功能
|
|
19
19
|
|
|
20
|
-
| 功能 | 说明
|
|
21
|
-
| ----------------- |
|
|
22
|
-
| 文档标题 | 从 YAML frontmatter、H1 或文件名提取标题
|
|
23
|
-
| 标题层级归一化 | 将最浅标题归一化为 H1,并修复标题层级跳跃
|
|
24
|
-
| 标题编号 | 生成 `1`、`1.1`、`1.1.1` 等编号,并可剥离常见的已有中英文编号
|
|
25
|
-
| 表格与图片编号 | 自动生成“表 1”“图 1:标题”等题注
|
|
26
|
-
| Mermaid 图表 | 使用 beautiful-mermaid 和 resvg-wasm 将 Mermaid 渲染为高 DPI PNG
|
|
27
|
-
| Word 样式 |
|
|
28
|
-
| Markdown 格式化 | 可只运行预处理流水线,输出格式化后的 Markdown
|
|
29
|
-
| 去除分隔符 | 默认移除 `---`、`***`、`___` 等分隔符行,减少无用页面间距
|
|
30
|
-
| 集中缓存 | 中间文件统一存储到 `~/.md2docx/`,不会在当前目录创建 `tmp/`
|
|
31
|
-
| Node 与可执行版本 | 支持 npm CLI,也支持构建不依赖 Node.js/Bun 的 Windows 可执行文件
|
|
20
|
+
| 功能 | 说明 |
|
|
21
|
+
| ----------------- | ---------------------------------------------------------------- |
|
|
22
|
+
| 文档标题 | 从 YAML frontmatter、H1 或文件名提取标题 |
|
|
23
|
+
| 标题层级归一化 | 将最浅标题归一化为 H1,并修复标题层级跳跃 |
|
|
24
|
+
| 标题编号 | 生成 `1`、`1.1`、`1.1.1` 等编号,并可剥离常见的已有中英文编号 |
|
|
25
|
+
| 表格与图片编号 | 自动生成“表 1”“图 1:标题”等题注 |
|
|
26
|
+
| Mermaid 图表 | 使用 beautiful-mermaid 和 resvg-wasm 将 Mermaid 渲染为高 DPI PNG |
|
|
27
|
+
| Word 样式 | 支持受控语义化配置和完整底层样式,也可从现有 DOCX 提取样式 |
|
|
28
|
+
| Markdown 格式化 | 可只运行预处理流水线,输出格式化后的 Markdown |
|
|
29
|
+
| 去除分隔符 | 默认移除 `---`、`***`、`___` 等分隔符行,减少无用页面间距 |
|
|
30
|
+
| 集中缓存 | 中间文件统一存储到 `~/.md2docx/`,不会在当前目录创建 `tmp/` |
|
|
31
|
+
| Node 与可执行版本 | 支持 npm CLI,也支持构建不依赖 Node.js/Bun 的 Windows 可执行文件 |
|
|
32
32
|
|
|
33
33
|
## 安装方式
|
|
34
34
|
|
|
@@ -97,7 +97,7 @@ md2docx clean
|
|
|
97
97
|
| `<markdown>` | 位置参数;仅在没有其他转换选项时允许使用 |
|
|
98
98
|
| `-f, --file <path>` | Markdown 输入文件 |
|
|
99
99
|
| `-c, --config <path>` | 自定义配置 JSON |
|
|
100
|
-
| `-s, --style <path>` |
|
|
100
|
+
| `-s, --style <path>` | 语义化样式配置或完整底层样式 JSON |
|
|
101
101
|
| `-o, --output <path>` | DOCX 输出路径 |
|
|
102
102
|
| `--force` | 覆盖已有输出 |
|
|
103
103
|
| `-h, --help` | 显示帮助 |
|
|
@@ -169,6 +169,7 @@ md2docx export style -f template.docx → ./template_style.json
|
|
|
169
169
|
│ └── mermaid_*.png
|
|
170
170
|
├── resources/
|
|
171
171
|
│ ├── config.json
|
|
172
|
+
│ ├── style-config.json
|
|
172
173
|
│ ├── style.json
|
|
173
174
|
│ └── add-inline-code.lua
|
|
174
175
|
└── style/
|
|
@@ -213,7 +214,43 @@ CLI 不支持覆盖单个配置项,所有配置都通过 JSON 文件管理。
|
|
|
213
214
|
|
|
214
215
|
## 样式定制
|
|
215
216
|
|
|
216
|
-
|
|
217
|
+
普通用户推荐使用受控的语义化样式配置,只修改程序明确开放的高频选项。其余颜色、尺寸、对齐方式、间距和 Word 样式继承关系继续由内置预设管理。
|
|
218
|
+
|
|
219
|
+
仓库中的 `config/style-config.json` 是可直接复制和修改的默认配置,`config/style-config.schema.json` 用于编辑器提示和校验。未指定 `--style` 时,转换会自动加载内嵌的默认 `style-config.json`,再将它编译到内置底层样式;不需要手工传入 `-s`。
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"$schema": "https://raw.githubusercontent.com/WXY-V1hZ/md2docx/main/config/style-config.schema.json",
|
|
224
|
+
"schemaVersion": 1,
|
|
225
|
+
"preset": "default",
|
|
226
|
+
"options": {
|
|
227
|
+
"body": {
|
|
228
|
+
"firstLineIndent": false
|
|
229
|
+
},
|
|
230
|
+
"headings": {
|
|
231
|
+
"1": {
|
|
232
|
+
"startOnNewPage": false
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"inlineCode": {
|
|
236
|
+
"background": false
|
|
237
|
+
},
|
|
238
|
+
"codeBlock": {
|
|
239
|
+
"border": false
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
将配置保存为 `style-config.json` 后使用:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
md2docx -f report.md -s style-config.json
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
字段缺失表示继承预设;`true` 表示使用预设效果;`false` 表示显式关闭。当前只开放正文首行缩进、一级标题另起一页、行内代码背景和代码块外框。完整设计见 [`docs/style-config-design.md`](docs/style-config-design.md)。
|
|
252
|
+
|
|
253
|
+
`config/style.json` 仍定义完整的底层 DOCX 样式,包括标题、段落、字符和表格样式。转换时,项目先将语义化配置编译到底层样式,再用 `docx` 生成 reference DOCX,最后通过 Pandoc 的 `--reference-doc` 应用样式。现有完整样式 JSON 可继续直接传给 `--style`。
|
|
217
254
|
|
|
218
255
|
### 从现有 DOCX 提取
|
|
219
256
|
|
|
@@ -222,7 +259,7 @@ md2docx export style -f template.docx
|
|
|
222
259
|
md2docx -f report.md -s template_style.json
|
|
223
260
|
```
|
|
224
261
|
|
|
225
|
-
###
|
|
262
|
+
### 高级手动维护
|
|
226
263
|
|
|
227
264
|
主要区域包括:
|
|
228
265
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/WXY-V1hZ/md2docx/main/config/style-config.schema.json",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"preset": "default",
|
|
5
|
+
"options": {
|
|
6
|
+
"body": {
|
|
7
|
+
"firstLineIndent": true
|
|
8
|
+
},
|
|
9
|
+
"headings": {
|
|
10
|
+
"1": {
|
|
11
|
+
"startOnNewPage": false
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"inlineCode": {
|
|
15
|
+
"background": true
|
|
16
|
+
},
|
|
17
|
+
"codeBlock": {
|
|
18
|
+
"border": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/WXY-V1hZ/md2docx/main/config/style-config.schema.json",
|
|
4
|
+
"title": "md2docx 语义化样式配置",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schemaVersion", "preset"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"schemaVersion": {
|
|
13
|
+
"const": 1
|
|
14
|
+
},
|
|
15
|
+
"preset": {
|
|
16
|
+
"const": "default"
|
|
17
|
+
},
|
|
18
|
+
"options": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"additionalProperties": false,
|
|
21
|
+
"properties": {
|
|
22
|
+
"body": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"properties": {
|
|
26
|
+
"firstLineIndent": {
|
|
27
|
+
"type": "boolean",
|
|
28
|
+
"description": "正文是否使用预设的首行缩进"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"headings": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": false,
|
|
35
|
+
"properties": {
|
|
36
|
+
"1": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"properties": {
|
|
40
|
+
"startOnNewPage": {
|
|
41
|
+
"type": "boolean",
|
|
42
|
+
"description": "一级标题是否另起一页"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"inlineCode": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"additionalProperties": false,
|
|
51
|
+
"properties": {
|
|
52
|
+
"background": {
|
|
53
|
+
"type": "boolean",
|
|
54
|
+
"description": "行内代码是否使用预设背景"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"codeBlock": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"additionalProperties": false,
|
|
61
|
+
"properties": {
|
|
62
|
+
"border": {
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"description": "代码块是否使用预设外框"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
package/config/style.json
CHANGED
|
@@ -559,7 +559,7 @@
|
|
|
559
559
|
"eastAsia": "zh-CN"
|
|
560
560
|
},
|
|
561
561
|
"shading": {
|
|
562
|
-
"
|
|
562
|
+
"type": "pct5",
|
|
563
563
|
"color": "auto",
|
|
564
564
|
"fill": "auto"
|
|
565
565
|
}
|
|
@@ -571,40 +571,40 @@
|
|
|
571
571
|
},
|
|
572
572
|
"wordWrap": false,
|
|
573
573
|
"shading": {
|
|
574
|
-
"
|
|
574
|
+
"type": "clear",
|
|
575
575
|
"color": "F3F4F4",
|
|
576
576
|
"fill": "FFFFFF",
|
|
577
577
|
"themeFill": "background1"
|
|
578
578
|
},
|
|
579
579
|
"border": {
|
|
580
580
|
"top": {
|
|
581
|
-
"
|
|
581
|
+
"style": "single",
|
|
582
582
|
"color": "BFBFBF",
|
|
583
|
-
"
|
|
583
|
+
"size": 8,
|
|
584
584
|
"space": 10,
|
|
585
585
|
"themeColor": "background1",
|
|
586
586
|
"themeShade": "BF"
|
|
587
587
|
},
|
|
588
588
|
"left": {
|
|
589
|
-
"
|
|
589
|
+
"style": "single",
|
|
590
590
|
"color": "BFBFBF",
|
|
591
|
-
"
|
|
591
|
+
"size": 8,
|
|
592
592
|
"space": 10,
|
|
593
593
|
"themeColor": "background1",
|
|
594
594
|
"themeShade": "BF"
|
|
595
595
|
},
|
|
596
596
|
"bottom": {
|
|
597
|
-
"
|
|
597
|
+
"style": "single",
|
|
598
598
|
"color": "BFBFBF",
|
|
599
|
-
"
|
|
599
|
+
"size": 8,
|
|
600
600
|
"space": 10,
|
|
601
601
|
"themeColor": "background1",
|
|
602
602
|
"themeShade": "BF"
|
|
603
603
|
},
|
|
604
604
|
"right": {
|
|
605
|
-
"
|
|
605
|
+
"style": "single",
|
|
606
606
|
"color": "BFBFBF",
|
|
607
|
-
"
|
|
607
|
+
"size": 8,
|
|
608
608
|
"space": 10,
|
|
609
609
|
"themeColor": "background1",
|
|
610
610
|
"themeShade": "BF"
|
|
@@ -750,7 +750,7 @@
|
|
|
750
750
|
"eastAsia": "zh-CN"
|
|
751
751
|
},
|
|
752
752
|
"shading": {
|
|
753
|
-
"
|
|
753
|
+
"type": "clear",
|
|
754
754
|
"color": "F3F4F4",
|
|
755
755
|
"fill": "FFFFFF",
|
|
756
756
|
"themeFill": "background1"
|
|
@@ -844,16 +844,16 @@
|
|
|
844
844
|
"eastAsia": "zh-CN"
|
|
845
845
|
},
|
|
846
846
|
"shading": {
|
|
847
|
-
"
|
|
847
|
+
"type": "clear",
|
|
848
848
|
"color": "F3F4F4",
|
|
849
849
|
"fill": "F2F2F2",
|
|
850
850
|
"themeFill": "background1",
|
|
851
851
|
"themeFillShade": "F2"
|
|
852
852
|
},
|
|
853
853
|
"border": {
|
|
854
|
-
"
|
|
854
|
+
"style": "none",
|
|
855
855
|
"color": "auto",
|
|
856
|
-
"
|
|
856
|
+
"size": 0,
|
|
857
857
|
"space": 0
|
|
858
858
|
}
|
|
859
859
|
}
|
package/dist/index.js
CHANGED
|
@@ -103482,7 +103482,7 @@ Actual: ` + parser.attribValue);
|
|
|
103482
103482
|
var options;
|
|
103483
103483
|
var pureJsParser = true;
|
|
103484
103484
|
var currentElement;
|
|
103485
|
-
function
|
|
103485
|
+
function validateOptions2(userOptions) {
|
|
103486
103486
|
options = helper.copyOptions(userOptions);
|
|
103487
103487
|
helper.ensureFlagExists("ignoreDeclaration", options);
|
|
103488
103488
|
helper.ensureFlagExists("ignoreInstruction", options);
|
|
@@ -103730,7 +103730,7 @@ Actual: ` + parser.attribValue);
|
|
|
103730
103730
|
var parser = pureJsParser ? sax.parser(true, {}) : parser = new expat.Parser("UTF-8");
|
|
103731
103731
|
var result = {};
|
|
103732
103732
|
currentElement = result;
|
|
103733
|
-
options =
|
|
103733
|
+
options = validateOptions2(userOptions);
|
|
103734
103734
|
if (pureJsParser) {
|
|
103735
103735
|
parser.opt = { strictEntities: true };
|
|
103736
103736
|
parser.onopentag = onStartElement;
|
|
@@ -103764,13 +103764,13 @@ Actual: ` + parser.attribValue);
|
|
|
103764
103764
|
require_xml2json = /* @__PURE__ */ __commonJSMin((exports, module) => {
|
|
103765
103765
|
var helper = require_options_helper();
|
|
103766
103766
|
var xml2js = require_xml2js();
|
|
103767
|
-
function
|
|
103767
|
+
function validateOptions2(userOptions) {
|
|
103768
103768
|
var options = helper.copyOptions(userOptions);
|
|
103769
103769
|
helper.ensureSpacesExists(options);
|
|
103770
103770
|
return options;
|
|
103771
103771
|
}
|
|
103772
103772
|
module.exports = function(xml, userOptions) {
|
|
103773
|
-
var options =
|
|
103773
|
+
var options = validateOptions2(userOptions), js = xml2js(xml, options), json, parentKey = "compact" in options && options.compact ? "_parent" : "parent";
|
|
103774
103774
|
if ("addParent" in options && options.addParent)
|
|
103775
103775
|
json = JSON.stringify(js, function(k, v) {
|
|
103776
103776
|
return k === parentKey ? "_" : v;
|
|
@@ -103784,7 +103784,7 @@ Actual: ` + parser.attribValue);
|
|
|
103784
103784
|
var helper = require_options_helper();
|
|
103785
103785
|
var isArray = require_array_helper().isArray;
|
|
103786
103786
|
var currentElement, currentElementName;
|
|
103787
|
-
function
|
|
103787
|
+
function validateOptions2(userOptions) {
|
|
103788
103788
|
var options = helper.copyOptions(userOptions);
|
|
103789
103789
|
helper.ensureFlagExists("ignoreDeclaration", options);
|
|
103790
103790
|
helper.ensureFlagExists("ignoreInstruction", options);
|
|
@@ -104064,7 +104064,7 @@ Actual: ` + parser.attribValue);
|
|
|
104064
104064
|
return xml.join("");
|
|
104065
104065
|
}
|
|
104066
104066
|
module.exports = function(js, options) {
|
|
104067
|
-
options =
|
|
104067
|
+
options = validateOptions2(options);
|
|
104068
104068
|
var xml = [];
|
|
104069
104069
|
currentElement = js;
|
|
104070
104070
|
currentElementName = "_root_";
|
|
@@ -128401,7 +128401,7 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
|
128401
128401
|
// package.json
|
|
128402
128402
|
var package_default = {
|
|
128403
128403
|
name: "@v1hz/md2docx",
|
|
128404
|
-
version: "2.
|
|
128404
|
+
version: "2.3.0",
|
|
128405
128405
|
description: "基于 pandoc 的 Markdown 转 Word 工具,自动格式化 Markdown,支持自定义样式,AI 友好,用户友好。",
|
|
128406
128406
|
keywords: [
|
|
128407
128407
|
"converter",
|
|
@@ -128471,7 +128471,7 @@ var package_default = {
|
|
|
128471
128471
|
// src/cli.ts
|
|
128472
128472
|
function createProgram(version, actions) {
|
|
128473
128473
|
const program2 = new Command;
|
|
128474
|
-
program2.name("md2docx").description("将 Markdown 转换为 Word 文档").version(version, "-v, --version", "显示版本号").helpOption("-h, --help", "显示帮助").helpCommand(false).showHelpAfterError("使用 --help 查看帮助").showSuggestionAfterError().enablePositionalOptions().argument("[markdown]", "Markdown 文件(仅无其他转换选项时)").option("-f, --file <path>", "Markdown 文件").option("-c, --config <path>", "自定义配置文件").option("-s, --style <path>", "
|
|
128474
|
+
program2.name("md2docx").description("将 Markdown 转换为 Word 文档").version(version, "-v, --version", "显示版本号").helpOption("-h, --help", "显示帮助").helpCommand(false).showHelpAfterError("使用 --help 查看帮助").showSuggestionAfterError().enablePositionalOptions().argument("[markdown]", "Markdown 文件(仅无其他转换选项时)").option("-f, --file <path>", "Markdown 文件").option("-c, --config <path>", "自定义配置文件").option("-s, --style <path>", "自定义底层样式或语义化样式配置").option("-o, --output <path>", "输出 DOCX 文件").option("--force", "覆盖已有文件").action(async function(markdown, options) {
|
|
128475
128475
|
const hasAdditionalOptions = options.config !== undefined || options.style !== undefined || options.output !== undefined || options.force !== undefined;
|
|
128476
128476
|
if (markdown !== undefined) {
|
|
128477
128477
|
if (options.file !== undefined || hasAdditionalOptions) {
|
|
@@ -146126,6 +146126,30 @@ function Code(el)
|
|
|
146126
146126
|
end
|
|
146127
146127
|
return { Code = Code }`;
|
|
146128
146128
|
|
|
146129
|
+
// config/style-config.json
|
|
146130
|
+
var style_config_default = `{
|
|
146131
|
+
"$schema": "https://raw.githubusercontent.com/WXY-V1hZ/md2docx/main/config/style-config.schema.json",
|
|
146132
|
+
"schemaVersion": 1,
|
|
146133
|
+
"preset": "default",
|
|
146134
|
+
"options": {
|
|
146135
|
+
"body": {
|
|
146136
|
+
"firstLineIndent": true
|
|
146137
|
+
},
|
|
146138
|
+
"headings": {
|
|
146139
|
+
"1": {
|
|
146140
|
+
"startOnNewPage": false
|
|
146141
|
+
}
|
|
146142
|
+
},
|
|
146143
|
+
"inlineCode": {
|
|
146144
|
+
"background": true
|
|
146145
|
+
},
|
|
146146
|
+
"codeBlock": {
|
|
146147
|
+
"border": true
|
|
146148
|
+
}
|
|
146149
|
+
}
|
|
146150
|
+
}
|
|
146151
|
+
`;
|
|
146152
|
+
|
|
146129
146153
|
// config/style.json
|
|
146130
146154
|
var style_default = `{
|
|
146131
146155
|
"default": {
|
|
@@ -146688,7 +146712,7 @@ var style_default = `{
|
|
|
146688
146712
|
"eastAsia": "zh-CN"
|
|
146689
146713
|
},
|
|
146690
146714
|
"shading": {
|
|
146691
|
-
"
|
|
146715
|
+
"type": "pct5",
|
|
146692
146716
|
"color": "auto",
|
|
146693
146717
|
"fill": "auto"
|
|
146694
146718
|
}
|
|
@@ -146700,40 +146724,40 @@ var style_default = `{
|
|
|
146700
146724
|
},
|
|
146701
146725
|
"wordWrap": false,
|
|
146702
146726
|
"shading": {
|
|
146703
|
-
"
|
|
146727
|
+
"type": "clear",
|
|
146704
146728
|
"color": "F3F4F4",
|
|
146705
146729
|
"fill": "FFFFFF",
|
|
146706
146730
|
"themeFill": "background1"
|
|
146707
146731
|
},
|
|
146708
146732
|
"border": {
|
|
146709
146733
|
"top": {
|
|
146710
|
-
"
|
|
146734
|
+
"style": "single",
|
|
146711
146735
|
"color": "BFBFBF",
|
|
146712
|
-
"
|
|
146736
|
+
"size": 8,
|
|
146713
146737
|
"space": 10,
|
|
146714
146738
|
"themeColor": "background1",
|
|
146715
146739
|
"themeShade": "BF"
|
|
146716
146740
|
},
|
|
146717
146741
|
"left": {
|
|
146718
|
-
"
|
|
146742
|
+
"style": "single",
|
|
146719
146743
|
"color": "BFBFBF",
|
|
146720
|
-
"
|
|
146744
|
+
"size": 8,
|
|
146721
146745
|
"space": 10,
|
|
146722
146746
|
"themeColor": "background1",
|
|
146723
146747
|
"themeShade": "BF"
|
|
146724
146748
|
},
|
|
146725
146749
|
"bottom": {
|
|
146726
|
-
"
|
|
146750
|
+
"style": "single",
|
|
146727
146751
|
"color": "BFBFBF",
|
|
146728
|
-
"
|
|
146752
|
+
"size": 8,
|
|
146729
146753
|
"space": 10,
|
|
146730
146754
|
"themeColor": "background1",
|
|
146731
146755
|
"themeShade": "BF"
|
|
146732
146756
|
},
|
|
146733
146757
|
"right": {
|
|
146734
|
-
"
|
|
146758
|
+
"style": "single",
|
|
146735
146759
|
"color": "BFBFBF",
|
|
146736
|
-
"
|
|
146760
|
+
"size": 8,
|
|
146737
146761
|
"space": 10,
|
|
146738
146762
|
"themeColor": "background1",
|
|
146739
146763
|
"themeShade": "BF"
|
|
@@ -146879,7 +146903,7 @@ var style_default = `{
|
|
|
146879
146903
|
"eastAsia": "zh-CN"
|
|
146880
146904
|
},
|
|
146881
146905
|
"shading": {
|
|
146882
|
-
"
|
|
146906
|
+
"type": "clear",
|
|
146883
146907
|
"color": "F3F4F4",
|
|
146884
146908
|
"fill": "FFFFFF",
|
|
146885
146909
|
"themeFill": "background1"
|
|
@@ -146973,16 +146997,16 @@ var style_default = `{
|
|
|
146973
146997
|
"eastAsia": "zh-CN"
|
|
146974
146998
|
},
|
|
146975
146999
|
"shading": {
|
|
146976
|
-
"
|
|
147000
|
+
"type": "clear",
|
|
146977
147001
|
"color": "F3F4F4",
|
|
146978
147002
|
"fill": "F2F2F2",
|
|
146979
147003
|
"themeFill": "background1",
|
|
146980
147004
|
"themeFillShade": "F2"
|
|
146981
147005
|
},
|
|
146982
147006
|
"border": {
|
|
146983
|
-
"
|
|
147007
|
+
"style": "none",
|
|
146984
147008
|
"color": "auto",
|
|
146985
|
-
"
|
|
147009
|
+
"size": 0,
|
|
146986
147010
|
"space": 0
|
|
146987
147011
|
}
|
|
146988
147012
|
}
|
|
@@ -147086,6 +147110,7 @@ var style_default = `{
|
|
|
147086
147110
|
|
|
147087
147111
|
// src/resources.ts
|
|
147088
147112
|
var DEFAULT_CONFIG_TEXT = config_default;
|
|
147113
|
+
var DEFAULT_STYLE_CONFIG_TEXT = style_config_default;
|
|
147089
147114
|
var DEFAULT_STYLE_TEXT = style_default;
|
|
147090
147115
|
function materializeResource(name2, content3) {
|
|
147091
147116
|
const directory = join4(TMP_DIR, "resources");
|
|
@@ -147102,18 +147127,216 @@ function materializeDefaultConfig() {
|
|
|
147102
147127
|
function materializeDefaultStyle() {
|
|
147103
147128
|
return materializeResource("style.json", DEFAULT_STYLE_TEXT);
|
|
147104
147129
|
}
|
|
147130
|
+
function materializeDefaultStyleConfig() {
|
|
147131
|
+
materializeDefaultStyle();
|
|
147132
|
+
return materializeResource("style-config.json", DEFAULT_STYLE_CONFIG_TEXT);
|
|
147133
|
+
}
|
|
147105
147134
|
function materializeLuaFilter() {
|
|
147106
147135
|
return materializeResource("add-inline-code.lua", add_inline_code_default);
|
|
147107
147136
|
}
|
|
147108
147137
|
|
|
147109
147138
|
// src/style/generate.ts
|
|
147110
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4
|
|
147139
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
|
|
147111
147140
|
import { writeFile } from "node:fs/promises";
|
|
147112
147141
|
import { createHash as createHash2 } from "node:crypto";
|
|
147113
147142
|
import { dirname as dirname4 } from "path";
|
|
147114
|
-
|
|
147143
|
+
|
|
147144
|
+
// src/style/compiler.ts
|
|
147145
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
147146
|
+
|
|
147147
|
+
// src/style/config.ts
|
|
147148
|
+
function isStyleConfig(value2) {
|
|
147149
|
+
if (!isRecord(value2))
|
|
147150
|
+
return false;
|
|
147151
|
+
return "schemaVersion" in value2 || "preset" in value2 || "options" in value2;
|
|
147152
|
+
}
|
|
147153
|
+
function validateStyleConfig(value2, path3) {
|
|
147154
|
+
const root2 = expectRecord2(value2, path3, "样式配置");
|
|
147155
|
+
rejectUnknown(root2, ["$schema", "schemaVersion", "preset", "options"], path3, "样式配置");
|
|
147156
|
+
if (root2.$schema !== undefined)
|
|
147157
|
+
expectString2(root2.$schema, path3, "$schema");
|
|
147158
|
+
if (root2.schemaVersion !== 1)
|
|
147159
|
+
invalidStyleConfig(path3, "schemaVersion", "目前只支持版本 1");
|
|
147160
|
+
if (root2.preset !== "default")
|
|
147161
|
+
invalidStyleConfig(path3, "preset", '目前只支持 "default"');
|
|
147162
|
+
if (root2.options !== undefined)
|
|
147163
|
+
validateOptions(root2.options, path3);
|
|
147164
|
+
return root2;
|
|
147165
|
+
}
|
|
147166
|
+
function validateOptions(value2, path3) {
|
|
147167
|
+
const options = expectRecord2(value2, path3, "options");
|
|
147168
|
+
rejectUnknown(options, ["body", "headings", "inlineCode", "codeBlock"], path3, "options");
|
|
147169
|
+
if (options.body !== undefined) {
|
|
147170
|
+
const body = expectRecord2(options.body, path3, "options.body");
|
|
147171
|
+
rejectUnknown(body, ["firstLineIndent"], path3, "options.body");
|
|
147172
|
+
if (body.firstLineIndent !== undefined) {
|
|
147173
|
+
expectBoolean2(body.firstLineIndent, path3, "options.body.firstLineIndent");
|
|
147174
|
+
}
|
|
147175
|
+
}
|
|
147176
|
+
if (options.headings !== undefined) {
|
|
147177
|
+
const headings = expectRecord2(options.headings, path3, "options.headings");
|
|
147178
|
+
rejectUnknown(headings, ["1"], path3, "options.headings");
|
|
147179
|
+
if (headings["1"] !== undefined) {
|
|
147180
|
+
const heading1 = expectRecord2(headings["1"], path3, 'options.headings["1"]');
|
|
147181
|
+
rejectUnknown(heading1, ["startOnNewPage"], path3, 'options.headings["1"]');
|
|
147182
|
+
if (heading1.startOnNewPage !== undefined) {
|
|
147183
|
+
expectBoolean2(heading1.startOnNewPage, path3, 'options.headings["1"].startOnNewPage');
|
|
147184
|
+
}
|
|
147185
|
+
}
|
|
147186
|
+
}
|
|
147187
|
+
if (options.inlineCode !== undefined) {
|
|
147188
|
+
const inlineCode2 = expectRecord2(options.inlineCode, path3, "options.inlineCode");
|
|
147189
|
+
rejectUnknown(inlineCode2, ["background"], path3, "options.inlineCode");
|
|
147190
|
+
if (inlineCode2.background !== undefined) {
|
|
147191
|
+
expectBoolean2(inlineCode2.background, path3, "options.inlineCode.background");
|
|
147192
|
+
}
|
|
147193
|
+
}
|
|
147194
|
+
if (options.codeBlock !== undefined) {
|
|
147195
|
+
const codeBlock = expectRecord2(options.codeBlock, path3, "options.codeBlock");
|
|
147196
|
+
rejectUnknown(codeBlock, ["border"], path3, "options.codeBlock");
|
|
147197
|
+
if (codeBlock.border !== undefined) {
|
|
147198
|
+
expectBoolean2(codeBlock.border, path3, "options.codeBlock.border");
|
|
147199
|
+
}
|
|
147200
|
+
}
|
|
147201
|
+
}
|
|
147202
|
+
function rejectUnknown(value2, allowed, path3, field) {
|
|
147203
|
+
const unknown2 = Object.keys(value2).find((key) => !allowed.includes(key));
|
|
147204
|
+
if (unknown2 !== undefined)
|
|
147205
|
+
invalidStyleConfig(path3, `${field}.${unknown2}`, "不是支持的配置项");
|
|
147206
|
+
}
|
|
147207
|
+
function expectRecord2(value2, path3, field) {
|
|
147208
|
+
if (!isRecord(value2))
|
|
147209
|
+
invalidStyleConfig(path3, field, "必须是对象");
|
|
147210
|
+
return value2;
|
|
147211
|
+
}
|
|
147212
|
+
function isRecord(value2) {
|
|
147213
|
+
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
147214
|
+
}
|
|
147215
|
+
function expectBoolean2(value2, path3, field) {
|
|
147216
|
+
if (typeof value2 !== "boolean")
|
|
147217
|
+
invalidStyleConfig(path3, field, "必须是 boolean");
|
|
147218
|
+
}
|
|
147219
|
+
function expectString2(value2, path3, field) {
|
|
147220
|
+
if (typeof value2 !== "string")
|
|
147221
|
+
invalidStyleConfig(path3, field, "必须是字符串");
|
|
147222
|
+
}
|
|
147223
|
+
function invalidStyleConfig(path3, field, reason) {
|
|
147224
|
+
throw new Error(`样式配置无效:${path3}
|
|
147225
|
+
位置:${field}
|
|
147226
|
+
原因:${reason}`);
|
|
147227
|
+
}
|
|
147228
|
+
|
|
147229
|
+
// src/style/compiler.ts
|
|
147230
|
+
function loadEffectiveStyles(stylePath) {
|
|
147231
|
+
const source = parseJson(readFileSync2(stylePath, "utf-8"), stylePath);
|
|
147232
|
+
if (!isStyleConfig(source))
|
|
147233
|
+
return expectStyleDefinition(source, stylePath);
|
|
147234
|
+
const config = validateStyleConfig(source, stylePath);
|
|
147235
|
+
const preset = expectStyleDefinition(parseJson(DEFAULT_STYLE_TEXT, "内置默认样式"), "内置默认样式");
|
|
147236
|
+
return compileStyleConfig(preset, config);
|
|
147237
|
+
}
|
|
147238
|
+
function compileStyleConfig(preset, config) {
|
|
147239
|
+
const styles = structuredClone(preset);
|
|
147240
|
+
const options = config.options;
|
|
147241
|
+
if (!options)
|
|
147242
|
+
return styles;
|
|
147243
|
+
const firstLineIndent = options.body?.firstLineIndent;
|
|
147244
|
+
if (firstLineIndent !== undefined)
|
|
147245
|
+
applyBodyFirstLineIndent(styles, firstLineIndent);
|
|
147246
|
+
const startOnNewPage = options.headings?.["1"]?.startOnNewPage;
|
|
147247
|
+
if (startOnNewPage !== undefined)
|
|
147248
|
+
applyHeading1PageBreak(styles, startOnNewPage);
|
|
147249
|
+
const inlineCodeBackground = options.inlineCode?.background;
|
|
147250
|
+
if (inlineCodeBackground !== undefined) {
|
|
147251
|
+
applyInlineCodeBackground(styles, inlineCodeBackground);
|
|
147252
|
+
}
|
|
147253
|
+
const codeBlockBorder = options.codeBlock?.border;
|
|
147254
|
+
if (codeBlockBorder !== undefined)
|
|
147255
|
+
applyCodeBlockBorder(styles, codeBlockBorder);
|
|
147256
|
+
return styles;
|
|
147257
|
+
}
|
|
147258
|
+
function applyBodyFirstLineIndent(styles, enabled) {
|
|
147259
|
+
const firstParagraph = findStyle(styles, "paragraphStyles", "First Paragraph");
|
|
147260
|
+
const bodyText = findStyle(styles, "paragraphStyles", "Body Text");
|
|
147261
|
+
for (const style of [firstParagraph, bodyText]) {
|
|
147262
|
+
const paragraph2 = ensureObject(style, "paragraph");
|
|
147263
|
+
paragraph2.indent = {
|
|
147264
|
+
...optionalObject(paragraph2.indent),
|
|
147265
|
+
firstLine: enabled ? 200 : 0,
|
|
147266
|
+
firstLineChars: enabled ? 200 : 0
|
|
147267
|
+
};
|
|
147268
|
+
}
|
|
147269
|
+
}
|
|
147270
|
+
function applyHeading1PageBreak(styles, enabled) {
|
|
147271
|
+
const heading1 = findStyle(styles, "paragraphStyles", "heading 1");
|
|
147272
|
+
ensureObject(heading1, "paragraph").pageBreakBefore = enabled;
|
|
147273
|
+
}
|
|
147274
|
+
function applyInlineCodeBackground(styles, enabled) {
|
|
147275
|
+
const inlineCode2 = findStyle(styles, "characterStyles", "Inline Code");
|
|
147276
|
+
ensureObject(inlineCode2, "run").shading = {
|
|
147277
|
+
type: "clear",
|
|
147278
|
+
color: enabled ? "F3F4F4" : "auto",
|
|
147279
|
+
fill: enabled ? "F2F2F2" : "auto"
|
|
147280
|
+
};
|
|
147281
|
+
}
|
|
147282
|
+
function applyCodeBlockBorder(styles, enabled) {
|
|
147283
|
+
const sourceCode = findStyle(styles, "paragraphStyles", "Source Code");
|
|
147284
|
+
const border = () => ({
|
|
147285
|
+
style: enabled ? "single" : "none",
|
|
147286
|
+
color: enabled ? "BFBFBF" : "auto",
|
|
147287
|
+
size: enabled ? 8 : 0,
|
|
147288
|
+
space: enabled ? 10 : 0
|
|
147289
|
+
});
|
|
147290
|
+
ensureObject(sourceCode, "paragraph").border = {
|
|
147291
|
+
top: border(),
|
|
147292
|
+
left: border(),
|
|
147293
|
+
bottom: border(),
|
|
147294
|
+
right: border()
|
|
147295
|
+
};
|
|
147296
|
+
}
|
|
147297
|
+
function findStyle(styles, collectionName, name2) {
|
|
147298
|
+
const collection = styles[collectionName];
|
|
147299
|
+
if (!Array.isArray(collection)) {
|
|
147300
|
+
throw new Error(`内置样式缺少 ${collectionName},无法应用样式配置`);
|
|
147301
|
+
}
|
|
147302
|
+
const normalizedName = name2.toLowerCase();
|
|
147303
|
+
const style = collection.find((entry) => isRecord2(entry) && typeof entry.name === "string" && entry.name.toLowerCase() === normalizedName);
|
|
147304
|
+
if (!style)
|
|
147305
|
+
throw new Error(`内置样式缺少“${name2}”,无法应用样式配置`);
|
|
147306
|
+
return style;
|
|
147307
|
+
}
|
|
147308
|
+
function ensureObject(parent, key) {
|
|
147309
|
+
const current = parent[key];
|
|
147310
|
+
if (isRecord2(current))
|
|
147311
|
+
return current;
|
|
147312
|
+
const created = {};
|
|
147313
|
+
parent[key] = created;
|
|
147314
|
+
return created;
|
|
147315
|
+
}
|
|
147316
|
+
function optionalObject(value2) {
|
|
147317
|
+
return isRecord2(value2) ? value2 : {};
|
|
147318
|
+
}
|
|
147319
|
+
function parseJson(text5, path3) {
|
|
147320
|
+
try {
|
|
147321
|
+
return JSON.parse(text5);
|
|
147322
|
+
} catch (error) {
|
|
147323
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
147324
|
+
throw new Error(`样式文件不是有效的 JSON:${path3}
|
|
147325
|
+
${message}`);
|
|
147326
|
+
}
|
|
147327
|
+
}
|
|
147328
|
+
function expectStyleDefinition(value2, path3) {
|
|
147329
|
+
if (!isRecord2(value2))
|
|
147330
|
+
throw new Error(`样式文件必须是 JSON 对象:${path3}`);
|
|
147331
|
+
return value2;
|
|
147332
|
+
}
|
|
147333
|
+
function isRecord2(value2) {
|
|
147334
|
+
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
147335
|
+
}
|
|
147336
|
+
|
|
147337
|
+
// src/style/generate.ts
|
|
147338
|
+
async function generateTemplateDocxFromStyles(raw, outputPath) {
|
|
147115
147339
|
const { Document: Document2, Packer: Packer3 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
|
|
147116
|
-
const raw = JSON.parse(readFileSync2(styleJsonPath, "utf-8"));
|
|
147117
147340
|
const tableStylesXml = raw.tableStylesXml;
|
|
147118
147341
|
const { tableStylesXml: _, ...styles } = raw;
|
|
147119
147342
|
const doc = new Document2({
|
|
@@ -147139,13 +147362,13 @@ async function generateTemplateDocx(styleJsonPath, outputPath) {
|
|
|
147139
147362
|
}
|
|
147140
147363
|
var generatePromises = new Map;
|
|
147141
147364
|
async function ensureTemplateDocx(styleJsonPath) {
|
|
147142
|
-
const
|
|
147143
|
-
const hash = createHash2("sha256").update(
|
|
147365
|
+
const styles = loadEffectiveStyles(styleJsonPath);
|
|
147366
|
+
const hash = createHash2("sha256").update(JSON.stringify(styles)).digest("hex").slice(0, 16);
|
|
147144
147367
|
const outputPath = styleTemplateDocx(hash);
|
|
147145
147368
|
if (!existsSync5(outputPath)) {
|
|
147146
147369
|
let promise = generatePromises.get(outputPath);
|
|
147147
147370
|
if (!promise) {
|
|
147148
|
-
promise =
|
|
147371
|
+
promise = generateTemplateDocxFromStyles(styles, outputPath).catch((error) => {
|
|
147149
147372
|
generatePromises.delete(outputPath);
|
|
147150
147373
|
throw error;
|
|
147151
147374
|
});
|
|
@@ -147162,7 +147385,7 @@ async function convertMarkdown(options) {
|
|
|
147162
147385
|
throw new Error("缺少必填参数:--file");
|
|
147163
147386
|
const input = resolveInputPath(options.file, [".md", ".markdown"], "Markdown 文件");
|
|
147164
147387
|
const configPath = options.config ? resolveInputPath(options.config, [".json"], "配置文件") : materializeDefaultConfig();
|
|
147165
|
-
const stylePath = options.style ? resolveInputPath(options.style, [".json"], "样式文件") :
|
|
147388
|
+
const stylePath = options.style ? resolveInputPath(options.style, [".json"], "样式文件") : materializeDefaultStyleConfig();
|
|
147166
147389
|
const baseName = parse4(input).name;
|
|
147167
147390
|
const output = resolveOutputPath(options.output, `${baseName}.docx`, [".docx"], "DOCX 输出文件");
|
|
147168
147391
|
prepareOutput(output, options.force ?? false);
|
|
@@ -147299,31 +147522,31 @@ function mapShd(shd) {
|
|
|
147299
147522
|
if (!shd)
|
|
147300
147523
|
return;
|
|
147301
147524
|
const sh = {};
|
|
147302
|
-
for (const a of [
|
|
147303
|
-
"val",
|
|
147304
|
-
"color",
|
|
147305
|
-
"fill",
|
|
147306
|
-
"themeColor",
|
|
147307
|
-
"themeTint",
|
|
147308
|
-
"themeFill",
|
|
147309
|
-
"themeFillShade",
|
|
147310
|
-
"themeShade"
|
|
147311
|
-
]) {
|
|
147525
|
+
for (const a of ["color", "fill"]) {
|
|
147312
147526
|
const v = wAttr(shd, a);
|
|
147313
147527
|
if (v !== null)
|
|
147314
147528
|
sh[a] = v;
|
|
147315
147529
|
}
|
|
147530
|
+
const type = wAttr(shd, "val");
|
|
147531
|
+
if (type !== null)
|
|
147532
|
+
sh.type = type;
|
|
147316
147533
|
return Object.keys(sh).length > 0 ? sh : undefined;
|
|
147317
147534
|
}
|
|
147318
147535
|
function mapBorder(bdr) {
|
|
147319
147536
|
if (!bdr)
|
|
147320
147537
|
return;
|
|
147321
147538
|
const b = {};
|
|
147322
|
-
for (const a of ["
|
|
147539
|
+
for (const a of ["color", "space"]) {
|
|
147323
147540
|
const v = wAttr(bdr, a);
|
|
147324
147541
|
if (v !== null)
|
|
147325
147542
|
b[a] = /^\d+$/.test(v) ? Number(v) : v;
|
|
147326
147543
|
}
|
|
147544
|
+
const style = wAttr(bdr, "val");
|
|
147545
|
+
if (style !== null)
|
|
147546
|
+
b.style = style;
|
|
147547
|
+
const size = wAttr(bdr, "sz");
|
|
147548
|
+
if (size !== null)
|
|
147549
|
+
b.size = /^\d+$/.test(size) ? Number(size) : size;
|
|
147327
147550
|
return Object.keys(b).length > 0 ? b : undefined;
|
|
147328
147551
|
}
|
|
147329
147552
|
function mapRunProps(rPr) {
|