VSTO中Word的查找方式

前言

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

正则全文搜索

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

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

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

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

如果这个文档都是由纯文本组成的,那么得到的位置,就是查找字符在全文中的真实位置。

然而实际上大多数情况下,文档还可能有图片、表格、公式和图表等格式组成,不仅仅是文本组成。

range位置的替换

Word在将文档转换成Text的过程中,如果格式是文本,那么一个字符A就将转换成一个字符A(复制);如果格式是富文本对象,比如图片,那个一个图片将会转换成一个空字符串“ ”,仅表示位置。

然而,在Word文档中,纯字符串类型的range的长度就是字符串的长度;非字符串对象的range的长度不确定。所以,经过Text的转换后,range的位置信息缺少了。查找到字符串在Text的位置,并不能找到该字符串在全文的range位置,也就无法对查找的字符串进行操作了。

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

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

  1. /// <summary>
  2. /// 从range中获取每一个字符的实际位置
  3. /// </summary>
  4. /// <param name="range">选中部分</param>
  5. /// <returns>位置列表</returns>
  6. public List<int> GetRangeLocation(Word.Range range)
  7. {
  8. var ret = new List<int> { };
  9. foreach (Word.Range c in range.Characters)
  10. {
  11. ret.Add(c.Start);
  12. }
  13. return ret;
  14. }

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

  1. /// <summary>
  2. /// 根据模式,找到所有匹配的位置
  3. /// </summary>
  4. /// <param name="range">选中部分</param>
  5. /// <param name="pattern">模式</param>
  6. /// <returns>匹配列表</returns>
  7. public List<Word.Range> SearchRangeInPattern(Word.Range range, string pattern)
  8. {
  9. var ret = new List<Word.Range> { };
  10. var content = range.Text;
  11. var doc = range.Document;
  12. //获取实际的字符位置
  13. var locationList = GetRangeLocation(range);
  14. var mc = System.Text.RegularExpressions.Regex.Matches(content, pattern);
  15. if (mc.Count > 0)
  16. {
  17. foreach (System.Text.RegularExpressions.Match m in mc)
  18. {
  19. var searchStart = m.Index;
  20. var searchEnd = m.Index + m.Value.Length;
  21. //将text位置转换为range位置
  22. var realStart = locationList[searchStart];
  23. var realEnd = locationList[searchEnd];
  24. //获取匹配的range位置
  25. var itemRange = doc.Range(realStart, realEnd);
  26. ret.Add(itemRange);
  27. }
  28. }
  29. return ret;
  30. }

实际运用

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

find和replace的API查找

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

  1. /// <summary>
  2. /// 替换选中部分的文字
  3. /// </summary>
  4. /// <param name="range">选中部分</param>
  5. /// <param name="search">待替换文字</param>
  6. /// <param name="replace">替换文字</param>
  7. public static void SearchReplace(Word.Range range, string search, string replace)
  8. {
  9. range.Find.ClearFormatting();
  10. range.Find.Text = search;
  11. //使用通配符搜索
  12. range.Find.MatchWildcards = true;
  13. range.Find.Replacement.ClearFormatting();
  14. range.Find.Replacement.Text = replace;
  15. object replaceAll = Word.WdReplace.wdReplaceAll;
  16. object missing = Type.Missing;
  17. range.Find.Execute(ref missing, ref missing, ref missing, ref missing, ref missing,
  18. ref missing, ref missing, ref missing, ref missing, ref missing,
  19. ref replaceAll, ref missing, ref missing, ref missing, ref missing);
  20. }

使用这种方法,查找速度快,执行时间短。

但是缺点也很明显,匹配经常不准确,比如空格和换行符,由于图片的悬浮位置影响,无法匹配。

此外基于通配符的匹配,毕竟不是正则表达式,不支持零字符位匹配和or匹配,所以用处有限,许多功能无法实现。

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

总结对比

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

VSTO中Word的查找方式的更多相关文章

  1. VSTO中Word的Range复制方式

    VSTO中Word的Range复制方式 前言 VSTO是一套用于创建自定义Office应用程序的Visual Studio工具包,通过Interop提供的增强Office对象,可以对Word文档进行编 ...

  2. VSTO中Word转换Range为Image的方法

    VSTO中Word转换Range为Image的方法 前言 VSTO是一套用于创建自定义Office应用程序的Visual Studio工具包,通过Interop提供的增强Office对象,可以对Wor ...

  3. <转>Python中的新式/经典类的查找方式

    在学习到深度和广度的时候,懵了很久.后来看到这篇文章,恍然大悟.写的很好.特意转过来. 经典类: 只要有父类, 就会沿着一直找, 即使已经找过了~ 新式类: 在类继承的多个类拥有共同父类的情况下, 会 ...

  4. ExtJS 4.2 组件的查找方式

    组件创建了,就有方法找到这些组件.在DOM.Jquery都有各自的方法查找元素/组件,ExtJS也有自己独特的方式查找组件.元素.本次从全局查找.容器内查找.form表单查找.通用组件等4个方面介绍组 ...

  5. linux中5条查找命令

    1 which which命令的作用是,在PATH变量指定的路径中,搜索某个系统命令的位置,并且返回第一个搜索结果. which [文件...] 参 数: -n<文件名长度> 指定文件名长 ...

  6. (转)javascript中的对象查找

    本文转自:http://otakustay.com/object-lookup-in-javascript/  ---很棒的一篇文章,作者的其他文章还暂时没读,但相信作者是一个谦虚 谨慎的好工程师 近 ...

  7. c++ --> c++中四种类型转换方式

    c++中四种类型转换方式   c风格转换的格式很简单(TYPE)EXPRESSION,但是c风格的类型转换有不少缺点, 1)它可以在任意类型之间转换,比如你可以把一个指向const对象的指针转换成指向 ...

  8. 在 Vim 中优雅地查找和替换(转)

    总有人问我 Vim 中能不能查找,当然能!而且是超级强的查找! 这篇文章来详细介绍 Vim 中查找相关的设置和使用方法. 包括查找与替换.查找光标所在词.高亮前景/背景色.切换高亮状态.大小写敏感查找 ...

  9. Linux中的文件查找技巧

    前言 Linux常用命令中,有些命令可以帮助我们查找二进制文件,帮助手册或源文件的位置,也有的命令可以帮助我们查找磁盘上的任意文件,今天我们就来看看这些命令如何使用. witch witch命令会在P ...

随机推荐

  1. 判断js中的数据类型的几种方法

    判断js中的数据类型有一下几种方法:typeof.instanceof. constructor. prototype. $.type()/jquery.type(),接下来主要比较一下这几种方法的异 ...

  2. 四、蛋炒饭(Egg fried rice)

    蛋炒饭,是一种常见菜肴.最早的记载见于1972年湖南长沙马王堆汉墓出土的竹简上有关"卵火高"的资料.经专家考证,"卵熇"是一种用黏米饭加鸡蛋制成的食品.有人推断 ...

  3. selenium webdriver定位不到元素的五种原因及解决办法

    1.动态id定位不到元素 for example:        //WebElement xiexin_element = driver.findElement(By.id("_mail_ ...

  4. 访问不了虚拟机 ubuntu中的myql,解决方案

    ============================================================== 1. 现象 2. 原因分析 2.1 访问虚拟机中的mysql的前提: 你的 ...

  5. 2018,你与 i 春秋的故事都在这

    年终岁末,深思回顾,过去的一年我们共同创造了很多回忆,有欢乐,有感动,更有收获.回首2018年,伴随着激情与挑战,我们共创了很多佳绩,一起来看看吧. 课程&实验 2018新增原创录制实战视频课 ...

  6. data自定义属性获取方法和设置

    <!--原生获取方法--> <div data-id="id=1"></div> <script> //js原生获取方法 var i ...

  7. #Java学习之路——面试题

    (一)[基础知识梳理——JAVAse部分]Java中的变量和常量        在程序中存在大量的数据来代表程序的状态,其中有些数据在程序的运行过程中值会发生改变,有些数据在程序运行过程中值不能发生改 ...

  8. [Swift]LeetCode280. 摆动排序 $ Wiggle Sort

    Given an unsorted array nums, reorder it in-place such that nums[0] <= nums[1] >= nums[2] < ...

  9. [Swift]LeetCode648. 单词替换 | Replace Words

    In English, we have a concept called root, which can be followed by some other words to form another ...

  10. [Swift]LeetCode868. 二进制间距 | Binary Gap

    Given a positive integer N, find and return the longest distance between two consecutive 1's in the ...