在VBA中结合正则表达式和查找功能给文档添加交叉连接

VBA中搜索文本有两种方式可用,一种是利用Range.Find对象(更常见的形式可能是Selection.FindSelectionRange的子类,Selection.Find其实就是特殊的Range.Find),另一种方法是利用正则表达式,但是,这两种方法各有各的问题。
Range.Find对象的问题是正则表达式功能太差。尽管可以通过将MatchWildcards 属性设置为True来使用通配符,但通配符表达式并不完全兼容常用的正则表达式语法,而且不同版本的VBA支持的通配符表达式语法还不一样,例如,我在Word2013中使用通配符表达式(#\d+)|([①-⑨]),它居然因为使用了|操作符,就不能得到预期的结果。

正则表达式的问题则是无法准确定位匹配项在文档中的位置。尽管理论上可以用下面这样的方法定位到匹配项,但是实际运行就会发现除了第一个能定位到,后面的全部会出错:

vbnet 复制代码
Dim i As Long
Dim rng As Word.Range
For i = 0 To matchColl.Count - 1
    ' 根据匹配项的位置信息创建Word.Range对象
    Set rng = doc.Range(matchColl(i).FirstIndex + 1, matchColl(i).FirstIndex + matchColl(i).Length + 1)
    rng.Select ' 选择第i个匹配项进行其他处理,如打印匹配项内容等    
Next i

为了各取所长避其所短,比较好的思路是将二者结合,先用正则表达式查找匹配项,再用Range.Find来定位匹配项。下面就用这个思路在Word文档主体内容中的注释引用和注释内容中的注释编号之间建立交叉连接来进行一个实践。

我们有这样一个文档:

要在这个文档中建立如图所描述的交叉链接,需要在主体内容的注释引用和注释区的注释编号位置分别插入书签以及连接到对方的超链接。当然,这里的查找内容用简单的通配符表达式也可以完成任务,但是如果编辑过程中出现失误,导致部分注释引用被替换成了别的样式,重新修复的时候就不得不用到|操作符,这时候Range.Find对象就不见得能按预期完成任务了。

下面的宏要求先在文档中选择主体内容,然后运行宏对主体内容进行处理,处理完后再选择注释中的内容,再次运行宏处理注释,交叉链接就建立完毕。我将插入书签和链接的功能写成了如下函数:

vbnet 复制代码
Function DealCrossLink(searchRange As Range, regStr As String, _
            chapter As String, contentStr As String, commentStr As String, _
            Optional ignoreCase As Boolean = True, Optional useSelection As Boolean = True)
            
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' 参数说明:
    '   searchRange:搜索范围
    '   regStr:应匹配的正则表达式
    '   chapter,contentStr,commentStr:用于命名书签的标志字符串
    '   ignoreCase:匹配内容时是否忽略大小写,默认为True
    '   useSelection:插入超链接时显示的文本是否用在文档中选择的文本,默认为True,否则显示#加阿拉伯数字
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    
    Dim regEx As RegExp
    Dim match, matches As Object
    Dim tmpRange As Range
    Dim i%, serial$, hyperText$
    Set regEx = CreateObject("VBScript.RegExp")
    With regEx
        .Global = True
        .ignoreCase = ignoreCase
        .Pattern = regStr '
    End With
    
    Set matches = regEx.Execute(searchRange.Text) ' 在搜索范围内执行匹配操作

    searchRange.Collapse Direction:=wdCollapseStart ' 将搜搜范围折叠到开头
    
    For Each match In matches
        Set tmpRange = searchRange
        With tmpRange.Find
            .Text = match.Value
            .Forward = True
            .Wrap = 1 ' wdFindContinue
            .Execute ' 执行查找,定位匹配项的位置
            If tmpRange.Find.found Then
                i = i + 1 ' 计数,用于书签命名
                serial = Trim(Str(i))
                With ActiveDocument.Bookmarks
                    .Add Range:=tmpRange, Name:=chapter & contentStr & serial
                    .DefaultSorting = wdSortByName
                    .ShowHidden = False
                End With
                If useSelection Then hyperText = tmpRange.Text Else hyperText = "#" & serial
                ActiveDocument.Hyperlinks.Add Anchor:=tmpRange, Address:="", _
                    SubAddress:=chapter & commentStr & serial, ScreenTip:="", TextToDisplay:=hyperText
                ' 调整搜索范围起始位置,准备定位下一个匹配项
                searchRange.SetRange start:=tmpRange.End, End:=searchRange.End
                searchRange.Collapse Direction:=wdCollapseStart
            End If
        End With
    Next match
    
End Function

上面的代码也展示了在选定区域中进行查找的方法。

调用上述函数的代码如下:

vbnet 复制代码
Sub test()
    Dim searchStr$, chapter$, contentStr$, commentStr$
    searchStr = "[①-⑨]"
    chapter = "c001"
    contentStr = "_cont_"
    commentStr = "_comm_"
    ' 处理主体内容中的书签和超链接,超链接文本用文档中的匹配文本
    DealCrossLink Selection.Range, searchStr, chapter, contentStr, commentStr
'    ' 处理注释内容中的书签和超链接,超链接文本用文档中的匹配文本
'    DealCrossLink Selection.Range, searchStr, chapter, commentStr, contentStr
'    ' 处理主体内容中的书签和超链接,超链接文本用#号连接阿拉伯数字编号
'    DealCrossLink Selection.Range, searchStr, chapter, commentStr, contentStr, , False
'    ' 处理注释内容中的书签和超链接,超链接文本用#号连接阿拉伯数字编号
'    DealCrossLink Selection.Range, searchStr, chapter, commentStr, contentStr, , False

End Sub

可以根据需要,将以上代码中最后四行具体调用函数的语句选择一条执行。

下面是选择主体内容后执行第一条语句的结果:

下面是选择注释内容执行第四条语句的结果:

主体内容中的"①"与注释内容中的"#1"之间成功建立起了交叉链接,其它编号也是如此。

如果觉得每次选一个段落有点麻烦,可以考虑在诗标题和校注前先插入连续型分节符(可参阅文档目录、页眉和文档章节标题之间插入相互链接的最佳实践中的过程Sub 指定级别标题前插入分节符()),然后遍历档中的所有节,各节第一段文本为"【校注】"的即为注释区,否则当做主体内容区,然后在调用DealCrossLink函数时将section.Range取代Selection.Range作为第一个参数传入,即可无需选择内容建立全文的交叉链接。

相关推荐
Oneforlove_twoforjob几秒前
【Java】正则表达式基础题+场景题练习
正则表达式
产幻少年7 小时前
正则表达式
正则表达式
Spcarrydoinb7 小时前
正则表达式
笔记·学习·正则表达式·脚本语言
大筒木老辈子7 小时前
编译原理复习---正则表达式+有穷自动机
正则表达式
火山上的企鹅1 天前
Qt WORD/PDF(三)使用 QAxObject 对 Word 替换(QML)
qt·pdf·word·qaxobject
╰つ゛木槿1 天前
使用正则表达式提取PDF文件页数的实现方案
正则表达式·pdf
winfredzhang1 天前
从 PDF 到 Word:一个简单的 PythonGUI转换器
python·pdf·word·转换
Lee_yayayayaya2 天前
latex中复制到word里面之后如何转变成word自带的公式
word
flashman9112 天前
python修改word的字体
开发语言·python·word·办公自动化