Scala - изменение вложенных элементов в xml
Я изучаю scala, и я ищу обновить вложенный node в некотором xml. У меня есть что-то работающее, но мне интересно, является ли его самым элегантным способом.
У меня есть xml:
val InputXml : Node =
<root>
<subnode>
<version>1</version>
</subnode>
<contents>
<version>1</version>
</contents>
</root>
И я хочу обновить версию node в субноме, но не в содержимом.
Вот моя функция:
def updateVersion( node : Node ) : Node =
{
def updateElements( seq : Seq[Node]) : Seq[Node] =
{
var subElements = for( subNode <- seq ) yield
{
updateVersion( subNode )
}
subElements
}
node match
{
case <root>{ ch @ _* }</root> =>
{
<root>{ updateElements( ch ) }</root>
}
case <subnode>{ ch @ _* }</subnode> =>
{
<subnode>{ updateElements( ch ) }</subnode>
}
case <version>{ contents }</version> =>
{
<version>2</version>
}
case other @ _ =>
{
other
}
}
}
Есть ли более сукцинный способ записи этой функции?
Ответы
Ответ 1
Я думаю, что оригинальная логика хороша.
Это тот же код с (смею ли я говорить?) Более Scala -ish flavor:
def updateVersion( node : Node ) : Node = {
def updateElements( seq : Seq[Node]) : Seq[Node] =
for( subNode <- seq ) yield updateVersion( subNode )
node match {
case <root>{ ch @ _* }</root> => <root>{ updateElements( ch ) }</root>
case <subnode>{ ch @ _* }</subnode> => <subnode>{ updateElements( ch ) }</subnode>
case <version>{ contents }</version> => <version>2</version>
case other @ _ => other
}
}
Он выглядит более компактным (но на самом деле тем же:):)
- Я избавился от всех ненужных
скобки
- Если требуется скобка, она начинается с
одна и та же строка
- updateElements просто определяет переменную var
и возвращает его, поэтому я избавился от этого
и напрямую вернул результат.
если вы хотите, вы также можете избавиться от updateElements. Вы хотите применить updateVersion ко всем элементам последовательности. Это метод . При этом вы можете переписать строку
case <subnode>{ ch @ _* }</subnode> => <subnode>{ updateElements( ch ) }</subnode>
с
case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion (_)) }</subnode>
Как версия обновления принимает только 1 параметр, я на 99% уверен, что вы можете опустить его и написать:
case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion) }</subnode>
И закончим с:
def updateVersion( node : Node ) : Node = node match {
case <root>{ ch @ _* }</root> => <root>{ ch.map(updateVersion )}</root>
case <subnode>{ ch @ _* }</subnode> => <subnode>{ ch.map(updateVersion ) }</subnode>
case <version>{ contents }</version> => <version>2</version>
case other @ _ => other
}
Как вы думаете?
Ответ 2
Все это время, и никто на самом деле не дал наиболее подходящего ответа! Теперь, когда я узнал об этом, вот, вот мой новый подход:
import scala.xml._
import scala.xml.transform._
object t1 extends RewriteRule {
override def transform(n: Node): Seq[Node] = n match {
case Elem(prefix, "version", attribs, scope, _*) =>
Elem(prefix, "version", attribs, scope, Text("2"))
case other => other
}
}
object rt1 extends RuleTransformer(t1)
object t2 extends RewriteRule {
override def transform(n: Node): Seq[Node] = n match {
case sn @ Elem(_, "subnode", _, _, _*) => rt1(sn)
case other => other
}
}
object rt2 extends RuleTransformer(t2)
rt2(InputXml)
Теперь, для нескольких объяснений. Класс RewriteRule
является абстрактным. Он определяет два метода, называемых transform
. Один из них принимает один Node
, другой a Sequence
of Node
. Это абстрактный класс, поэтому мы не можем его создать напрямую. Добавив определение, в этом случае переопределите один из методов transform
, мы создаем его анонимный подкласс. Каждый RewriteRule нуждается в одной задаче, хотя он может сделать много.
Далее, класс RuleTransformer
принимает в качестве параметров переменное число RewriteRule
. Метод transform принимает значение Node
и возвращает Sequence
из Node
, применяя каждый RewriteRule
, используемый для его создания.
Оба класса получают из BasicTransformer
, который определяет несколько методов, с которыми не нужно беспокоиться о себе на более высоком уровне. Метод apply
вызывает transform
, хотя оба RuleTransformer
и RewriteRule
могут использовать связанный с ним синтаксический сахар. В этом примере первая делает, а позже не делает.
Здесь мы используем два уровня RuleTransformer
, так как первый применяет фильтр к узлам более высокого уровня, а второй применяет изменение к тому, что проходит фильтр.
Также используется экстрактор Elem
, поэтому нет необходимости беспокоиться о таких деталях, как пространство имен или атрибуты или нет. Не то, что содержимое элемента version
полностью отбрасывается и заменяется на 2
. Его также можно сопоставить, если необходимо.
Обратите внимание также, что последний параметр экстрактора _*
, а не _
. Это означает, что эти элементы могут иметь несколько дочерних элементов. Если вы забудете *
, совпадение может потерпеть неудачу. В этом примере совпадение не получится, если не было пробелов. Поскольку пробелы преобразуются в элементы Text
, одно пробел в subnode
будет приводить к сбою совпадения.
Этот код больше, чем другие представленные предложения, но он имеет то преимущество, что имеет гораздо меньше знаний о структуре XML, чем другие. Он изменяет любой элемент с именем version
, который ниже - независимо от того, сколько уровней - элемент с именем subnode
, независимо от пространств имен, атрибутов и т.д.
Кроме того... хорошо, если у вас есть много преобразований, рекурсивное сопоставление шаблонов становится быстро неуступчивым. Используя RewriteRule
и RuleTransformer
, вы можете эффективно заменить файлы xslt
кодом Scala.
Ответ 3
Вы можете использовать Lift CSS Selector Transforms и написать:
"subnode" #> ("version *" #> 2)
См. http://stable.simply.liftweb.net/#sec:CSS-Selector-Transforms
Ответ 4
С тех пор я узнал больше и представил то, что считаю хорошим решением в другом ответе. Я также исправил это, поскольку я заметил, что не смог объяснить ограничение subnode
.
Спасибо за вопрос! Я просто узнал некоторые интересные вещи, когда занимаюсь XML. Вот что вы хотите:
def updateVersion(node: Node): Node = {
def updateNodes(ns: Seq[Node], mayChange: Boolean): Seq[Node] =
for(subnode <- ns) yield subnode match {
case <version>{ _ }</version> if mayChange => <version>2</version>
case Elem(prefix, "subnode", attribs, scope, children @ _*) =>
Elem(prefix, "subnode", attribs, scope, updateNodes(children, true) : _*)
case Elem(prefix, label, attribs, scope, children @ _*) =>
Elem(prefix, label, attribs, scope, updateNodes(children, mayChange) : _*)
case other => other // preserve text
}
updateNodes(node.theSeq, false)(0)
}
Теперь объяснение. Утверждения первого и последнего случая должны быть очевидными. Последний существует, чтобы поймать те части XML, которые не являются элементами. Или, другими словами, текст. Однако в первом утверждении обратите внимание, что тест против флага указывает, можно ли изменить version
или нет.
В заявлении второго и третьего случаев будет использоваться шаблонное сопоставление с объектом Elem. Это сломает элемент во всех его составных частях. Последний параметр, "children @_ *", будет соответствовать дочерним элементам списка. Или, точнее, Seq [ Node]. Затем мы восстанавливаем элемент с выделенными нами частями, но передаем Seq [Node] для updateNodes, делая шаг рекурсии. Если мы сопоставляем элемент subnode
, то мы изменим флаг mayChange на true
, включив изменение версии.
В последней строке мы используем node.theSeq для генерации Seq [Node] из Node и (0), чтобы получить в качестве результата первый элемент Seq [Node]. Поскольку updateNodes по существу является функцией отображения (для... yield переведена на карту), мы знаем, что результат будет иметь только один элемент. Мы передаем флаг false
, чтобы гарантировать, что no version
будет изменен, если элемент subnode
не является предком.
Существует несколько иной способ сделать это, более мощный, но немного более подробный и неясный:
def updateVersion(node: Node): Node = {
def updateNodes(ns: Seq[Node], mayChange: Boolean): Seq[Node] =
for(subnode <- ns) yield subnode match {
case Elem(prefix, "version", attribs, scope, Text(_)) if mayChange =>
Elem(prefix, "version", attribs, scope, Text("2"))
case Elem(prefix, "subnode", attribs, scope, children @ _*) =>
Elem(prefix, "subnode", attribs, scope, updateNodes(children, true) : _*)
case Elem(prefix, label, attribs, scope, children @ _*) =>
Elem(prefix, label, attribs, scope, updateNodes(children, mayChange) : _*)
case other => other // preserve text
}
updateNodes(node.theSeq, false)(0)
}
Эта версия позволяет вам изменять любой тег "версия", независимо от того, какой префикс, атрибуты и область действия.
Ответ 5
Scales Xml предоставляет инструменты для редактирования "на месте". Конечно, все это неизменное, но здесь решение в шкалах:
val subnodes = top(xml).\*("subnode"l).\*("version"l)
val folded = foldPositions( subnodes )( p =>
Replace( p.tree ~> "2"))
Синтаксис XPath как функция подписи Scales, l
после того, как строка указывает, что у него не должно быть пространства имен (только локальное имя).
foldPositions
выполняет итерацию по результирующим элементам и преобразует их, объединяя результаты вместе.
Ответ 6
Один из подходов - линзы (например, scalaz). См. http://arosien.github.io/scalaz-base-talk-201208/#slide35 для очень четкой презентации.
Ответ 7
Я действительно не знаю, как это можно сделать элегантно. FWIW, я бы пошел на другой подход: используйте пользовательский класс модели для информации, которую вы обрабатываете, и имеете конверсию в Xml и из нее. Вероятно, вам удастся найти лучший способ обработки данных, и это еще более сукцинт.
Однако есть хороший способ сделать это с Xml напрямую, я бы хотел его увидеть.