私はいつもXMLの処理がやや面倒だと思っていました。 XMLパーサーの実装について話しているのではありません。SAXパーサーのような既存のストリームベースのパーサーを使用してusingについて話しているノードごとのXML。
はい、これらのパーサーのさまざまなAPIを学ぶのは本当に簡単ですが、XMLを処理するコードを見るときはいつでも、多少複雑なことに気づきます。重要な問題は、XMLドキュメントが論理的に個々のノードに分離されているにもかかわらず、データのタイプと属性が実際のデータから分離されていることが多いようです。入れ子の複数のレベルによって。したがって、特定のノードを個別に処理する場合、現在の場所およびを特定するために、多くの追加の状態を維持する必要があります)次に何をする必要があるか
たとえば、典型的なXMLドキュメントからのスニペットが与えられた場合:
_<book>
<title>Blah blah</title>
<author>Blah blah</author>
<price>15 USD</price>
</book>
_
...本のタイトルを含むテキストノードに遭遇したことをどのように判断しますか?イテレータのように機能する単純なXMLパーサーがあり、XMLParser.getNextNode()
を呼び出すたびにXMLドキュメントの次のノードを取得するとします。私は必然的に次のようなコードを書いています。
_boolean insideBookNode = false;
boolean insideTitleNode = false;
while (!XMLParser.finished())
{
....
XMLNode n = XMLParser.getNextNode();
if (n.type() == XMLTextNode)
{
if (insideBookNode && insideTitleNode)
{
// We have a book title, so do something with it
}
}
else
{
if (n.type() == XMLStartTag)
{
if (n.name().equals("book")) insideBookNode = true
else if (n.name().equals("title")) insideTitleNode = true;
}
else if (n.type() == XMLEndTag)
{
if (n.name().equals("book")) insideBookNode = false;
else if (n.name().equals("title")) insideTitleNode = false;
}
}
}
_
基本的に、XML処理はすぐに巨大なステートマシン主導のループに変わり、多くの状態変数は以前に見つけた親ノードを示すために使用されます。それ以外の場合は、すべてのネストされたタグを追跡するために、スタックオブジェクトを維持する必要があります。これはすぐにエラーが発生しやすくなり、保守が困難になります。
繰り返しますが、問題は、対象のデータが個々のノードに直接関連付けられていないことです。もちろん、次のようなXMLを記述した場合は、そうなる可能性があります。
_<book title="Blah blah" author="blah blah" price="15 USD" />
_
...しかし、これが実際にXMLが使用される方法はめったにありません。ほとんどの場合、親ノードの子としてテキストノードがあり、テキストノードの参照先を決定するために親ノードを追跡する必要があります。
それで...私は何か間違ったことをしていますか?もっと良い方法はありますか? XMLストリームベースのパーサーを使用するのが煩雑になり、本格的なDOMパーサーが必要になるのはどの時点ですか?他のプログラマーに、ストリームベースのパーサーでXMLを処理するときにどのようなイディオムを使用するのか聞いてみたいと思います。ストリームベースのXML解析は常に巨大なステートマシンになる必要がありますか?
私にとって、問題はその逆です。 XML文書が煩雑になり、DOMの代わりにSAXの使用を開始する必要があるのはどの時点ですか?
SAXを使用するのは、サイズが非常に大きく、サイズが不確定なストリームのみです。または、XMLが呼び出すことを意図されている動作が本当にイベント駆動型であり、したがってSAXに似ている場合。
あなたが与える例は、私にとって非常にDOMに似ています。
編集:私はまた、形式が正しくない可能性があるストリームにもSAXを使用しますが、データを取り出す際に推測を行います。
私はXMLをあまり使用していません。私の意見では、XMLをライブラリーで構文解析する最良の方法の1つは、XPathを使用することです。
ツリーをたどって特定のノードを見つける代わりに、そのノードへのパスを指定します。例(疑似コード)の場合、次のようになります。
books = parent.xpath( "/ book")//これにより、すべての本のノード for-each book in books title = book.xpath( "/ title/text() ") author = book.xpath("/author/text() ") price = book.xpath("/price/text() ") //データを使って物事を行う
XPathはそれよりもはるかに強力です。条件(値と属性の両方)を使用して検索し、リスト内の特定のノードを選択し、ツリー内でレベルを移動できます。使用方法に関する情報を探すことをお勧めします。これは、多くの解析ライブラリに実装されています(私は.Net FrameworkバージョンとPythonのlxmlを使用しています)。
ストリームベースのXML解析は常に巨大なステートマシンになる必要がありますか?
通常はそうです。
本格的なDOMパーサーを使用するように指示するのは、たとえば、ドキュメント内の相互参照を解決できるようにするために、メモリ内のファイル階層の一部を模倣する必要があるときです。
一般に、解析は単にステートマシンを駆動するだけであり、XML解析も同じです。ストリームベースの解析は常に手間がかかります。祖先ノードを追跡するために常に何らかのスタックを構築し、多くのイベントと、タグまたはパスレジストリをチェックしてイベントを発生させるある種のイベントディスパッチャーを定義します。一致する場合。コアコードはかなりタイトですが、イベントハンドラーのhuge wadが必要になります。これは、主に次のテキストノードの値を構造体のフィールドに割り当てることで構成されています。そこにもビジネスロジックを混在させる必要がある場合は、かなり毛むくじゃらになります。
サイズやパフォーマンスの問題が特に指示されない限り、私は常にDOMを使用します。
完全に言語にとらわれないわけではありませんが、私は通常、XMLを解析することを考えるのではなく、オブジェクトに逆シリアル化します。速度の問題がある場合にのみ、解析戦略自体を心配する必要があります。
イテレータを提供するパーサーを見つけた場合、それをレクサーとして扱い、ステートマシンジェネレーターを使用することを考えましたか?
XPathを使用できれば、煩雑さが大幅に軽減されます。 .NETランドでは、LINQ to XMLはあまり魅力的でないものも抽象化しています。 (編集-もちろん、DOMアプローチが必要です)
基本的に、ストリームベースのアプローチを採用している場合(DOMを必要とするより優れた抽象化を使用できない場合)、これは常にかなり面倒であり、これを回避する方法があるかどうかはわかりません。