InfoQ主催のQConに行ってきました。書きたいことはたくさんあるけど、まずはMartin FowlerのDSLのお話を聞いて思ったことです。DSLに関しては僕も一回「英語に近いからDSLっておかしくね?」という思いをLTで発表したことがあるぐらいなので、色々思うところはあるわけです。まぁ、あんまり注目されたLTではなかったのですし、将来DSLが流行ったときに「既にこういうことを考えているヤツがいた!」と僕の名前が発掘されるのが僕の希望です。
Fowlerは、XMLよりもRubyの方が見やすいとか、そういうことを説明されていましたが、ステートチャートは上から下にシーケンシャルに流れるのではなくて、平面空間上にネットワーク構造を構成しています。フローチャートもUMLのアクティビティ図も、UMLのステートチャートも全部そうですけど。それならばいっそのこと、2Dで表現できるツールを使った方がいいですよね?Rubyで書かれたネットリストよりもね。ということで、このときにすでに考えていた「PowerPointのオートシェープを使ってネットワーク構造を表現するDSLってありじゃないの?」というのを実装してみました。Python+win32comです。矢印の向き情報を参照してノード間の接続情報を取得して出しています。また、吹き出しはコメント扱いで無視する実装になっています。

このパワーポイントを処理した結果は以下のようになります。
id:2056 text:End id:2052 text:Start id:2053 text:State1 id:2054 text:State2 2053 -> 2056 2052 -> 2053 2053 -> 2054 2054 -> 2053 2054 -> 2056
発展の余地はたくさんあって、形状に意味を持たせたり、矢印の色や太さに意味を持たせる、というのがまず考えられますよね。トランジションというか、矢印に意味というかテキストを載っけるのも検討すると幅が広がります(おそらく座標値でヒットテストをする必要あり)。もちろん、図形に載っけるテキストの文法を定義するのもありですよね。調査したところ、OpenOfficeでもAPIが色々あるので大丈夫そうですが、業務でプログラマ以外の人にこのファイルを扱ってもらう(読む by Fowler)というDSLのメリットを生かしてもらうには今のところPower Pointかな、と思います。あ、残念ながら、Google DocsのPresentationはAPIで絵の情報が取得できないので使用できません。KeyNoteは手元に環境がないのでパス。
以下、ソースコードです。
import win32com.client import pythoncom class ExternalPowerPointDsl(object): def __init__(self, file_name): pythoncom.CoInitializeEx(pythoncom.COINIT_APARTMENTTHREADED) ppt = win32com.client.DispatchEx('Powerpoint.Application') ppt_obj = ppt.Presentations.open(file_name, False, False, False) self.connectors = [] self.entities = {} for slide in self._slides(ppt_obj): for shape in self._shapes(slide): if self._is_callout(shape): continue elif self._is_connector(shape): self._check_connector(shape) else: self.entities[shape.Id] = self._label(shape) def _label(self, shape): return shape.TextFrame.TextRange.Text def _is_callout(self, shape): callout_types = range(105, 124) return shape.AutoShapeType in callout_types def _is_connector(self, shape): connector_type = -2 return shape.AutoShapeType == connector_type def _shapes(self, slide): for i in range(slide.Shapes.Count): yield slide.Shapes(i+1) def _slides(self, ppt): for i in range(ppt.Slides.Count): yield ppt.Slides(i+1) def _check_connector(self, shape): format = shape.ConnectorFormat style = shape.Line if not format.BeginConnected or not format.EndConnected: return shapes = [format.BeginConnectedShape.Id, format.EndConnectedShape.Id] if style.BeginArrowheadStyle != 1 and style.EndArrowheadStyle != 1: self.connectors.append((shapes[0], shapes[1])) self.connectors.append((shapes[1], shapes[0])) elif style.BeginArrowheadStyle != 1: self.connectors.append((shapes[1], shapes[0])) elif style.EndArrowheadStyle != 1: self.connectors.append((shapes[0], shapes[1])) if __name__ == "__main__": dsl = ExternalPowerPointDsl("test_data.ppt") for key, value in dsl.entities.items(): print "id:%s text:%s" % (key, value) for begin, end in dsl.connectors: print "%s -> %s" % (begin ,end)
最後に僕が以前発表したスライドも貼り付けておきますね。