在 Scala 中编组/解组 XML

Posted

技术标签:

【中文标题】在 Scala 中编组/解组 XML【英文标题】:Marshalling/unmarshalling XML in Scala 【发布时间】:2011-06-07 13:11:06 【问题描述】:

我正在研究在 Scala 和 XML 之间编组/解组数据的各种方法,并且我有兴趣获得社区反馈(最好基于第一手知识/经验)。

我们目前正在使用 JAXB,这很好,但我希望有一个纯 Scala 解决方案。我正在考虑以下方法:

    使用 Scala 的内置 XML 工具:Scala->XML 会很容易,但我猜如果换一个方向会相当痛苦。另一方面,这种方法支持任意翻译逻辑。

    数据绑定:scalaxb 目前似乎有些不成熟,不能处理我们当前的模式,而且我不知道有任何其他的 Scala 数据绑定库.与 JAXB 一样,需要一个额外的翻译层来支持相关的转换。

    XML pickler 组合器:GData Scala Client 库提供 XML pickler 组合器,但最近的项目活动很少,我不知道当前状态如何。

问题:

    您对我列出的方法/库有何经验? 各自的相对优缺点是什么? 我还应该考虑其他方法或 Scala 库吗?

编辑:

我在自己对这个问题的回答中添加了一些关于我对pickler 组合器的早期印象的注释,但我仍然对真正深入了解各种方法的人的反馈非常感兴趣。我希望的是一个比较全面的比较,可以帮助开发人员根据他们的需求选择正确的方法。

【问题讨论】:

如果你能把架构发给我(eed3si9n at gmail),我也许可以修复 scalaxb。 【参考方案1】:

我建议使用 Scala 的内置 XML 功能。我刚刚为一个看起来像这样的文档结构实现了反序列化:

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

请注意,这些段可以相互嵌套。

一个段的实现如下:

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

要反序列化 XML,请执行以下操作:

val mySegments = topLevelSegments(bodyXML)

...topLevelSegments 的实现只是几行代码。注意递归,它挖掘了 XML 结构:

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 

希望对您有所帮助。

【讨论】:

我想这种方法并不像我预期的那么麻烦,但我想知道它是多么容易扩展到更复杂的模式并随着时间的推移进行维护。数据绑定和pickler组合器的一个明显优势是您可以同时指定序列化/反序列化,这样您就不必担心维护两个并行的代码体。 尽管如此,您在代码库中混入的任何附加技术都会带来开销:要学习的语法、要破译的一组错误消息、要加入的用户组、可能是部署调整。 “活动部件”越少越好。【参考方案2】:

为了比较,我使用GData Scala Client 库中的pickler 组合器实现了David's example:

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])

这段代码是指定Segments 和 XML 之间的两个转换方向所必需的,而在使用 Scala XML 库时,类似数量的代码只指定一个转换方向。在我看来,这个版本也更容易理解(一旦你知道了pickler DSL)。当然,正如 David 在评论中指出的那样,这种方法需要一个额外的依赖项和另一个开发人员必须熟悉的 DSL。

将 XML 转换为 Segment 就像

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

换一种方式看起来像

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

就组合器库而言,它的状态似乎不错,并且可以在 Scala 2.8.1 中编译。我最初的印象是该库缺少一些可以很容易地修复的细节(例如oneOrMore 组合器)。我还没有时间看看它处理不良输入的能力如何,但到目前为止它看起来足以满足我的需求。

【讨论】:

"一个或多个" 这不就是rep1 的作用吗? @soc 我假设您指的是标准库中的 rep1 解析器组合器。不幸的是,XML pickler 库中没有这样的组合器。【参考方案3】:

将 scala.xml.Node 写入字符串并不是什么大问题。 PrettyPrinter 应该满足您的需求。 scala.xml.XML.save() 将写入文件,scala.xml.XML.write() 输出到 Writer

【讨论】:

感谢您的回答,但这根本不是我想要的。我对 XML 文档和特定领域对象模型之间的转换很感兴趣。

以上是关于在 Scala 中编组/解组 XML的主要内容,如果未能解决你的问题,请参考以下文章

JAXB 继承,解组到编组类的子类

如何编组/解组在 Go 中有两种不同格式的通用 JSON 和 BSON 键/字段?

JAXB使用CDATA编组解组

在 Golang 中解组 XML 时如何在 interface 中获取数据?

如何简化此解析方法?

解组忽略空字段