2011-12-15 11 views
8

非常に多数の独立したレコードからなるXML文書を処理する必要があります。大きなXML文書からストリーミングイテレータ[Node]を取得するにはどうすればよいですか?

<employees> 
    <employee> 
     <firstName>Kermit</firstName> 
     <lastName>Frog</lastName> 
     <role>Singer</role> 
    </employee> 
    <employee> 
     <firstName>Oscar</firstName> 
     <lastName>Grouch</lastName> 
     <role>Garbageman</role> 
    </employee> 
    ... 
</employees> 

場合によっては、これらは単なる大きなファイルですが、ストリーミングソースからのものである場合もあります。

scala.xml.XmlLoader.load()は、ドキュメント全体をメモリに保持する(または入力ストリームが閉じるのを待つ)必要がないため、作業する必要があるだけなので一度に1つのレコード。私は、XmlEventReaderを使用して、入力を一連のXmlEventsとしてストリーミングできることを知っています。しかし、これらはscala.xml.Nodeよりも操作するのがずっと簡単ではありません。

だから私は制御の下でメモリ使用量を維持しながら、便利なScalaの構文を使用して、個々のレコードの上で動作するために、何とかこのうち怠惰なイテレータ[ノード]を取得したいのですが。これを自分で行うには

、私は、XMLEventReaderので始まり、各マッチング開始と終了タグの間のイベントのバッファを構築し、そのノードからツリーを構築することができます。しかし、私が見落とした簡単な方法はありますか?どんな洞察にも感謝します!

答えて

8

XMLEventReaderConstructingParserで使用される基礎となるパーサーを使用して、コールバックを使用してトップレベル以下の従業員ノードを処理することができます。あなたは、すぐに処理されたデータを破棄注意する必要があります:

import scala.xml._ 

def processSource[T](input: Source)(f: NodeSeq => T) { 
    new scala.xml.parsing.ConstructingParser(input, false) { 
    nextch // initialize per documentation 
    document // trigger parsing by requesting document 

    var depth = 0 // track depth 

    override def elemStart(pos: Int, pre: String, label: String, 
     attrs: MetaData, scope: NamespaceBinding) { 
     super.elemStart(pos, pre, label, attrs, scope) 
     depth += 1 
    } 
    override def elemEnd(pos: Int, pre: String, label: String) { 
     depth -= 1 
     super.elemEnd(pos, pre, label) 
    } 
    override def elem(pos: Int, pre: String, label: String, attrs: MetaData, 
     pscope: NamespaceBinding, nodes: NodeSeq): NodeSeq = { 
     val node = super.elem(pos, pre, label, attrs, pscope, nodes) 
     depth match { 
     case 1 => <dummy/> // dummy final roll up 
     case 2 => f(node); NodeSeq.Empty // process and discard employee nodes 
     case _ => node // roll up other nodes 
     } 
    } 
    } 
} 

次に、あなたが取得されていません秒レベルでのノードを想定した(一定のメモリ内に第二のレベルで各ノードを処理するために、このように使用します子供の任意の数):

processSource(src){ node => 
    // process here 
    println(node) 
} 

XMLEventReaderに比べて利益が2つのスレッドを使用しないことです。また、提案されたソリューションと比較してノードを2回解析する必要もありません。欠点は、ConstructingParserの内部動作に依存していることです。

+0

ブリリアント!これは素晴らしいです。このジェネレータスタイルのものからIteratorを得るにはあまりにも難しくありません。私の他の答えを見てください。どうもありがとう! –

5

TraversableOnce[Node]にhuynhjlの発電溶液から得るthis trickを使用するには:

def generatorToTraversable[T](func: (T => Unit) => Unit) = 
    new Traversable[T] { 
    def foreach[X](f: T => X) { 
     func(f(_)) 
    } 
    } 

def firstLevelNodes(input: Source): TraversableOnce[Node] = 
    generatorToTraversable(processSource(input)) 

generatorToTraversableの結果が複数回トラバース可能ではありません(新しいConstructingParserは、各foreachの呼び出しでインスタンス化されていても)入力理由streamはSourceであり、Iteratorです。ただし、Traversable.isTraversableAgainをオーバーライドすることはできません。

本当に私たちはイテレータを返すことによって、これを強制したいと思います。ただし、Traversable.toIteratorとTraversable.view.toIteratorの両方が中間ストリームを作成します。このストリームはすべてのエントリをキャッシュします(この練習の目的をすべて破棄します)。しかたがない;ストリームに2回アクセスすると例外がスローされるようにします。

はまた、全体のことはスレッドセーフではありません注意してください。

このコードは素晴らしい実行され、私はまだ大きな入力でそれを試していないけれども、私は、全体的なソリューションは怠け者と(したがって、定数メモリ)をキャッシュしないの両方であると信じます。

+0

私はこの素晴らしいトリックについて知らなかった! – huynhjl

関連する問題