Scala XML Building: добавление дочерних элементов к существующим узлам - PullRequest
20 голосов
/ 04 февраля 2010

У меня есть XML-узел, к которому я хочу добавить потомков:

val root: Node = <model></model>

Но я не вижу таких методов, как addChild () , которые я хотел бы написатьчто-то вроде:

def addToModel() = {
    root.addChild(<subsection>content</subsection>)
}

Так что после одного вызова этого метода корневой XML будет:

<model><subsection>content</subsection></model>

Единственный класс, который я могу видеть, который имеет возможность добавлятьУзел - это NodeBuffer.Я что-то упустил здесь?

Ответы [ 9 ]

29 голосов
/ 04 февраля 2010

Хорошо, начните с этого:

def addChild(n: Node, newChild: Node) = n match {
  case Elem(prefix, label, attribs, scope, child @ _*) =>
    Elem(prefix, label, attribs, scope, child ++ newChild : _*)
  case _ => error("Can only add children to elements!")
}

Метод ++ работает здесь, потому что child это Seq[Node], а newChild это Node, что расширяет NodeSeq,который расширяет Seq[Node].

Теперь это ничего не меняет, потому что XML в Scala неизменен.Он создаст новый узел с необходимыми изменениями.Единственная стоимость - это создание нового Elem объекта, а также создание Seq дочерних объектов.Дочерние узлы сами по себе не копируются, а просто упоминаются, что не вызывает проблем, поскольку они неизменны.

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

Однако вы можете использовать scala.xml.transform с правилом, которое изменит конкретный узел для добавления новогоребенок.Сначала напишите новый класс преобразователя:

class AddChildrenTo(label: String, newChild: Node) extends RewriteRule {
  override def transform(n: Node) = n match {
    case n @ Elem(_, `label`, _, _, _*) => addChild(n, newChild)
    case other => other
  }
}

Затем используйте его следующим образом:

val newXML = new RuleTransformer(new AddChildrenTo(parentName, newChild)).transform(oldXML).head

В Scala 2.7 замените head на first.

Пример на Scala 2.7:

scala> val oldXML = <root><parent/></root>
oldXML: scala.xml.Elem = <root><parent></parent></root>

scala> val parentName = "parent"
parentName: java.lang.String = parent

scala> val newChild = <child/>
newChild: scala.xml.Elem = <child></child>

scala>     val newXML = new RuleTransformer(new AddChildrenTo(parentName, newChild)).transform(oldXML).first
newXML: scala.xml.Node = <root><parent><child></child></parent></root>

Вы можете усложнить получение нужного элемента, если недостаточно только родительского элемента.Однако, если вам нужно добавить дочерний элемент к родителю с общим именем определенного индекса, вам, вероятно, нужно пойти по пути молний.

Например, если у вас есть <books><book/><book/></books>, и вы хотите добавить <author/> ко второму, это будет трудно сделать с помощью преобразователя правил.Вам понадобится RewriteRule против books, который затем получит его child (который действительно должен был бы называться children), найдите в них n th book, добавьте новыйдочерний к этому, а затем перекомпоновать дочерние элементы и построить новый узел.Выполнимо, но молнии могут быть проще, если вам придется делать это слишком много.

8 голосов
/ 04 февраля 2010

В Scala узлы xml являются неизменяемыми, но могут делать это:

var root = <model/>

def addToModel(child:Node) = {
  root = root match {
    case <model>{children@ _*}</model> => <model>{children ++ child}</model>
    case other => other
  }
}

addToModel(<subsection>content</subsection>)

Переписывает новый xml, делая копию старого и добавляя свой узел как дочерний.

Редактировать: Брайан предоставил больше информации, и я подумал, что нужно сопоставить другое.

Чтобы добавить дочерний элемент в произвольный узел в 2.8, вы можете сделать:

def add(n:Node,c:Node):Node = n match { case e:Elem => e.copy(child=e.child++c) }

Это вернет новую копию родительского узла с добавленным дочерним узлом.Предполагая, что вы сложили свои дочерние узлы, когда они стали доступны:

scala> val stack = new Stack[Node]()
stack: scala.collection.mutable.Stack[scala.xml.Node] = Stack()

Как только вы решили, что закончили с извлечением дочерних элементов, вы можете позвонить родителю, чтобы добавить всех дочерних элементов в стек.как это:

stack.foldRight(<parent/>:Node){(c:Node,n:Node) => add(n,c)}

Я понятия не имею о влиянии на производительность использования Stack и foldRight, поэтому в зависимости от того, сколько детей вы сложили, вам, возможно, придется повозиться ... Тогда вывозможно, придется позвонить stack.clear тоже.Надеюсь, это позаботится о неизменной природе Node, а также о вашем процессе по мере необходимости.

6 голосов
/ 13 апреля 2013

Поскольку в scala 2.10.0 конструктор экземпляров Elem изменился, если вы хотите использовать наивное решение, написанное @Daniel C. Sobral, оно должно быть:

xmlSrc match {
  case xml.Elem(prefix, label, attribs, scope, child @ _*) =>
       xml.Elem(prefix, label, attribs, scope, child.isEmpty, child ++ ballot : _*)
  case _ => throw new RuntimeException
}

Для меня это работает очень хорошо.

2 голосов
/ 08 ноября 2010

Я согласен, что вы должны работать с XML "наоборот". Имейте в виду, что вам не нужно иметь доступ ко всему документу XML, когда информация становится доступной, вам нужно только составить XML, когда приложение должно его прочитать.

Сохраняйте состояние своего подраздела так, как хотите, когда вам нужен XML, оберните все это вместе.

  val subsections : List[Elem]

  def wrapInModel(f : => Elem) = {
    <model>{f}</model>
  }

  wrapInModel(subsections)

или

  def wrapInModel(f : => Elem) = {
    <model>{f}</model>
  }
  wrapInModel(<subsection>content</subsection>)
2 голосов
/ 04 февраля 2010

Поскольку XML равны immutable, вам нужно создавать новый каждый раз, когда вы хотите добавить узел, вы можете использовать Pattern matching для добавления вашего нового узла:

    var root: Node = <model></model>
    def addToModel(newNode: Node) = root match {
       //match all the node from your model
       // and make a new one, appending old nodes and the new one
        case <model>{oldNodes@_*}</model> => root = <model>{oldNodes}{newNode}</model>
    }
    addToModel(<subsection>content</subsection>)
2 голосов
/ 04 февраля 2010

В обычном стиле Scala все экземпляры Node, Elem и т. Д. Являются неизменяемыми. Вы можете работать по-другому:

  scala> val child = <child>foo</child>
  child: scala.xml.Elem = <child>foo</child>

  scala> val root = <root>{child}</root>
  root: scala.xml.Elem = <root><child>foo</child></root>

См. http://sites.google.com/site/burakemir/scalaxbook.docbk.html для получения дополнительной информации.

1 голос
/ 09 августа 2017

ваше корневое определение на самом деле является объектом Elem, подклассом узла, поэтому, если вы отбросите ненужную типизацию Node (которая скрывает его реализацию), вы на самом деле можете создать для него ++, поскольку класс Elem имеет этот метод. *

val root = <model/>
val myChild = <myChild/>
root.copy(child = root.child ++ myChild)

scala ev:

root: scala.xml.Elem = <model/>
myChild: scala.xml.Elem = <mychild/>
res2: scala.xml.Elem = <model><mychild/></model>

Так как каждый Элем и каждый Узел является NodeSeq, вы можете добавить их довольно эффективно, даже если то, что вы добавляете, является неизвестной последовательностью:

val root = <model/>
//some node sequence of unknown subtype or structure
val children: scala.xml.NodeSeq = <node1><node2/></node1><node3/> 
root.copy(child = root.child ++ children)

scala ev:

root: scala.xml.Elem = <model/>
children: scala.xml.NodeSeq = NodeSeq(<node1><node2/></node1>, <node3/>)
res6: scala.xml.Elem = <model><node1><node2/></node1><node3/></model>
1 голос
/ 05 ноября 2011

Scales Xml допускает простые изменения на месте путем свертывания по XPath, добавление дочерних элементов к конкретному подузлу вписывается в этот подход.

Подробнее см. Преобразования на месте .

0 голосов
/ 09 ноября 2016

Я реализую свой метод appendChild следующим образом:

  def appendChild(elem: Node, child: Node, names: String) = {
    appendChild(elem, child, names.split("/"))
  }

  private def appendChild(elem: Node, child: Node, names: Array[String]) = {
    var seq = elem.child.diff(elem \ names.head)
    if (names.length == 1)
      for (re <- elem \ names.head)
        seq = seq ++ re.asInstanceOf[Elem].copy(child = re.child ++ child)
    else
      for (subElem <- elem \ names.head)
        seq = seq ++ appendChild(subElem, child, names.tail)
    elem.asInstanceOf[Elem].copy(child = seq)
  }

Метод добавляет детей к вашим узлам рекурсивным способом. В операторе 'if' он просто вызывает метод 'copy' класса Elem для создания новых экземпляров затронутых потомков (это может быть множественное число). Затем в операторе else рекурсивные вызовы метода appendChild подтверждают, что полученный XML будет перестроен. До «если-еще» есть последовательность, которая построена из не затронутых детей. В конце нам нужно скопировать эту последовательность в элемент origin.

val baz = <a><z x="1"/><b><z x="2"/><c><z x="3"/></c><z x="4"/></b></a>
println("Before: \n" + XmlPrettyPrinter.format(baz.toString()))

val res = appendChild(baz, <y x="5"/>, "b/c/z")
println("After: \n" + XmlPrettyPrinter.format(res.toString()))

Результаты:

Before: 
<a>
  <z x="1"/>
  <b>
    <z x="2"/>
    <c>
      <z x="3"/>
    </c>
    <z x="4"/>
  </b>
</a>

After: 
<a>
  <z x="1"/>
  <b>
    <z x="2"/>
    <z x="4"/>
    <c>
      <z x="3">
        <y x="5"/>
      </z>
    </c>
  </b>
</a>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...