3

长期潜伏者,第一次海报。如果我的问题不清楚,请告诉我。

我有一个需要解析的奇怪 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。到目前为止,一切都很好。

使用:

  • 斯卡拉 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],因此我提出了解析标签、遍历节点的想法直到我找到结束标签,或者在“数据”标签的情况下,直到我找到另一个“数据”开始标签或“项目”结束标签。但是,无论我怎么想这个问题,我都无法避免保存状态。

我决定尝试使用拉链。

首先,由于我需要遍历直到找到给定的标记并同时对找到的每个元素执行某些操作,因此我正在尝试使用 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,由于 Iteratee 从 Scalaz 6 到 7 的更改(一些参考不在同一个地方),我会从 Scales 得到很多错误。

我的问题:

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

评论:

  • 强大的命令式编程背景,特别是Java。
  • 我以前使用过 Scala,但很多时候我不得不回到一种命令式的做事方式,因为我被卡住了(就像这次一样)而且很耗时。
  • 我以前没有与 Scalaz 合作过。我的函数式编程知识很基础,但我很乐意学习新东西,而且我喜欢函数式编程。
4

0 回答 0