Marshalling/unmarshalling XML in Scala

て烟熏妆下的殇ゞ 提交于 2019-12-01 02:35:41
David

I recommend using Scala's built-in XML features. I've just implemented deserialization for a document structure that looks like this:

val bodyXML = <body><segment uri="foo"><segment uri="bar" /></segment></body>

Note that the segments can be nested within each other.

A segment is implemented as follows:

case class Segment(uri: String, children: Seq[Segment])

To deserialize the XML, you do this:

val mySegments = topLevelSegments(bodyXML)

...and the implementation of topLevelSegments is just a few lines of code. Note the recursion, which digs through the XML structure:

def topLevelSegments(bodyXML: Node): Seq[Segment] = 
    (bodyXML \ "segment") map { nodeToSegment }

def nodeToSegment = (n: Node) => Segment((n \ "@uri")(0) text, childrenOf(n))

def childrenOf(n: Node): Seq[Segment] = (n \ "segment") map { nodeToSegment }

Hope that helps.

Aaron Novstrup

For comparison, I implemented David's example using the pickler combinators from the GData Scala Client library:

def segment: Pickler[Segment] =
   wrap(elem("segment", 
           attr("uri", text) 
           ~ rep(segment))) {    // rep = zero or more repetitions
      // convert (uri ~ children) to Segment(uri, children), for unpickling
      Segment.apply 
   } {
      // convert Segment to (uri ~ children), for pickling
      (s: Segment) => new ~(s.uri, s.children toList)
   }

def body = elem("body", rep(segment))

case class Segment(uri: String, children: List[Segment])

This code is all that is necessary to specify both directions of the translation between Segments and XML, whereas a similar amount of code specifies only one direction of the translation when using the Scala XML library. In my opinion, this version is also easier to understand (once you know the pickler DSL). Of course, as David pointed out in a comment, this approach requires an additional dependency and another DSL that developers have to be familiar with.

Translating XML to Segments is as simple as

body.unpickle(LinearStore.fromFile(filename)) // returns a PicklerResult[List[Segment]]

and translating the other way looks like

xml.XML.save(filename, body.pickle(segments, PlainOutputStore.empty).rootNode)

As far as the combinator library is concerned, it seems to be in decent shape and compiles in Scala 2.8.1. My initial impression is that the library is missing a few niceties (e.g. a oneOrMore combinator) that could be remedied fairly easily. I haven't had time to see how well it handles bad input, but so far it looks sufficient for my needs.

Writing a scala.xml.Node to a string isn't a big deal. PrettyPrinter should take care of you needs. scala.xml.XML.save() will write to a file and scala.xml.XML.write() outputs to a Writer.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!