我有一个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上,替换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>
Run Code Online (Sandbox Code Playgroud)
如果只是父元素不够,你可以使得获得正确元素变得更复杂.但是,如果您需要将子项添加到具有特定索引的公用名的父项,那么您可能需要采用拉链方式.
例如,如果你有<books><book/><book/></books>
,并且想要添加<author/>
到第二个,那么使用规则变换器很难做到.你需要一个RewriteRule books
,然后得到它child
(它本应该被命名children
),找到它们中的第n 个 book
,将新的子项添加到其中,然后重构子项并构建新节点.可行,但如果你必须做太多,拉链可能会更容易.
在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)
我不知道使用的性能含义的想法Stack
和foldRight
所以这取决于你有多少孩子堆叠,你可能要鼓捣......然后,你可能需要调用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)
对我来说,它非常好.
归档时间: |
|
查看次数: |
16126 次 |
最近记录: |