Scala XML Building:将子节点添加到现有节点

Bef*_*rem 22 xml scala

我有一个XML节点,我希望随着时间的推移添加子节点:

val root: Node = <model></model>
Run Code Online (Sandbox Code Playgroud)

但我看不到像addChild()这样的方法,因为我想写下以下内容:

def addToModel() = {
    root.addChild(<subsection>content</subsection>)
}
Run Code Online (Sandbox Code Playgroud)

因此,在单个调用此方法之后,根xml将是:

<model><subsection>content</subsection></model>
Run Code Online (Sandbox Code Playgroud)

我能看到的唯一可以附加节点的类是NodeBuffer.我错过了一些基本的东西吗?

Dan*_*ral 30

从这开始吧:

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!")
}
Run Code Online (Sandbox Code Playgroud)

这个方法++在这里起作用,因为它child是a Seq[Node],并且newChild是一个Node扩展的NodeSeq,它是扩展的Seq[Node].

现在,这不会改变任何东西,因为Scala中的XML是不可变的.它将生成一个具有所需更改的新节点.唯一的成本是创建一个新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
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,像这样使用它:

val newXML = new RuleTransformer(new AddChildrenTo(parentName, newChild)).transform(oldXML).head
Run Code Online (Sandbox Code Playgroud)

在Scala 2.7上,替换headfirst.

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>
Run Code Online (Sandbox Code Playgroud)

如果只是父元素不够,你可以使得获得正确元素变得更复杂.但是,如果您需要将子项添加到具有特定索引的公用名的父项,那么您可能需要采用拉链方式.

例如,如果你有<books><book/><book/></books>,并且想要添加<author/>到第二个,那么使用规则变换器很难做到.你需要一个RewriteRule books,然后得到它child(它本应该被命名children),找到它们中的第n book,将新的子项添加到其中,然后重构子项并构建新节点.可行,但如果你必须做太多,拉链可能会更容易.


huy*_*hjl 8

在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>)
Run Code Online (Sandbox Code Playgroud)

它通过复制旧的xml并将节点添加为子节点来重写新的xml.

编辑:布莱恩提供了更多信息,我想到了一个不同的匹配.

要将子项添加到2.8中的任意节点,您可以执行以下操作:

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

这将返回添加子项的父节点的新副本.假设您在子节点可用时堆叠了它们:

scala> val stack = new Stack[Node]()
stack: scala.collection.mutable.Stack[scala.xml.Node] = Stack()
Run Code Online (Sandbox Code Playgroud)

一旦你认为你已经完成了检索子项,你就可以在父项上调用以添加堆栈中的所有子项,如下所示:

stack.foldRight(<parent/>:Node){(c:Node,n:Node) => add(n,c)}
Run Code Online (Sandbox Code Playgroud)

我不知道使用的性能含义的想法StackfoldRight所以这取决于你有多少孩子堆叠,你可能要鼓捣......然后,你可能需要调用stack.clear了.希望这可以照顾Node你不可改变的本质,也可以根据你的需要来处理你的过程.


小智 6

由于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
}
Run Code Online (Sandbox Code Playgroud)

对我来说,它非常好.