しばらく前からやりたかったことではあるのだけど、androidのlayout.xmlを構文補完するためのスキーマを作れないかという話を受けていたこともあって、そのための方法をいろいろ模索していた。
標準的な手法でlayout.xmlに記述できるのは、基本的にはandroid.view.Viewクラスの派生クラスの名前に対応する要素群と、それらの要素で使用できるandroid:*属性の集合からなるツリーだ。カスタムウィジェットの場合は、そのパッケージ名も含めた名前を記述すればいけると思う。layout.xmlがどのようにロードされるかは、android.view.LayoutInflaterという非公開クラスのソースを見ると分かる。
要素の方はさほど難しくない。AndroidのJava APIは、基本的にはソースコードのAPI定義XMLにすべて記述されていて、原理的にはここからViewのすべてのサブクラスを探していけば良い。
もっとずっとお手軽な方法として、Viewのサブクラスの探索については、javadocのViewのページに"known direct subclasses"と"known indirect subclasses"という一覧があって、これをHTMLスクレイピングした方が楽だし早いだろう。今はとりあえずこれで実装してみている。(ちなみに、Androidのjavadocには、属性のマークアップに'<'が含まれているせいで、tidyがエラー扱いして整形出力してくれないという問題があるので、今回はSgmlReaderを使って解決した。)
要素については、どのような子要素が許されるかを調べなければならないのだけど、現時点でまだそこまで実装できていない。
というのは、属性の方がややこしいからだ。
layout.xmlで使用される属性はandroid:*のようなグローバル属性になっていて、独自の名前空間(http://schemas.android.com/apk/res/android)を使用している。各Viewで使用できる属性の定義は、APIからは取得できない。Viewのソースを見ると、コンストラクタにAttributeSetを渡せるものがあって、そこでソースコードが分岐処理している...API定義でなく実装の中でswitch-caseを適用しているのでは、外部から吸い出しようがない。
ただ、これもjavadocのページを見ると、どのページでも同一の形式で "XML Attributes"という表を見せているので、これをHTML scrapingすれば、一覧に出ている属性を取得できる。
これでスキーマを生成できる...!と思ったが、実はこのテーブルは完全ではなかった(!) なんと他にも利用可能な属性が用意されていたのだった。たとえばViewGroupのjavadocには、android:layout_widthの記述がないのだ。
いったいどうやってこの属性を取得すれば良いのか途方に暮れたが、基本に戻ってandroid.view.Viewなどのソースを眺め直して、解決法に思い至った。
まず、適用可能な属性はR.attrクラスにすべて列挙されている。そしてR.styleableクラスには、Viewの派生クラスのそれぞれに対応する「適用可能なスタイル属性」を定義した配列が用意されているようだ。そして、Viewのソースを見る限り、適用可能な属性はこれをもとにswitch-caseで適用しているように見える。これをリフレクションで探していけば良いだろう。
この辺は思いついたばかりなのでまだ実装していない。実のところjavaで実装していなかったのだけど、.NET環境では未実装だったので、javaで書き直すしかないかと思っている。