Python(pywin32)でWordを操作する[9] - Word文書をgrepする(docgrep.rb移植)


概要

docgrep.rbをpythonに移植してWord文書をgrepするツール(docgrep.py)を作ります。

参考:
実用上はWord/Excel/PDFをgrepできる「SearchApp」というGUIツールのほうがいいかもしれません。

次回は、ページ数を表示するようにdocgrep.pyを改造します。
※行数も表示してみようかと思いますが、Wordの行(Sentence)はテキストエディタと違って句点[。]やピリオド[ . ]で区切られる概念なのでイメージと合わない気もする?

ExcelGrepとdocgrep.rb-日本語を活かした情報処處理入門

先づ、docgrep.rb、Rubyで書かれたワード用のgrepコマンドである。勿論、正規表現が使へる。ただし、このコマンドはファイル名は表示しても、頁數や行數は表示してくれない。

使い方

python docgrep.py [-o] <expr> files... 
引数 省略可否 3
-o 省略可能 ヒットしたWord文書を表示する。省略した場合はprint出力。
<expr> 必須 検索文字列(正規表現)
files... 必須 ファイル名(複数可)

単純移植したらワイルドカード展開が使えなくなっていました。

コマンドライン引数のワイルドカードを展開する-blog.PanicBlanket.com

Ruby はコマンドラインのワイルドカードを展開してくれる。
・・・
でも、Python はそんな気の利いたことしてくれない。
そこで、glob モジュールの出番になる。

次回で解決

コード

docgrep.py
# -*- coding: utf-8 -*-

import argparse

import win32com.client
import re
import pathlib

def docgrep(expr="", files=[], args_o=False):

    pattern = re.compile(expr)
    wd=win32com.client.DispatchEx("Word.Application")

    quit =True
    for doc in files:
        doc = str(pathlib.Path(doc).resolve())
        print(f"file: {doc}")
        found = False
        wdoc = wd.Documents.Open(FileName=doc, ConfirmConversions=None, ReadOnly=True)
        for sentence in wdoc.Sentences:
            txt = sentence.Text
            m = pattern.search(txt)
            if m:
                found = True
                quit = False
                if not args_o:
                    print(txt)
                else:
                    sentence.Select()
                    break
        if not found or not args_o:
            wdoc.Close(SaveChanges=0)

    if quit or not args_o:
        wd.Quit()
    else:
        wd.Visible = True

if __name__ == "__main__":
    parser = argparse.ArgumentParser(usage="%(prog)s [-o] <expr> files...")
    parser.add_argument("-o", action="store_true")
    parser.add_argument("expr",  type=str)
    parser.add_argument("files", type=str, nargs="*")
    args = parser.parse_args()

    docgrep(expr=args.expr, files=args.files, args_o=args.o)

docgrep.rbについて

Rubyを256倍使うための本 邪道編, arton(著)」のWordをgrepするRubyスクリプトです。

サンプルプログラムがwebarchiveから取得できます。

資料室
Rubyを256倍使うための本 邪道編のサンプルプログラム 著者、artonさんのホームページはこちら

ruby 2.2.1で動作させたときのパッチを置いときます。

docgrep.rbのパッチ

  1. getopts → optparseに変更
  2. GetAbsolutePathNameで絶対パスに変換
  3. usageの文字列を修正(書籍に合わせた)
diff.patch
--- C:/tmp/docgrep.rb   Sun Jun 24 20:32:09 2018
+++ C:/tmp/docgrep_new.rb   Tue Jun 26 01:20:14 2018
@@ -1,11 +1,14 @@
 require "win32ole"                               #(A)
-require "getopts"
+require "optparse"

 def usage
-  print "usage: ruby docgrep.rb [-[o]] <expr> files...\n"
+  print "usage: ruby docgrep.rb [-o] <expr> files...\n"
 end

-unless getopts("o")
+
+begin
+  args = ARGV.getopts('o')
+rescue OptionParser::InvalidOption
   usage
   exit(1)
 end
@@ -17,10 +20,12 @@

 pattern = Regexp.new(ARGV.shift, Regexp::IGNORECASE)
 wd = WIN32OLE.new("Word.Application")            #(B)
+fso = WIN32OLE.new("Scripting.FileSystemObject")  

 quit = true
 for doc in ARGV
   begin
+    doc = fso.GetAbsolutePathName(doc)
     print "file: " + doc + "\n"
     found = false
     wdoc = wd.Documents.open doc                 #(C)
@@ -30,7 +35,7 @@
       if $&.nil? == false
         found = true
         quit = false
-        if $OPT_o.nil?
+        if !args["o"]
           print txt + "\n"
         else
           sentence.select                        #(F)
@@ -38,13 +43,13 @@
         end
       end
     end
-    if found == false || $OPT_o.nil?
+    if found == false || !args["o"]
       wdoc.close                                 #(G)
     end
   end
 end

-if quit || $OPT_o.nil?
+if quit || !args["o"]
   wd.Quit                                        #(H)
 else
   wd.visible = true                              #(I)

GetAbsolutePathNameで絶対パスに変換

Rubyを256倍使う本 邪道編の間違い探しや訂正などなど - COM Meets Ruby

(略) 相対パスで引数を記述するとNTや2000だとdocgrep.rbはちゃんと動かない。だから、51ページのリストの#Cの部分を

wdoc = wd.Documents.open File.expand_path(doc)

としてください。ここからわかること。MSもパスの区切りに'\'を使うのが必ずしも良いと思ってるわけじゃなさそうだということ。Wordは'/'でもちゃんと処理する。

File.expand_path(doc)を使ったところ、ファイル名/フォルダ名のスペースが%20でWordに渡ってしまい、これが処理できなさそうです。

FileSystemObjectのGetAbsolutePathNameを使うといいようにやってくれます。

関連

Python(pywin32)でWordを操作する[1] - Wordオブジェクトモデル
Python(pywin32)でWordを操作する[2] - Wordを起動/終了する
Python(pywin32)でWordを操作する[3] - 新規ドキュメント作成
Python(pywin32)でWordを操作する[4] - 文字列を入力/取得/削除する
Python(pywin32)でWordを操作する[5] - ドキュメントをファイルに保存する、Wordのオプション変更
Python(pywin32)でWordを操作する[6] - 特定のタイトルが付いているウィンドウの操作
Python(pywin32)でWordを操作する[7] - 既存文書を開く/閉じる(Documents.Open(), Document.Close())
Python(pywin32)でWordを操作する[8] - 段落単位の文字列取得, 統計(ページ数, 段落数,etc)取得
Python(pywin32)でWordを操作する[9] - Word文書をgrepする(docgrep.rb移植)
Python(pywin32)でWordを操作する[10] - Word文書をgrepする(ページ数表示, 行数表示, ワイルドカード展開)

参考