VSTO中Word的查找方式

发布日期:2019-03-12

VSTO中Word的查找方式

前言

使用C#在VSTO开发Word插件的过程,经常需要对文档中的内容进行查找和替换。在Word中进行文本的查找替换,和一般对纯文本的查找替换却不太一样。因为Word文档是一个富文本对象,对文本的查找实际上是对一个对象的查找,而这个或者这种对象对于开发者是未知不可见的,因此和纯文本搜索比较,不仅存在许多不一样的地方,也存在一定的难度。本文主要对这些差异进行了讨论和分析。

正则全文搜索

通常情况下,对一个文本进行查找,我们会使用正则表达式,找到匹配模式的位置,如下所示。

var pattern = "[0-9]+?" var content = "第1个" var mc = System.Text.RegularExpressions.Regex.Matches(content pattern) if (mc.Count > 0) { foreach (System.Text.RegularExpressions.Match m in mc) { //获取匹配字符串在输入中的索引位置 int searchIndex = m.Index } }

而在Word文档中,我们可以通过range.Text属性获取到文档的字符串表示,利用相同的正则表达式来进行查找。

var pattern = "[0-9]+?" //获取文档的全部字符 var content = doc.Range().Text var mc = System.Text.RegularExpressions.Regex.Matches(content pattern) if (mc.Count > 0) { foreach (System.Text.RegularExpressions.Match m in mc) { //获取匹配字符串在输入中的索引位置 int searchIndex = m.Index } }

如果这个文档都是由纯文本组成的,那么得到的位置,就是查找字符在全文中的真实位置。然而实际上大多数情况下,文档还可能有图片、表格、公式和图表等格式组成,不仅仅是文本组成。

range位置的替换

Word在将文档转换成Text的过程中,如果格式是文本,那么一个字符A就将转换成一个字符A(复制);如果格式是富文本对象,比如图片,那个一个图片将会转换成一个空字符串“ ”,仅表示位置。然而,在Word文档中,纯字符串类型的range的长度就是字符串的长度;非字符串对象的range的长度不确定。所以,经过Text的转换后,range的位置信息缺少了。查找到字符串在Text的位置,并不能找到该字符串在全文的range位置,也就无法对查找的字符串进行操作了。

//*表示任意字符,range长度为1//A表示一个图片,range长度为4//B表示一个公式,range长度为6//Word文档中全文的range位置****A**B**(1)(2)(3)(4)(8)(9)(10)(17)(18)(19)//Text的位置 如图**** ** **(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)

既然字符串的range位置丢失了,索性事先把所有对象的位置先存储起来。

/// <summary>/// 从range中获取每一个字符的实际位置/// </summary>/// <param name="range">选中部分</param>/// <returns>位置列表</returns>public List<int> GetRangeLocation(Word.Range range){ var ret = new List<int> { } foreach (Word.Range c in range.Characters) { ret.Add(c.Start) } return ret}

利用位置信息,终于可以得到一个可以正确查找文本的方法了。

/// <summary>/// 根据模式,找到所有匹配的位置/// </summary>/// <param name="range">选中部分</param>/// <param name="pattern">模式</param>/// <returns>匹配列表</returns>public List<Word.Range> SearchRangeInPattern(Word.Range range string pattern){ var ret = new List<Word.Range> { } var content = range.Text var doc = range.Document //获取实际的字符位置 var locationList = GetRangeLocation(range) var mc = System.Text.RegularExpressions.Regex.Matches(content pattern) if (mc.Count > 0) { foreach (System.Text.RegularExpressions.Match m in mc) { var searchStart = m.Index var searchEnd = m.Index + m.Value.Length //将text位置转换为range位置 var realStart = locationList[searchStart] var realEnd = locationList[searchEnd] //获取匹配的range位置 var itemRange = doc.Range(realStart realEnd) ret.Add(itemRange) } } return ret}

实际运用

在实际运用的过程中,基本不能采用这种全文的正则查找方式,除非要搜索的文本内容长度很小。因为经过测试,获取字符的实际range位置,具有非常大的时间开销。原因在于获取每一个字符都是一次COM调用,调用时间数量级在10毫秒左右。多次的COM调用,使得总调用时间非常大。

find和replace的API查找

在Word的API存在定义好的查找函数,可以使用Word定义的规则(类似于正则表达式)的方式,进行通配符查找。

/// <summary>/// 替换选中部分的文字/// </summary>/// <param name="range">选中部分</param>/// <param name="search">待替换文字</param>/// <param name="replace">替换文字</param>public static void SearchReplace(Word.Range range string search string replace){ range.Find.ClearFormatting() range.Find.Text = search //使用通配符搜索 range.Find.MatchWildcards = true range.Find.Replacement.ClearFormatting() range.Find.Replacement.Text = replace object replaceAll = Word.WdReplace.wdReplaceAll object missing = Type.Missing range.Find.Execute(ref missing ref missing ref missing ref missing ref missing ref missing ref missing ref missing ref missing ref missing ref replaceAll ref missing ref missing ref missing ref missing)}

使用这种方法,查找速度快,执行时间短。但是缺点也很明显,匹配经常不准确,比如空格和换行符,由于图片的悬浮位置影响,无法匹配。此外基于通配符的匹配,毕竟不是正则表达式,不支持零字符位匹配和or匹配,所以用处有限,许多功能无法实现。

捕获0-无限个数字[0-9]* //正则表达式,若干个数字,包括0个[0-9]{1} //Word,若干个数字,必须1个以上捕获数字或者字母[0-9]|[a-z] //正则表达式,一个数字或一个字母[0-9] then [a-z] //word,只能分成两次来匹配,不支持or的匹配

总结对比

方式查找速度匹配准确度匹配模式
正则全文搜索非常慢 多次COM调用正则表达式,类型多,只支持文本
Find查找快,一次COM调用通配符,类型少,支持多种对象查找