码农高天
该项目因缺乏嵌套支持、语法闭合校验及对段落 Run 样式的保护,在非理想输入下会误删正文并抹除富文本格式,暴露出缺乏编译原理支撑的解析器极度脆弱。
AI-assisted summary of 【Code Review】parser是那么好写的么?超级脆弱的代码 — original source. Check the original for full context.
Source published:
Key ideas
- 码农高天通过对开源项目 docx_tag_parser 的代码审查,指出编写通用 Parser 门槛极高,当前实现因缺乏嵌套支持与边界检查而极度脆弱。 (0:00)
- 该解析器在正则匹配上存在严重漏洞,未加行结尾限制会导致段首的行内标签误判为段落标签,直接触发整段内容被错误删除。 (5:00)
- 在处理 Word 段落替换时,代码粗暴清空所有 Run 并塞入单一 Run,彻底破坏了文档原有的富文本样式与局部排版格式。 (10:00)
- 高天建议,若仅作为自用受限工具可做小修,但若目标是通用解析工具,由于缺少语法检查与转义机制,前路漫长甚至建议换方向。 (10:00)
Detailed notes
## Background 该视频由 YouTube 频道“码农高天”于 2024 年 10 月 26 日发布。视频内容是对动态点赞最多的开源项目 docx_tag_parser 进行 Code Review,旨在通过剖析该解析器极其基础且脆弱的代码,向观众展示编写 parser 的难度以及作者遗漏的诸多 Corner Case。
## TL;DR 本期视频深入审查了一个基于 python-docx 的 Word 自定义标签解析项目 docx_tag_parser。高天指出了该项目在基础编码、架构设计以及底层机制上的诸多致命漏洞:包括 DataContextManager 中冗余的参数与对标准库的不熟练、状态机无法支持嵌套 if 且缺少语法检查、正则缺少行尾限定符导致正常段落被误删、在遍历 elements 循环中原地删除的危险操作,以及行内替换时粗暴清空 Run 导致 Word 富文本格式全毁。高天最终总结该代码极度脆弱,只能在完美理想输入下碰巧运行,若要成为通用工具前路漫长,建议作者重新审视设计方向。
## Atmosphere & style 视频整体呈现出一种专业严谨却又不失幽默风趣的程序员 Code Review 氛围。高天在逐行拆解代码时语调从容犀利,看到“call_me_poor_regex”或冗余属性等离谱设计时会忍不住打趣吐槽,令人会心一笑。整个过程充满抽丝剥茧的逻辑推演感,观众能清晰感受到从“看似可用”到“千疮百孔”的戏剧性反差,既硬核解构了工程细节,又发人深省地敲响了边界处理与基础功底的警钟。
## Bulletin - [00:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=0s) 码农高天通过对开源项目 docx_tag_parser 的代码审查,指出编写通用 Parser 门槛极高,当前实现因缺乏嵌套支持与边界检查而极度脆弱。 - [05:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=300s) 该解析器在正则匹配上存在严重漏洞,未加行结尾限制会导致段首的行内标签误判为段落标签,直接触发整段内容被错误删除。 - [10:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=600s) 在处理 Word 段落替换时,代码粗暴清空所有 Run 并塞入单一 Run,彻底破坏了文档原有的富文本样式与局部排版格式。 - [10:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=600s) 高天建议,若仅作为自用受限工具可做小修,但若目标是通用解析工具,由于缺少语法检查与转义机制,前路漫长甚至建议换方向。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=0s) 开源项目 docx_tag_parser 旨在结合外部 JSON 数据变量与 Word 内的 XML 风格标签,动态过滤并生成目标文档。 - [00:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=0s) DataContextManager 存在基础语法不熟练的问题,未能有效利用 json.loads 与 json.load 等 Python 标准库特性。 - [00:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=0s) 代码中包含大量从未使用的冗余参数和死属性(如 element_discard),平白增加了代码的阅读和维护难度。 - [05:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=300s) 状态机设计过于简陋且仅维护单一全局状态,导致该解析器完全无法支持 if 标签的嵌套场景。 - [05:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=300s) 解析器缺失语法结构性校验,既不检查标签的成对闭合,也不限制 if/else 的排列次序,只能在理想输入下碰巧运行。 - [05:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=300s) 状态枚举中 NONE 与 FOUND_END 语义完全重合,反映出状态机设计在概念抽象上的冗余与混乱。 - [05:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=300s) 匹配控制标签的正则表达式未加行尾限制符($),段首出现行内标签时会导致整段正文被意外删除。 - [05:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=300s) 在遍历文档 element 的 for 循环中直接对底层元素执行原地删除,是极度危险且脆弱的编程禁忌。 - [10:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=600s) check_status 复杂的布尔判断和重复分支可通过高层逻辑重构,简化为“移除元素”或“执行行内替换”两条主线。 - [10:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=600s) 替换行内标签时清空段落所有 Run 并强行合并,暴露出对 python-docx 和 Word 富文本结构理解的不足,破坏了局部样式。 - [10:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=600s) 实现可靠的通用 Parser 需要完备的转义(Escape)机制与编译原理支持,缺乏系统考量的自制解析器难以应对复杂的生产场景。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=0s) 项目背景与脆弱的 DataContextManager
这次 Code Review 挑选了动态下方点赞最多的开源项目 `docx_tag_parser`。码农高天首先翻阅了项目的 README。该项目的功能从文档说明上看略显晦涩,但核心目标其实很直观:处理 Word 文档(`.docx` 文件),解析其中的 XML 风格自定义标签(如类似 `<if var="var_name">`),根据外部输入的数据变量判断条件是否为真,进而决定是否保留某些段落,同时还支持段落内的行内标签(inline tag)以及 `else` 分支匹配。
```mermaid flowchart LR accTitle: 文档标签解析流程 accDescr: 展现 docx 文档与 JSON 变量经过解析器后生成过滤后文档的过程 DOCX["DOCX 输入文档<br>(含 <if> / <else> 标签)"] --> Parser["docx_tag_parser<br>(基于 python-docx)"] JSON["JSON 变量数据<br>(Data Context)"] --> Parser Parser --> Output["过滤后的 Word 文档<br>(根据条件保留/剔除段落)"] ```
在梳理代码结构时,程序入口主要依赖著名的第三方库 `python-docx` 读取和修改 Word 文档。随后进入的 `DataContextManager` 类暴露出许多基本功不扎实的问题:
1. **变量重复赋值与标准库不熟**:在加载 JSON 时,作者既支持传入路径也支持传字符串,但代码第 14 行直接把已经拿到的 `self.json` 又重新赋了一遍值。实际上用同一个变量保存即可。此外,Python 的 `json` 模块本身就区分了 `json.loads`(读字符串)和 `json.load`(直接读文件对象),作者的处理方式显得对标准库不太熟悉。 2. **冗余且未使用的参数**:类中提供了一个按 key 和 datacontext 读取 value 的方法。然而在整个项目所有调用的地方,作者从来没有传过 `data_context`,每一次调用都只给了 key。高天直言:“这就是平白无故的增加了代码阅读难度,就是这个功能你也用不着,你写它干嘛?” 3. **荒谬的错误防御与混乱的键查询**:代码中甚至专门写了分支应对传入参数是 `list` 并返回空字符串的逻辑,在 JSON 数据设计完全未作说明的情况下显得极为突兀。在字典查 key 时,代码逻辑多此一举地搞了 `None` 判断再去一个叫 `sharing` 的键下查询,既冗余又有抛出 `KeyError` 的隐患。更让人费解的是类中同时维护了 `self.json` 与 `self.data_context`,虽然高天推测作者可能是想实现一套“默认数据与覆盖数据”的机制,但在实际运行中两个变量完全是一回事。
主程序为 Word 文档创建了核心的 `IfElseTagParser`,遍历文档中的每一个 element(如自然段、图片、表格分割线等),并对它们依次调用 `process_element`。
---
[05:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=300s) 状态机缺陷、正则表达式漏洞与边遍历边删除
在审查核心处理方法 `process_element` 时,高天指出了架构设计上的几大硬伤。首先是类中定义了一个名为 `self.element_discard` 的属性,全局搜索发现它只被赋过三次值,后续完全没有任何地方去读取它,纯属干扰代码理解的“废属性”。
代码的核心流程是获取段落(paragraph)文本,并使用正则表达式判断该段落是否为 `if`、`else` 或 `endif` 标签。高天对这里的状态管理设计提出了严厉的批判:
- **无法支持嵌套(Nested If)**:作者采用了一个简易的有限状态机机制,但全局只有一个状态标识。在面对 `if` 嵌套 `if` 的场景时,内层退出时根本无法知晓外层状态,直接失效。 - **缺失语法结构性检查**:状态机没有校验标签闭合与配对。“你在里面完全可以写成 `if else else else` 你发现不了,或者我可以写成 `if else if else if else` 一直没有 end 你也发现不了。”由于完全没有语法检查,这套逻辑只有在开发者写的标签“全对”时才能碰巧工作。 - **冗余等价的枚举状态**:在枚举状态中定义了四个值:`NONE`、`FOUND_IF`、`FOUND_ELSE` 和 `FOUND_END`。然而初始状态 `NONE` 和 `FOUND_END` 在语义与执行路径上完全等价,都代表“不在任何条件块中”,状态机的设计本身就存在概念重复。
随后,高天看到了代码中被命名为 `call_me_poor_regex` 之类的正则表达式函数,打趣道“这种代码一出来一般人肯定就不敢用这个库了”。而其中的正则表达式逻辑更存在严重的 Corner Case 漏判: 段落起始的匹配规则只要开头匹配到了 `if` 语法就会认为整段是控制标签。假若用户在自然段开头写了一个行内标签(inline if),由于整行首部匹配成功,该正则会误判整个自然段为标签行,进而执行整段删除,导致原本需要保留的正文数据彻底丢失。匹配段落级别标签时必须使用行结束符(如 `$`)做严格边界限制。
```mermaid flowchart TD accTitle: 正则误删逻辑示意 accDescr: 展示段首包含行内标签时被正则误判为整段标签并遭删除的逻辑漏洞 A["自然段包含 Inline If<br>(如: '<if cond>保留文本<endif> 正文后续...')"] --> B{"正则匹配 if_start<br>(未限制行结尾 $)"} B -- "头部命中规则" --> C["误判:整段均为控制 Tag"] C --> D["删除整个 Paragraph 元素"] D --> E["严重 Bug:用户真实数据被意外抹除"] ```
在删除逻辑上,作者通过 `python-docx` 将 element 从 parent 中移除,但在外层代码中,这个删除操作是直接发生在遍历 elements 的 `for` 循环内部的。高天强调,在任何情况下对正在迭代的数据结构做原地删除都是大忌,即使当前库可能未直接崩溃,也是极其脆弱、极易引发越界或漏判的危险写法。
---
[10:00](https://www.youtube.com/watch?v=cUTYk-LyWQY&t=600s) 逻辑分支重构与破坏样式的 Inline 替换
在段落不属于控制标签时的状态检查与行内替换环节,作者编写的 `check_status` 充斥着大量重复和晦涩的条件分支。该方法按 `if` 满足、`if` 不满足、`else` 满足、`else` 不满足拆成了四个冗长的大条件,并返回一个布尔值去告诉外部“是否属于 tag”从而决定要不要再调用 `check_inline_if`。
高天重构了这部分代码的抽象思维:这个布尔值名义上表示是否在 tag 里,实际上暗示的是“该段落此前有没有跑过 inline 检查”。这种扭曲的逻辑应当从更高层次进行合并,本质上无非两种结果: 1. 若处于未满足的 `if` 块中,或处于已满足的 `else` 块中,直接移除当前元素; 2. 其余情况下,执行 `check_inline_if` 即可。 如此重构后函数不再需要别扭的返回值,逻辑分支也从一堆纠缠的 `if-else` 精简为清晰明了的两条主线。
而在处理行内标签替换的 `check_inline_if` 函数中,高天指出了一个更为致命的 Word 格式破坏问题: Word 文档中的每个段落(Paragraph)是由多个具有独立样式属性的 `Run` 组成的(例如局部加粗、斜体或不同字色)。作者在通过正则把匹配到的 tag 文本做替换时,粗暴地把段落里的所有 `Run` 全部删空,然后把替换后的文本整体硬塞进第一个 `Run` 之中。这导致段落内原有的所有复杂富文本样式被一并抹杀,所有文字全部退化成单一格式。
```mermaid flowchart TD accTitle: Run 被破坏对比 accDescr: 对比原始带有丰富格式的段落与被粗暴覆盖后的退化状态 subgraph Original["原始段落 (Paragraph)"] R1["Run 1: 普通文本"] --- R2["Run 2: 红色加粗文本"] --- R3["Run 3: 标签 <if>...</if>"] end subgraph Corrupted["作者处理后的段落"] R_all["所有后续 Run 被清空<br>全段合并入 Run 1 单一格式"] end Original -->|"粗暴替换 text"| Corrupted ```
高天最后总结道,这个项目的初衷十分有趣且具有实用潜力,但作者目前的技术储备显然难以支撑起一个稳健的 Parser。现存代码仅在“天时地利人和一切都完美”的理想输入下才能跑通,一旦输入稍微偏离预期就会发生灾难性的行为。
编写 Parser 本身是门槛极高的领域,即便是纯文本输入也需要扎实的编译原理与状态机知识。代码中不仅缺少基本的闭合检查与转义机制(例如正文中恰好包含与 tag 相同的字符时没有任何 Escape 手段),对数据结构与遍历安全的处理也欠缺考量。高天给出的最终建议是:“如果你只是想做一个小工具你自己用,你可以保证你的输入满足一定的条件……你可以把这个代码稍微修缮一下自己用;但是如果你想把这个代码做成一个通用的工具……我觉得前路还非常漫长,建议你换个方向。”
## Mentioned 码农高天, docx_tag_parser, python-docx, cesaryuan
## Video description Code Review是提升代码水平最快的方式之一,这期视频我们review一个parser的代码。这个parser写的非常基础,我们可以从这个基础的代码中了解为什么写parser是个难度很大的事情,感受一下有多少corner case是没有被作者考虑到的。
本次代码:https://github.com/cesaryuan/docx_tag_parser
[Source](https://www.youtube.com/watch?v=cUTYk-LyWQY)