dom4jを使用してストリームから単一のXMLドキュメントを読み取る
質問
dom4jを使用して一度に1つのXMLドキュメントをストリームから読み取って処理し、ストリーム上の次のドキュメントに進みます。残念ながら、dom4jのSAXReader(カバーの下でJAXPを使用)は、次のドキュメント要素の読み取りとチョークを続けています。
SAXReaderがドキュメント要素の終わりを見つけたら、ストリームの読み取りを停止する方法はありますか?これを達成するためのより良い方法はありますか?
解決
いくつかの内部JAXPクラスを使用して、これをいくつかの体操で動作させることができました:
- XMLNSDocumentScannerImplのサブクラスであるカスタムスキャナーを作成する
- XMLNSDocumentScannerImpl.Driverの実装であるカスタムドライバーを、宣言または要素を検出したときにEND_DOCUMENTを返すカスタムスキャナー内に作成します。 fElementScanner.getCurrentEntity()からScannedEntityを取得します。エンティティにPushbackReaderがある場合、エンティティバッファ内の残りの未読文字をリーダーにプッシュバックします。
- コンストラクターで、fTrailingMiscDriverをこのカスタムドライバーのインスタンスに置き換えます。
- カスタム構成クラス、XIncludeAwareParserConfigurationのサブクラスを作成します。これは、コンストラクターで、ストックDOCUMENT_SCANNERをこのカスタムスキャナーのインスタンスに置き換えます。
- このカスタム構成クラスのインスタンスを<!> quot; com.sun.org.apache.xerces.internal.xni.parser.XMLParserConfiguration <!> quot;としてインストールします。 dom4jのSAXReaderクラスがJAXP XMLReaderを作成しようとするときにインスタンス化されます。
- Readerをdom4jのSAXReader.read()メソッドに渡すとき、1文字のデフォルトよりもかなり大きいバッファーサイズのPushbackReaderを指定します。 JAXPのApache2のコピー内のXMLEntityManagerのデフォルトのバッファーサイズをサポートするには、少なくとも8192で十分です。
これは内部JAXPクラスのサブクラス化を伴うため、最もクリーンなソリューションではありませんが、機能します。
他のヒント
ほとんどの場合、同じストリームに同時に複数のドキュメントを含めることは望ましくありません。 SAXReaderは、最初のドキュメントの最後に到達したときに停止するほどスマートではないと思います。このように同じストリームに複数のドキュメントが必要なのはなぜですか?
アダプタを追加する必要があると思います。アダプタを追加して、ストリームをラップし、次のドキュメントの先頭が見つかったときにファイルの終わりを返すようにします。私の知る限り、書かれたパーサーはファイルの終わりまたはエラーまで続きます...そして別の<?xml version="1.0"?>
を見るのは間違いなくエラーになります。
最初にドキュメントをストリームに配置する責任があると仮定すると、何らかの方法でドキュメントを簡単に区切ることができるはずです。例:
// Any value that is invalid for an XML character will do. static final char DOC_TERMINATOR=4; BOOL addDocumentToStream(BufferedWriter streamOut, char xmlData[]) { streamOut.write(xmlData); streamOut.write(DOC_TERMINATOR); }
その後、DOC_TERMINATORが検出されるまで、配列から読み込まれたストリームから読み込むとき。
char *getNextDocuument(BufferedReader streamIn) { StringBuffer buffer = new StringBuffer(); int character; while (true) { character = streamIn.read(); if (character == DOC_TERMINATOR) break; buffer.append(character); } return buffer.toString().toCharArray(); }
4は無効な文字値であるため、明示的に追加する場合を除いて発生しません。したがって、ドキュメントを分割できます。これで、SAXへの入力用に、結果のchar配列をラップするだけでいいのです。
... XMLReader xmlReader = XMLReaderFactory.createXMLReader(); ... while (true) { char xmlDoc = getNextDocument(streamIn); if (xmlDoc.length == 0) break; InputSource saxInputSource = new InputSource(new CharArrayReader(xmlDoc)); xmlReader.parse(saxInputSource); } ...
長さ0のドキュメントを取得するとループが終了することに注意してください。これは、getNextDocument()でストリームの終わりを検出するために何かを追加する必要がある最後のドキュメントの後に2番目のDOC_TERMINATORを追加する必要があることを意味します
これまでは、ベースリーダーを、非常に単純な解析機能を持つ独自の作成の別のリーダーでラップすることでこれを行ってきました。ドキュメントの終了タグを知っていると仮定すると、ラッパーは単に一致するかどうかを解析します。 <!> quot; <!> lt; / MyDocument <!> gt; <!> quot; EOFを返すことを検出すると。ラッパーは、最初の開始タグを解析し、一致する終了タグでEOFを返すことにより、アダプティブにすることができます。終了タグのレベルを実際に検出する必要はないことがわかりました。ドキュメントタグ自体を使用したドキュメントがないため、終了タグの最初の出現でドキュメントが終了することが保証されました。
覚えているように、DOMリーダーが入力ソースを閉じるため、トリックの1つはラッパーブロックをclose()にすることでした。
したがって、リーダーの入力が与えられると、コードは次のようになります。
SubdocReader sdr=new SubdocReader(input);
while(!sdr.eof()) {
sdr.next();
// read doc here using DOM
// then process document
}
input.close();
EOFが検出されると、eof()メソッドはtrueを返します。 next()メソッドは、read()に対して-1を返すのを停止するようにリーダーにフラグを立てます。
うまくいけば、これは有用な方向を示してくれます。
- キウイ。
入力ストリームを内部バッファに読み込みます。予想される合計ストリームサイズに応じて、ストリーム全体を読み取って解析するか、あるxmlと次のxmlの境界を検出します(
1つのxmlを持つストリームと複数のxmlを持つストリームを処理する場合の唯一の本当の違いは、バッファーと分割ロジックです。