从[String,Option [NodeSeq]]中提取元素值的优雅方法

时间:2013-04-16 05:51:24

标签: xml scala refactoring

假设我得到了如下XML:

<a><b><c>hello</c><d>world</d><e>again</e></b></a>

我得到一个函数:getXmlStream,它具有以下签名:

def getXmlStream(xmlPath:String):Either[String,Option[NodeSeq]]

当我调用带有路径的getXmlStream时,我会得到Either,其中Left是错误,右边是Option [NodeSeq]。

现在,如果NodeSeq不是None,我需要获取元素的值,分别是“hello”和“world”。

我尝试获取元素如下:

val elems = (getXmlStream(xmlFilePath)) match {
               case Left(error:String) => None
               case Right(xmlStreamOpt) => {
                 xmlStreamOpt map {
                  (r \\ "c" text, r \\ "d" text)
                 } 
               }  
            }).getOrElse("","")

elems现在将包含一个值为("hello","world")的元组,如果它们存在,那么它将是一个空字符串的元组。

我不认为我上面写的片段是惯用的scala。有人可以建议我如何重构它。

我觉得第二个问题是我在解码片段中硬编码节点“c”和“d”。如果现在,要求提取“e”,我是否会将表达式(r \\ "c" text, r \\ "d" text)修改为(r \\ "c" text, r \\ "d" text, r \\ "e" text)?是否有可能使xml元素提取更具动态性?

2 个答案:

答案 0 :(得分:1)

这个怎么样:

scala> getXmlStream(path) match {
     |   case Right(Some(xml)) => (xml\\"c" text, xml\\"d" text)
     |   case _ => ("", "")
     | }

答案 1 :(得分:1)

这是一个单行,我认为仍然很清楚:

res.right.toOption.flatten.fold(("", ""))(r => (r \\ "c" text, r \\ "d" text))

我们可以逐步完成这里发生的事情:首先我们对Either进行正确的投影,并通过将Option映射到Left将其转换为None。现在我们有一个嵌套的Option,我们可以将其展平以获得Option[NodeSeq]。然后我们折叠Option的可能形状(参见例如this answer及其中的链接以进行更多讨论)。

请注意,fold仅出现在2.10中的Option上。如果您之前使用的是Scala版本,则以下内容完全等效:

res.right.toOption.flatten.map(
  r => (r \\ "c" text, r \\ "d" text)
).getOrElse(("", ""))

标准库并没有提供您在上一段中描述的方式使用元组的方式,但有ScalazShapeless等库可以。例如,使用Scalaz的Bifunctor,您可以编写当前版本:

res.right.toOption.flatten.fold(("", ""))(r => ("c", "d").umap(r \\ _ text))

而Shapeless将允许您更容易地向元组添加元素(但这有点复杂,可能最好在新问题中解决)。