使用Scales Xml(Scala)解析Big XML,功能方式 - 使用Zippers的StackOverFlowError

时间:2013-10-18 03:23:08

标签: scala xml-parsing scalaz scalaz7

长时间潜伏,第一次海报。如果我的问题不明确,请告诉我。

我有一个奇怪的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,所以我选择它来解决这个问题。和过去一样,我使用StAX(Apache AXIOM)进行解析,我在Scala中寻找类似的东西,我找到了Scales。到现在为止还挺好。

使用:

  • Scala 2.10.2
  • Scales 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

同样适用于“data”标签,但由于数据没有嵌套,并且假设拉解析为您提供了一个XmlPull,它只是一个Iterator [PullType],我想到了解析标签的想法,遍历节点,直到找到结束标记,或者在“data”标记的情况下,直到我找到另一个“数据”开始标记或“项目”结束标记。但是,无论我如何看待这个问题,我都无法避免挽救状态。

我决定尝试拉链。

首先,因为我需要遍历,直到找到一个给定的标签并同时对我发现的每个元素做一些事情,我正在尝试使用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,而它是(至少内部函数它)但是如果我将依赖项更改为7.0.4,我会从Scales中获得很多错误,因为Iteratee从Scalaz 6更改为7(某些引用不在同一位置)。

我的问题:

  • 我是否过度使用过程?我应该采取另一种更简单的方法来解决这个问题吗?
  • 如果我按照描述的方式继续这样做,有什么我应该考虑的吗?有没有办法在Scalaz 7中使用Scales?

说明:

  • 强大的命令式编程背景,特别是在Java中。
  • 之前我曾与Scala合作过,但很多时候我不得不回到必要的做事方式,因为我遇到了困难(就像这次一样)而且耗费时间。
  • 我之前没有和Scalaz合作过。我对函数式编程的了解很基础,但我非常乐意学习新东西,而且我更喜欢函数式编程。

0 个答案:

没有答案