Использование сопоставления шаблонов 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
})

но похоже, что должен быть более умный способ.


person David Moles    schedule 18.02.2015    source источник


Ответы (1)


Вы можете использовать объект Elem для соответствия:

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

Источник Elem находится здесь, так что вы можете увидеть определение unapplySeq. В источнике даже есть комментарий:

Можно деконструировать любой экземпляр Node (не являющийся SpecialNode или Group), используя синтаксис case Elem(prefix, label, attribs, scope, child @ _*) => ....

Другой альтернативой является использование альтернатив шаблона. :

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

Обратите внимание, что альтернативы шаблона не могут связывать переменные, кроме подстановочных знаков.

Если для вас это обычная операция, вы можете подумать о написании собственного экстрактора (как описано здесь ). Например:

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, match и Option.

person Ben Reich    schedule 18.02.2015