在Word文档编辑过程中,有时需要对文档的所有段落的内容进行有规律的修改,这时就可以利用VBA来遍历文档所有段落,并对段落进行操作。如果对段落内容的修改不影响到段落的分段符,不会引起什么问题,但是,如果修改涉及分段符,那么结果可能会出乎意料。
例如我们有下面这样一个Word文档:
现在要将上述文档中样式为“标题 2”的段落用HTML标签“<h2></h2>”包裹,样式为“列表段落”的段落用HTML标签“<p class="content"></p>”包裹,样式为“正文”的段落用HTML标签“<p class="comment"></p>”包裹。我们通常想到的VBA代码如下:
Sub test()
Dim aPara As Paragraph
For Each aPara In ActiveDocument.Paragraphs
If aPara.Style = "列表段落" Then
aPara.Range.Text = "<p class=""content"">" & aPara.Range.Text & "</p>"
ElseIf aPara.Style = "标题 2" Then
aPara.Range.Text = "<h2>" & aPara.Range.Text & "</h2>"
ElseIf aPara.Style = "标题 3" Then
aPara.Range.Text = "<h3>" & aPara.Range.Text & "</h3>"
ElseIf aPara.Style = "正文" Then
aPara.Range.Text = "<p class=""comment"">" & aPara.Range.Text & "</p>"
End If
Next aPara
End Sub
执行以后你会发现陷入了死循环。按Ctrl+Break终止运行,会发现宏始终在第一个段落上重复用“<p class="content"></p>”标签包裹“<h2></h2>”标签之间的内容。这主要是因为“aPara.Range.Text = "<h2>" & aPara.Range.Text & "</h2>"”语句将标题段落末尾的分段符删除了,导致此段落与下段并到了一起,段落样式也成了与下段一样的样式,同时,“aPara”变量依然保持着此前标题段落的内容。为了解决这个问题,我们将上面的宏稍微修改一下:
Sub test()
Dim aPara As Paragraph, i%
For Each aPara In ActiveDocument.Paragraphs
i = i + 1
If aPara.Style = "列表段落" Then
aPara.Range.Text = "<p class=""content"">" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</p>"
ElseIf aPara.Style = "标题 2" Then
aPara.Range.Text = "<h2>" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</h2>"
ElseIf aPara.Style = "标题 3" Then
aPara.Range.Text = "<h3>" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</h3>"
ElseIf aPara.Style = "正文" Then
aPara.Range.Text = "<p class=""comment"">" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</p>"
End If
Next aPara
Debug.Print i
End Sub
这次不会陷入死循环了,但是整篇文档变成了一个样式为“正文”的段落,并且还插入了4个“<p class="comment">”标签。这是因为插入的内容不包含分段符,导致所有段落的内容合并到了最后一个段落,但“aPara”变量已经正常遍历每一个段落了。现在再做一个修改,在输入的内容末尾添加分段符:
Sub test()
Dim aPara As Paragraph, i%
For Each aPara In ActiveDocument.Paragraphs
i = i + 1
If aPara.Style = "列表段落" Then
aPara.Range.Text = "<p class=""content"">" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</p>" & vbCrLf
ElseIf aPara.Style = "标题 2" Then
aPara.Range.Text = "<h2>" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</h2>" & vbCrLf
ElseIf aPara.Style = "标题 3" Then
aPara.Range.Text = "<h3>" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</h3>" & vbCrLf
ElseIf aPara.Style = "正文" Then
aPara.Range.Text = "<p class=""comment"">" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</p>" & vbCrLf
End If
Next aPara
Debug.Print i
End Sub
这次仍然陷入了死循环,中断执行后发现,与第一次不同的是,这次标题段落没有与下一个段落合并到一起,但是标题段落的样式已经变成了与后续段落相同的样式。
以上三次失败无一例外修改时都变动了段落末尾的分段符。实际上,采取“For Each aPara In ActiveDocument.Paragraphs”方式遍历文档时,下一次循环中如果当前段落的分段符发生过变化,“aPara”仍然指向当前段落。对“aPara.Range.Text”赋值的时候,会删除原来的分段符,导致发生段落及样式的合并,这时候如果添加了新的分段符,鉴于原来的分段符发生了变化,“aPara”将始终指向第一个被修改内容的段落,陷入死循环。因此,正确的做法是用“ActiveDocument.Range(aPara.Range.Start, aPara.Range.End - 1)”构造出一个排除了分段符的范围,再修改此范围中的内容。正确的代码如下:
Sub test()
Dim aPara As Paragraph, paraRng As Range
For Each aPara In ActiveDocument.Paragraphs
' 构造出排除了分段符的文本范围并对其进行赋值
Set paraRng = ActiveDocument.Range(aPara.Range.Start, aPara.Range.End - 1)
If aPara.Style = "列表段落" Then
paraRng.Text = "<p class=""content"">" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</p>"
ElseIf aPara.Style = "标题 2" Then
paraRng.Text = "<h2>" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</h2>"
ElseIf aPara.Style = "标题 3" Then
paraRng.Text = "<h3>" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</h3>"
ElseIf aPara.Style = "正文" Then
paraRng.Text = "<p class=""comment"">" & Left(aPara.Range.Text, Len(aPara.Range.Text) - 1) & "</p>"
End If
Next aPara
End Sub
上述代码顺利运行结束,并且所有段落都被正确的标签包裹。参见下图: