zarkdown 2.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zarkdown-2.0.1/PKG-INFO +187 -0
- zarkdown-2.0.1/README.md +174 -0
- zarkdown-2.0.1/pyproject.toml +26 -0
- zarkdown-2.0.1/setup.cfg +4 -0
- zarkdown-2.0.1/src/zarkdown/__init__.py +0 -0
- zarkdown-2.0.1/src/zarkdown/core.py +607 -0
- zarkdown-2.0.1/src/zarkdown.egg-info/PKG-INFO +187 -0
- zarkdown-2.0.1/src/zarkdown.egg-info/SOURCES.txt +9 -0
- zarkdown-2.0.1/src/zarkdown.egg-info/dependency_links.txt +1 -0
- zarkdown-2.0.1/src/zarkdown.egg-info/entry_points.txt +2 -0
- zarkdown-2.0.1/src/zarkdown.egg-info/top_level.txt +1 -0
zarkdown-2.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zarkdown
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: Zarkdown - 键盘友好型纯文本标记语言
|
|
5
|
+
Author-email: yangzizhoudiwuxuande <yangzizhou2026@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yangzizhoudiwuxuande/zarkdown
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/yangzizhoudiwuxuande/zarkdown/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# ✨ Zarkdown (版本2.0.1)
|
|
15
|
+
|
|
16
|
+
> **键盘友好型纯文本标记语言** —— 所有符号均位于键盘主键区,无需按 Shift 组合键。
|
|
17
|
+
|
|
18
|
+
[](https://python.org)
|
|
19
|
+
[](LICENSE)
|
|
20
|
+
[](https://github.com/yangzizhoudiwuxuande/zarkdown/stargazers)
|
|
21
|
+
|
|
22
|
+
Zarkdown 是一种全新的纯文本标记语言,专为**快速写作**和**极致键盘效率**而设计。它适合用来写笔记、技术文档、博客文章,甚至可以作为配置文件的格式。
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🎯 设计哲学
|
|
27
|
+
|
|
28
|
+
- **键盘友好**:所有语法符号都在主键区,手指不需要大范围移动
|
|
29
|
+
- **直观易记**:`/` 表示标题(像路径层级),`?` 表示加粗(像强调语气)
|
|
30
|
+
- **纯文本**:任何文本编辑器都能打开,人类可读性强
|
|
31
|
+
- **可扩展**:支持表格、代码块、脚注等高级功能
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 📜 完整语法表
|
|
36
|
+
|
|
37
|
+
| 效果 | 语法 | 示例 | 渲染为 |
|
|
38
|
+
| :--- | :--- | :--- | :--- |
|
|
39
|
+
| **标题 1~4 级** | `/` `//` `///` `////` + 空格 | `/ 大标题` | `<h1>`~`<h4>` |
|
|
40
|
+
| **粗体** | `?文字?` | `?重要?` | `<strong>` |
|
|
41
|
+
| **斜体** | `\文字\` | `\斜体\` | `<i>` |
|
|
42
|
+
| **删除线** | `-文字-` | `-删掉-` | `<del>` |
|
|
43
|
+
| **超链接** | `*文字*(链接)` | `*点击*(https://x.com)` | `<a>` |
|
|
44
|
+
| **图片** | `$图片名$(链接)` | `$logo$(./pic.png)` | `<img>` |
|
|
45
|
+
| **行内代码** | `~文字~` | `~npm i~` | `<code>` |
|
|
46
|
+
| **多行代码块** | `~语言` 开头 + `~` 结尾 | `~python`...`~` | `<pre><code>` |
|
|
47
|
+
| **无序列表** | `!文字`(行首) | `!苹果` | `<ul><li>` |
|
|
48
|
+
| **有序列表** | `•文字`(行首,U+2022) | `•第一` | `<ol><li>` |
|
|
49
|
+
| **引用** | `\|文字`(行首) | `\| 引文` | `<blockquote>` |
|
|
50
|
+
| **表格** | `\| 表头 \|` + `------` | 见下方示例 | `<table>` |
|
|
51
|
+
| **注释** | `<文字>`(不含URL) | `<备注>` | `<!-- -->` |
|
|
52
|
+
| **脚注** | `[数字]<文字>` | `[1]<说明>` | `<sup title="">` |
|
|
53
|
+
| **分割线** | `-----`(独占一行) | | `<hr>` |
|
|
54
|
+
| **转义** | `^` 加在任意符号前 | `^/ 不是标题` | 原样输出 |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🚀 快速上手
|
|
59
|
+
|
|
60
|
+
### 安装
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# 克隆仓库
|
|
64
|
+
git clone https://github.com/yangzizhoudiwuxuande/zarkdown.git
|
|
65
|
+
cd zarkdown
|
|
66
|
+
|
|
67
|
+
# 创建虚拟环境(推荐)
|
|
68
|
+
python3 -m venv venv
|
|
69
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
70
|
+
|
|
71
|
+
# 安装
|
|
72
|
+
pip install -e .
|
|
73
|
+
```
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 使用
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# 转换 .zkdn 文件为 .html
|
|
80
|
+
zarkdown example.zkdn -o example.html
|
|
81
|
+
|
|
82
|
+
# 直接输出到终端(不生成文件)
|
|
83
|
+
zarkdown example.zkdn
|
|
84
|
+
```
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 示例
|
|
88
|
+
### 转为HTML
|
|
89
|
+
创建一个`hello.zkdn`文件:
|
|
90
|
+
```zkdn
|
|
91
|
+
/ Zarkdown 欢迎页
|
|
92
|
+
|
|
93
|
+
?恭喜?,您的格式已经跑通了!
|
|
94
|
+
|
|
95
|
+
这里有一个链接:*点击访问*(https://github.com/yangzizhoudiwuxuande)
|
|
96
|
+
|
|
97
|
+
~python
|
|
98
|
+
print("Hello Zarkdown!")
|
|
99
|
+
~
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
然后运行:
|
|
103
|
+
```bash
|
|
104
|
+
zarkdown hello.zkdn -w
|
|
105
|
+
```
|
|
106
|
+
浏览器会自动打开,显示渲染后的 HTML 页面。修改 `hello.zkdn` 并保存,页面会自动刷新。
|
|
107
|
+
### 转为Word/LaTex
|
|
108
|
+
转为Word/LaTex需要安装Pandoc。
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
brew install pandoc
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
转为Word
|
|
115
|
+
```bash
|
|
116
|
+
zarkdown my_note.zkdn -f docx
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
转为LaTex
|
|
120
|
+
```bash
|
|
121
|
+
zarkdown my_note.zkdn -f latex
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 📸 效果预览
|
|
125
|
+
[](https://imgchr.com/i/pm4y4lF)
|
|
126
|
+
|
|
127
|
+
## 贡献
|
|
128
|
+
欢迎任何形式的贡献!你可以:
|
|
129
|
+
|
|
130
|
+
* 🐛 报告 Bug(在 Issues 中描述)
|
|
131
|
+
* 💡 提出新功能建议
|
|
132
|
+
* 📝 完善文档
|
|
133
|
+
* 🔧 提交 Pull Request
|
|
134
|
+
## 📄 许可证
|
|
135
|
+
本项目采用 MIT License 开源协议,你可以自由使用、修改、分发。
|
|
136
|
+
## ❤️ 致谢
|
|
137
|
+
感谢你阅读这份文档!如果觉得 Zarkdown 有趣,欢迎给项目点一颗 ⭐ 星,让更多人看到它。
|
|
138
|
+
## 📝 更新日志
|
|
139
|
+
**v2.0.1 (2026-08-01)**
|
|
140
|
+
- **语法错误检查器**
|
|
141
|
+
转换前自动扫描全文,检测未闭合的成对符号(`?`、`\`、`-`、`~`、`*`、`$`、`[`、`<`),并精确报告行号和列号,帮助用户快速定位问题。
|
|
142
|
+
|
|
143
|
+
- **括号配对检查**
|
|
144
|
+
针对超链接 `*文字*(链接)` 和图片 `$图片名$(链接)`,自动检测缺失的闭合括号 `)`,避免生成无效链接。
|
|
145
|
+
|
|
146
|
+
- **多行表格支持**
|
|
147
|
+
表格单元格内允许换行,通过缩进续行自动合并内容,并在输出中转换为 `<br>` 换行符。
|
|
148
|
+
|
|
149
|
+
- **转义字符 `^`**
|
|
150
|
+
在任意特殊符号前添加 `^` 即可原样输出,避免被解析器处理,例如 `^/ 不是标题`。
|
|
151
|
+
|
|
152
|
+
- **智能跳过机制**
|
|
153
|
+
错误检查器自动跳过分割线(以 `-----` 开头的行)、表格分隔行以及多行代码块内部内容,避免误报。
|
|
154
|
+
|
|
155
|
+
- **自动生成输出文件名**
|
|
156
|
+
若不指定 `-o`,输出文件名将根据输入文件名和格式自动生成(如 `note.html`、`note.pdf` 等)。
|
|
157
|
+
|
|
158
|
+
**v2.0.0 (2026-07-28)**
|
|
159
|
+
* 支持导出为Word/LaTex等格式(需安装Pandoc)
|
|
160
|
+
|
|
161
|
+
**v1.2.2 (2026-07-26)**
|
|
162
|
+
* 支持多行表格
|
|
163
|
+
|
|
164
|
+
**v1.2.1 (2026-07-26)**
|
|
165
|
+
* 支持嵌套语法(粗体里套斜体、超链接里套粗体等)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
**v1.2.0 (2026-07-25)**
|
|
169
|
+
* 使引用和表格变得更好看
|
|
170
|
+
|
|
171
|
+
**v1.1.0 (2026-07-25)**
|
|
172
|
+
* ✨ 新增 `-w` 监听模式,保存即自动重新转换
|
|
173
|
+
* ✨ 自动在浏览器中打开生成 HTML,实现实时预览
|
|
174
|
+
* 🐛 修复中文编码问题,兼容带 BOM 的 UTF-8 文件
|
|
175
|
+
* 🐛 修复 macOS 下相对路径打开失败的问题
|
|
176
|
+
* 📝 完善 README 文档,增加完整语法表和示例
|
|
177
|
+
|
|
178
|
+
**v1.0.0 (2026-07-25)**
|
|
179
|
+
* 🎉 首次发布,支持完整的 Zarkdown 语法
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
Happy Writing with Zarkdown!
|
|
185
|
+
|
|
186
|
+
## 联系
|
|
187
|
+
yangzizhou2026@outlook.com
|
zarkdown-2.0.1/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# ✨ Zarkdown (版本2.0.1)
|
|
2
|
+
|
|
3
|
+
> **键盘友好型纯文本标记语言** —— 所有符号均位于键盘主键区,无需按 Shift 组合键。
|
|
4
|
+
|
|
5
|
+
[](https://python.org)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/yangzizhoudiwuxuande/zarkdown/stargazers)
|
|
8
|
+
|
|
9
|
+
Zarkdown 是一种全新的纯文本标记语言,专为**快速写作**和**极致键盘效率**而设计。它适合用来写笔记、技术文档、博客文章,甚至可以作为配置文件的格式。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🎯 设计哲学
|
|
14
|
+
|
|
15
|
+
- **键盘友好**:所有语法符号都在主键区,手指不需要大范围移动
|
|
16
|
+
- **直观易记**:`/` 表示标题(像路径层级),`?` 表示加粗(像强调语气)
|
|
17
|
+
- **纯文本**:任何文本编辑器都能打开,人类可读性强
|
|
18
|
+
- **可扩展**:支持表格、代码块、脚注等高级功能
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📜 完整语法表
|
|
23
|
+
|
|
24
|
+
| 效果 | 语法 | 示例 | 渲染为 |
|
|
25
|
+
| :--- | :--- | :--- | :--- |
|
|
26
|
+
| **标题 1~4 级** | `/` `//` `///` `////` + 空格 | `/ 大标题` | `<h1>`~`<h4>` |
|
|
27
|
+
| **粗体** | `?文字?` | `?重要?` | `<strong>` |
|
|
28
|
+
| **斜体** | `\文字\` | `\斜体\` | `<i>` |
|
|
29
|
+
| **删除线** | `-文字-` | `-删掉-` | `<del>` |
|
|
30
|
+
| **超链接** | `*文字*(链接)` | `*点击*(https://x.com)` | `<a>` |
|
|
31
|
+
| **图片** | `$图片名$(链接)` | `$logo$(./pic.png)` | `<img>` |
|
|
32
|
+
| **行内代码** | `~文字~` | `~npm i~` | `<code>` |
|
|
33
|
+
| **多行代码块** | `~语言` 开头 + `~` 结尾 | `~python`...`~` | `<pre><code>` |
|
|
34
|
+
| **无序列表** | `!文字`(行首) | `!苹果` | `<ul><li>` |
|
|
35
|
+
| **有序列表** | `•文字`(行首,U+2022) | `•第一` | `<ol><li>` |
|
|
36
|
+
| **引用** | `\|文字`(行首) | `\| 引文` | `<blockquote>` |
|
|
37
|
+
| **表格** | `\| 表头 \|` + `------` | 见下方示例 | `<table>` |
|
|
38
|
+
| **注释** | `<文字>`(不含URL) | `<备注>` | `<!-- -->` |
|
|
39
|
+
| **脚注** | `[数字]<文字>` | `[1]<说明>` | `<sup title="">` |
|
|
40
|
+
| **分割线** | `-----`(独占一行) | | `<hr>` |
|
|
41
|
+
| **转义** | `^` 加在任意符号前 | `^/ 不是标题` | 原样输出 |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 🚀 快速上手
|
|
46
|
+
|
|
47
|
+
### 安装
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# 克隆仓库
|
|
51
|
+
git clone https://github.com/yangzizhoudiwuxuande/zarkdown.git
|
|
52
|
+
cd zarkdown
|
|
53
|
+
|
|
54
|
+
# 创建虚拟环境(推荐)
|
|
55
|
+
python3 -m venv venv
|
|
56
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
57
|
+
|
|
58
|
+
# 安装
|
|
59
|
+
pip install -e .
|
|
60
|
+
```
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 使用
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 转换 .zkdn 文件为 .html
|
|
67
|
+
zarkdown example.zkdn -o example.html
|
|
68
|
+
|
|
69
|
+
# 直接输出到终端(不生成文件)
|
|
70
|
+
zarkdown example.zkdn
|
|
71
|
+
```
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 示例
|
|
75
|
+
### 转为HTML
|
|
76
|
+
创建一个`hello.zkdn`文件:
|
|
77
|
+
```zkdn
|
|
78
|
+
/ Zarkdown 欢迎页
|
|
79
|
+
|
|
80
|
+
?恭喜?,您的格式已经跑通了!
|
|
81
|
+
|
|
82
|
+
这里有一个链接:*点击访问*(https://github.com/yangzizhoudiwuxuande)
|
|
83
|
+
|
|
84
|
+
~python
|
|
85
|
+
print("Hello Zarkdown!")
|
|
86
|
+
~
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
然后运行:
|
|
90
|
+
```bash
|
|
91
|
+
zarkdown hello.zkdn -w
|
|
92
|
+
```
|
|
93
|
+
浏览器会自动打开,显示渲染后的 HTML 页面。修改 `hello.zkdn` 并保存,页面会自动刷新。
|
|
94
|
+
### 转为Word/LaTex
|
|
95
|
+
转为Word/LaTex需要安装Pandoc。
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
brew install pandoc
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
转为Word
|
|
102
|
+
```bash
|
|
103
|
+
zarkdown my_note.zkdn -f docx
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
转为LaTex
|
|
107
|
+
```bash
|
|
108
|
+
zarkdown my_note.zkdn -f latex
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 📸 效果预览
|
|
112
|
+
[](https://imgchr.com/i/pm4y4lF)
|
|
113
|
+
|
|
114
|
+
## 贡献
|
|
115
|
+
欢迎任何形式的贡献!你可以:
|
|
116
|
+
|
|
117
|
+
* 🐛 报告 Bug(在 Issues 中描述)
|
|
118
|
+
* 💡 提出新功能建议
|
|
119
|
+
* 📝 完善文档
|
|
120
|
+
* 🔧 提交 Pull Request
|
|
121
|
+
## 📄 许可证
|
|
122
|
+
本项目采用 MIT License 开源协议,你可以自由使用、修改、分发。
|
|
123
|
+
## ❤️ 致谢
|
|
124
|
+
感谢你阅读这份文档!如果觉得 Zarkdown 有趣,欢迎给项目点一颗 ⭐ 星,让更多人看到它。
|
|
125
|
+
## 📝 更新日志
|
|
126
|
+
**v2.0.1 (2026-08-01)**
|
|
127
|
+
- **语法错误检查器**
|
|
128
|
+
转换前自动扫描全文,检测未闭合的成对符号(`?`、`\`、`-`、`~`、`*`、`$`、`[`、`<`),并精确报告行号和列号,帮助用户快速定位问题。
|
|
129
|
+
|
|
130
|
+
- **括号配对检查**
|
|
131
|
+
针对超链接 `*文字*(链接)` 和图片 `$图片名$(链接)`,自动检测缺失的闭合括号 `)`,避免生成无效链接。
|
|
132
|
+
|
|
133
|
+
- **多行表格支持**
|
|
134
|
+
表格单元格内允许换行,通过缩进续行自动合并内容,并在输出中转换为 `<br>` 换行符。
|
|
135
|
+
|
|
136
|
+
- **转义字符 `^`**
|
|
137
|
+
在任意特殊符号前添加 `^` 即可原样输出,避免被解析器处理,例如 `^/ 不是标题`。
|
|
138
|
+
|
|
139
|
+
- **智能跳过机制**
|
|
140
|
+
错误检查器自动跳过分割线(以 `-----` 开头的行)、表格分隔行以及多行代码块内部内容,避免误报。
|
|
141
|
+
|
|
142
|
+
- **自动生成输出文件名**
|
|
143
|
+
若不指定 `-o`,输出文件名将根据输入文件名和格式自动生成(如 `note.html`、`note.pdf` 等)。
|
|
144
|
+
|
|
145
|
+
**v2.0.0 (2026-07-28)**
|
|
146
|
+
* 支持导出为Word/LaTex等格式(需安装Pandoc)
|
|
147
|
+
|
|
148
|
+
**v1.2.2 (2026-07-26)**
|
|
149
|
+
* 支持多行表格
|
|
150
|
+
|
|
151
|
+
**v1.2.1 (2026-07-26)**
|
|
152
|
+
* 支持嵌套语法(粗体里套斜体、超链接里套粗体等)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
**v1.2.0 (2026-07-25)**
|
|
156
|
+
* 使引用和表格变得更好看
|
|
157
|
+
|
|
158
|
+
**v1.1.0 (2026-07-25)**
|
|
159
|
+
* ✨ 新增 `-w` 监听模式,保存即自动重新转换
|
|
160
|
+
* ✨ 自动在浏览器中打开生成 HTML,实现实时预览
|
|
161
|
+
* 🐛 修复中文编码问题,兼容带 BOM 的 UTF-8 文件
|
|
162
|
+
* 🐛 修复 macOS 下相对路径打开失败的问题
|
|
163
|
+
* 📝 完善 README 文档,增加完整语法表和示例
|
|
164
|
+
|
|
165
|
+
**v1.0.0 (2026-07-25)**
|
|
166
|
+
* 🎉 首次发布,支持完整的 Zarkdown 语法
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
Happy Writing with Zarkdown!
|
|
172
|
+
|
|
173
|
+
## 联系
|
|
174
|
+
yangzizhou2026@outlook.com
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zarkdown"
|
|
7
|
+
version = "2.0.1"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "yangzizhoudiwuxuande", email = "yangzizhou2026@outlook.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "Zarkdown - 键盘友好型纯文本标记语言"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = { text = "MIT" } # 使用 SPDX 表达式,代替 classifier
|
|
14
|
+
requires-python = ">=3.6"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
# 移除了 License 分类器
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
zarkdown = "zarkdown.core:main" # 命令行入口点
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/yangzizhoudiwuxuande/zarkdown"
|
|
26
|
+
"Bug Tracker" = "https://github.com/yangzizhoudiwuxuande/zarkdown/issues"
|
zarkdown-2.0.1/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Zarkdown 核心转换引擎
|
|
6
|
+
支持嵌套语法、转义、实时预览、多行表格、多格式导出 (HTML/PDF/DOCX/LaTeX)
|
|
7
|
+
内置错误检查器(跳过代码块、分割线/表格分隔符)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
import argparse
|
|
13
|
+
import os
|
|
14
|
+
import time
|
|
15
|
+
import webbrowser
|
|
16
|
+
|
|
17
|
+
# ------------------------------------------------------------
|
|
18
|
+
# 可选依赖:pypandoc(用于 PDF/DOCX/LaTeX 导出)
|
|
19
|
+
# ------------------------------------------------------------
|
|
20
|
+
try:
|
|
21
|
+
import pypandoc
|
|
22
|
+
except ImportError:
|
|
23
|
+
pypandoc = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ============================================================
|
|
27
|
+
# 0. 错误检查器(修复:跳过分割线/表格分隔符)
|
|
28
|
+
# ============================================================
|
|
29
|
+
|
|
30
|
+
def validate_zarkdown(text):
|
|
31
|
+
"""
|
|
32
|
+
检查 Zarkdown 文本中的语法错误:
|
|
33
|
+
1. 未闭合的成对符号(? \\ - ~ * $ [ <)
|
|
34
|
+
2. 超链接/图片中缺少闭合括号 )
|
|
35
|
+
自动跳过:
|
|
36
|
+
- 多行代码块(~python ... ~)
|
|
37
|
+
- 分割线/表格分隔符(以 5 个以上 - 开头的行)
|
|
38
|
+
"""
|
|
39
|
+
errors = []
|
|
40
|
+
lines = text.split('\n')
|
|
41
|
+
|
|
42
|
+
symmetric_pairs = {'?', '\\', '-', '~', '*', '$'}
|
|
43
|
+
asymmetric_pairs = {'[': ']', '<': '>'}
|
|
44
|
+
|
|
45
|
+
in_code_block = False
|
|
46
|
+
|
|
47
|
+
for line_num, line in enumerate(lines, start=1):
|
|
48
|
+
stripped = line.strip()
|
|
49
|
+
|
|
50
|
+
# ---- 检测多行代码块 ----
|
|
51
|
+
if not in_code_block:
|
|
52
|
+
if re.match(r'^~[a-zA-Z0-9_]+$', stripped):
|
|
53
|
+
in_code_block = True
|
|
54
|
+
continue
|
|
55
|
+
else:
|
|
56
|
+
if stripped == '~':
|
|
57
|
+
in_code_block = False
|
|
58
|
+
continue
|
|
59
|
+
continue # 代码块内不检查
|
|
60
|
+
|
|
61
|
+
# ---- 跳过分割线和表格分隔符(以 5 个以上 - 开头) ----
|
|
62
|
+
if re.match(r'^-{5,}', stripped):
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# ---- 正常行检查 ----
|
|
66
|
+
stack = []
|
|
67
|
+
i = 0
|
|
68
|
+
n = len(line)
|
|
69
|
+
while i < n:
|
|
70
|
+
ch = line[i]
|
|
71
|
+
|
|
72
|
+
if ch == '^' and i + 1 < n:
|
|
73
|
+
i += 2
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
if ch in symmetric_pairs:
|
|
77
|
+
if stack and stack[-1][0] == ch:
|
|
78
|
+
stack.pop()
|
|
79
|
+
else:
|
|
80
|
+
stack.append((ch, i + 1))
|
|
81
|
+
i += 1
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
if ch in asymmetric_pairs:
|
|
85
|
+
stack.append((ch, i + 1))
|
|
86
|
+
i += 1
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
if ch in asymmetric_pairs.values():
|
|
90
|
+
expected_open = {v: k for k, v in asymmetric_pairs.items()}[ch]
|
|
91
|
+
if stack and stack[-1][0] == expected_open:
|
|
92
|
+
stack.pop()
|
|
93
|
+
else:
|
|
94
|
+
errors.append({
|
|
95
|
+
'line': line_num,
|
|
96
|
+
'col': i + 1,
|
|
97
|
+
'symbol': ch,
|
|
98
|
+
'msg': f"多余的 '{ch}' 结束符,没有对应的起始符"
|
|
99
|
+
})
|
|
100
|
+
i += 1
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
i += 1
|
|
104
|
+
|
|
105
|
+
for sym, col in stack:
|
|
106
|
+
errors.append({
|
|
107
|
+
'line': line_num,
|
|
108
|
+
'col': col,
|
|
109
|
+
'symbol': sym,
|
|
110
|
+
'msg': f"未闭合的 '{sym}' 起始符"
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
# ---- 检查超链接/图片的括号 () ----
|
|
114
|
+
i = 0
|
|
115
|
+
n = len(line)
|
|
116
|
+
while i < n:
|
|
117
|
+
ch = line[i]
|
|
118
|
+
|
|
119
|
+
# 超链接模式:*文字*(
|
|
120
|
+
if ch == '*' and i + 1 < n:
|
|
121
|
+
j = i + 1
|
|
122
|
+
while j < n and line[j] != '*':
|
|
123
|
+
if line[j] == '^' and j + 1 < n:
|
|
124
|
+
j += 2
|
|
125
|
+
continue
|
|
126
|
+
j += 1
|
|
127
|
+
if j < n and line[j] == '*':
|
|
128
|
+
if j + 1 < n and line[j + 1] == '(':
|
|
129
|
+
k = j + 2
|
|
130
|
+
while k < n and line[k] != ')':
|
|
131
|
+
k += 1
|
|
132
|
+
if k >= n:
|
|
133
|
+
errors.append({
|
|
134
|
+
'line': line_num,
|
|
135
|
+
'col': j + 2,
|
|
136
|
+
'symbol': '(',
|
|
137
|
+
'msg': "超链接缺少闭合的 ')'"
|
|
138
|
+
})
|
|
139
|
+
i = j + 1
|
|
140
|
+
continue
|
|
141
|
+
else:
|
|
142
|
+
i += 1
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
# 图片模式:$图片名$(
|
|
146
|
+
if ch == '$' and i + 1 < n:
|
|
147
|
+
j = i + 1
|
|
148
|
+
while j < n and line[j] != '$':
|
|
149
|
+
if line[j] == '^' and j + 1 < n:
|
|
150
|
+
j += 2
|
|
151
|
+
continue
|
|
152
|
+
j += 1
|
|
153
|
+
if j < n and line[j] == '$':
|
|
154
|
+
if j + 1 < n and line[j + 1] == '(':
|
|
155
|
+
k = j + 2
|
|
156
|
+
while k < n and line[k] != ')':
|
|
157
|
+
k += 1
|
|
158
|
+
if k >= n:
|
|
159
|
+
errors.append({
|
|
160
|
+
'line': line_num,
|
|
161
|
+
'col': j + 2,
|
|
162
|
+
'symbol': '(',
|
|
163
|
+
'msg': "图片缺少闭合的 ')'"
|
|
164
|
+
})
|
|
165
|
+
i = j + 1
|
|
166
|
+
continue
|
|
167
|
+
else:
|
|
168
|
+
i += 1
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
i += 1
|
|
172
|
+
|
|
173
|
+
return errors
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def print_errors(errors):
|
|
177
|
+
"""友好地打印错误信息"""
|
|
178
|
+
if not errors:
|
|
179
|
+
return
|
|
180
|
+
print("❌ 发现以下语法错误:\n")
|
|
181
|
+
for err in errors:
|
|
182
|
+
print(f" 第 {err['line']} 行,第 {err['col']} 列:{err['msg']} (符号: {err['symbol']})")
|
|
183
|
+
print(f"\n共发现 {len(errors)} 个错误,请修正后再试。")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ============================================================
|
|
187
|
+
# 1. 转义处理(^符号)
|
|
188
|
+
# ============================================================
|
|
189
|
+
|
|
190
|
+
def escape_text(text):
|
|
191
|
+
"""将 ^符号 替换为不可打印占位符"""
|
|
192
|
+
def repl(m):
|
|
193
|
+
return f'\x00ESC_{ord(m.group(1))}\x00'
|
|
194
|
+
return re.sub(r'\^([/\\?~\-*$•|!<\[\]])', repl, text)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def unescape_text(text):
|
|
198
|
+
"""恢复占位符为原字符"""
|
|
199
|
+
return re.sub(r'\x00ESC_(\d+)\x00', lambda m: chr(int(m.group(1))), text)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# ============================================================
|
|
203
|
+
# 2. 递归解析器(处理嵌套行内标记)
|
|
204
|
+
# ============================================================
|
|
205
|
+
|
|
206
|
+
class _InlineParser:
|
|
207
|
+
def __init__(self, text):
|
|
208
|
+
self.text = text
|
|
209
|
+
self.pos = 0
|
|
210
|
+
self.n = len(text)
|
|
211
|
+
|
|
212
|
+
def parse(self):
|
|
213
|
+
parts = []
|
|
214
|
+
while self.pos < self.n:
|
|
215
|
+
ch = self.text[self.pos]
|
|
216
|
+
if ch == '^':
|
|
217
|
+
self.pos += 1
|
|
218
|
+
if self.pos < self.n:
|
|
219
|
+
parts.append(self.text[self.pos])
|
|
220
|
+
self.pos += 1
|
|
221
|
+
continue
|
|
222
|
+
if ch == '?':
|
|
223
|
+
parts.append(self._parse_until('?', '<strong>', '</strong>'))
|
|
224
|
+
elif ch == '\\':
|
|
225
|
+
parts.append(self._parse_until('\\', '<i>', '</i>'))
|
|
226
|
+
elif ch == '-':
|
|
227
|
+
parts.append(self._parse_until('-', '<del>', '</del>'))
|
|
228
|
+
elif ch == '~':
|
|
229
|
+
parts.append(self._parse_until('~', '<code>', '</code>'))
|
|
230
|
+
elif ch == '*':
|
|
231
|
+
parts.append(self._parse_link())
|
|
232
|
+
else:
|
|
233
|
+
parts.append(ch)
|
|
234
|
+
self.pos += 1
|
|
235
|
+
return ''.join(parts)
|
|
236
|
+
|
|
237
|
+
def _parse_until(self, end_char, open_tag, close_tag):
|
|
238
|
+
self.pos += 1
|
|
239
|
+
inner_chars = []
|
|
240
|
+
while self.pos < self.n and self.text[self.pos] != end_char:
|
|
241
|
+
ch = self.text[self.pos]
|
|
242
|
+
if ch == '^':
|
|
243
|
+
self.pos += 1
|
|
244
|
+
if self.pos < self.n:
|
|
245
|
+
inner_chars.append(self.text[self.pos])
|
|
246
|
+
self.pos += 1
|
|
247
|
+
continue
|
|
248
|
+
inner_chars.append(ch)
|
|
249
|
+
self.pos += 1
|
|
250
|
+
if self.pos < self.n and self.text[self.pos] == end_char:
|
|
251
|
+
self.pos += 1
|
|
252
|
+
inner_text = ''.join(inner_chars)
|
|
253
|
+
parsed_inner = _InlineParser(inner_text).parse()
|
|
254
|
+
return f'{open_tag}{parsed_inner}{close_tag}'
|
|
255
|
+
|
|
256
|
+
def _parse_link(self):
|
|
257
|
+
self.pos += 1
|
|
258
|
+
text_start = self.pos
|
|
259
|
+
while self.pos < self.n and self.text[self.pos] != '*':
|
|
260
|
+
if self.text[self.pos] == '^':
|
|
261
|
+
self.pos += 1
|
|
262
|
+
if self.pos < self.n:
|
|
263
|
+
self.pos += 1
|
|
264
|
+
continue
|
|
265
|
+
self.pos += 1
|
|
266
|
+
if self.pos >= self.n or self.text[self.pos] != '*':
|
|
267
|
+
return '*' + self.text[text_start:self.pos]
|
|
268
|
+
link_text = self.text[text_start:self.pos]
|
|
269
|
+
self.pos += 1
|
|
270
|
+
if self.pos < self.n and self.text[self.pos] == '(':
|
|
271
|
+
self.pos += 1
|
|
272
|
+
url_start = self.pos
|
|
273
|
+
while self.pos < self.n and self.text[self.pos] != ')':
|
|
274
|
+
self.pos += 1
|
|
275
|
+
if self.pos < self.n and self.text[self.pos] == ')':
|
|
276
|
+
url = self.text[url_start:self.pos]
|
|
277
|
+
self.pos += 1
|
|
278
|
+
parsed_text = _InlineParser(link_text).parse()
|
|
279
|
+
return f'<a href="{url}">{parsed_text}</a>'
|
|
280
|
+
else:
|
|
281
|
+
return '*' + link_text + '*(' + self.text[url_start:self.pos]
|
|
282
|
+
else:
|
|
283
|
+
return '*' + link_text + '*'
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# ============================================================
|
|
287
|
+
# 3. 行内解析入口
|
|
288
|
+
# ============================================================
|
|
289
|
+
|
|
290
|
+
def parse_inline(text):
|
|
291
|
+
escaped_text = escape_text(text)
|
|
292
|
+
def replace_comment(m):
|
|
293
|
+
if re.search(r'https?://|ftp://|@', m.group(1)):
|
|
294
|
+
return m.group(0)
|
|
295
|
+
return f'<!-- {m.group(1)} -->'
|
|
296
|
+
escaped_text = re.sub(r'<([^>]+)>', replace_comment, escaped_text)
|
|
297
|
+
escaped_text = re.sub(r'\[(\d+)\]<([^>]+)>', r'<sup title="\2">[\1]</sup>', escaped_text)
|
|
298
|
+
escaped_text = re.sub(r'\$(.+?)\$\(([^\)]+)\)', r'<img src="\2" alt="\1">', escaped_text)
|
|
299
|
+
parser = _InlineParser(escaped_text)
|
|
300
|
+
result = parser.parse()
|
|
301
|
+
result = unescape_text(result)
|
|
302
|
+
return result
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# ============================================================
|
|
306
|
+
# 4. 块级解析(支持多行表格)
|
|
307
|
+
# ============================================================
|
|
308
|
+
|
|
309
|
+
def zarkdown_to_html(text):
|
|
310
|
+
lines = text.split('\n')
|
|
311
|
+
result, i = [], 0
|
|
312
|
+
|
|
313
|
+
while i < len(lines):
|
|
314
|
+
raw_line = lines[i]
|
|
315
|
+
escaped_line = escape_text(raw_line)
|
|
316
|
+
|
|
317
|
+
# ---- 多行代码块 ----
|
|
318
|
+
if re.match(r'^~[a-zA-Z0-9_]+$', raw_line.strip()):
|
|
319
|
+
lang = raw_line.strip()[1:]
|
|
320
|
+
code_lines = []
|
|
321
|
+
i += 1
|
|
322
|
+
while i < len(lines) and lines[i].strip() != '~':
|
|
323
|
+
code_lines.append(lines[i])
|
|
324
|
+
i += 1
|
|
325
|
+
if i < len(lines) and lines[i].strip() == '~':
|
|
326
|
+
i += 1
|
|
327
|
+
result.append(f'<pre><code class="language-{lang}">{"\n".join(code_lines)}</code></pre>')
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
# ---- 表格(支持多行单元格) ----
|
|
331
|
+
if re.match(r'^\|.*\|$', escaped_line) and i + 2 < len(lines):
|
|
332
|
+
next_esc = escape_text(lines[i+1])
|
|
333
|
+
after_esc = escape_text(lines[i+2]) if i+2 < len(lines) else ''
|
|
334
|
+
if re.match(r'^-{5,}$', next_esc.strip()) and re.match(r'^\|.*\|$', after_esc):
|
|
335
|
+
headers = [h.strip() for h in escaped_line.split('|')[1:-1]]
|
|
336
|
+
html = ['<table><thead><tr>']
|
|
337
|
+
for h in headers:
|
|
338
|
+
html.append(f'<th>{parse_inline(h)}</th>')
|
|
339
|
+
html.append('</tr></thead><tbody>')
|
|
340
|
+
j = i + 2
|
|
341
|
+
current_cells = None
|
|
342
|
+
in_data_row = False
|
|
343
|
+
while j < len(lines):
|
|
344
|
+
cur_raw = lines[j]
|
|
345
|
+
cur_esc = escape_text(cur_raw)
|
|
346
|
+
trimmed = cur_esc.strip()
|
|
347
|
+
if re.match(r'^-{5,}$', trimmed):
|
|
348
|
+
j += 1
|
|
349
|
+
continue
|
|
350
|
+
if re.match(r'^\|.*\|$', cur_esc):
|
|
351
|
+
if in_data_row and current_cells is not None:
|
|
352
|
+
html.append('<tr>')
|
|
353
|
+
for cell in current_cells:
|
|
354
|
+
html.append(f'<td>{parse_inline(cell)}</td>')
|
|
355
|
+
html.append('</tr>')
|
|
356
|
+
current_cells = None
|
|
357
|
+
in_data_row = False
|
|
358
|
+
cells = [c.strip() for c in cur_esc.split('|')[1:-1]]
|
|
359
|
+
while len(cells) < len(headers):
|
|
360
|
+
cells.append('')
|
|
361
|
+
current_cells = cells
|
|
362
|
+
in_data_row = True
|
|
363
|
+
j += 1
|
|
364
|
+
continue
|
|
365
|
+
elif in_data_row and cur_raw.startswith(' '):
|
|
366
|
+
if current_cells and len(current_cells) > 0:
|
|
367
|
+
content = cur_raw.lstrip()
|
|
368
|
+
current_cells[-1] = current_cells[-1] + '[Z_BR]' + content
|
|
369
|
+
j += 1
|
|
370
|
+
continue
|
|
371
|
+
else:
|
|
372
|
+
break
|
|
373
|
+
if in_data_row and current_cells is not None:
|
|
374
|
+
html.append('<tr>')
|
|
375
|
+
for cell in current_cells:
|
|
376
|
+
html.append(f'<td>{parse_inline(cell)}</td>')
|
|
377
|
+
html.append('</tr>')
|
|
378
|
+
html.append('</tbody></table>')
|
|
379
|
+
result.append(''.join(html))
|
|
380
|
+
i = j
|
|
381
|
+
continue
|
|
382
|
+
|
|
383
|
+
# ---- 标题 ----
|
|
384
|
+
if re.match(r'^//// ', escaped_line):
|
|
385
|
+
result.append(f'<h4>{parse_inline(escaped_line[5:])}</h4>')
|
|
386
|
+
elif re.match(r'^/// ', escaped_line):
|
|
387
|
+
result.append(f'<h3>{parse_inline(escaped_line[4:])}</h3>')
|
|
388
|
+
elif re.match(r'^// ', escaped_line):
|
|
389
|
+
result.append(f'<h2>{parse_inline(escaped_line[3:])}</h2>')
|
|
390
|
+
elif re.match(r'^/ ', escaped_line):
|
|
391
|
+
result.append(f'<h1>{parse_inline(escaped_line[2:])}</h1>')
|
|
392
|
+
|
|
393
|
+
# ---- 无序列表 ----
|
|
394
|
+
elif escaped_line.startswith('!'):
|
|
395
|
+
items = []
|
|
396
|
+
while i < len(lines) and escape_text(lines[i]).startswith('!'):
|
|
397
|
+
items.append(f'<li>{parse_inline(escape_text(lines[i])[1:].strip())}</li>')
|
|
398
|
+
i += 1
|
|
399
|
+
result.append(f'<ul>{"".join(items)}</ul>')
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
# ---- 有序列表 ----
|
|
403
|
+
elif escaped_line.startswith('•'):
|
|
404
|
+
items = []
|
|
405
|
+
while i < len(lines) and escape_text(lines[i]).startswith('•'):
|
|
406
|
+
items.append(f'<li>{parse_inline(escape_text(lines[i])[1:].strip())}</li>')
|
|
407
|
+
i += 1
|
|
408
|
+
result.append(f'<ol>{"".join(items)}</ol>')
|
|
409
|
+
continue
|
|
410
|
+
|
|
411
|
+
# ---- 引用 ----
|
|
412
|
+
elif escaped_line.startswith('|'):
|
|
413
|
+
result.append(f'<blockquote>{parse_inline(escaped_line[1:].strip())}</blockquote>')
|
|
414
|
+
|
|
415
|
+
# ---- 分割线 ----
|
|
416
|
+
elif re.match(r'^-{5,}$', escaped_line.strip()):
|
|
417
|
+
result.append('<hr>')
|
|
418
|
+
|
|
419
|
+
# ---- 普通段落 ----
|
|
420
|
+
else:
|
|
421
|
+
if raw_line.strip():
|
|
422
|
+
result.append(f'<p>{parse_inline(escaped_line)}</p>')
|
|
423
|
+
else:
|
|
424
|
+
result.append('')
|
|
425
|
+
i += 1
|
|
426
|
+
|
|
427
|
+
# 拼接 HTML 内容
|
|
428
|
+
html_content = '\n'.join(result)
|
|
429
|
+
# 将占位符 [Z_BR] 替换为真正的 <br>
|
|
430
|
+
html_content = html_content.replace('[Z_BR]', '<br>')
|
|
431
|
+
|
|
432
|
+
# 返回完整 HTML 页面
|
|
433
|
+
return f'''<!DOCTYPE html>
|
|
434
|
+
<html>
|
|
435
|
+
<head>
|
|
436
|
+
<meta charset="UTF-8">
|
|
437
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
438
|
+
<title>Zarkdown 输出</title>
|
|
439
|
+
<style>
|
|
440
|
+
body {{
|
|
441
|
+
max-width: 800px;
|
|
442
|
+
margin: 40px auto;
|
|
443
|
+
padding: 0 20px;
|
|
444
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
445
|
+
line-height: 1.6;
|
|
446
|
+
color: #1e1e2f;
|
|
447
|
+
background-color: #fafafa;
|
|
448
|
+
}}
|
|
449
|
+
h1, h2, h3, h4 {{ color: #1e1e2f; margin-top: 1.5em; }}
|
|
450
|
+
h1 {{ border-bottom: 2px solid #eee; padding-bottom: 0.3em; }}
|
|
451
|
+
blockquote {{
|
|
452
|
+
margin: 1.2em 0;
|
|
453
|
+
padding: 0.8em 1.2em;
|
|
454
|
+
border-left: 4px solid #4a90d9;
|
|
455
|
+
background-color: #f0f4ff;
|
|
456
|
+
border-radius: 0 4px 4px 0;
|
|
457
|
+
color: #2c3e50;
|
|
458
|
+
}}
|
|
459
|
+
table {{
|
|
460
|
+
border-collapse: collapse;
|
|
461
|
+
width: 100%;
|
|
462
|
+
margin: 1em 0;
|
|
463
|
+
font-size: 0.95em;
|
|
464
|
+
}}
|
|
465
|
+
th, td {{
|
|
466
|
+
border: 1px solid #ddd;
|
|
467
|
+
padding: 8px 12px;
|
|
468
|
+
text-align: left;
|
|
469
|
+
vertical-align: top;
|
|
470
|
+
}}
|
|
471
|
+
th {{
|
|
472
|
+
background-color: #f2f2f2;
|
|
473
|
+
font-weight: 600;
|
|
474
|
+
}}
|
|
475
|
+
tr:nth-child(even) {{ background-color: #f9f9f9; }}
|
|
476
|
+
pre {{
|
|
477
|
+
background: #f6f8fa;
|
|
478
|
+
padding: 16px;
|
|
479
|
+
border-radius: 6px;
|
|
480
|
+
overflow-x: auto;
|
|
481
|
+
}}
|
|
482
|
+
code {{
|
|
483
|
+
font-family: "SF Mono", "Fira Code", monospace;
|
|
484
|
+
font-size: 0.9em;
|
|
485
|
+
}}
|
|
486
|
+
hr {{
|
|
487
|
+
border: none;
|
|
488
|
+
border-top: 2px dashed #ccc;
|
|
489
|
+
margin: 2em 0;
|
|
490
|
+
}}
|
|
491
|
+
sup {{
|
|
492
|
+
background: #eee;
|
|
493
|
+
padding: 0.1em 0.4em;
|
|
494
|
+
border-radius: 4px;
|
|
495
|
+
font-size: 0.75em;
|
|
496
|
+
cursor: help;
|
|
497
|
+
}}
|
|
498
|
+
img {{
|
|
499
|
+
max-width: 100%;
|
|
500
|
+
height: auto;
|
|
501
|
+
}}
|
|
502
|
+
</style>
|
|
503
|
+
</head>
|
|
504
|
+
<body>
|
|
505
|
+
{html_content}
|
|
506
|
+
</body>
|
|
507
|
+
</html>'''
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
# ============================================================
|
|
511
|
+
# 5. 命令行入口(集成错误检查器,支持 --no-check)
|
|
512
|
+
# ============================================================
|
|
513
|
+
|
|
514
|
+
def main():
|
|
515
|
+
parser = argparse.ArgumentParser(description="将 Zarkdown 文件转为 HTML/PDF/DOCX/LaTeX")
|
|
516
|
+
parser.add_argument("input", help="输入的 .zkdn 文件路径")
|
|
517
|
+
parser.add_argument("-o", "--output", help="输出的文件路径(默认:根据输入文件名和格式自动生成)", default=None)
|
|
518
|
+
parser.add_argument("-w", "--watch", action="store_true", help="监听文件变化,自动重新转换并刷新浏览器(仅 HTML 格式)")
|
|
519
|
+
parser.add_argument("-f", "--format",
|
|
520
|
+
choices=['html', 'pdf', 'docx', 'latex'],
|
|
521
|
+
default='html',
|
|
522
|
+
help="输出格式: html, pdf, docx, latex (默认: html)")
|
|
523
|
+
parser.add_argument("--no-check", action="store_true", help="跳过语法错误检查(不推荐)")
|
|
524
|
+
args = parser.parse_args()
|
|
525
|
+
|
|
526
|
+
# 自动生成输出文件名
|
|
527
|
+
if args.output is None:
|
|
528
|
+
base = args.input.rsplit('.', 1)[0]
|
|
529
|
+
if args.format == 'html':
|
|
530
|
+
args.output = base + '.html'
|
|
531
|
+
elif args.format == 'pdf':
|
|
532
|
+
args.output = base + '.pdf'
|
|
533
|
+
elif args.format == 'docx':
|
|
534
|
+
args.output = base + '.docx'
|
|
535
|
+
elif args.format == 'latex':
|
|
536
|
+
args.output = base + '.tex'
|
|
537
|
+
else:
|
|
538
|
+
args.output = base + '.html'
|
|
539
|
+
|
|
540
|
+
def convert():
|
|
541
|
+
try:
|
|
542
|
+
with open(args.input, 'r', encoding='utf-8-sig') as f:
|
|
543
|
+
content = f.read()
|
|
544
|
+
|
|
545
|
+
# ---- 执行错误检查(除非用户指定 --no-check) ----
|
|
546
|
+
if not args.no_check:
|
|
547
|
+
errors = validate_zarkdown(content)
|
|
548
|
+
if errors:
|
|
549
|
+
print_errors(errors)
|
|
550
|
+
return # 停止转换
|
|
551
|
+
|
|
552
|
+
# ---- 正常转换 ----
|
|
553
|
+
html = zarkdown_to_html(content)
|
|
554
|
+
|
|
555
|
+
if args.format == 'html':
|
|
556
|
+
with open(args.output, 'w', encoding='utf-8') as f:
|
|
557
|
+
f.write(html)
|
|
558
|
+
print(f"✅ {args.input} -> {args.output} ({time.strftime('%H:%M:%S')})")
|
|
559
|
+
if args.watch:
|
|
560
|
+
webbrowser.open('file://' + os.path.abspath(args.output))
|
|
561
|
+
else:
|
|
562
|
+
if pypandoc is None:
|
|
563
|
+
print("❌ 导出 PDF/DOCX/LaTeX 需要安装 pypandoc 和 pandoc 工具。")
|
|
564
|
+
print(" pip install pypandoc")
|
|
565
|
+
print(" https://pandoc.org/installing.html")
|
|
566
|
+
return
|
|
567
|
+
extra_args = []
|
|
568
|
+
if args.format == 'pdf':
|
|
569
|
+
extra_args = ['--pdf-engine=xelatex']
|
|
570
|
+
try:
|
|
571
|
+
pypandoc.convert_text(
|
|
572
|
+
html,
|
|
573
|
+
to=args.format,
|
|
574
|
+
format='html',
|
|
575
|
+
outputfile=args.output,
|
|
576
|
+
extra_args=extra_args
|
|
577
|
+
)
|
|
578
|
+
print(f"✅ {args.input} -> {args.output} ({args.format}) ({time.strftime('%H:%M:%S')})")
|
|
579
|
+
except Exception as e:
|
|
580
|
+
print(f"❌ 导出 {args.format} 失败: {e}")
|
|
581
|
+
print(" 请确认 pandoc 已正确安装,且 PDF 导出需要 LaTeX 引擎 (如 xelatex)")
|
|
582
|
+
except FileNotFoundError:
|
|
583
|
+
print(f"❌ 错误:找不到文件 {args.input}")
|
|
584
|
+
except Exception as e:
|
|
585
|
+
print(f"❌ 转换出错: {e}")
|
|
586
|
+
|
|
587
|
+
# 首次转换
|
|
588
|
+
convert()
|
|
589
|
+
|
|
590
|
+
if args.watch and args.format == 'html':
|
|
591
|
+
print(f"👀 正在监听 {args.input} 的变化(按 Ctrl+C 停止)...")
|
|
592
|
+
try:
|
|
593
|
+
last_mtime = os.path.getmtime(args.input)
|
|
594
|
+
while True:
|
|
595
|
+
current_mtime = os.path.getmtime(args.input)
|
|
596
|
+
if current_mtime > last_mtime:
|
|
597
|
+
last_mtime = current_mtime
|
|
598
|
+
convert()
|
|
599
|
+
time.sleep(0.5)
|
|
600
|
+
except KeyboardInterrupt:
|
|
601
|
+
print("\n👋 停止监听")
|
|
602
|
+
elif args.watch and args.format != 'html':
|
|
603
|
+
print("⚠️ 监听模式仅支持 HTML 格式。请使用 -f html 或移除 -w 参数。")
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
if __name__ == "__main__":
|
|
607
|
+
main()
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zarkdown
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: Zarkdown - 键盘友好型纯文本标记语言
|
|
5
|
+
Author-email: yangzizhoudiwuxuande <yangzizhou2026@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yangzizhoudiwuxuande/zarkdown
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/yangzizhoudiwuxuande/zarkdown/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# ✨ Zarkdown (版本2.0.1)
|
|
15
|
+
|
|
16
|
+
> **键盘友好型纯文本标记语言** —— 所有符号均位于键盘主键区,无需按 Shift 组合键。
|
|
17
|
+
|
|
18
|
+
[](https://python.org)
|
|
19
|
+
[](LICENSE)
|
|
20
|
+
[](https://github.com/yangzizhoudiwuxuande/zarkdown/stargazers)
|
|
21
|
+
|
|
22
|
+
Zarkdown 是一种全新的纯文本标记语言,专为**快速写作**和**极致键盘效率**而设计。它适合用来写笔记、技术文档、博客文章,甚至可以作为配置文件的格式。
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🎯 设计哲学
|
|
27
|
+
|
|
28
|
+
- **键盘友好**:所有语法符号都在主键区,手指不需要大范围移动
|
|
29
|
+
- **直观易记**:`/` 表示标题(像路径层级),`?` 表示加粗(像强调语气)
|
|
30
|
+
- **纯文本**:任何文本编辑器都能打开,人类可读性强
|
|
31
|
+
- **可扩展**:支持表格、代码块、脚注等高级功能
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 📜 完整语法表
|
|
36
|
+
|
|
37
|
+
| 效果 | 语法 | 示例 | 渲染为 |
|
|
38
|
+
| :--- | :--- | :--- | :--- |
|
|
39
|
+
| **标题 1~4 级** | `/` `//` `///` `////` + 空格 | `/ 大标题` | `<h1>`~`<h4>` |
|
|
40
|
+
| **粗体** | `?文字?` | `?重要?` | `<strong>` |
|
|
41
|
+
| **斜体** | `\文字\` | `\斜体\` | `<i>` |
|
|
42
|
+
| **删除线** | `-文字-` | `-删掉-` | `<del>` |
|
|
43
|
+
| **超链接** | `*文字*(链接)` | `*点击*(https://x.com)` | `<a>` |
|
|
44
|
+
| **图片** | `$图片名$(链接)` | `$logo$(./pic.png)` | `<img>` |
|
|
45
|
+
| **行内代码** | `~文字~` | `~npm i~` | `<code>` |
|
|
46
|
+
| **多行代码块** | `~语言` 开头 + `~` 结尾 | `~python`...`~` | `<pre><code>` |
|
|
47
|
+
| **无序列表** | `!文字`(行首) | `!苹果` | `<ul><li>` |
|
|
48
|
+
| **有序列表** | `•文字`(行首,U+2022) | `•第一` | `<ol><li>` |
|
|
49
|
+
| **引用** | `\|文字`(行首) | `\| 引文` | `<blockquote>` |
|
|
50
|
+
| **表格** | `\| 表头 \|` + `------` | 见下方示例 | `<table>` |
|
|
51
|
+
| **注释** | `<文字>`(不含URL) | `<备注>` | `<!-- -->` |
|
|
52
|
+
| **脚注** | `[数字]<文字>` | `[1]<说明>` | `<sup title="">` |
|
|
53
|
+
| **分割线** | `-----`(独占一行) | | `<hr>` |
|
|
54
|
+
| **转义** | `^` 加在任意符号前 | `^/ 不是标题` | 原样输出 |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🚀 快速上手
|
|
59
|
+
|
|
60
|
+
### 安装
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# 克隆仓库
|
|
64
|
+
git clone https://github.com/yangzizhoudiwuxuande/zarkdown.git
|
|
65
|
+
cd zarkdown
|
|
66
|
+
|
|
67
|
+
# 创建虚拟环境(推荐)
|
|
68
|
+
python3 -m venv venv
|
|
69
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
70
|
+
|
|
71
|
+
# 安装
|
|
72
|
+
pip install -e .
|
|
73
|
+
```
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 使用
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# 转换 .zkdn 文件为 .html
|
|
80
|
+
zarkdown example.zkdn -o example.html
|
|
81
|
+
|
|
82
|
+
# 直接输出到终端(不生成文件)
|
|
83
|
+
zarkdown example.zkdn
|
|
84
|
+
```
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 示例
|
|
88
|
+
### 转为HTML
|
|
89
|
+
创建一个`hello.zkdn`文件:
|
|
90
|
+
```zkdn
|
|
91
|
+
/ Zarkdown 欢迎页
|
|
92
|
+
|
|
93
|
+
?恭喜?,您的格式已经跑通了!
|
|
94
|
+
|
|
95
|
+
这里有一个链接:*点击访问*(https://github.com/yangzizhoudiwuxuande)
|
|
96
|
+
|
|
97
|
+
~python
|
|
98
|
+
print("Hello Zarkdown!")
|
|
99
|
+
~
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
然后运行:
|
|
103
|
+
```bash
|
|
104
|
+
zarkdown hello.zkdn -w
|
|
105
|
+
```
|
|
106
|
+
浏览器会自动打开,显示渲染后的 HTML 页面。修改 `hello.zkdn` 并保存,页面会自动刷新。
|
|
107
|
+
### 转为Word/LaTex
|
|
108
|
+
转为Word/LaTex需要安装Pandoc。
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
brew install pandoc
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
转为Word
|
|
115
|
+
```bash
|
|
116
|
+
zarkdown my_note.zkdn -f docx
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
转为LaTex
|
|
120
|
+
```bash
|
|
121
|
+
zarkdown my_note.zkdn -f latex
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 📸 效果预览
|
|
125
|
+
[](https://imgchr.com/i/pm4y4lF)
|
|
126
|
+
|
|
127
|
+
## 贡献
|
|
128
|
+
欢迎任何形式的贡献!你可以:
|
|
129
|
+
|
|
130
|
+
* 🐛 报告 Bug(在 Issues 中描述)
|
|
131
|
+
* 💡 提出新功能建议
|
|
132
|
+
* 📝 完善文档
|
|
133
|
+
* 🔧 提交 Pull Request
|
|
134
|
+
## 📄 许可证
|
|
135
|
+
本项目采用 MIT License 开源协议,你可以自由使用、修改、分发。
|
|
136
|
+
## ❤️ 致谢
|
|
137
|
+
感谢你阅读这份文档!如果觉得 Zarkdown 有趣,欢迎给项目点一颗 ⭐ 星,让更多人看到它。
|
|
138
|
+
## 📝 更新日志
|
|
139
|
+
**v2.0.1 (2026-08-01)**
|
|
140
|
+
- **语法错误检查器**
|
|
141
|
+
转换前自动扫描全文,检测未闭合的成对符号(`?`、`\`、`-`、`~`、`*`、`$`、`[`、`<`),并精确报告行号和列号,帮助用户快速定位问题。
|
|
142
|
+
|
|
143
|
+
- **括号配对检查**
|
|
144
|
+
针对超链接 `*文字*(链接)` 和图片 `$图片名$(链接)`,自动检测缺失的闭合括号 `)`,避免生成无效链接。
|
|
145
|
+
|
|
146
|
+
- **多行表格支持**
|
|
147
|
+
表格单元格内允许换行,通过缩进续行自动合并内容,并在输出中转换为 `<br>` 换行符。
|
|
148
|
+
|
|
149
|
+
- **转义字符 `^`**
|
|
150
|
+
在任意特殊符号前添加 `^` 即可原样输出,避免被解析器处理,例如 `^/ 不是标题`。
|
|
151
|
+
|
|
152
|
+
- **智能跳过机制**
|
|
153
|
+
错误检查器自动跳过分割线(以 `-----` 开头的行)、表格分隔行以及多行代码块内部内容,避免误报。
|
|
154
|
+
|
|
155
|
+
- **自动生成输出文件名**
|
|
156
|
+
若不指定 `-o`,输出文件名将根据输入文件名和格式自动生成(如 `note.html`、`note.pdf` 等)。
|
|
157
|
+
|
|
158
|
+
**v2.0.0 (2026-07-28)**
|
|
159
|
+
* 支持导出为Word/LaTex等格式(需安装Pandoc)
|
|
160
|
+
|
|
161
|
+
**v1.2.2 (2026-07-26)**
|
|
162
|
+
* 支持多行表格
|
|
163
|
+
|
|
164
|
+
**v1.2.1 (2026-07-26)**
|
|
165
|
+
* 支持嵌套语法(粗体里套斜体、超链接里套粗体等)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
**v1.2.0 (2026-07-25)**
|
|
169
|
+
* 使引用和表格变得更好看
|
|
170
|
+
|
|
171
|
+
**v1.1.0 (2026-07-25)**
|
|
172
|
+
* ✨ 新增 `-w` 监听模式,保存即自动重新转换
|
|
173
|
+
* ✨ 自动在浏览器中打开生成 HTML,实现实时预览
|
|
174
|
+
* 🐛 修复中文编码问题,兼容带 BOM 的 UTF-8 文件
|
|
175
|
+
* 🐛 修复 macOS 下相对路径打开失败的问题
|
|
176
|
+
* 📝 完善 README 文档,增加完整语法表和示例
|
|
177
|
+
|
|
178
|
+
**v1.0.0 (2026-07-25)**
|
|
179
|
+
* 🎉 首次发布,支持完整的 Zarkdown 语法
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
Happy Writing with Zarkdown!
|
|
185
|
+
|
|
186
|
+
## 联系
|
|
187
|
+
yangzizhou2026@outlook.com
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/zarkdown/__init__.py
|
|
4
|
+
src/zarkdown/core.py
|
|
5
|
+
src/zarkdown.egg-info/PKG-INFO
|
|
6
|
+
src/zarkdown.egg-info/SOURCES.txt
|
|
7
|
+
src/zarkdown.egg-info/dependency_links.txt
|
|
8
|
+
src/zarkdown.egg-info/entry_points.txt
|
|
9
|
+
src/zarkdown.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zarkdown
|