【问题标题】:Improve performance of Search Replace in Word document using OLE and Delphi使用 OLE 和 Delphi 提高 Word 文档中搜索替换的性能
【发布时间】:2012-03-01 16:41:05
【问题描述】:

经过一些实验后,我最终使用以下代码在 MSWord 中执行搜索和替换。此代码在页眉和页脚中也能完美运行,包括首页或奇数/偶数页的页眉和/或页脚不同的情况。

问题是我需要为我替换的每个字符串调用MSWordSearchAndReplaceInAllDocumentParts,并且我得到了一个不可接受的性能(4 页文档单词中大约 50 个字符串需要 2 分钟)。理想情况下,它当然应该是“即时的”。

在处理页眉和页脚之前,我只是在主文档中进行搜索和替换(使用 wdSeekMainDocument)。在那种情况下,性能是可以接受的(即使很慢)。我只是想知道为什么这么慢:切换视图需要时间吗?通常页眉或页脚包含很少的单词,所以我预计页眉和页脚中的所有搜索和替换不会使整体性能变得如此糟糕。但这不是我观察到的。

这是代码,在底部我放了探查器结果:

// global variable (just for convenience of posting to Stack Overflow)   
var
 aWordApp: OLEVariant; // global

// This is the function that is executed once per every  string I replace
function MSWordSearchAndReplaceInAllDocumentParts;
begin
    try
      iseekValue := aWordApp.ActiveWindow.ActivePane.View.SeekView;
      iViewType := aWordApp.ActiveWindow.ActivePane.View.Type;
      if iViewType <> wdPrintView then
        aWordApp.ActiveWindow.ActivePane.View.Type := wdPrintView;
      if aWordApp.ActiveDocument.PageSetup.OddAndEvenPagesHeaderFooter then
      begin
        Try
          aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekEvenPagesFooter;
          SearchAndReplaceInADocumentPart;
        Except
            // do nothing ..it was not able to set above view
        end;
        Try
          aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekEvenPagesHeader;
          SearchAndReplaceInADocumentPart;
        Except
          // do nothing ..it was not able to set above view
        end;
      end;
      if aWordApp.ActiveDocument.PageSetup.DifferentFirstPageHeaderFooter then
      begin
        Try
          aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekFirstPageFooter;
          SearchAndReplaceInADocumentPart;
        Except
          // do nothing ..it was not able to set above view
        end;
        Try
          aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekFirstPageHeader;
          SearchAndReplaceInADocumentPart;
        Except
          // do nothing ..it was not able to set above view
        end;
      end;
      //Replace in Main Docpart
      Try
        aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekMainDocument;
        SearchAndReplaceInADocumentPart;
      Except
          // do nothing ..it was not able to set above view
      end;
      //Replace in Header
      Try
        aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekCurrentPageHeader;
        SearchAndReplaceInADocumentPart;
      Except
          // do nothing ..it was not able to set above view
      end;
      //Replace in Footer
      Try
        aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekCurrentPageFooter;
        SearchAndReplaceInADocumentPart;
      Except
          // do nothing ..it was not able to set above view
      end;
      //Replace in Header
      Try
        aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekPrimaryHeader;
        SearchAndReplaceInADocumentPart;
      Except
        // do nothing ..it was not able to set above view
      end;
      //Replace in Footer
      Try
        aWordApp.ActiveWindow.ActivePane.View.SeekView := wdSeekPrimaryFooter;
        SearchAndReplaceInADocumentPart;
      Except
        // do nothing ..it was not able to set above view
      end;
    finally
      aWordApp.ActiveWindow.ActivePane.View.SeekView := iseekValue;
      if iViewType <> wdPrintView then
        aWordApp.ActiveWindow.ActivePane.View.Type := iViewType;
    end;
end;

// This is the function that performs Search And Replace in the selected View
 // it is called once per view

function SearchAndReplaceInADocumentPart;
begin
    aWordApp.Selection.Find.ClearFormatting;
    aWordApp.Selection.Find.Text := aSearchString;
    aWordApp.Selection.Find.Replacement.Text := aReplaceString;
    aWordApp.Selection.Find.Forward := True;
    aWordApp.Selection.Find.MatchAllWordForms := False;
    aWordApp.Selection.Find.MatchCase := True;
    aWordApp.Selection.Find.MatchWildcards := False;
    aWordApp.Selection.Find.MatchSoundsLike := False;
    aWordApp.Selection.Find.MatchWholeWord := False;
    aWordApp.Selection.Find.MatchFuzzy := False;
    aWordApp.Selection.Find.Wrap := wdFindContinue;
    aWordApp.Selection.Find.Format := False;
    { Perform the search}
    aWordApp.Selection.Find.Execute(Replace := wdReplaceAll);
end;

我在这里粘贴分析结果(我有 aqtime pro):

你能帮我找出问题吗?

【问题讨论】:

  • 如果您真的需要性能,那么通过 OLE/ActiveX 使用 Word 基本上不会削减它...是否使用库(不依赖 Word)来处理 Word 文档?
  • 如果你能提供一个合适的样本文档来进行基准测试会更好。
  • 您能否详细说明分析结果:是以秒还是毫秒为单位的时间,是每次点击的时间还是所有点击的累积时间?
  • @The_Fox 以秒为单位,这是替换后发生的情况。在那种情况下,实际上我调用了该函数 153 次,所以我替换了 150 个不同的单词(在普通数据库中是 50,然后如果用户添加自定义字段,数字会增加)。
  • @Yahia 是的,我同意这个方法足够快,直到我添加了页眉和页脚的替换。我主要担心的是,添加页眉和页脚替换似乎会变得慢得多,就像在进行搜索和替换时解析完整文档并且活动视图仅是页眉一样。

标签: delphi activex word-automation


【解决方案1】:

在我的机器上进行测试时,我没有看到如此糟糕的性能,但仍有一些方法可以提高性能。

最大的改进是在调用 MSWordSearchAndReplaceInAllDocumentParts 之前将 aWordApp.ActiveWindow.Visible 设置为 False

第二个改进是将aWordApp.ScreenUpdating 设置为False

当您连续多次调用 MSWordSearchAndReplaceInAllDocumentParts 时,应用上述设置一次。另外,在多次调用 MSWordSearchAndReplaceInAllDocumentParts 之前,将ActiveWindow.ActivePane.View.Type 设置为wdPrintView

编辑:

通过更改查找/替换的方式,我获得了另一项改进:不是更改 SeekView,而是遍历所有部分并自己获取文档、页眉和页脚的范围,然后在这些范围上执行查找/替换。

procedure TForm1.MSWordSearchAndReplaceInAllDocumentParts(const aDoc: OleVariant);
var
  i: Integer;
  lSection: OleVariant;
  lHeaders: OleVariant;
  lFooters: OleVariant;
  lSections: OleVariant;
begin
  lSections := aDoc.Sections;
  for i := 1 to lSections.Count do
  begin
    lSection := lSections.Item(i);
    lHeaders := lSection.Headers;
    lFooters := lSection.Footers;
    if lSection.PageSetup.OddAndEvenPagesHeaderFooter then
    begin
      SearchAndReplaceInADocumentPart(lHeaders.Item(wdHeaderFooterEvenPages).Range);
      SearchAndReplaceInADocumentPart(lFooters.Item(wdHeaderFooterEvenPages).Range);
    end;
    if lSection.PageSetup.DifferentFirstPageHeaderFooter then
    begin
      SearchAndReplaceInADocumentPart(lHeaders.Item(wdHeaderFooterFirstPage).Range);
      SearchAndReplaceInADocumentPart(lFooters.Item(wdHeaderFooterFirstPage).Range);
    end;
    SearchAndReplaceInADocumentPart(lHeaders.Item(wdHeaderFooterPrimary).Range);
    SearchAndReplaceInADocumentPart(lFooters.Item(wdHeaderFooterPrimary).Range);

    SearchAndReplaceInADocumentPart(lSection.Range);
  end;
end;

procedure TForm1.SearchAndReplaceInADocumentPart(const aRange: OleVariant);
begin
  aRange.Find.ClearFormatting;
  aRange.Find.Text := aSearchString;
  aRange.Find.Replacement.Text := aReplaceString;
  aRange.Find.Forward := True;
  aRange.Find.MatchAllWordForms := False;
  aRange.Find.MatchCase := True;
  aRange.Find.MatchWildcards := False;
  aRange.Find.MatchSoundsLike := False;
  aRange.Find.MatchWholeWord := False;
  aRange.Find.MatchFuzzy := False;
  aRange.Find.Wrap := wdFindContinue;
  aRange.Find.Format := False;

  { Perform the search}
  aRange.Find.Execute(Replace := wdReplaceAll);
end;

如果您在应用程序不可见的情况下打开要修改的文档,或者使用 Visible := False; 打开文档,您将看到更大的改进。 (再次设置应用程序可见也将设置文档可见)。

【讨论】:

  • 感谢您的建议,我会尝试一下,它们很有意义。唯一不明白的是wdPrintVIew,这样做有什么好处?
  • 另一条评论:在我的情况下性能很糟糕,因为我替换了大约 150 个字符串(根据分析器结果)。
  • 此外,我已经将 aWordApp.AcitveWINdow.Visible 设置为 False。
  • 我尝试了“编辑”部分中的建议,但没有发现任何改进。可能是一个非常小的。你说你改进了它,但改进了多少?
  • 我纠正了自己。通过做一些技巧,我能够使用您的建议从 21 秒移动到 13 秒。现在最终的优化是调用搜索仅替换所需的字段。您知道一种将所有文档作为 rtf 获取的方法,包括 hedaer 和 footer?那么我可以使用 Delphi Pos 函数来定位字符串吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-29
  • 2019-01-14
  • 2012-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多