使用Scala模式匹配来提取具有特定名称的XML元素,而不管内容如何

时间:2015-02-18 00:06:10

标签: scala pattern-matching scala-xml

给出以下XML元素 -

val nodes = List(
    <foo/>,
    <bar/>,
    <baz/>,
    <bar>qux</bar>,
    <bar quux="corge"/>,
    <bar quux="grauply">waldo</bar>,
    <bar quux="fred"></bar>
)

- 如何构建一个匹配所有<bar/>的模式?我试过了,例如:

nodes flatMap (_ match {
  case b @ <bar/> => Some(b)
  case _ => None
})

但这只与空箱匹配。

res17: List[scala.xml.Elem] = List(<bar/>, <bar quux="corge"/>, <bar quux="fred"></bar>)

如果我允许内容占位符:

nodes flatMap (_ match {
  case b @ <bar>{content}</bar> => Some(b)
  case _ => None
})

这只匹配清空。

res20: List[scala.xml.Elem] = List(<bar>qux</bar>, <bar quux="grauply">waldo</bar>)

我当然可以放弃XML文字而只写

nodes flatMap (_ match {
  case e: Elem if e.label == "bar" => Some(e)
  case _ => None
})

但似乎必须有一种更聪明的方式。

1 个答案:

答案 0 :(得分:4)

您可以使用Elem对象进行匹配:

nodes collect { case b @ Elem(_, "bar", _, _, _*) => b }

Elem的来源为here,因此您可以看到unapplySeq的定义。消息来源甚至有评论:

  

可以使用语法Node解析任何SpecialNode个实例(不是Groupcase Elem(prefix, label, attribs, scope, child @ _*) => ...

另一种方法是使用pattern alternatives

 nodes collect { case b @ (<bar/> | <bar>{_}</bar>) => b }

请注意,模式替代项不能绑定除通配符之外的变量。

如果这是您的常用操作,那么您可以考虑编写自己的提取器(如文档here所述)。例如:

object ElemLabel { 
    def unapply(elem: Elem): Option[String] = Some(elem.label) 
}

然后:

nodes collect { case b @ ElemLabel("bar") => b }

当然,在您提供的示例中,您只是过滤,在这种情况下:

nodes filter { _.label == "bar" }

就足够了,可能是你最好的选择。即使您计划在过滤器之后执行其他操作,并且您关注性能和构建中间集合,也可以使用view并避免此问题。

另请注意,collect始终使用flatMap,这是一种更加惯用的方法,可以使用matchOption进行过滤,映射和匹配,以及{{1}}。