前言
使用 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 调用 | 高 | 正则表达式 https://www.jb51.net/tools/zhengze.html ,类型多,只支持文本 |
Find 查找 | 快,一次 COM 调用 | 中 | 通配符 ,类型少,支持多种对象查找 |
来源: https://www.cnblogs.com/senhtry/p/9324015.html