最近在工作上會很需要經常使用 MS Word,對多份稿件進行大量的文字搜尋及取代。雖然早先已寫了一支 Python 程式來處理,然而在後續檢查內容的過程裡,卻發現原先撰寫的取代功能處理得未盡確實,例如文字方塊(textbox)裡的內容就無法被取代掉。經過上網搜尋後找到了一項解法,儘管確實可行,不過 DR 仍覺得其背後的設計邏輯還蠻弔詭的。
原本的寫法是像這樣:
word.Selection.Find.Execute(FindText="Jack",
ReplaceWith="Stanley",
Replace=win32com.client.constants.wdReplaceAll,
MatchCase=True,
MatchWildcards=False)
在 DR 的期望裡,Word 若在未指定任何範圍的情況下,應該要能夠做到全域的搜尋與取代。然而實際的執行結果並非如此,如前所述,例如文字方塊裡的內容就無法被取代到。而解決方案則是改從文件中的本文範圍(StoryRanges)著手,不過其弔詭之處就在於:這項作法的搜尋方法(Find.Execute)必須要用兩次,才能夠順利取代所有本文範圍中的內容。
以下是完整的範例程式碼(word_replace_all.py),該程式會開啟程式所在目錄中的「test.docx」文件,然後搜尋所有的「Jack」字串並取代為「Stanley」:
# -*- coding: utf-8 -*-
import os, sys
import win32com.client
from win32com.client import constants
def area_replace(area, find_text, replace_text):
area.Find.Execute(FindText=find_text,
ReplaceWith=replace_text,
Replace=constants.wdReplaceAll,
MatchCase=True,
MatchWildcards=False)
def word_replace_all():
local_encoding = sys.getfilesystemencoding()
appdir = os.path.abspath(os.path.dirname(sys.argv[0])).decode(local_encoding)
os.chdir(appdir)
filename = "test.docx"
find_text = "Jack"
replace_text = "Stanley"
word = win32com.client.gencache.EnsureDispatch("Word.Application")
word.Visible = False
word.Documents.Open(os.path.abspath(filename))
for story in word.ActiveDocument.StoryRanges:
area_replace(story, find_text, replace_text)
while(True):
if story.NextStoryRange:
story = story.NextStoryRange
area_replace(story, find_text, replace_text)
else:
break
word.ActiveDocument.Save()
word.ActiveDocument.Close(False)
word.Quit()
if __name__ == "__main__":
word_replace_all()
為了突顯重點,上述的範例程式碼並不包含一些實務上會很需要的功能,例如一一開啟目錄中所有的 Word 文件,以及字串取代清單的讀取與套用。除了一般的內文以外,上述程式碼可以處理的區塊包含了文字方塊、註腳(footnote)、文字藝術師(WordArt)以及頁首頁尾(header & footer)。