VSTO中Word的查找方式 前言 使用C 在VSTO開發Word插件的過程,經常需要對文檔中的內容進行查找和替換。在Word中進行文本的查找替換,和一般對純文本的查找替換卻不太一樣。因為Word文檔是一個富文本對象,對文本的查找實際上是對一個對象的查找,而這個或者這種對象對於開發者是未知不可見的, ...
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調用 | 中 | 通配符,類型少,支持多種對象查找 |