wxPythonでAA管理ツールを作ろう!第六回 PyMigemoでインクリメンタルサーチ

あいさつ

つかさです。
今回は、PyMigemoを使います。

正規表現について

まずは、python正規表現について。reモジュールを使うことで、正規表現が使えます。

  1. パターンをコンパイルし、正規表現オブジェクトを作る
  2. そのオブジェクトから、match()あるいはsearch()で、検索したい語を入れ、返り値を得る

という順序です。

PyMigemoを使うと、この正規表現のためのパターンを得ることができます。

PyMigemoのインストール

migemo.pydと、migemo.dllをスクリプトのあるフォルダにおけば、このモジュールを使えるようになります。
また、migemo用のDictもどこかからダウンロードしましょう。これも同じフォルダにおきます。


migemoでパターンを得る

例えば、Migemoオブジェクトを作った後、query()にtukaというのを与えると

([捕遺仕疲遣柄攫束塚使冢掴閊障把支捉司掌元曹搏]|tuka|tuka|つか|付かぬ事|ツカ)

このようなパターンを得ることができます。
このパターンを正規表現に利用すれば、ローマ字でも日本語を検索できるわけです。

プログラム

以前から変更したのは大きくは二点。

  1. IncrementalSearchクラスを付け加えた
  2. OnKeyTxtを付け加えた
  • AAlist07.py
import wx,os,migemo,re


migemo_object = None

class MyPopupWindow(wx.PopupWindow):
    def __init__(self, parent, style):
        wx.PopupWindow.__init__(self, parent, style)
        self.st = wx.StaticText(self, -1,pos=(10,10))

    def ChangeTxt(self,message,pos):
        
        self.st.SetLabel(message)
        sz = self.st.GetBestSize()
        gps = self.GetSize()
        self.SetDimensions(pos[0], pos[1], sz.width + 20 , sz.height + 20)
        self.Show(True)

class IncrementalSearch:
    def __init__(self,pattern,list):
        self.pattern = pattern
        self.new_list = []
        self.old_list = list

    def GetBackList(self):
        global migemo_object
        
        if migemo_object==None:
            migemo_object = migemo.Migemo('Dict\migemo-dict')
        re_pattern = migemo_object.query(self.pattern)
        try:
            migemo_re_object = re.compile(re_pattern, re.IGNORECASE)
        except re.error:
            return False
                    
        for x in self.old_list:
            if migemo_re_object.search(x):
                self.new_list.append(x)
        return self.new_list


class MyTxtFrm(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id)
        
        aa_list = os.listdir("AAList")
        self.AA_Dict = {}
        for x in aa_list:
            key = os.path.splitext(x)[0].decode("shift-jis")
            path = os.path.join("AAlist",x)
            self.AA_Dict[key] = path

        self.win = MyPopupWindow(self,
                                 wx.SIMPLE_BORDER)

        self.TxtCtr = wx.TextCtrl(self, -1)
        self.LBox = wx.ListBox(self, -1, choices = self.AA_Dict.keys())
        self.TxtCtr.Bind(wx.EVT_KEY_DOWN, self.OnKeyChar)
        self.TxtCtr.Bind(wx.EVT_TEXT,self.OnText)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.TxtCtr, 0, wx.EXPAND)
        self.sizer.Add(self.LBox, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)
        self.sizer.Fit(self)


    def OnKeyChar(self,event):
        key = event.GetKeyCode()
        if key ==  wx.WXK_ESCAPE:
            wx.Exit()
        elif key == wx.WXK_UP:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() - 1
            if next >=  0:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(count - 1)
            
            f = open(self.AA_Dict[self.LBox.GetStringSelection()])
            fr = f.read()
            
            gps = self.GetSize()
            gp = self.GetPosition()
            pos = (gp.x + gps.x, gp.y + 45)
            self.win.ChangeTxt(fr,pos)
        
        elif key == wx.WXK_DOWN:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() + 1
            if next < count:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(0)
            
            f = open(self.AA_Dict[self.LBox.GetStringSelection()])
            fr = f.read()
            
            gps = self.GetSize()
            gp = self.GetPosition()
            pos = (gp.x + gps.x, gp.y + 45)
            self.win.ChangeTxt(fr,pos)

        elif key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
            c_board = wx.Clipboard()
            f = open(self.AA_Dict[self.LBox.GetStringSelection()])
            fr = f.read()
            c_board.SetData(wx.TextDataObject(fr))
            c_board.Flush()
            c_board.Close()
            wx.Exit()
        else: event.Skip()

    def OnText(self,event):
        word = self.TxtCtr.GetValue()
        isearch = IncrementalSearch(word,self.AA_Dict.keys())
        try:
            new_list = isearch.GetBackList()
        except:
            return False
        if new_list:
            self.LBox.Set(new_list)
            self.LBox.SetSelection(0)
            self.ShowToolTip()
        else: event.Skip()
        event.Skip()
        

    def ShowToolTip(self):
        f = open(self.AA_Dict[self.LBox.GetStringSelection()])
        fr = f.read()
        
        gps = self.GetSize()
        gp = self.GetPosition()
        pos = (gp.x + gps.x, gp.y + 45)
        self.win.ChangeTxt(fr,pos)


class MyApp(wx.PySimpleApp): 
    def OnInit(self):

        self.TxtFrm = MyTxtFrm(None, -1)
        self.TxtFrm.SetSize( (180, 200) )
        self.TxtFrm.Show()
        self.TxtFrm.TxtCtr.SetFocus()
        return 1

app = MyApp()
app.MainLoop()


IncrementalSearchクラス

検索文字列と調べたいリストを受け取り、検索文字列に一致したもののみのリストを返すIncrementalSearchクラスを作製しましょう。

class IncrementalSearch:
    def __init__(self,pattern,list):
        self.pattern = pattern
        self.new_list = []
        self.old_list = list

    def GetBackList(self):
        global migemo_object
        
        if migemo_object==None:
            migemo_object = migemo.Migemo('Dict\migemo-dict')
        re_pattern = migemo_object.query(self.pattern)
        try:
            migemo_re_object = re.compile(re_pattern, re.IGNORECASE)
        except re.error:
            return False
                    
        for x in self.old_list:
            if migemo_re_object.search(x):
                self.new_list.append(x)
        return self.new_list

__init__のところを見てください。引数として

  • 検索に用いたい文字列
  • その文字列で検索したいリスト

を受け取ります。

GetBackListは、受け取ったリストを受け取った文字列でmigemo検索し、それにヒットした要素のみを、新しいリストとして返す関数です。GetBackListについて説明します。

Migemoオブジェクトの作成
        if migemo_object==None:
            migemo_object = migemo.Migemo('Dict\migemo-dict')

まず、Migemoオブジェクトがあるかどうかで分岐します。無い場合は、作製します。
一々オブジェクトを作ると、時間がかかるのでこのようにしています。

検索パターンの取得とコンパイル
        re_pattern = migemo_object.query(self.pattern)
        try:
            migemo_re_object = re.compile(re_pattern, re.IGNORECASE)
        except re.error:
            return False

migemoオブジェクトに、引数として受け取ったエディットボックスに入力された文字を入れ、正規表現用のパターンを取得します。
その後、それをコンパイルします。

一致したリストを返す
        for x in self.old_list:
            if migemo_re_object.search(x):
                self.new_list.append(x)
        return self.new_list

ついで、引数として取得したリストの一々の要素について、それがさきに作った正規表現パターンに一致するかを調べていきます。もし一致したら、それを新しいリストに加えます。
こうやって、古いリストから正規表現に一致したリストを新しく作り直すわけですね。
このようにして出来た新しいリストを、returnで返します。

本体と結びつける

OnText関数

エディットボックスで何か入力されるたびにこのクラスを呼び出し、リストを受け取り、ListBoxにセットすればいいわけですね。
そのための関数OnTextを作製します。

    def OnText(self,event):
        word = self.TxtCtr.GetValue()
        isearch = IncrementalSearch(word,self.AA_Dict.keys())
        try:
            new_list = isearch.GetBackList()
        except:
            return False
        if new_list:
            self.LBox.Set(new_list)
            self.LBox.SetSelection(0)
            self.ShowToolTip()
        else: event.Skip()
        event.Skip()

GetValue()でエディットボックスの文字列を受け取ります。
次に、先に作ったIncrementalSearchにその文字列と、ファイル名のリストを投げます。
そして戻ってきた新しいリストをListBoxにセットし、ListBoxの一番上を選択した状態にし、ツールチップを表示します。

EVT_TEXTで結びつけ

ついで、これをエディットボックスと結びつけます。class MyTxtFrmの初期化メソッド__init__に、次の一文を付け加えます。

        self.TxtCtr.Bind(wx.EVT_TEXT,self.OnText)

これで、エディットボックスに文字が入力されるたびに、OnText関数が呼び出されることになります。