久しぶりの潜伏者、初めてのポスター。私の質問が明確でない場合はお知らせください。
解析する必要があるちょっと変わった XML ファイルがあります (クラス内にデータを入れて、内部で処理します)。ネストされるべきだと誰もが通常考えるものは、そうではないので、私はそれが奇妙だと言います。例を挙げましょう:
<root>
<item id="XX" rank="YY">
<top>
<description a="XX"><s>***</s>content<s>+++</s></description>
<mainterm t="XX">term</mainterm>
<description a="YY">more content</description>
</top>
<!-- All examples directly below a data correspond to that data.
Shouldn't they be nested? -->
<data level="10" x="4">data here </data>
<example f="45"> example 1</example>
<example f="12"> example 2</example>
<example f="44"> example 3</example>
<data level="11" x="1">data here </data>
<example f="33"> example 1</example>
<example f="6"> example 2</example>
<example f="18"> example 3</example>
<!-- More data tags with and without examples below -->
</item>
</root>
ファイルには、数万のアイテムが続きます。データをまったく含まないアイテムもあれば、何も入っていないデータ タグもあります。
これをどのように解析するかについては完全な自由が与えられました。私は Scala をマスターしようとしているので、タスクを解決するために Scala を選択しました。以前は StAX (Apache AXIOM) を使用してプル解析を行っていましたが、Scala で同様のものを探していたところ、Scales を見つけました。ここまでは順調ですね。
使用:
- スカラ 2.10.2
- スケール 0.4.5
必要なのは、各タグのコンテンツと属性だけでなく、各タグの生のコンテンツです。たとえば、上記の XML では、「top」タグに次のようなものがあります。
case class Top(descriptions:List[Description], key: Key, rawContent: String)
rawContent は次のようになります。
<description a="XX"><s>***</s>content<s>+++</s></description>
<mainterm t="XX">term</mainterm>
<description a="YY">more content</description
同じことが「データ」タグにも当てはまりますが、データがネストされていないため、プル解析によって XmlPull が Iterator[PullType] に過ぎないことを考えると、タグを解析してノードをトラバースするというアイデアが生まれました。終了タグが見つかるまで、または「data」タグの場合は、別の「data」開始タグまたは「item」終了タグが見つかるまで。しかし、どう考えても状態の保存は避けられません。
ジッパーでやってみることにしました。
まず、特定のタグが見つかるまでトラバースし、その間に見つかった各要素で何かを行う必要があるため、findBy を試しています。以下は私が今試しているコードです。指定されたタグの属性とその中のすべての生のコンテンツを取得しようとします。
/* Some helpers. Ignore */
class PullTypeValue(pt: PullType) {
private val NUM_DEL_CHARS = 2
// Tag names are returned like "{}tagname". Getting rid of the first 2 characters
private def stripHeadChars(s: String) = s.substring(NUM_DEL_CHARS)
// Get the tag name or the value of the given PullType
def getNameOrValue = pt match {
case Left(e:Elem) => stripHeadChars(e.name.toString)
case Left(i:XmlItem) => i.value
case Right(e) => stripHeadChars(e.name.toString)
}
}
class PullTypeZipper(z: Zipper[PullType]) {
implicit def toPullTypeValue(e: Elem) = new PullTypeValue(e)
def moveToTag(tag: String) = {
z.findNext(_ match {
case Left(e:Elem) => e.getNameOrValue == tag
case _ => false
})
}
}
implicit def toPulltTypeValue(pt: PullType) = new PullTypeValue(pt)
implicit def toPullTypeValue(e: Elem) = new PullTypeValue(e)
implicit def toPullTypeValue(i: XmlItem) = new PullTypeValue(i)
implicit def toPullTypeValue(e: EndElem) = new PullTypeValue(e)
implicit def toPullTypeZipper(z: Zipper[PullType]) = new PullTypeZipper(z)
/* End of helpers */
/************* Parsing function here *******************/
def parseTag(currentNode: Option[Zipper[PullType]], currentTagName: String) = {
var attrs: Map[String,String] = Map.empty
val ltags = ListBuffer[String]()
val getAttributes = (z: Zipper[PullType]) =>
z.focus match {
case Left(e:Elem) if e.getNameOrValue == currentTagName =>
attrs = e.attributes.map {a => (a.name.toString.substring(2), a.value)}.toMap
ltags += "<" + e.getNameOrValue + ">"
z.next
case Left(e:Elem) =>
ltags += "<" + e.getNameOrValue + ">"
z.next
case Left(t:Text) =>
ltags += t.value
z.next
case Left(i:XmlItem) =>
ltags += i.value
z.next
case Right(e) =>
ltags += "</" + e.getNameOrValue + ">"
(e.getNameOrValue == currentTagName) ? z.some | z.next
}
/* Traverse until finding the close tag for the given tag name
and extract raw contents from each found tag.
Return the zipper with focus on the next element (if any)
*/
val nextNode = currentNode >>= {_.findBy(getAttributes)(_ match {
case Right(e) => e.getNameOrValue == currentTagName
case _ => false
})} >>= {_.next}
(attrs,ltags.mkString(""),nextNode)
}
/************** End of parsing function ************************/
val zipper = pullXml(new FileReader("MyXmlFile.xml")).toStream.toZipper
val (attrs,rawContents,nextNode) = parseTag(zipper >>= {_.moveToTag("top")}, "top")
// Do something with the values...
このコードは「top」タグで機能しますが、「item」タグで試してみると、StackOverFlowError が発生します。
Exception in thread "main" java.lang.StackOverflowError
at com.ctc.wstx.util.SymbolTable.size(SymbolTable.java:332)
at com.ctc.wstx.util.SymbolTable.mergeChild(SymbolTable.java:291)
at com.ctc.wstx.stax.WstxInputFactory.updateSymbolTable(WstxInputFactory.java:202)
at com.ctc.wstx.sr.BasicStreamReader.close(BasicStreamReader.java:1179)
at scales.xml.XmlPulls$$anon$1.close(XmlPull.scala:134)
at scales.xml.XmlPulls$$anon$1.internalClose(XmlPull.scala:130)
at scales.xml.XmlPull$class.pumpEvent(PullIterator.scala:201)
at scales.xml.XmlPulls$$anon$1.pumpEvent(XmlPull.scala:118)
at scales.xml.XmlPull$class.next(PullIterator.scala:149)
at scales.xml.XmlPulls$$anon$1.next(XmlPull.scala:118)
at scales.xml.XmlPulls$$anon$1.next(XmlPull.scala:118)
at scala.collection.Iterator$class.toStream(Iterator.scala:1143)
at scales.xml.XmlPulls$$anon$1.toStream(XmlPull.scala:118)
at scala.collection.Iterator$$anonfun$toStream$1.apply(Iterator.scala:1143)
at scala.collection.Iterator$$anonfun$toStream$1.apply(Iterator.scala:1143)
at scala.collection.immutable.Stream$Cons.tail(Stream.scala:1085)
at scala.collection.immutable.Stream$Cons.tail(Stream.scala:1077)
at scala.collection.immutable.Stream$$hash$colon$colon$.unapply(Stream.scala:1058)
at scalaz.Zipper$class.next(Zipper.scala:45)
at scalaz.Zippers$$anon$1.next(Zipper.scala:269)
at parser.XMLParser$$anonfun$6$$anonfun$apply$7.apply(XMLParser.scala:258)
at parser.XMLParser$$anonfun$6$$anonfun$apply$7.apply(XMLParser.scala:258)
at scalaz.BooleanW$$anon$1.$bar(BooleanW.scala:142)
at parser.XMLParser$$anonfun$6.apply(XMLParser.scala:258)
at parser.XMLParser$$anonfun$6.apply(XMLParser.scala:237)
at scalaz.Zipper$class.findBy(Zipper.scala:178)
at scalaz.Zippers$$anon$1.findBy(Zipper.scala:269)
at scalaz.Zipper$$anonfun$findBy$1.apply(Zipper.scala:178)
at scalaz.Zipper$$anonfun$findBy$1.apply(Zipper.scala:178)
at scala.Option.flatMap(Option.scala:170)
at scalaz.Bind$$anon$21.bind(Bind.scala:112)
at scalaz.Bind$$anon$21.bind(Bind.scala:111)
at scalaz.MA$class.$greater$greater$eq(MA.scala:73)
at scalaz.MAsLow$$anon$2.$greater$greater$eq(MAB.scala:50)
at scalaz.MASugar$class.$u2217(MA.scala:329)
at scalaz.MAsLow$$anon$2.$u2217(MAB.scala:50)
at scalaz.Zipper$class.findBy(Zipper.scala:178)
at scalaz.Zippers$$anon$1.findBy(Zipper.scala:269)
at scalaz.Zipper$$anonfun$findBy$1.apply(Zipper.scala:178)
at scalaz.Zipper$$anonfun$findBy$1.apply(Zipper.scala:178)
at scala.Option.flatMap(Option.scala:170)
at scalaz.Bind$$anon$21.bind(Bind.scala:112)
at scalaz.Bind$$anon$21.bind(Bind.scala:111)
at scalaz.MA$class.$greater$greater$eq(MA.scala:73)
at scalaz.MAsLow$$anon$2.$greater$greater$eq(MAB.scala:50)
at scalaz.MASugar$class.$u2217(MA.scala:329)
at scalaz.MAsLow$$anon$2.$u2217(MAB.scala:50)
at scalaz.Zipper$class.findBy(Zipper.scala:178)
at scalaz.Zippers$$anon$1.findBy(Zipper.scala:269)
at scalaz.Zipper$$anonfun$findBy$1.apply(Zipper.scala:178)
at scalaz.Zipper$$anonfun$findBy$1.apply(Zipper.scala:178)
... and so on
少し調査を行って、それが関連しているかどうか確信が持てなかったので、Scales が Scalaz 6.0.4 を使用していることがわかりました。そこでは、Zipper.findBy は tailrec ではありませんが、(少なくともそれが使用する内部関数は) Scala 7。しかし、依存関係を 7.0.4 に変更すると、Scalaz 6 から 7 への Iteratee への変更により、Scales から多くのエラーが発生します (同じ場所にないいくつかの参照)。
私の質問:
- 私はすべてのプロセスを過度に殺していますか?このタスクに取り組むために取るべきもっと簡単なアプローチは他にありますか?
- このまま続けていく場合、何か気を付けることはありますか?Scalaz 7 でスケールを使用する方法はありますか?
備考:
- 特に Java での、強力な命令型プログラミングのバックグラウンド。
- 以前に Scala を使用したことがありますが、(今回のように) 行き詰まり、時間がかかるため、多くの場合、命令型の方法に戻らなければなりません。
- 私は以前に Scalaz を使ったことがありません。関数型プログラミングに関する私の知識は基本的なものですが、新しいことを学ぶのはとても楽しいですし、関数型プログラミングが好きです。