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

#概要

[docgrep.rb](https://qiita.com/int_main_void/items/18fd401a627409be5d49#docgreprb%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)をpythonに移植してWord文書をgrepするツール(docgrep.py)を作ります。

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

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

[ExcelGrepとdocgrep.rb-日本語を活かした情報処處理入門](https://ameblo.jp/jpinfo/entry-10782844461.html)
> 先づ、docgrep.rb、Rubyで書かれたワード用のgrepコマンドである。勿論、正規表現が使へる。ただし、このコマンドはファイル名は表示しても、頁數や行數は表示してくれない。


#使い方

```console
python docgrep.py [-o] <expr> files... 
```

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


###注

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

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

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

⇒[次回](https://qiita.com/int_main_void/items/21df991ce7ae66002d46)で解決


#コード

```python: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(著)][ruby256_book]」のWordをgrepするRubyスクリプトです。

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

- [オブジェクト指向スクリプト言語Rubyを256倍使うための本のためのホームページ][ruby256_site]＠webarchive

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

ruby 2.2.1で動作させたときの[パッチ](#docgrep.rbのパッチ)を置いときます。


##docgrep.rbのパッチ

1. getopts → optparseに変更
2. [GetAbsolutePathNameで絶対パスに変換](https://qiita.com/int_main_void/items/18fd401a627409be5d49#getabsolutepathname%E3%81%A7%E7%B5%B6%E5%AF%BE%E3%83%91%E3%82%B9%E3%81%AB%E5%A4%89%E6%8F%9B)
3. usageの文字列を修正(書籍に合わせた)

```diff: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][com_meet_ruby]

>(略) 相対パスで引数を記述するとNTや2000だとdocgrep.rbはちゃんと動かない。だから、51ページのリストの#Cの部分を
>
```vb
wdoc = wd.Documents.open File.expand_path(doc)
```
> としてください。ここからわかること。MSもパスの区切りに'\'を使うのが必ずしも良いと思ってるわけじゃなさそうだということ。Wordは'/'でもちゃんと処理する。 


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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/34777/b7568269-c445-bd1b-dd34-224766ce790e.png)


FileSystemObjectの[GetAbsolutePathName](https://msdn.microsoft.com/ja-jp/library/cc428007.aspx)を使うといいようにやってくれます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/34777/da0d10cf-b844-b1bd-4c61-c4b4926f4a27.png)

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

#参考

- [Rubyを256倍使うための本 邪道編][ruby256_book]
- [Rubyを256倍使う本 邪道編の間違い探しや訂正などなど - COM Meets Ruby][com_meet_ruby]
- [オブジェクト指向スクリプト言語Rubyを256倍使うための本のためのホームページ][ruby256_site](webarchive)
- [SearchApp - Vector][SearchApp]
- [FileSystemObject オブジェクト-msdn][fso]
- [コマンドライン引数のワイルドカードを展開する-blog.PanicBlanket.com][wildcard]
- [ExcelGrepとdocgrep.rb-日本語を活かした情報処處理入門](https://ameblo.jp/jpinfo/entry-10782844461.html)


[wildcard]:http://blog.panicblanket.com/archives/1736
[com_meet_ruby]:http://www.geocities.co.jp/SiliconValley-PaloAlto/9251/ruby/r256.html
[fso]:https://msdn.microsoft.com/ja-jp/library/cc428071.aspx
[SearchApp]:https://www.vector.co.jp/soft/winnt/util/se492847.html
[ruby256_book]:http://amzn.asia/4tVsjZN
[ruby256_site]:https://web.archive.org/web/20100228231124/http://ruby256.hp.infoseek.co.jp:80/