Metadata-Version: 2.4
Name: qparse
Version: 0.1.6
Summary: Structured Markdown lesson plans to DOCX, with .qmd pack/unpack tooling.
Author: hanbuhuai
License: MIT
Project-URL: Homepage, https://github.com/hanbuhuai/qparse
Project-URL: Repository, https://github.com/hanbuhuai/qparse
Project-URL: Issues, https://github.com/hanbuhuai/qparse/issues
Keywords: markdown,docx,pandoc,lesson-plan,qmd
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing :: Markup
Classifier: Topic :: Education
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: beautifulsoup4>=4.12
Requires-Dist: python-docx>=1.1
Requires-Dist: docxcompose>=1.4
Requires-Dist: Jinja2>=3.1
Requires-Dist: pypandoc>=1.13
Requires-Dist: typer>=0.12
Dynamic: license-file

# QParse

将结构化 Markdown 教案渲染为 Word（DOCX），并支持 `.qmd` 单文件打包分发。

典型场景：数学备课 / 习题讲义。支持：

- `@import` 分题导入
- `$...$` LaTeX 公式
- 填空 / 选择 / 多选 / 简答
- 自动生成参考答案
- Markdown 表格与 HTML 表格
- 图片、分页、主题模板
- `.qmd` 打包 / 解包（资源按 SHA-256 去重）

---

## 目录结构

```text
QParse/
├── qparse/                     # 核心库
│   ├── markdwon_loader.py      # Markdown 加载、@import、题号装饰
│   ├── markdwon_render.py      # render.md 解析与整篇渲染
│   ├── markdwon_document/      # 文档节点树（题干/答案/表格/图片）
│   ├── docx_render/            # Pandoc + python-docx 输出 DOCX
│   ├── render_option/          # 默认主题、模板、referance.docx
│   ├── qmd_packer.py           # .qmd 打包
│   ├── qmd_unpacker.py         # .qmd 解包
│   └── utils/                  # 空格/换行保护等工具
├── qmd_cli.py                  # Typer 命令行入口
├── dev.py                      # 开发调试脚本
├── render.md                   # 渲染入口配置示例
├── QParse.code-workspace       # 工作区（代码 + 备课目录）
├── qmd-package-design.md       # .qmd 设计说明
└── README.md
```

---

## 环境依赖

建议使用项目内虚拟环境：

```powershell
cd d:\dev\QParse
.\venv\Scripts\activate
```

主要依赖：

| 包 | 用途 |
|----|------|
| `beautifulsoup4` | HTML/Markdown 中间结构解析 |
| `pypandoc` + Pandoc | Markdown/HTML → DOCX |
| `python-docx` | DOCX 后处理（表格、边框、图片） |
| `docxcompose` | 多段 DOCX 合并 |
| `jinja2` | 题干/答案模板渲染 |
| `typer` | 命令行工具 |

系统需已安装 [Pandoc](https://pandoc.org/)。

---

## 快速开始

### 1. 为 main.md 生成 render.md

```powershell
python qmd_cli.py init "d:\workspace\lesson_plan\...\main.md" -o ".\render.md" -f
```

生成示例：

```markdown
<render src="D:\workspace\...\main.md">
    <file type="docx" dist="习题版.docx">
        <doc theme="Worksheet">
            <after>
<pagebreak></pagebreak>
# 参考答案

<reference-answer></reference-answer>

            </after>
        </doc>
    </file>
</render>
```

### 2. 直接渲染 DOCX

```powershell
python qmd_cli.py render .\render.md
```

### 3. 打包为 .qmd 再解包渲染

```powershell
# 打包
python qmd_cli.py pack .\render.md -o .\lesson.qmd

# 解包（目录保留）
python qmd_cli.py unpack .\lesson.qmd -t .\lesson_unpacked

# 渲染解包后的入口
python qmd_cli.py render .\lesson_unpacked\render.md

# 或一键：打包 → 解包 → 渲染
python qmd_cli.py build .\render.md -o .\lesson.qmd -u .\lesson_unpacked
```

---

## 命令行工具

入口文件：`qmd_cli.py`

```powershell
python qmd_cli.py --help
```

| 命令 | 说明 |
|------|------|
| `init` | 输入 `main.md`，生成默认 `render.md` |
| `pack` | 将 `render.md` 及依赖打包为 `.qmd` |
| `unpack` | 解包 `.qmd` 到工作目录 |
| `render` | 根据 `render.md` 渲染 DOCX |
| `build` | 打包 + 解包 + 渲染一键完成 |

### init

```powershell
python qmd_cli.py init <main.md> [-o render.md] [-d 习题版.docx] [--theme Worksheet] [-f]
```

- 默认输出到 `main.md` 同级的 `render.md`
- `-o` 可指定输出路径（例如项目根目录）
- `-f` 覆盖已存在文件

### pack

```powershell
python qmd_cli.py pack <render.md> [-o lesson.qmd] [-w QParse.code-workspace]
```

### unpack

```powershell
python qmd_cli.py unpack <lesson.qmd> [-t lesson_unpacked]
```

### render

```powershell
python qmd_cli.py render <render.md>
```

### build

```powershell
python qmd_cli.py build <render.md> [-o lesson.qmd] [-u lesson_unpacked]
```

---

## Python API

```python
from qparse import (
    MarkdownLoader,
    MarkdwonRender,
    QmdPacker,
    QmdUnpacker,
)

# 加载 Markdown（展开 @import，装饰题号）
loader = MarkdownLoader(r"d:\path\to\main.md")
soup = loader.get_soup()
answers = loader.get_reference_answer_markdwon()

# 按 render.md 渲染
render = MarkdwonRender(src=r"d:\dev\QParse\render.md")
outputs = render.render()  # List[Path]

# 打包 / 解包
packer = QmdPacker()
qmd_path = packer.pack(entry=r"d:\dev\QParse\render.md", output=r"d:\dev\QParse\lesson.qmd")

unpacker = QmdUnpacker(str(qmd_path))
work_dir = unpacker.unpack(target=r"d:\dev\QParse\lesson_unpacked")
entry = unpacker.entry_path
```

开发调试可参考 `dev.py`：

```powershell
python dev.py
```

---

## Markdown 约定

### 分文件导入

```markdown
@import "./01/main.md"
@import "./02/main.md"
```

### 题目结构（示意）

```html
<question>
  <stem>
    在 $\triangle ABC$ 中，……（  ）。
  </stem>
  <span class="chiose">C</span>
</question>
```

题型 class：

| class | 含义 |
|-------|------|
| `blank` | 填空 |
| `chiose` / `choice` | 单选 |
| `multiple-choice` / `multiple-chiose` | 多选 |
| `answer` | 简答 |

Loader 会自动：

1. 为每个 `<question>` 写入连续 `question_id`
2. 标记 `span[type=answer]`
3. 写入 `answer_types`

### 参考答案

在 `render.md` 的 `before` / `after` 中使用：

```html
<reference-answer></reference-answer>
```

生成规则：

- 填空 / 选择 / 多选：连续客观题合并为 Markdown 表格，每行列数由 `reference-answer.table_max_columns` 控制（默认 4）
- 简答：`题号、答案`（使用顿号，避免 Pandoc 识别为有序列表）
- 答案只取文本，不保留原始 `<span>`

示例：

```markdown
| 1 | 2 |
| --- | --- |
| $\sqrt{3}-1$ | C |

3、（Ⅰ）$\frac{8\sqrt{10}}{9}$；（Ⅱ）$\frac{\sqrt{6}}{2}$
```

### 公式与换行

- 行内公式：`$...$`
- 源文件中的 `\n` 在加载阶段转为 `<br/>`，DOCX 使用 Pandoc `hard_line_breaks`
- 普通空格与 Tab 经 `HtmlFullProtector` 保护，避免 BeautifulSoup 丢失

### 图片

```markdown
![说明](./assets/img/demo.png)
```

打包时图片进入 `resources/<sha256>.ext`，解包后复制到文档同级 `assets/`。

---

## render.md 说明

`render.md` 是渲染编排入口，不是正文本身。

```html
<render src="绝对或相对路径/main.md" theme="可选主题json">
    <file type="docx" dist="习题版.docx">
        <doc theme="Worksheet">
            <before>…</before>
            <after>
                <pagebreak></pagebreak>
                # 参考答案
                <reference-answer></reference-answer>
            </after>
        </doc>
    </file>
</render>
```

要点：

- `src`：正文 `main.md`（相对路径相对 `render.md` 所在目录解析）
- `file@dist`：输出 DOCX 文件名/路径
- `doc@theme`：主题名（来自默认 theme 或外部 json）
- `before` / `after`：插入正文前后的内容
- `<reference-answer>`：自动替换为参考答案 Markdown

---

## DOCX 渲染流程

```text
render.md
  → MarkdownLoader 加载入口与正文
  → 展开 @import / 图片 / 题号
  → before/after 插入（含参考答案）
  → MarkdwonDocument 生成中间 Markdown/HTML buffer
  → Pandoc 转 DOCX（tex_math_dollars + hard_line_breaks + reference-doc）
  → python-docx 后处理表格样式、图片、合并
  → 复制到 dist
  → 清理本次 .qmd_runtime/<uuid>
```

表格相关默认配置（可在 `render_option` 中调整）：

- 宽度 100%
- 表头加粗
- 表头底色
- 边框颜色
- 单元格居中

并发渲染时，每个 `DocxRender` 使用独立临时子目录，避免互相删除文件。

---

## .qmd 打包设计（摘要）

详细设计见 [qmd-package-design.md](./qmd-package-design.md)。

包内结构：

```text
lesson.qmd
├── manifest.json
├── render.md
├── documents/          # 保持目录关系的 Markdown
├── resources/          # 二进制资源，按 SHA-256 去重
├── themes/
└── templates/
```

规则：

1. 绝对路径资源复制后转为包内相对路径
2. 相同内容只存一份（哈希去重）
3. 解包时按需把资源放到各 Markdown 同级 `assets/`
4. 编辑器可直接预览 `./assets/...`
5. 重新打包时重新计算哈希并清理未引用资源

---

## 工作区

`QParse.code-workspace` 通常包含：

1. 代码目录：`d:\dev\QParse`
2. 备课目录：如 `02-必修2备课`

打包器会读取 workspace folders，将文档放入：

```text
documents/<index>-<folder-name>/...
```

避免多个工作区目录同名冲突。

---

## 常见问题

### 1. 简答题被识别成 Word 有序列表

参考答案使用 `3、` 而不是 `3.`。

### 2. 解包后找不到 main.md

`MarkdwonRender` 会把 `render.md` 中的相对 `src` 解析为相对 `render.md` 所在目录。请用解包后的 `render.md` 作为渲染入口。

### 3. 两个进程同时渲染报临时文件不存在

临时目录已改为 `.qmd_runtime/<uuid>/`，每个渲染实例独立。

### 4. 表格公式丢失

Markdown 分支使用：

```text
markdown+tex_math_dollars+hard_line_breaks
```

HTML 表格分支使用：

```text
html+tex_math_dollars
```

### 5. 图片在打包后预览失败

确认解包步骤已执行；图片应出现在对应 Markdown 同级 `assets/` 下。

---

## 开发提示

- 项目内历史拼写 `markdwon` 为既有命名，调用时请保持一致，不要只改函数名不改调用方
- 调试加载结果可写 `soup.html` / `pv.md`（见 `dev.py`）
- 修改主题与题干模板：`qparse/render_option/`
- 修改 DOCX 后处理：`qparse/docx_render/write_buffer.py`

---

## 许可证

内部备课工具，按团队约定使用。
