使用TRAE solo 整理 法律法规到md文档

借助 TRAE SOLO 新的桌面应用,我初步完成一个法律法规的展示小应用

使用的是MTC模式,这个新模式。我让他直接帮我分析了所有法律法规和相关文档涉及到的 公文文种和格式,然后逐步一个种类一个种类完成解析等工作。

国家法律法规数据库批量下载了现行有效的法律、部门规则、地方法律、司法解释等docx文档。

从docx——md——json——md的一个过程。要中途倒一遍json的原因是还有一些其他的用途。

截图:

以下这个是AI的总结

从零构建法律法规文档转换流水线:DOCX → MD → JSON → MD 全流程实战

本文记录了在一个实际项目中,如何从零开始构建一套完整的法律法规文档转换系统。该系统将 Word 格式的法律法规文档(.docx)经过 DOCX → Markdown → JSON → Markdown 的四步流水线处理,最终通过 VitePress 构建出一个支持全文搜索的规章制度文档站,收录了超过 1500 件 法律法规文件。


一、项目背景与需求分析

1.1 为什么要做这个?

在一个人事管理系统中,"规则制度"模块需要提供法律法规的在线查询功能。原始数据来源是大量的 Word 文档(.docx/.doc),这些文档存在以下问题:

  • 格式不统一:不同来源的文档使用了不同的标题样式、编号方式
  • 结构层次深:法律文件有"编 > 分编 > 章 > 节 > 条 > 款 > 项"多达 7 级的嵌套结构
  • 文种多样:涉及法律、条例、规定、意见、通知、司法解释等 17 种 不同文种
  • 网页残留:部分文档从网页抓取,包含发布时间、字体标签等残留信息
  • 需要在线查询:最终要构建一个可搜索、可浏览的文档站

1.2 整体架构

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────────┐
│  DOCX    │───>│   MD     │───>│   JSON   │───>│   MD     │───>│  VitePress   │
│ (原始文档)│    │ (基础转换)│    │ (结构化) │    │ (标准化) │    │  (文档站)    │
└──────────┘    └──────────┘    └──────────┘    └──────────┘    └──────────────┘
   步骤 0          步骤 1          步骤 2          步骤 3          步骤 4
  docx_to_md     md_to_json      json_to_md     distribute_md   vitepress build

核心设计思想:为什么不直接 DOCX → MD?因为原始 Markdown 缺乏结构化信息,无法支持:

  • 按文种差异化渲染(法律的"条"和意见的"一、"渲染方式完全不同)
  • 目录自动生成与导航配置
  • 文档结构验证(序号连贯性检查)
  • VitePress 的 frontmatter 和 outline 配置

所以中间引入了 JSON 作为结构化的中间表示,实现了"解析"与"渲染"的解耦。


二、技术选型

环节 技术选择 理由
DOCX 解析 Python python-docx 原生支持 .docx 格式,可精确控制段落样式和格式提取
.doc 格式 pandoc 通用格式转换工具,支持旧版 .doc 格式
文本处理 Python re 正则 中文法律法规的编号模式复杂,正则是最灵活的工具
并发处理 concurrent.futures.ThreadPoolExecutor IO 密集型任务,多线程即可高效处理
文档站 VitePress Vue 驱动的静态文档站,内置全文搜索、侧边栏、导航
部署 Express 静态文件服务 与主应用共用 Nginx,通过 /regulations 路径访问

三、步骤 0:DOCX → Markdown(基础转换)

3.1 目标

将 Word 文档转换为"干净的" Markdown,仅保留基础格式信息(加粗、斜体、表格、标题层级),不做任何结构化处理。

3.2 关键实现

# 使用 python-docx 遍历文档元素
for element in doc.element.body.iterchildren():
    if isinstance(element, CT_P):      # 段落
        para = Paragraph(element, doc)
        style = get_paragraph_style(para)  # 识别标题样式
        formatted_text = process_runs(para)  # 保留加粗/斜体
    elif isinstance(element, CT_Tbl):  # 表格
        table = Table(element, doc)
        md_table = _convert_table_to_md(table)

3.3 关键注意事项

① 合并单元格的去重处理

python-docx 对合并单元格的处理会导致 row.cells 返回重复引用。必须使用 _tc 的 XML id 去重:

seen_tc_ids = set()
for cell in row.cells:
    tc_id = id(cell._tc)
    if tc_id in seen_tc_ids:
        continue  # 跳过合并单元格的重复引用
    seen_tc_ids.add(tc_id)

② 段落样式识别

Word 文档的样式名不统一(如 Heading 1heading1标题 1),需要做模糊匹配:

def get_paragraph_style(para) -> str:
    style_name = para.style.name if para.style else "Normal"
    if "title" in style_name.lower():
        return "title"
    elif "heading 1" in style_name.lower():
        return "heading1"
    # ... 更多匹配规则

③ .doc 格式的兼容

旧版 .doc 文件无法用 python-docx 处理,需要通过 pandoc 转换:

subprocess.run([
    "pandoc", "-s", doc_file, "-o", output_file,
    "--wrap=none", "--markdown-headings=atx"
])

④ 批量多线程处理

1500+ 文件逐个转换太慢,使用线程池加速:

with ThreadPoolExecutor(max_workers=8) as executor:
    futures = {executor.submit(convert, f): f for f in docx_files}
    for future in as_completed(futures):
        # 统计成功/失败数

四、步骤 1:Markdown → JSON(按文种解析)

4.1 目标

将"扁平"的 Markdown 文本解析为结构化的 JSON,提取元数据、识别文种、解析层级结构。

4.2 处理流程

原始 MD → 清理网页残留 → 合并断行 → 提取元数据 → 检测文种 → 选择解析器 → 结构化解析 → 输出 JSON

4.3 预处理:清理网页残留

从网页抓取的文档常包含各种残留信息,必须在解析前清理:

def clean_web_artifacts(content: str) -> str:
    # 清理零宽空格等不可见字符
    content = re.sub(r"[\u200b-\u200f\u202a-\u202e\ufeff]", "", content)
    # 清理"发布时间: XXXX年XX月XX日"
    content = re.sub(r"\n*发布时间:\s*\d{4}年\d{1,2}月\d{1,2}日\s*\n*", "\n", content)
    # 清理"字体:[ 大 中 小 ]"
    content = re.sub(r"字体:\s*\[.*?\]", "", content)
    # 清理标题行末尾紧跟的文号
    content = re.sub(r"(#[^\n]*?\*{0,2})\s*((?:国发|人社部发)[^〕」\n]*[号〕」」])\s*$",
                     r"\1\n\n**\2**", content, flags=re.MULTILINE)

4.4 预处理:合并断行

这是整个流水线中最棘手的问题之一。网页抓取或 OCR 产生的 Markdown 常出现标题和段落被不自然断行的情况:

# 中华人民共和国
劳动合同法

(2012年修正)

第一条  为了完善劳动合同制度,明确劳动合同双方当事人的权利和义务,保护劳动者的合法权益,
构建和发展和谐稳定的劳动关系,制定本法。

合并策略需要考虑多种场景:

def merge_broken_lines(paragraphs: list) -> list:
    # 1. 标题续行:# 开头的行 + 不以句号/括号结尾的短文本
    if current.startswith("#") and not next_p.endswith("。"):
        should_merge = True

    # 2. 短文本续行:长度 < 50 且不以句号结尾
    elif len(current) < 50 and not current.endswith("。"):
        should_merge = True

    # 3. 日期信息续行:(2023年修订 + 根据XXX规定)
    elif re.match(r"^[((]\d{4}年", current):
        should_merge = True

    # 4. 但要排除:颁布信息行、结构行(第X章/节/条)、极短独立行

关键注意事项

  • 颁布信息行(含"发布"“通过”"施行"关键词)不能与正文合并
  • 结构行(第X章、第X条)不能合并
  • 极短行(≤10字符)视为独立标题,不合并

4.5 文种自动检测

系统定义了 17 种文种,每种有独立的解析器和渲染器:

编号 文种 代码 典型结构
1 法律 law 编 > 分编 > 章 > 节 > 条 > 款 > 项
2 条例 regulation 章 > 节 > 条 > 款 > 项
3 规定 regulation_rule 章 > 条 > 款 > 项 或 无结构
4 细则/规则 detailed_rule 章 > 条 > 款 > 项
5 意见 opinion 一、二、三 > (一)(二)> 正文
6 通知 notice 一、二、三 / 第X条 / 纯段落
7 公告/通告 announcement 公告头 + 条款结构
8 司法解释 judicial_interpretation 条 > 款 > 项
9 决定/决议 decision 章 > 条 / 一、二、三
10 命令/令 command 第X条 / 一、二、三
11 批复/复函 reply 第X条 / 一、二、三
12 安排 arrangement 第X条 / 一、二、三
13 修正案 amendment 一、二、三 > 修改内容
14 方案 plan 一、二、三 / 第X条
15 守则/工作规则 rules 一、二、三 / 纯段落
16 法典 code 编 > 分编 > 章 > 节 > 条
17 其他 other 自动检测

检测逻辑按优先级排列:

DOCUMENT_TYPE_RULES = [
    (DocumentType.CIVIL_CODE, ["民法典"], []),           # 民法典优先
    (DocumentType.CRIMINAL_LAW, ["刑法"], ["刑事诉讼法"]), # 刑法(排除刑诉法)
    (DocumentType.JUDICIAL_INTERPRETATION, ["解释"], []),  # 司法解释
    # ... 更多规则
]

关键注意事项

  • 民法典法典优先级最高,因为标题中包含"法"字,容易被误判为普通法律
  • 刑法需要排除"刑事诉讼法"等包含"刑法"二字的其他法律
  • 司法解释需要特殊处理:标题中可能不包含"解释"二字,但发布机关是"最高人民法院"或"最高人民检察院"
  • 公告只有当"公告"作为独立行标题出现时才判定,避免正文中出现"公告"二字被误判

4.6 结构化解析

以最复杂的"法律"类型为例,使用 StructureParser 提取层级结构:

class UnifiedLawParser(BaseParser):
    def parse(self, paragraphs: List[str]) -> Dict[str, Any]:
        structure = self.structure_parser.extract_hierarchy(paragraphs, flat=True)
        return {"chapters": structure["chapters"], "preamble": structure.get("preamble", "")}

输出的 JSON 结构示例:

{
  "metadata": {
    "title": "中华人民共和国宪法",
    "doc_classification": "law",
    "passage_info": "1982年12月4日第五届全国人民代表大会第五次会议通过...",
    "publish_date": "1982年12月4日",
    "document_number": "",
    "source_path": "/path/to/temp/md/laws/宪法/中华人民共和国宪法_20180311.md"
  },
  "content": {
    "preamble": "中国是世界上历史最悠久的国家之一...",
    "chapters": [
      {
        "title": "第一章 总纲",
        "articles": [
          {"title": "第一条", "content": "中华人民共和国是工人阶级领导的..."},
          {"title": "第二条", "content": "中华人民共和国的一切权力属于人民。", "sub_items": ["人民行使国家权力的机关是..."]}
        ]
      }
    ]
  },
  "validation": {
    "is_valid": true,
    "has_issues": false,
    "errors": [],
    "warnings": []
  }
}

4.7 文档验证

每个文档解析后都会进行结构验证:

  • 必要要素检查:标题是否存在
  • 序号连贯性检查:编、章、节、条的编号是否连续(如"第三章"后面不应直接跳到"第五章")
  • 目录准确率评估:自动提取的目录与实际结构的匹配度
class ValidationResult:
    errors: List[str]    # 严重问题(如缺失标题)
    warnings: List[str]  # 警告(如序号不连贯)
    info: List[str]      # 信息提示

五、步骤 2:JSON → Markdown(按文种渲染)

5.1 目标

将结构化的 JSON 数据渲染为标准化的 VitePress Markdown 文档,不同文种使用不同的渲染策略。

5.2 渲染流程

class UnifiedLawRenderer(BaseRenderer):
    def render(self, data: Dict[str, Any]) -> str:
        # 1. 生成 frontmatter(VitePress 配置)
        md_lines.append(self.render_frontmatter(metadata))
        # 2. 渲染标题
        md_lines.append(self.render_title(metadata.get("title", "")))
        # 3. 渲染颁布信息
        md_lines.append(self.render_passage_info(metadata.get("passage_info", "")))
        # 4. 渲染序言(宪法、法典特有)
        md_lines.append(self.render_preamble(preamble))
        # 5. 渲染章节(动态层级)
        md_lines.extend(self.render_chapters(chapters))
        # 6. 渲染附录
        md_lines.append(self.render_appendix(appendix))

5.3 动态 Heading 级别分配

这是渲染器最精妙的设计。不同法律的层级深度不同,需要动态分配 Markdown heading 级别:

民法典 (5层): 编(h2) > 分编(h3) > 章(h4) > 节(h5) > 条
刑法   (4层): 编(h2) > 章(h3) > 节(h4) > 条
宪法   (3层): 章(h2) > 节(h3) > 条
普通法律(2层): 章(h2) > 条

实现方式:用 分隔符编码层级路径,渲染时动态计算 heading 级别:

def _get_heading_level(self, parts: List[str], index: int) -> int:
    level = 2 + index  # 从 h2 开始
    return min(level, 5)  # 最高到 h5

5.4 不同文种的渲染差异

法律/条例:条号加粗,首行缩进 2em

**第一条** 为了持续优化营商环境,制定本条例。

意见:中文序号作为 h2 标题,不加粗

## 一、总体要求

### (一)指导思想

司法解释:条号加粗,无缩进(与法律的区别)

**第一条** 为正确审理...

修正案:中文序号加粗

## **一、** 将刑法第...

5.5 VitePress Frontmatter

每个文档都生成 frontmatter,用于配置搜索和目录:

---
title: 优化营商环境条例
category: 法规
doc_type: regulation
publish_date: 2019年10月8日
publisher: ""
document_number: 国务院令第722号
outline: [2, 2]        # VitePress 目录范围
source_path: /path/to/...
---

5.6 自动目录分发

渲染后的 Markdown 文件根据 source_path 自动分发到对应目录:

source_path: .../temp/md/laws/法律/经济法/中华人民共和国环境保护税法.md
输出路径:     docs-regulations/法律/法律/经济法/中华人民共和国环境保护税法.md

目录映射规则:

SOURCE_DIR_MAP = {
    "hr": "人力资源",
    "party": "党建",
    "union": "工会",
    "laws": "法律",
    "social_security": "社会保障",
}

六、步骤 3:VitePress 配置生成

6.1 自动生成导航配置

distribute_md.py 根据 docs/ 源目录结构自动生成 VitePress 的完整配置:

def generate_vitepress_config(docs_root, reg_dir, output_dir):
    # 1. 扫描目录树
    tree = scan_docs_tree(docs_root)
    # 2. 生成 sidebar、nav、categories
    # 3. 写入配置文件
    #    - sidebar.json(侧边栏)
    #    - nav.json(导航栏)
    #    - categories.json(分类信息)
    #    - config.ts(VitePress 主配置)
    #    - index.md(首页)

6.2 三级分类排序

法律板块支持三级分类,每级都有固定排序:

法律/
├── 宪法/                    # 一级分类
├── 法律/
│   ├── 宪法相关法/           # 二级分类
│   ├── 民法商法/
│   ├── 行政法/
│   ├── 经济法/
│   ├── 社会法/
│   ├── 生态环境法/
│   ├── 刑法/
│   ├── 诉讼与非诉讼程序法/
│   └── 其他/
├── 监察法规/
├── 行政法规/
├── 地方法规/
└── 司法解释/

6.3 全文搜索配置

VitePress 内置的 MiniSearch 配置:

search: {
  provider: 'local',
  options: {
    miniSearch: {
      searchOptions: {
        boost: { title: 20, titles: 15, text: 1 },  // 标题权重最高
        fuzzy: 0.1,       // 模糊匹配
        prefix: true      // 前缀匹配
      }
    }
  }
}

七、流水线编排

7.1 一键执行

所有步骤通过 orchestrate.py 统一编排:

# 完整流程(从 MD → JSON 开始,默认步骤 1-3)
python3 src/scripts/orchestrate.py

# 包含 DOCX → MD(步骤 0)
python3 src/scripts/orchestrate.py --step 0

# 完整流程 + 验证
python3 src/scripts/orchestrate.py --validate

# 仅执行指定步骤
python3 src/scripts/orchestrate.py --step 1
python3 src/scripts/orchestrate.py --step 2
python3 src/scripts/orchestrate.py --step 3

# 指定 16 线程
python3 src/scripts/orchestrate.py -w 16

# 清理临时文件
python3 src/scripts/orchestrate.py --clean

7.2 目录结构

workspace/
├── docs/                          # 原始 DOCX 文档(按分类组织)
│   ├── laws/
│   │   ├── 宪法/
│   │   ├── 法律/
│   │   │   ├── 宪法相关法/
│   │   │   ├── 民法商法/
│   │   │   └── ...
│   │   ├── 行政法规/
│   │   └── 司法解释/
│   ├── social_security/
│   ├── party/
│   ├── union/
│   └── hr/
├── temp/
│   ├── md/                        # 步骤 0 输出:基础 Markdown
│   ├── json/                      # 步骤 1 输出:结构化 JSON
│   └── final_md/                  # 步骤 2 输出:标准化 Markdown
├── docs-regulations/              # 最终输出:VitePress 文档站
│   ├── .vitepress/
│   │   ├── config.ts
│   │   ├── nav.json
│   │   ├── sidebar.json
│   │   └── categories.json
│   ├── 法律/
│   ├── 社会保障/
│   ├── 工会/
│   ├── 党建/
│   └── 人力资源/
└── src/scripts/                   # 转换脚本
    ├── orchestrate.py             # 流程编排
    ├── docx_to_md.py              # DOCX → MD
    ├── md_to_json.py              # MD → JSON
    ├── json_to_md.py              # JSON → MD
    ├── distribute_md.py           # VitePress 配置生成
    ├── doc_config.py              # 17 种文种配置
    ├── validator.py               # 文档验证器
    ├── parsers/                   # 17 种文种解析器
    │   ├── base.py
    │   ├── unified_law.py
    │   ├── structure_parser.py
    │   ├── opinion.py
    │   ├── notice.py
    │   ├── judicial.py
    │   └── ...
    └── renderers/                 # 17 种文种渲染器
        ├── base.py
        ├── unified_law.py
        ├── opinion.py
        ├── notice.py
        ├── judicial.py
        └── ...

八、踩坑记录与关键注意事项

8.1 DOCX 解析阶段

问题 解决方案
合并单元格导致内容重复 使用 id(cell._tc) 去重
标题样式名不统一 模糊匹配("heading 1" in style_name.lower()
.doc 格式不支持 回退到 pandoc 转换
表格嵌套复杂 仅保留基础表格,过滤空行
编码问题 统一使用 UTF-8,Windows 下设置 io.TextIOWrapper

8.2 MD → JSON 解析阶段

问题 解决方案
网页残留信息干扰解析 clean_web_artifacts() 多层正则清理
标题被不自然断行 merge_broken_lines() 多规则合并
文种误判(如"刑法"vs"刑事诉讼法") 排除词列表 + 优先级排序
公告类型误判 仅当"公告"作为独立行标题时判定
目录与正文混淆 检测目录结束标志(下一行是"第X条"则目录结束)
颁布信息被当作正文 前 10 行内匹配标题/颁布信息的行自动跳过

8.3 JSON → MD 渲染阶段

问题 解决方案
不同法律层级深度不同 动态 heading 级别分配(h2-h5)
条号格式不统一 统一为 **第X条** 加粗格式
款项缩进 CSS text-indent: 2em
VitePress outline 范围 通过 frontmatter outline: [2, 5] 配置
文件路径分发 source_path 解析目标路径,按 SOURCE_DIR_MAP 映射

8.4 VitePress 构建阶段

问题 解决方案
1500+ 文件构建慢 分批构建,增量更新
搜索索引过大 配置 chunkSizeWarningLimit: 1000
侧边栏过长 使用 collapsed: true 折叠子目录
中文搜索不准确 配置 fuzzy: 0.1 + prefix: true

九、最终效果

9.1 文档统计

分类 文档数量
法律 1177 件
社会保障 30 件
工会 2 件
党建 若干
人力资源 若干
合计 1500+ 件

9.2 渲染效果示例

宪法(法律类型,有序言):

---
title: 中华人民共和国宪法
doc_type: law
outline: [2, 5]
---

# 中华人民共和国宪法

**1982年12月4日第五届全国人民代表大会第五次会议通过...**

(序言内容...)

## 第一章 总纲

**第一条** 中华人民共和国是工人阶级领导的...

**第二条** 中华人民共和国的一切权力属于人民。

优化营商环境条例(条例类型):

---
title: 优化营商环境条例
doc_type: regulation
outline: [2, 2]
document_number: 国务院令第722号
---

# 优化营商环境条例

**2019年10月8日国务院第66次常务会议通过...**

**第一条** 为了持续优化营商环境...

## 第二章 市场主体保护

**第十条** 国家坚持权利平等...

十、总结

这套文档转换流水线的核心设计理念是 “解析与渲染分离”

  1. DOCX → MD:只做格式转换,保持"干净"
  2. MD → JSON:负责"理解"文档——识别文种、提取结构、验证完整性
  3. JSON → MD:负责"表达"文档——按文种差异化渲染、生成 VitePress 配置

这种设计使得:

  • 新增文种只需添加一对解析器/渲染器,不影响其他环节
  • 修改渲染样式不需要重新解析文档
  • JSON 中间格式可以作为数据源用于其他用途(如全文搜索索引、知识图谱)

整个系统处理 1500+ 件法律法规文档,覆盖 17 种文种,最终构建出一个支持全文搜索、分类浏览、层级导航的规章制度文档站。


本文基于实际项目代码整理,项目使用 Python + VitePress + Express 技术栈。

3 个赞

司法助手?

类似于司法知识库吧,我看最好渲染的效果是yaml格式的,就是方便检索对应的内容

1 个赞

有现成的格式化过的文档就好,没有现成的就都一样,手上有一些pdf和docx的文档,条款这种的,先用法律法规这些规范的东西练练手。

1 个赞

不是助手,只是方便展示和日常翻看。

1 个赞

法规文档存储

1 个赞