使用 Scala 模式匹配提取具有特定名称的 XML 个元素,而不考虑内容

Using Scala pattern matching to extract XML elements with a certain name, regardless of content

给定以下 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
})

不过好像还有更巧妙的办法。

可以使用Elem对象来匹配:

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

Elem的来源是here,所以可以看到unapplySeq的定义。消息来源甚至有评论:

It is possible to deconstruct any Node instance (that is not a SpecialNode or a Group) using the syntax case 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 的使用,这是使用 flatMapmatchOption.