我有一个XML节点,我希望随着时间的推移添加子节点:
val root: Node = <model></model>
但我看不到像 addChild()这样的方法,因为我想写下以下内容:
def addToModel() = {
root.addChild(<subsection>content</subsection>)
}
因此,在对此方法进行一次调用后,根xml将为:
<model><subsection>content</subsection></model>
我能看到的唯一可以附加节点的类是NodeBuffer。我在这里遗漏了一些基本的东西吗?
答案 0 :(得分:29)
从这开始吧:
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]
。
现在,这并没有改变任何东西,因为Scala中的XML是不可变的。它将生成一个具有所需更改的新节点。唯一的成本是创建新的Elem
对象,以及创建新的Seq
个孩子。子节点本身不会被复制,只是被引用,这不会导致问题,因为它们是不可变的。
但是,如果要将子节点添加到XML层次结构的节点上,事情会变得复杂。一种方法是使用拉链,如this blog中所述。
但是,您可以使用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/>
添加到第二个,那么使用规则转换器很难做到。你需要一个针对books
的RewriteRule,然后得到它的child
(它本应该被命名为children
),找到n th {{ 1}}在其中,将新子项添加到其中,然后重构子项并构建新节点。可行,但如果你必须做太多,拉链可能会更容易。
答案 1 :(得分: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>)
它重写了一个新的xml,复制了旧的xml并将你的节点添加为子节点。
编辑:Brian提供了更多信息,我想要一个不同的匹配。
要将子项添加到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
的性能影响因此,根据您堆积的孩子数量,您可能需要修补...然后您可能需要致电{ {1}}也是。希望这可以照顾stack.clear
的不变性,也可以根据需要处理你的过程。
答案 2 :(得分: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
}
对我而言,它的效果非常好。
答案 3 :(得分:2)
以通常的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。
答案 4 :(得分:2)
由于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>)
答案 5 :(得分:2)
我同意你必须“反过来”使用XML。请记住,当信息可用时,您不必拥有整个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>)
答案 6 :(得分:1)
Scales Xml允许通过折叠XPath进行简单的就地更改,将子节点添加到特定的子节点就适合这种方法。
有关详细信息,请参阅In-Place Transformations。
答案 7 :(得分:1)
你的根定义实际上是一个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>
由于每个Elem和每个Node都是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>
答案 8 :(得分:0)
我通过以下方式实现我的'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'语句中,它只是调用Elem类的'copy'方法来产生受影响儿童的新实例(可能是复数)。然后在'else'语句中递归调用'appendChild'方法验证生成的XML将被重建。在'if-else'之前,有一些序列是由不受影响的孩子建立的。 最后,我们需要将此序列复制到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>