Подтвердить что ты не робот

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
     }
   }
 }

Есть ли более сукцинный способ записи этой функции?

4b9b3361

Ответ 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.

Ответ 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 выполняет итерацию по результирующим элементам и преобразует их, объединяя результаты вместе.

Ответ 7

Я действительно не знаю, как это можно сделать элегантно. FWIW, я бы пошел на другой подход: используйте пользовательский класс модели для информации, которую вы обрабатываете, и имеете конверсию в Xml и из нее. Вероятно, вам удастся найти лучший способ обработки данных, и это еще более сукцинт.

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