括弧付の構文をパースする簡単なスクリプト /w python
プレーンファイルのデータ変換をいろいろとやっていると、表題のようなことが必要になることがある。
たとえば、
- 各種設定ファイル
- ログファイル
- SQLとか、言語
- そのほか ...
最近はxmlのものも多くて、その場合はxmlライブラリ(pythonならbeautifulsoupか)を使って組める。beautifulsoupはインターフェースがかなり直感的に書けて、何も考えないでも、書いた後 try & error でスクリプトを調整すれば割と要件を満たせる。
xmlではなく、言語のような独自の構文であったりする場合、データとして取り込むときに、とりあえず、文字列の中の括弧の構造を木構造にしたい。
例えば、次のような文があったとする。
- 本日(今日のこと(日曜日だね))は晴天([めちゃ日差し強いよ]晴れのこと)なり。
括弧とその中の文を、"子の文"として、リストに入れる。元の文には別の文字を置き換える。pythonらしく"%"で置き換えるとすると、次の3タプル構造で文を表現する。
- ( "" , "本日%は晴天%なり。" , [...] )
タプルの1番目は、文全体がどんな括弧にくくられているかを示すもので、""の場合は地の文となる。タプルの2番目は置き換えた後の本文で、3番目は子の文のリストである。3番目のリストをちゃんと書くと、
- [("(","今日のこと%",[("(","日曜日だね",)]),("(","%晴れのこと",[("[","めっちゃ日差し強いよ",)])]
となる。
下準備
parseするかっこの種類を辞書式でセットしておく。これのキーがタプル構造の1番目にはいる。また、文字列のどの部分までをparseしたかを取り出せるように、一文の3タプル構造を引数として、parseされた文字列の長さを返す関数を用意しておく。
bracketPair={u'(':u')','(':')','[':']',u'[':u']'} def countlen(d): l= len(d[1]) -len(d[2]) if d[0]!="":l+=len(d[0] + bracketPair[d[0]]) return l+sum(map(countlen, d[2]))
直感的に書く
いずれにしても再帰を使うのだが、ある程度、手続き的に直感で書くとこんな感じ。whileで途中までたどる。
def parseBracket(hd,st): d=[hd,"",[]] idx=0 while idx<len(st): if hd!="" and st[idx]==bracketPair[hd]: break if st[idx] in bracketPair: c =parseBracket(st[idx],st[idx+1:]) d[2].append(c) d[1]+= "%" idx += countlen(c) else: d[1]+= st[idx] idx += 1 return tuple(d)
再帰的
再帰だけで書くとこうなる。
def parseBracket(hd,st): if st=="" or ( hd!="" and st[0]==bracketPair[hd] ): return (hd,"",[]) if st[0] in bracketPair: c=parseBracket(st[0],st[1:]) n=parseBracket(hd,st[countlen(c):]) return (n[0],"%"+n[1],[c]+n[2]) n=parseBracket(hd,st[1:]) return (n[0],st[0]+n[1],n[2])
どっちがお好みかな?