码农高天拆解 docx_tag_parser 状态机与正则漏洞破坏 Word 排版

【Code Review】parser是那么好写的么?超级脆弱的代码 — 码农高天

码农高天

该项目因缺乏嵌套支持、语法闭合校验及对段落 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>(含 &lt;if&gt; / &lt;else&gt; 标签)"] --> 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>(如: '&lt;if cond&gt;保留文本&lt;endif&gt; 正文后续...')"] --> 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: 标签 &lt;if&gt;...&lt;/if&gt;"] 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)

中文简报

码农高天拆解 docx_tag_parser:状态机与正则漏洞致 Word 排版遭破坏 该项目因缺乏嵌套支持、语法闭合校验及对段落 Run 样式的保护,在非理想输入下会误删正文并抹除富文本格式,暴露出缺乏编译原理支撑的解析器极度脆弱。 本期视频深入审查了一个基于 python-docx 的 Word 自定义标签解析项目 docx_tag_parser。高天指出了该项目在基础编码、架构设计以及底层机制上的诸多致命漏洞:包括 DataContextManager 中冗余的参数与对标准库的不熟练、状态机无法支持嵌套 if 且缺少语法检查、正则缺少行尾限定符导致正常段落被误删、在遍历 elements 循环中原地删除的危险操作,以及行内替换时粗暴清空 Run 导致 Word 富文本格式全毁。高天最终总结该代码极度脆弱,只能在完美理想输入下碰巧运行,若要成为通用工具前路漫长,建议作者重新审视设计方向。 码农高天通过对开源项目 docx_tag_parser 的代码审查,指出编写通用 Parser 门槛极高,当前实现因缺乏嵌套支持与边界检查而极度脆弱。 (0:00) 该解析器在正则匹配上存在严重漏洞,未加行结尾限制会导致段首的行内标签误判为段落标签,直接触发整段内容被错误删除。 (5:00) 在处理 Word 段落替换时,代码粗暴清空所有 Run 并塞入单一 Run,彻底破坏了文档原有的富文本样式与局部排版格式。 (10:00) 高天建议,若仅作为自用受限工具可做小修,但若目标是通用解析工具,由于缺少语法检查与转义机制,前路漫长甚至建议换方向。 (10:00)

Reading summary

码农高天对开源项目 docx_tag_parser 进行了代码审查,指出该项目虽有自动化处理 Word 模板的实用初衷,但在状态管理、语法校验、正则边界与底层格式处理上存在严重设计缺陷,只能在极其理想的输入下勉强运行。 数据管理混乱与标准库使用不当 (0:00) 该项目的核心目标是结合 JSON 变量解析 Word 文档中的条件标签,以决定段落的保留与行内文本替换。然而其数据上下文管理类 DataContextManager 暴露了基础功底不足的问题:不仅存在重复赋值以及对标准库 json.load 与 json.loads 机制理解不清的现象,还声明了在整个项目中从未被传入的冗余参数,无端增加了阅读复杂度。 此外,数据查询逻辑中包含了对输入为 list 时返回空字符串等突兀的防御逻辑,在字典取值时还引入了容易引发 KeyError 的多余判断。类内部同时维护语义与执行完全重合的 self.json 和 self.data_context 两个属性,整体数据处理机制缺乏清晰的设计规划。 状态机缺陷与危险的原地删除 (5:00) 在核心遍历与状态判断中,作者采用的单变量简易状态机无法应对实际开发中常见的嵌套 if 场景,内层退出后外层状态直接丢失。同时,该状态机完全缺失结构闭合与配对检查,即使用户输入多个连续的 else 或未闭合的 if 也不会报错,仅在输入标签完全正确时碰巧工作。枚举状态中的 NONE 与 FOUND_END 在语义和执行路径上也完全等价。 在底层操作上,代码直接在遍历文档 elements 的 for 循环内部执行删除操作。高天指出,对正在迭代的数据结构做原地删除是编程大忌,极易引发元素漏判或越界隐患;同时类中还保留了只被赋值三次却从不被读取的废弃属性。 正则漏洞与段落格式破坏 (10:00) 解析逻辑中的正则表达式存在明显的边界缺陷。段落起始的 if 匹配未设置行结束符($),导致段首若存在行内标签,正则就会误将整行正文当成控制标签,进而直接抹除整段用户数据。 在处理行内标签替换时,作者未能正确处理 Word 底层数据结构。Word 段落由承载不同字色、加粗等样式的多个 Run 组成,而代码在替换文本时粗暴地清空了段落中的所有后续 Run,把全段文本强制塞进第一个 Run 中,导致原文档中所有的局部富文本样式全部退化并丢失。此外,高天将冗长别扭的 check_status 四分支判断重构为“非满足块删除”与“正常块检查 inline”两条主线,避免了混乱的中间状态判断。 高天总结认为,编写稳健的 Parser 需要扎实的编译原理、状态机设计与转义校验基础。该项目如果仅作为输入完全受控的个人小脚本尚可通过简单修缮使用,但若想作为通用工具发布,现阶段代码过于脆弱,前路依然漫长。

Original transcript

[0:00] 这次code review 我们选了动态下方点赞最多的代码这个是Repple 照例先看readme这个readme可能不太好懂他就是把一个 docx 文件啊也就是我们的 word 的文档进行一个 tag 的 parse也就是拿到一个DOCX文件然后根据中间的一些text选一些药另外一些不要非常难理解是吧我们看看例子大概就是这样啊他在他的word文档里面加入一些类似这种XML的标签然后根据这个变量的值决定是否保留这段文字如果这个标签长成这样 那就说明他会给你一个名字叫Warname的Bullet如果这个值是true的话就保留 如果是false就不保留包括他支持段落里的这种inline的tag然后还支持else给的这个值也可以是个 string 比如在这个时候就检查 var name 是不是等于一个 string这让大家就可以理解这个项目的目的了对吧就是一个word的文档里面有很多这样的tag然后他根据一组给定的数据值决定哪些段落要保留哪些段落不要好 我们来看代码他这代码呢,相对来说读起来比较费劲,所以我来先给大家整理一下这代码的结构。这个部分就是它的入口这里他是用了一个非常知名的库啊Python DOCX用它来读取和修改沃尔伦党大家如果有印象的话我之前用沃尔的画战鹰的那期视频也用了这个酷接下来这个Data Context Manager它这个名字起的不是很好啊它里面就是保存我们刚才说的那些变量的值这里面可以看到 其实代码挺简单的就是把这项露了进来然后读值说白了就是把这些if需要的值提前给写进JSON里面那这里我们就能看到一些毛病对吧首先这个JSON data它支持传路径啊也支持传street这里稍微有点奇怪 我们不深究但是第14行是不是看着非常便宜你之前一定会拿到一个self.json这时候为什么还把它重新复制一下呢它俩表示的是同一个值你用同一个变量保存就可以了呀另外这个部分呢就是对JSON这个酷不太熟J3这个酷既支持Low Stream也支持直接从文件里面漏的他们俩实际上就是J-SUN.L的F好,下个这个函数啊,给一个key,给一个datacontext,然后拿value。这里很别扭的一点是他从来就没有给过这个Data Context在所有他使用这个method的地方他都只给了key这就平白无故的增加了代码掠读难度就是这个功能你也用不着你写它干嘛所以这个部分其实就没什么用包括这30跟31行其实我不太知道他要干嘛就什么情况下传进来一个list然后为什么返回一个constraint这有点荒谬的感觉啊是说那个JSON文件本身有可能写成一个list吗?那明显就是一个错误的输入对吧?你要去应对这个错误的输入就感觉这块逻辑不顺啊下面的部分 尝试从Diction里面拿到key对应的value后面这如果没拿着从一个sharing里面拿这块因为他完全没有解释这个JSON文件是什么样的所以我们不确定他的逻辑到底想完成一个什么事情但是我们就单纯的从逻辑上说只看这个部分他是不是完全没有必要搞这个NUN他完全可以把第35行这个默认值改成一个constring对吧这样37行的逻辑就完全不需要了直接return well就好了同时你在其他的地方那么注意用get来避免key error那你的这个Json里面保证有sharing这个key吗如果没有这个key 这时候就报错还是那个问题我并不知道他这个JSON文件有什么保证因为他通篇都没有写关于他的事儿同时我们可以看到在这个函数里面出现了self.json也出现了self.data context然而他们俩根本就是一个东西我猜测这里作者把它写成两个变量的原因应该是他想支持一个所谓data context的覆盖就他认为这个self.json是一个原始数据原始数据里面的sharing部分是总是可用的而这个data context只有在传难的时候才是这个self data context如果传的不是那 你可以自己传一个DataContext进来 这样就优先用DataContext的数据但是即便是这个逻辑下 你也完全不需要用两个变量 它没有任何的逻辑区别还有像我前面说的变量压根没有人用过我们回到这个主程序它建立了一个 parser if else tag parser这个 parser是这个文件的核心内容接下来对于这个word的文档里面的每一个element他都进行一个process element那 DOCX 文件本质上有点像一个XML它是一块一块的这个dog.element.body就是文档的内容那每一个element可能是一个自然段可能是一个分割线啊可能是一个图片之类的对于每一个element它都process [5:00] 我们重点呢就看这个process是怎么进行的process在马上在这里首先我们要批判一下self-element discard我们可以找一下这个属性被使用的地方可以看到三个被使用的地方都是给他复制也就是说这个属性从来没有被用过它是一个废属性也是平白无故地给阅读增加障碍这边如果这个element是一个paragraph也就是说是一个自然段他就拿到这个paragraph和paragraph里面的文字这两个有什么区别呢paragraph是一个比较复杂的数据结构里面还包含了一些格式之类的东西之类我们不深究接下来对于text也就是对于这些文字他进行了一个 regular expression search他这里期望做到的事情是看一看这个段落是不是if tag或者是 else tag,或者是那个 and if tag如果是的话改一下selfare status意思就是我们已经在一个if tag里面了同时对于iftec检查一下这个条件有没有被满足这里有若干的问题 我们一个一个说首先它的逻辑用了一个类似State Machine的方式这种方式本身就会产生很多问题比如说你就没有办法支持Nested If就你if里面套if 你就支持不了了因为你从那层这个if里面出来的时候你不知道干嘛你只有一层的status你的Status只有在if里在else里和都不在第二,你没有办法做逻辑块的结构性检查,就是你这里甚至都没有检查每一个if是不是都对应一个end我在里面完全可以写成if else else else 你发现不了或者我可以写成if else if else if else一直没有按的你也发现不了就是他没有他设计的这个语法上的检查也就是说只有在语法全对的时候他这条东西才能正确的工作第三我们看这里有一个foundif,foundelse,foundend对吧这三个东西是一个inner这个A那么里面B发现了四个状态NONE, found if, found else,跟 found end在最开始的时候这个状态设置成了让我们大家想一下这个none跟这个foundend,它有任何的区别吗?我们会发现他们俩是完全一样完全等价的都代表着目前我的状态是不在任何if和else里面也就是说它的有限状态机有两个状态是完全增加的这是一个非常不好的事情那接下来我们看他刚才那几个regular expression首先映入眼帘的肯定是一个非常可爱的Call Me Poe Regex 对吧这种代码一出来一般人肯定就不敢用的裤了不管韩树名看里面的内容这个if start就是找我们刚才那样的if块他看起来没有什么问题但是我们想一下这个逻辑如果我有一个paragraph他第一句就是一个inline if也就是说它起始的部分是满足这个if start的只不过它并不是一整段就是这个tag它实际上是inline的写法那么在这个Regular Expression的Search里面,是不是它会被找到?按照他的这个逻辑是不是就会被认为整个自然段实际上是一个大的tack而由于他认为这个自然段是一个tag,他会把这个段落整个删掉,我们是不是就删掉了我们本来需要用的数据?这个应该还是比较容易想到的corner case所以这边哪怕你要这么search我也建议在这个pattern的最后面加上一个行结束符你要确定自然段里面只有这个东西你才去删它找到condition之后相当于对condition做了evaluation然后把if是否满足给保存到变量里我最开始有点担心他这个check if condition的时候用了一些eval之类的玩意儿导致他这个程序有安全漏洞现在看来还好啊,他只是把这个string拿出来,然后把这个string作为key去找对应的value这部分呢没有太大的问题啊起码相对于他刚才那些结构上的问题呢他已经很不错了好 接下来如果他认为整个paragraph是一个tag的话 他就把它删除删除的函数看着非常没有必要对吧我们已经说了这个Element discard是用不着的这个remove element是直接从util里面拿的所以他完全没有必要再写一个函数啊直接用remove element就行Remove element呢实际上是用的python docx这个库里面的功能但是啊,这里注意一下他是真的把这个element从parent里面 remove 掉了我们回头看一下这个代码它在一个full loop里面我在这里没有去确认 Python DOCX 究竟是怎么工作的但是在任何情况下,当你对一个数据进行循环的时候,你不应该从它里面删东西这是一个比较基础的概念对吧你一边循环一边删很有可能会出问题这里究竟会不会出问题会出什么问题我不知道但是我总觉得这块 [10:00] 像有点问题接下来如果这个paragraph不是一个tag的话做一下check status包括这个 element它压根又不是一个paragraph也做check status那我们就来看一下这个check status这里四个大类 如果是在if块里 并且if没有满足 删掉衣服块里满足了检查else块里满足了删掉,else块里没满足检查这里我的感觉是上面这个部分跟下面这个部分应该写到一个block里就大概写成这样我觉得是更合理的形式也更容易看懂他把它拆成这四个大衣服说话有点便扭好 那只要在if else块里面他就返回一个true 否则返回false 这个返回的true跟false有什么用呢唯一的用处就是在这里他明面上的意思是他是不是在tag里而实际上他要表达的意思是这个自然段是不是已经check了inlineif就是有没有给这个自然段本身过过这个inline的那些if-tech如果不在tag里说明没有过过In-line-if这里就要再过一下 inline if这个逻辑是不是非常奇怪为什么奇怪呢因为你完全可以保证在这个check status里面已经过好了这个inlineif对不对我们只需要在paragraph既不属于if也不属于else的时候跑一下这个check inlineif就可以了呀这样这个函数就不需要任何的Return值了并且这个check in life永远都只会从这个check status里面走如果这样修改的话我们再看一下这个模块我们会发现实际上只有两个逻辑要么把这个element给移除要么做check in line if函数的等价逻辑是不是其实就是这样的如果在if里且条件没满足或者在else里且条件满足了 把这个东西给remove否则的话,check inline-in这个逻辑是不是比刚才要清晰很多同样的这个部分的逻辑 我们也可以把它改掉了如果paragraph是tagRemove然后return否则就做一个check status当然前面提前把paragraph复制成这样的话 逻辑是不是也更好懂了其实这个变换就是你理解了你的程序在做什么之后从一个更高层次去想我的程序到底在干什么问有没有哪些逻辑分支是可以合并的最后我们看一下这个check inline if,它这里依然用的是regular expression啊他去找这个字眼段里面完整的这个tag然后如果条件合适的话他把这个paragraph里面的text直接给replace掉这里的replace text 它处理的有问题但是这个问题不是拍档级别的问题在docx里面每一个paragraph可以有若干和run每个run大家可以理解为一段格式相同的文字这里呢他相当于把所有的这些 run 都删掉了然后把这个text放到第一个run里面把第一个run的文字颜色改一下就结束了这就意味着如果原来的那个段落里有一些文字是有自己特殊的格式比如说不同的颜色比如说加粗鞋体这些格式全部会被覆盖掉这个段落里的所有文字最后都会变成统一的一个格式这个显然是不太对的 对吧同时这个地方其实不太好处理所以为了省事他可能就压根没有做这件事那我们最后来总结一下啊这个项目本身的想法是一个挺有趣的想法但是作者本人目前的背景知识显然还没有到可以实现一个正确的parser的水平他现在这个Parser在天时地利人和一切都完美的情况下是可以工作的但是基本上任何一个地方出了一点小问题他都会做一些很奇怪的事情Parser这个事情其实难度是偏高的哪怕我们扔掉DOCX,我们就做一个纯文本的parser,就它这一套逻辑的设计,这套tag,我们用纯文本作为输入。都不是一个简单的事情这里很有可能需要会议一些基础的编译原理的知识我们点出了那么多语法检查的问题,实际上还有很多的corner case比如说最常见的我的这个文章原文里就是有和你tag一模一样的一段文字你怎么处理你就不让我写这些东西了吗任何这种字符串级别的tag你都要提供一个Escape的方式对吧比如HTML里面怎么输入左键头右键头就这实际上是一个偏大的问题所以我的建议是如果你只是想做一个小工具你自己用你可以保证你的输入满足一定的条件你确实会用到这个功能你可以把这个代码稍微修缮一下自己用但是如果你想把这个代码做成一个通用的工具在满足 [15:00] 在自己的语法定义的条件下都可以使我觉得前路还非常漫长建议你换个方向好 这期视频就到这里 喜欢的话记得点关注 别忘了意见三零哦优优独播剧场——YoYo Television Series Exclusive