Python实现word文档自动插入交叉引用

Python实现word文档自动插入交叉引用

0 需求介绍

Word文档(doc,docx)支持的 交叉引用(Cross-reference) 功能在长文档写作中尤为重要.
手动插入交叉引用易错、低效. 使用Word VBA能够部分实现需求, 但基于Python的实现更易与其他脚本相结合.
本文将使用Pythonwin32com库, 在Word文档中自动插入交叉引用.
另外, 由于Microsoft Learn官方的介绍并不完善, 本文通过实验确定了具体的使用方法和代码行为.

1 环境准备

需要安装Python, 并安装win32com库. 可以使用pip来安装:
pip install pypiwin32

2 win32com基本用法介绍

新建文档
from win32com.client import Dispatch
app = Dispatch('Word.Application')
doc = app.Documents.Add()
app.Visible = True
打开现有文档
import win32com.client
word = win32com.client.Dispatch('Word.Application')
doc = word.Documents.Open(r"C:\PATH\xxx.docx")

根据上面的代码, Dispatch('Word.Application') 打开的是Word这个App, 而App内支持同时打开多个Document.

3 获取所有交叉引用项

allCrossRef = doc.GetCrossReferenceItems(0)
此处, GetCrossReferenceItems()是Word VBA所支持的函数, 可以参考下面的链接.
https://learn.microsoft.com/en-us/office/vba/api/word.document.getcrossreferenceitems
GetCrossReferenceItems()的用法为: expression.GetCrossReferenceItems(ReferenceType)
其中,expressionDocument对象, 即Word App内的某个Document.
而该函数的参数为 ReferenceType , 为必需项, 其值为WdReferenceType枚举的常量, 用于表示该函数将要获取的交叉引用项的类型.

WdReferenceType枚举

可参考 https://learn.microsoft.com/en-us/office/vba/api/word.wdreferencetype

NameValueDescription插入方式
wdRefTypeEndnote4Endnote. 尾注, 显示在文档末尾引用(顶部功能区)->脚注(组)->插入尾注
wdRefTypeFootnote3Footnote. 脚注, 显示在页面底部引用(顶部功能区)->脚注(组)->插入脚注
wdRefTypeBookmark2Bookmark. 书签插入(顶部功能区)->链接(组)->书签
wdRefTypeHeading1Heading. 标题开始(顶部功能区)->样式(组)->标题
wdRefTypeNumberedItem0Numbered item. 编号项目开始(顶部功能区)->段落(组)->编号/多级列表

通过向GetCrossReferenceItems()传入对应的参数, 它会以 tuple(元组) 数据类型, 返回document中的交叉引用项.

4 定义和实现

在此,我们将通过GetCrossReferenceItems()获取的交叉引用项,视作cross_reference_target(CR_tgt).
则接下来,我们需要在文档中确定cross_reference_source(CR_src). 在1个Document中,1个CR_tgt可以被多个CR_src引用.
为了确定CR_src在文档中的位置,我们可以使用如 ‘#’ , ‘^’ 等文档中不常用的符号作为占位符号.

5 Word中Range的概念

在开始编写代码之前,我们还需要了解Word VBA常用的Range概念.
关于Word文档中Range的概念,请参考链接:
https://learn.microsoft.com/zh-cn/office/vba/api/word.range
关于如何处理Word文档中的Range对象,请参考链接:
https://learn.microsoft.com/zh-cn/office/vba/word/concepts/working-with-word/working-with-range-objects
Range对象表示文档中的一个连续区域, 每个Range对象由一个起始字符位置和一个终止字符位置定义,
Range.StartRange.End,
这2个属性均为Long数据类型. 它们的位置值从文章开头的字符开始计算, 第一个位置值为0.
注意, Range将计算所有字符, 包括非打印字符, 隐藏字符即便未显示也计算在内.
Range属性 适用于多种对象,如Paragraph,Bookmark,Cell,Document

start_position = ActiveDocument.Paragraphs(1).Range.Start  # 获取Range的起始字符位置
end_position = ActiveDocument.Paragraphs(2).Range.End      # 获取Range的终止字符位置
paragraph_string = ActiveDocument.Paragraphs(3).Range.Text # 获取Range字符串

#使用Range方法新建Range对象
my_range = ActiveDocument.Range(0,10) # 返回ActiveDocument对象的前10个字符所对应的range对象

#使用SetRange方法重新设置已经声明的range对象的范围
my_range = ActiveDocument.Paragraphs(4).Range
myrange.SetRange(ActiveDocument.Paragraphs(5).Range.Start, ActiveDocument.Paragraphs(6).Range.Start)

6 确定CR_src的位置

在确定使用占位符号标记CR_src后, 我们可以通过遍历文档并查找占位字符, 来确定CR_src的位置.

for para in doc.paragraphs:     #遍历文档中所有paragraph对象,一般地,一个换行符对应一个paragraph
    parastr = para.Range.Text   #获取paragraph的文本
    #将'&','#'作为占位符号,并获取它们在文档(doc)中的位置
    CR_src_head_position = [(i+para.Range.Start) for i,c in enumerate(parastr) if c == '&'] 
    CR_src_tail_position = [(i+para.Range.Start) for i,c in enumerate(parastr) if c == '#']

实际应用场景下, CR_src的顺序会不同于GetCrossReferenceItems()所返回数据的顺序; 返回值也未必一一对应.
因此, 还需要获取CR_src的文本来与GetCrossReferenceItems()的返回值进行比较或文本匹配.

7 插入交叉引用链接

要在Word中插入交叉引用链接,可以参考下面的链接:
https://learn.microsoft.com/zh-cn/office/vba/api/word.range.insertcrossreference
Range.InsertCrossReference()可以插入对标题、书签、脚注、尾注或定义了题注标签的项(如公式、图表或表格)的交叉引用.
其语法为:

expression.InsertCrossReference(  
     _ReferenceType_ , _ReferenceKind_ , _ReferenceItem_ , _InsertAsHyperlink_ ,   
     _IncludePosition_ , _SeparateNumbers_ , _SeparatorString_ )  

例如CR_src_range.InsertCrossReference(0, -1, 1, True)

参数名称是否为可选参数数据类型说明
ReferenceType必需Variant为要插入交叉引用的项的类型.可以是任何 WdReferenceType 或 WdCaptionLabelID 常量或用户定义的标题标签
ReferenceKind必需WdReferenceKind要包含在交叉引用中的信息类型
ReferenceItem必需Variant如果 ReferenceType==wdRefTypeBookmark,该参数指定的书签名称;对于所有其他 ReferenceType 值,此参数在 交叉引用对话框中的 引用类型选项中指定物料编号或名称. 使用 GetCrossReferenceItems 方法返回可与此参数一起使用的项名称的列表
InsertAsHyperlink可选VariantTrue 则将交叉引用作为超链接来插入
IncludePosition可选VariantTrue 则根据引用项相对于交叉引用的位置插入"见上方"或"见下方,"
SeperateNumbers可选Variant若( ReferenceType==wdRefTypeNumberedItem 且 ReferenceKind==wdNumberFullContext)才可将该参数设置为True, 否则会报错; 若True 则用 SeperateString 以从associated text中分离numbers
SeperateString可选VariantSeperateNumbers==True, 则用该参数作为分隔符分离数字, 否则会报错

参数说明较为抽象,接下来详细说明:
ReferenceType 使用的WdReferenceType枚举, 如前文所示.
ReferenceKind 使用WdReferenceKind枚举, 如下所示:

NameValue说明
wdContentText-1插入指定项的文本值. 例如,插入指定标题的文本
wdEndnoteNumber6插入尾注引用标记.
wdEndnoteNumberFormatted17插入带格式的尾注引用标记.
wdEntireCaption2插入指定的公式、图表或表格的标签、编号和任何其他题注.
wdFootnoteNumber5插入脚注引用标记.
wdFootnoteNumberFormatted16插入带格式的脚注引用标记.
wdNumberFullContext-4插入完整的标题或段落编号.
wdNumberNoContext-3插入标题或段落,但不插入它在多级符号列表中的相对位置.
wdNumberRelativeContext-2插入标题或段落,并且插入它在多级符号列表中的相对位置,该相对位置应足以标识该项目.
wdOnlyCaptionText4只插入指定公式、图表或表格的题注文字.
wdOnlyLabelAndNumber3只插入指定公式、图表或表格的标签和编号.
wdPageNumber7插入指定项的页码.
wdPosition15根据需要插入单词"Above"或"Below".

ReferenceItem : 即GetCrossReferenceItem()返回值所对应的项目.
Eg: 对于编号项, GetCrossReferenceItem(0)的返回值: ('1.1 第一个编号','1.2 第二个编号').
则当InsertCrossReference(_ReferenceItem_ =1)时, 插入指向('1.1 第一个编号')的交叉引用.

InsertAsHyperLink : 所插入的交叉引用是否形成超链接.
该值为True, 则插入交叉引用超链接(右键该交叉引用,点击切换域代码,可查看域代码;按住ctrl点击该交叉引用,跳转到对应位置)
该值为False,则插入交叉引用(右键该交叉引用,点击切换域代码,可查看域代码;按住ctrl点击该交叉引用,不跳转)

IncludePosition : 该值为True, 则根据插入交叉引用的位置, 插入"见上方"或"见下方".

一些例子

Eg1: 对于编号项, GetCrossReferenceItem(0)的返回值: (‘1.1 第一个编号’,‘1.2 第二个编号’).
运行代码: my_range.InsertCrossReference(0,RefKind,1,True)
RefKind = 1, 在 my_range 位置插入交叉引用超链接,显示的文本为"第一个编号"
RefKind in [-2,-3,-4], 在 my_range 位置插入交叉引用超链接, 显示的文本为"1.1"
RefKind = 7, 在 my_range 位置插入交叉引用超链接, 显示的文本为交叉引用所在页码
RefKind = 15, 在 my_range 位置插入交叉引用超链接, 显示的文本为"见上方"或"见下方"(根据交叉引用所在位置决定)
RefKind in [2,3,4,5,6,16,17], 由于 ReferenceTypeReferenceKind 不匹配,会报错如下:

File “<COMObject Range>”, line 3, in InsertCrossReference
pywintypes.com_error: (-2147352567, ‘发生意外.’, (0, ‘Microsoft Word’, ‘命令失败’, ‘wdmain11.chm’, 36966, -2146824090), None)

Eg2: 文档内容如下:

1. 第1个编号(\r\n)
    a) 第1a个编号(\r\n)
        i. 第1ai个编号(\r\n)
.....(some text here)

GetCrossReferenceItem(0)的返回值: ('1. 第一个编号', ' a) 第1a个编号', ' i. 第1ai个编号').
运行代码: my_range.InsertCrossReference(0, -4, 3, True, False , True,"-" ).
则会在 my_range 位置插入指向 i. 第1ai个编号 的交叉引用链接, 插入的链接的文本为"1.-a)-i".

Eg3: 文档内容如下:

1. 第1个编号(\r\n)
    a) 第1a个编号(\r\n)
        i. 第1ai个编号      ##(注意, 此处没有后续内容)

GetCrossReferenceItem(0)的返回值: ('1. 第一个编号', ' a) 第1a个编号', ' i. 第1ai个编号').
运行代码: my_range.InsertCrossReference(0, -4, 3, True, False , True,"-" ).
则由于 i. 第ai个编号 所处的已经是doc的最后一个paragraph, 导致InsertCrossReference()ReferenceItem=3时, 会出现溢出, 导致报错.

File “”, line 3, in InsertCrossReference
pywintypes.com_error: (-2147352567, ‘发生意外。’, (0, ‘Microsoft Word’, ‘命令失败’, ‘wdmain11.chm’, 36966, -2146824090), None)

这种GetCrossReferenceItem()InsertCrossReference() 行为不一致的现象, 需要特别关注.

8 总结

通过阅读本文, 可以实现需求中预期实现的功能, 即通过Python win32com库在Word文档中自动地插入交叉引用. 希望能提高工作效率.

  • 29
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值