@cliven/mddocx 1.0.1 → 1.0.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/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +6 -15
- package/package.json +1 -1
- package/skills/mddoc/SKILL.md +13 -4
- package/skills/mddoc/scripts/md2docx.py +21 -10
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mddocx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Markdown 转学术格式 Word 文档 (DOCX)。支持三线表、图题/表题自动编号、页码、页眉等学术论文排版规范。",
|
|
5
|
-
"author": "Quan Guanyu",
|
|
5
|
+
"author": { "name": "Quan Guanyu" },
|
|
6
6
|
"keywords": ["markdown", "docx", "word", "academic", "论文", "学术", "转换"],
|
|
7
7
|
"skills": "./skills/"
|
|
8
8
|
}
|
package/README.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# mddocx — Markdown 转学术格式 DOCX
|
|
2
2
|
|
|
3
|
-
[](https://
|
|
3
|
+
[](https://www.claudepluginhub.com/plugins/trisia-mddocx)
|
|
4
4
|
[](https://github.com/openai/codex)
|
|
5
5
|
[](https://cursor.com)
|
|
6
6
|
[](https://opencode.ai)
|
|
7
|
+
[](https://www.npmjs.com/package/@cliven/mddocx)
|
|
7
8
|
[](https://github.com/Trisia/mddocx/releases)
|
|
8
9
|
[](LICENSE)
|
|
9
10
|
[](https://python.org)
|
|
@@ -18,24 +19,14 @@
|
|
|
18
19
|
|
|
19
20
|
### 🤖 一键安装
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
将以下内容复制粘贴给任意智能体(Claude Code / Codex / OpenCode / Cursor)即可安装。
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
请帮我安装 mddocx 插件:
|
|
25
|
-
|
|
26
|
-
git clone https://github.com/Trisia/mddocx /tmp/mddocx
|
|
27
|
-
mkdir -p ~/.claude/skills/mddoc
|
|
28
|
-
cp -r /tmp/mddocx/skills/mddoc/* ~/.claude/skills/mddoc/
|
|
29
|
-
rm -rf /tmp/mddocx
|
|
24
|
+
> 各平台专属安装说明详见 **[INSTALL.md](INSTALL.md)**。
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
```copy
|
|
27
|
+
请根据 https://github.com/Trisia/mddocx/blob/main/INSTALL.md 中对应平台的说明,帮我安装 mddocx
|
|
33
28
|
```
|
|
34
29
|
|
|
35
|
-
### 📖 完整安装指南
|
|
36
|
-
|
|
37
|
-
支持 **Claude Code** / **Codex** / **OpenCode** / **Cursor** / **通用** 等多种方式,详见 **[INSTALL.md](INSTALL.md)**。
|
|
38
|
-
|
|
39
30
|
### 依赖
|
|
40
31
|
|
|
41
32
|
```bash
|
package/package.json
CHANGED
package/skills/mddoc/SKILL.md
CHANGED
|
@@ -28,7 +28,7 @@ python <skill-path>/scripts/md2docx.py --text "# 标题\n\n正文内容" -o out.
|
|
|
28
28
|
1. **读取输入** — 若用户粘贴 Markdown 文本则直接读取;若用户提供文件路径(含 `@` 引用)则读取该文件
|
|
29
29
|
2. **安装依赖**(如未安装)— `pip install python-docx Pillow requests mistune`
|
|
30
30
|
3. **执行转换** — 优先使用内置脚本 `scripts/md2docx.py`;若 Markdown 结构特殊则参照下方格式规范编写自定义脚本
|
|
31
|
-
4. **确定输出** — 文件路径输入→同目录;粘贴内容→当前目录;文件名=「题目.docx
|
|
31
|
+
4. **确定输出** — 文件路径输入→同目录;粘贴内容→当前目录;文件名=「题目.docx」(题目从第一个 `# 标题` 提取;若无 `#` 标题则使用输入文件名,`--text` 模式为「未命名文档.docx」)
|
|
32
32
|
5. **验证** — 检查 outline level、图片嵌入、页眉、分页符
|
|
33
33
|
|
|
34
34
|
---
|
|
@@ -118,6 +118,14 @@ set_cn_font(run, '黑体', size_pt=16)
|
|
|
118
118
|
add_empty(doc)
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
### 无标题文档
|
|
122
|
+
|
|
123
|
+
当输入 Markdown 没有 `#` 标题时:
|
|
124
|
+
- 跳过标题页,文档从第一个内容节点开始排版
|
|
125
|
+
- 页眉右侧显示「未命名文档」代替标题
|
|
126
|
+
- 页眉左侧「xxxxx」保持不变
|
|
127
|
+
- 输出文件名:文件输入使用输入文件名(如 `paper.md` → `paper.docx`),`--text` 模式使用「未命名文档.docx」
|
|
128
|
+
|
|
121
129
|
### 一级标题(后续 `#`)
|
|
122
130
|
|
|
123
131
|
三号黑体(16pt)、居中、上下各空一行、outline_level=1、**前加分页符**。
|
|
@@ -205,7 +213,7 @@ p.add_run().add_picture(img_path, width=img_width(img_path))
|
|
|
205
213
|
|
|
206
214
|
### 图题
|
|
207
215
|
|
|
208
|
-
图片下方(不空行)、小五(9pt)
|
|
216
|
+
图片下方(不空行)、小五(9pt)宋体加粗居中、逐章编号。有章标题时格式 `图<章>-<序号> <alt>`,无章标题时格式 `图<序号> <alt>`。若 alt 为空,自动使用图片文件名(不含扩展名)作为图题。
|
|
209
217
|
|
|
210
218
|
```python
|
|
211
219
|
p = doc.add_paragraph()
|
|
@@ -217,7 +225,7 @@ add_empty(doc) # 图题下方空一行
|
|
|
217
225
|
|
|
218
226
|
### 表格 + 表题
|
|
219
227
|
|
|
220
|
-
**三线表**(顶线1.5pt/表头底线0.75pt/底线1.5pt粗,无竖线)。表题在上方(不空行)、五号(10.5pt)
|
|
228
|
+
**三线表**(顶线1.5pt/表头底线0.75pt/底线1.5pt粗,无竖线)。表题在上方(不空行)、五号(10.5pt)宋体加粗居中。有章标题时格式`表<章>-<序号> <描述>`,无章标题时格式`表<序号> <描述>`。
|
|
221
229
|
|
|
222
230
|
```python
|
|
223
231
|
# 表题
|
|
@@ -462,6 +470,7 @@ set_cn_font(run_cont, '宋体', size_pt=10.5)
|
|
|
462
470
|
## 生成后自检清单
|
|
463
471
|
|
|
464
472
|
- [ ] 题目:16pt黑体、居中、无outline、上下各空一行
|
|
473
|
+
- [ ] 无标题文档:跳过标题页、页眉显示「未命名文档」
|
|
465
474
|
- [ ] 一级标题:16pt黑体、outline_level=1、前有分页符、上下各空一行
|
|
466
475
|
- [ ] 二级标题:14pt黑体、顶格、outline_level=2、不加粗、**上下不空行**
|
|
467
476
|
- [ ] 三级标题:12pt宋体、首行缩进Pt(21)、outline_level=3、不加粗、**上下不空行**
|
|
@@ -482,7 +491,7 @@ set_cn_font(run_cont, '宋体', size_pt=10.5)
|
|
|
482
491
|
|
|
483
492
|
## 注意事项
|
|
484
493
|
|
|
485
|
-
- **第一个 `#` 是题目**(不设 outline),后续 `#` 是一级标题(outline_level=1
|
|
494
|
+
- **第一个 `#` 是题目**(不设 outline),后续 `#` 是一级标题(outline_level=1);若无 `#` 标题仍可正常生成文档
|
|
486
495
|
- **图片尺寸用 `Cm()`,不手算 EMU** — `add_picture(width=Cm(x))` 自动转换
|
|
487
496
|
- **图片下载必设 User-Agent** — 否则 CDN/Wikipedia 返回 400
|
|
488
497
|
- **图题编号自动生成** — alt text 作为描述文字,图/表编号独立逐章编序
|
|
@@ -838,7 +838,7 @@ def extract_title_text(title_node):
|
|
|
838
838
|
return text.strip()
|
|
839
839
|
|
|
840
840
|
|
|
841
|
-
def generate_docx(nodes, output_path, title_text):
|
|
841
|
+
def generate_docx(nodes, output_path, title_text=None):
|
|
842
842
|
"""根据节点列表生成 docx 文件"""
|
|
843
843
|
doc = Document()
|
|
844
844
|
|
|
@@ -869,7 +869,7 @@ def generate_docx(nodes, output_path, title_text):
|
|
|
869
869
|
hp_right = header.add_paragraph()
|
|
870
870
|
hp_right.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
871
871
|
hp_right.paragraph_format.space_before = Pt(0)
|
|
872
|
-
run_r = hp_right.add_run(title_text)
|
|
872
|
+
run_r = hp_right.add_run(title_text or '未命名文档')
|
|
873
873
|
set_run_font(run_r, '黑体', size_pt=9)
|
|
874
874
|
|
|
875
875
|
# --- 页脚/页码设置 ---
|
|
@@ -906,6 +906,7 @@ def generate_docx(nodes, output_path, title_text):
|
|
|
906
906
|
|
|
907
907
|
# --- 计数器 ---
|
|
908
908
|
chapter_path = [1] # 一级标题序号
|
|
909
|
+
has_chapter = False # 是否遇到章标题(# 一级标题)
|
|
909
910
|
fig_counter = {} # key: chapter index tuple, value: counter
|
|
910
911
|
tab_counter = {}
|
|
911
912
|
eq_counter = {}
|
|
@@ -929,10 +930,14 @@ def generate_docx(nodes, output_path, title_text):
|
|
|
929
930
|
return eq_counter[key]
|
|
930
931
|
|
|
931
932
|
def make_fig_label():
|
|
932
|
-
|
|
933
|
+
if has_chapter:
|
|
934
|
+
return f"图{chapter_path[0]}-{incr_fig()}"
|
|
935
|
+
return f"图{incr_fig()}"
|
|
933
936
|
|
|
934
937
|
def make_tab_label():
|
|
935
|
-
|
|
938
|
+
if has_chapter:
|
|
939
|
+
return f"表{chapter_path[0]}-{incr_tab()}"
|
|
940
|
+
return f"表{incr_tab()}"
|
|
936
941
|
|
|
937
942
|
# --- 遍历节点生成内容 ---
|
|
938
943
|
for node in nodes:
|
|
@@ -950,6 +955,7 @@ def generate_docx(nodes, output_path, title_text):
|
|
|
950
955
|
lv = node['level']
|
|
951
956
|
if lv == 1:
|
|
952
957
|
# 一级标题:新页 + 三号黑体居中
|
|
958
|
+
has_chapter = True
|
|
953
959
|
chapter_path[0] += 1
|
|
954
960
|
chapter_path[1:] = [0] # reset sub-levels
|
|
955
961
|
|
|
@@ -1017,6 +1023,9 @@ def generate_docx(nodes, output_path, title_text):
|
|
|
1017
1023
|
run_img.add_picture(img_path, width=w)
|
|
1018
1024
|
|
|
1019
1025
|
# 图题:小五宋体加粗居中
|
|
1026
|
+
# alt 为空时,使用图片文件名(不含扩展名)作为默认图题
|
|
1027
|
+
if not alt:
|
|
1028
|
+
alt = os.path.splitext(os.path.basename(url))[0]
|
|
1020
1029
|
p_cap = doc.add_paragraph()
|
|
1021
1030
|
p_cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
1022
1031
|
run_cap = p_cap.add_run(f'{make_fig_label()} {alt}')
|
|
@@ -1177,19 +1186,21 @@ def main():
|
|
|
1177
1186
|
# 解析
|
|
1178
1187
|
nodes = parse_markdown(md_content)
|
|
1179
1188
|
|
|
1180
|
-
#
|
|
1189
|
+
# 提取题目(可选)
|
|
1181
1190
|
title_node = next((n for n in nodes if n['type'] == 'title'), None)
|
|
1182
|
-
if title_node
|
|
1183
|
-
print("错误:未找到文档标题(第一个 # 标题)", file=sys.stderr)
|
|
1184
|
-
sys.exit(1)
|
|
1185
|
-
title_text = extract_title_text(title_node)
|
|
1191
|
+
title_text = extract_title_text(title_node) if title_node else None
|
|
1186
1192
|
|
|
1187
1193
|
# 确定输出路径
|
|
1188
1194
|
if args.output:
|
|
1189
1195
|
output_path = args.output
|
|
1190
|
-
|
|
1196
|
+
elif title_text:
|
|
1191
1197
|
safe_name = re.sub(r'[\\/*?:"<>|]', '', title_text)
|
|
1192
1198
|
output_path = os.path.join(source_dir, f'{safe_name}.docx')
|
|
1199
|
+
elif args.text:
|
|
1200
|
+
output_path = os.path.join(source_dir, '未命名文档.docx')
|
|
1201
|
+
else:
|
|
1202
|
+
stem = os.path.splitext(os.path.basename(args.input))[0]
|
|
1203
|
+
output_path = os.path.join(source_dir, f'{stem}.docx')
|
|
1193
1204
|
|
|
1194
1205
|
# 生成
|
|
1195
1206
|
generate_docx(nodes, output_path, title_text)
|