操作OOXML - docx有序列表重新编号
背景
最近要做markdown文档转word文档的功能,
由于自定义样式的要求比较高,所以没用pandas,,
emmmm,也有可能是我不熟,,,不要在意
我用的是markdown-it-py解析结构,python-docx写入内容,,
具体代码怎么实现的,这不重要,
重要的是,我遇到了一个问题,最后转出来的docx文档,有序列表是连贯的,,
这就很糟心,,然后我就开始想办法处理了...
数据
## 异常行为侦测
1. 用户点击“异常行为侦测”菜单,系统自动加载实时事件流,校验用户权限并记录访问日志,输出最新异常事件列表。
1. aabb
2. ccdd
2. 用户筛选事件等级与网关,系统同步过滤数据,状态栏显示当前筛选条件,输出命中事件统计与风险指数。
1. aabb
2. ccdd
3. 用户点击单条事件,系统自动调取详细信息,更新右侧风险画像,输出行为分布、建议处置与历史黑名单。
1. aabb
2. ccdd
4. 用户执行“分派值守”或“一键联动”,系统校验操作权限,生成处置任务,记录操作日志并通知相关人员,输出任务状态与处置结果。
- aabb
- ccdd
5. 用户导出事件数据,系统自动生成CSV文件,校验导出权限,记录导出日志,输出文件下载链接。
- aabb
- ccdd
## 一键封控
1. 用户点击“一键封控”,系统校验高危号段及权限,生成封控任务,记录操作日志,输出影响预估与生效时间。
1. aabb
2. ccdd
2. 用户选择“双签并执行封控”,系统自动同步审批流程,校验L3双签,更新任务状态,通知相关业务方,输出封控执行结果。
1. aabb
2. ccdd
3. 用户可缩短封控时长,系统校验操作合法性,调整封控策略,记录变更日志,输出新生效时间。
- aabb
- ccdd
4. 操作完成后,系统自动生成封控事件报告,支持导出PDF,记录全流程日志,输出报告下载链接。
- aabb
- ccdd
## 事件详情
1. 用户点击异常事件,系统自动调取事件详情,校验权限并记录访问日志,输出事件关联链路与建议处置。
2. 用户选择“加入观察名单”,系统校验操作合法性,更新名单状态,记录操作日志,通知相关监控人员,输出名单变更结果。
3. 用户点击“导出取证”,系统生成事件证据JSON文件,校验导出权限,记录导出日志,输出下载链接。
4. 用户应用推荐处置,系统自动同步策略,更新事件状态,记录处置日志,通知业务方,输出处置结果。
过程
当然是直接上chatgpt了,,hhh,中途换了codex,给的代码都没用,,
也查了python-docx文档,说是目前不支持有序列表重新编号,,
最后我用了万能的控制变量法,,
将导出的docx复制了一份,手动重新编号,然后解压,
挨个查看xml的区别,再一点点修改,看有没有效果,,
这里推荐vscode控件
OOXML Viewer,
可以直接修改,然后双击打开docx查看效果,不需要重命名,解压,打包这一串复杂操作
最后找到了重新编号的办法,,
结构
这就是docx解压后的文件夹结构,我们需要关注的有两个document.xml、numbering.xml

正常的有序列表结构如下,在document.xml中
<w:p w14:paraId="02CF8387">
<w:pPr>
<w:pStyle w:val="14"/>
<w:kinsoku/>
<w:overflowPunct/>
<w:adjustRightInd/>
<w:snapToGrid/>
<w:spacing w:before="0" w:after="0" w:line="240" w:lineRule="auto"/>
<w:jc w:val="left"/>
<w:textAlignment w:val="auto"/>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="微软雅黑" w:hAnsi="微软雅黑" w:eastAsia="微软雅黑" w:cs="微软雅黑"/>
<w:b w:val="0"/>
<w:i w:val="0"/>
<w:color w:val="000000"/>
<w:sz w:val="22"/>
<w:u w:val="none"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>用户点击“一键封控”,系统校验高危号段及权限,生成封控任务,记录操作日志,输出影响预估与生效时间。</w:t>
</w:r>
</w:p>
如果需要重新编号的话,需要改为以下结构,需注意的标签为w:numPr
<w:p w14:paraId="02CF8387">
<w:pPr>
<w:pStyle w:val="14"/>
<w:numPr>
<w:numId w:val="56258"/>
</w:numPr>
<w:kinsoku/>
<w:overflowPunct/>
<w:adjustRightInd/>
<w:snapToGrid/>
<w:spacing w:before="0" w:after="0" w:line="240" w:lineRule="auto"/>
<w:jc w:val="left"/>
<w:textAlignment w:val="auto"/>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="微软雅黑" w:hAnsi="微软雅黑" w:eastAsia="微软雅黑" w:cs="微软雅黑"/>
<w:b w:val="0"/>
<w:i w:val="0"/>
<w:color w:val="000000"/>
<w:sz w:val="22"/>
<w:u w:val="none"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>用户点击“一键封控”,系统校验高危号段及权限,生成封控任务,记录操作日志,输出影响预估与生效时间。</w:t>
</w:r>
</w:p>
经过我的测试,这个numId的val,放一个随机正整数都可以生效,
相对的,需要在numbering.xml中添加内容,只要numId能对上即可
<w:num w:numId="56258">
<w:abstractNumId w:val="7"/>
<w:lvlOverride w:ilvl="0">
<w:startOverride w:val="1"/>
</w:lvlOverride>
</w:num>
w:lvlOverride w:ilvl="0"这是编号层级,0代表第一层
w:startOverride w:val="1"决定的是重新编号后的起始值
w:abstractNumId w:val="7"决定的是编号模板,格式如下,各字段释义询问ai
<w:abstractNum w:abstractNumId="7">
<w:nsid w:val="FFFFFF88"/>
<w:multiLevelType w:val="singleLevel"/>
<w:tmpl w:val="D0A62B40"/>
<w:lvl w:ilvl="0">
<w:start w:val="1"/>
<w:numFmt w:val="decimal"/>
<w:pStyle w:val="ListNumber"/>
<w:lvlText w:val="%1."/>
<w:lvlJc w:val="left"/>
<w:pPr>
<w:tabs>
<w:tab w:val="num" w:pos="360"/>
</w:tabs>
<w:ind w:left="360" w:hanging="360"/>
</w:pPr>
</w:lvl>
</w:abstractNum>
以上是结构说明
代码操作
我先在numbering.xml中写入了一个自定义的abstractNum,
这个结构支持三层有序列表,不够可以自己加,样式可以让ai改,
<w:abstractNum w:abstractNumId="999">
<w:multiLevelType w:val="multilevel"/>
<!-- 一级 -->
<w:lvl w:ilvl="0">
<w:start w:val="1"/>
<w:numFmt w:val="decimal"/>
<w:lvlText w:val="%1."/>
<w:lvlJc w:val="left"/>
<w:pPr>
<w:ind w:left="360" w:hanging="360"/>
</w:pPr>
</w:lvl>
<!-- 二级 -->
<w:lvl w:ilvl="1">
<w:start w:val="1"/>
<w:numFmt w:val="decimal"/>
<w:lvlText w:val="%1.%2"/>
<w:lvlJc w:val="left"/>
<w:pPr>
<w:ind w:left="720" w:hanging="360"/>
</w:pPr>
</w:lvl>
<!-- 三级 -->
<w:lvl w:ilvl="2">
<w:start w:val="1"/>
<w:numFmt w:val="decimal"/>
<w:lvlText w:val="%1.%2.%3"/>
<w:lvlJc w:val="left"/>
<w:pPr>
<w:ind w:left="1080" w:hanging="360"/>
</w:pPr>
</w:lvl>
</w:abstractNum>
然后在document.xml中,为每项有序列表绑定numId(随机数)
<!-- 第一层,第一项 -->
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="97281"/>
</w:numPr>
<!-- 第一层,第N项 -->
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="52486"/>
</w:numPr>
<!-- 第二层,第一项 -->
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="98496"/>
</w:numPr>
<!-- 第二层,第N项 -->
<w:numPr>
<w:ilvl w:val="1"/>
<w:numId w:val="25463"/>
</w:numPr>
<!-- 以此类推 -->
接着继续往numbering.xml写入内容,将numId绑定到自定义的abstractNum上,
变动的内容就两个,一个是numId,一个是w:ilvl
<!-- 第一层,第一项 -->
<w:num w:numId="97281">
<w:abstractNumId w:val="999"/>
<w:lvlOverride w:ilvl="0">
<w:startOverride w:val="1"/>
</w:lvlOverride>
</w:num>
<!-- 第一层,第N项 -->
<w:num w:numId="52486">
<w:abstractNumId w:val="999"/>
</w:num>
<!-- 第二层,第一项 -->
<w:num w:numId="98496">
<w:abstractNumId w:val="999"/>
<w:lvlOverride w:ilvl="1">
<w:startOverride w:val="1"/>
</w:lvlOverride>
</w:num>
<!-- 第二层,第N项 -->
<w:num w:numId="25463">
<w:abstractNumId w:val="999"/>
</w:num>
<!-- 以此类推 -->
最后打包成zip、再重命名为docx就行了,,,
好了,我知道自己菜,大家凑合看看就行了,,
这里仅作记录,

浙公网安备 33010602011771号