概要
XMLドキュメントは階層ドキュメントであり、同じ要素名と名前空間が複数の場所で発生し、意味が異なり、深さが無限に(再帰的に)発生する可能性があります。通常のように、大きな問題の解決策は、それらを小さな問題に分割することです。XML解析のコンテキストでは、これは、そのXMLに固有のメソッドでXMLの特定の部分を解析することを意味します。たとえば、1つのロジックがアドレスを解析します。
<Address>
<Street>Odins vei</Street>
<Building>4</Building>
<Door>b</Door>
</Address>
つまり、あなたは方法を持っているでしょう
AddressType parseAddress(...);
または
void parseAddress(...);
ロジックのどこかで、XML入力引数を取り、オブジェクトを返します(Bの結果は後でフィールドからフェッチできます)。
SAX
SAXはXMLイベントを「プッシュ」し、XMLイベントがプログラム/データのどこに属するかを決定するのはあなたに任されています。
// method in stock SAX handler
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
// .. your logic here for start element
}
'Building' start要素の場合、実際にAddressを解析していることを確認してから、Addressを解釈するジョブを持つメソッドにXMLイベントをルーティングする必要があります。
StAX
StAXはXMLイベントを「プル」し、プログラム/データのどこでXMLイベントを受信するかを決定するのはあなたに任されています。
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
}
もちろん、Addressを解釈するジョブを持つメソッドで「Building」イベントを常に受信する必要があります。
考察
SAXとStAXの違いは、プッシュとプルの違いです。どちらの場合も、解析状態は何らかの方法で処理する必要があります。
これは、SAXでは一般的な方法B、StAXでは方法Aに変換されます。さらに、SAXはB個の個別のXMLイベントを提供する必要がありますが、StAXは(XMLStreamReaderインスタンスを渡すことによって)A個の複数のイベントを提供できます。
したがって、Bは最初に解析の前の状態をチェックし、次に個々のXMLイベントを処理してから、状態を(フィールドに)格納します。方法Aは、満足するまでXMLStreamReaderに複数回アクセスすることにより、XMLイベントを一度に処理できます。
結論
StAXを使用すると、XML構造に従って解析(データバインディング)コードを構造化できます。したがって、SAXに関連して、「状態」はStAXのプログラムフローから暗黙的に示されますが、SAXでは、ほとんどのイベント呼び出しで、常に何らかの状態変数を保持し、その状態に従ってフローをルーティングする必要があります。
最も単純なドキュメントを除くすべてのドキュメントにStAXをお勧めします。後で最適化としてSAXに移行します(ただし、それまでにバイナリに移行することをお勧めします)。
StAXを使用して解析する場合は、次のパターンに従ってください。
public MyDataBindingObject parse(..) {
XMLStreamReader reader = ....;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
break;
}
} while(reader.hasNext());
MyDataBindingObject object = new MyDataBindingObject();
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
if(reader.getLocalName().equals("Whatever1")) {
WhateverObject child = parseSubTreeForWhatever(reader);
level --;
object.setWhatever(child);
}
if(level == 2) {
parseSubTreeForWhateverAtRelativeLevel2(reader);
level --;
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
}
} while(level > 0);
return object;
}
したがって、サブメソッドはほぼ同じアプローチ、つまりカウントレベルを使用します。
private MySubTreeObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySubTreeObject object = new MySubTreeObject();
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
if(reader.getLocalName().equals("Whatever2")) {
MyWhateverObject child = parseMySubelementTree(reader);
level --;
object.setWhatever(child);
}
if(level == 2) {
MyWhateverObject child = parseMySubelementTree(reader);
level --;
object.setWhatever(child);
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
}
} while(level > 0);
return object;
}
そして最終的には、基本タイプを読み取るレベルに到達します。
private MySetterGetterObject parseSubTree(XMLStreamReader reader) throws XMLStreamException {
MySetterGetterObject myObject = new MySetterGetterObject();
int level = 1;
do {
int event = reader.next();
if(event == XMLStreamConstants.START_ELEMENT) {
level++;
if(reader.getLocalName().equals("FirstName")) {
String text = reader.getElementText()
if(text.length() > 0) {
myObject.setName(text)
}
level--;
} else if(reader.getLocalName().equals("LastName")) {
}
} else if(event == XMLStreamConstants.END_ELEMENT) {
level--;
}
} while(level > 0);
return myObject;
}
これは非常に簡単で、誤解の余地はありません。レベルを正しくデクリメントすることを忘れないでください:
A.文字を期待したが、文字を含む必要があるタグにEND_ELEMENTを取得した後(上記のパターン):
<Name>Thomas</Name>
代わりに
<Name></Name>
欠落しているサブツリーについても同じことが言えます。
B.開始要素で呼び出されるサブ解析メソッドを呼び出した後、対応する終了要素の後に戻ります。つまり、パーサーはメソッド呼び出しの前よりも1レベル低くなります(上記のパターン)。
より堅牢な実装のために、このアプローチが「無視できる」空白も完全に無視することに注意してください。
パーサーは、ほとんどの機能について
はWoodstoxを使用し、速度についてはAaalto-xmlを使用します。