無いなら作ろう
これも先日のエントリだが、J2SE6のStAXプルパーサのカーソルAPIは.NETで言うところのサブツリーを抽出する操作ができないと書いた。
できないだけで終わるのは癪だし、実際に移植する際にはサブツリーを処理するロジックを書く必要がある訳で、であればサブツリーを処理するだけのクラスを作ってしまおうということで書いてみた。
StAXの読み込み用のカーソルAPIで使用するインタフェースはXMLStreamReaderである。直接の実装クラスで公開されているものは無いが、フィルタを構成するためのStreamReaderDelegateという、XMLStreamReaderに全ての処理を委譲する(デコレータ? プロキシ?どっちだっけか※1)おあつらえ向きのクラスがあったのでこれを利用することにした。
public class XMLStreamSubReader extends StreamReaderDelegate { protected String currentNode; protected boolean breakCurrentNode; public XMLStreamSubReader(XMLStreamReader delegateReader) { super(delegateReader); this.currentNode = delegateReader.getLocalName(); this.breakCurrentNode = false; } @Override public boolean hasNext() throws XMLStreamException { if ( this.breakCurrentNode ) { return false; } else { return this.getParent().hasNext(); } } @Override public int next() throws XMLStreamException { int result = this.getParent().next(); switch (this.getParent().getEventType()) { case XMLStreamConstants.START_ELEMENT: if ( this.getParent().getLocalName().equals(this.currentNode)) { this.breakCurrentNode = false; } break; case XMLStreamConstants.END_ELEMENT: if ( this.getParent().getLocalName().equals(this.currentNode)) { this.breakCurrentNode = true; } break; case XMLStreamConstants.END_DOCUMENT: this.breakCurrentNode = true; break; } return result; } }
内容としてはイベント処理中にカレントノードがブレークしたらそこで終了。その後続けて使用することは考えていない。また、自分が使う予定が無いのでXML名前空間のことも全く考えていない。
使い方は、ノードが開始したと思われるコンテキストで
//サブツリーの処理 XMLStreamReader subReader = new XMLStreamSubReader(reader); for (; subReader.hasNext(); subReader.next()) { //カレントノードのサブツリーのみを処理 }
などと使う。
XMLStreamReaderは「カーソルAPI」と呼ばれるが、それにしてはカーソルのポジションを取得したり制御することが全くできない。(次のタグ要素にスキップすることはできる)これができればもっとスマートに書けると思うのだが。※2
※1 デザインパターンで熱くなっていた時代が遠い昔のように感じる。騒がれるようになってからまだせいぜい7〜8年程度しか経っていないと思うんだが。
※2 getLocation()という現在のポジションを返すメソッドはあるが、これは主にエラーが発生した時などの詳細を得るのが目的であり、カーソル制御には使えない