Scala XML 构建:将子节点添加到现有节点
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2199040/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Scala XML Building: Adding children to existing Nodes
提问by BefittingTheorem
I Have an XML Node that I want to add children to over time:
我有一个 XML 节点,我想随着时间的推移向其中添加子项:
val root: Node = <model></model>
But I cannot see methods such as addChild(), as I would like to write something along the lines of:
但是我看不到诸如addChild() 之类的方法,因为我想写一些类似的东西:
def addToModel() = {
root.addChild(<subsection>content</subsection>)
}
So after a single call to this method the root xml would be:
因此,在一次调用此方法后,根 xml 将是:
<model><subsection>content</subsection></model>
The only class I can see that has the ability to append a Node is the NodeBuffer. Am I missing something fundamental here?
我能看到的唯一能够附加节点的类是 NodeBuffer。我在这里错过了一些基本的东西吗?
回答by Daniel C. Sobral
Well start with this:
那么从这个开始:
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!")
}
The method ++works here because childis a Seq[Node], and newChildis a Node, which extends NodeSeq, which extends Seq[Node].
该方法++在这里起作用child是因为是 a Seq[Node],并且newChild是 a Node,它扩展了NodeSeq,它扩展了Seq[Node]。
Now, this doesn't change anything, because XML in Scala is immutable. It will produce a new node, with the required changes. The only cost is that of creating a new Elemobject, as well as creating a new Seqof children. The children node, themselves, are not copied, just referred to, which doesn't cause problems because they are immutable.
现在,这不会改变任何东西,因为 Scala 中的 XML 是不可变的。它将产生一个具有所需更改的新节点。唯一的成本是创建一个新Elem对象,以及创建一个新Seq的孩子。子节点本身不会被复制,只是被引用,这不会导致问题,因为它们是不可变的。
However, if you are adding children to a node way down on the XML hierarchy, things get complicated. One way would be to use zippers, such as described in this blog.
但是,如果您将子项添加到 XML 层次结构中向下的节点,事情就会变得复杂。一种方法是使用拉链,例如本博客中所述。
You can, however, use scala.xml.transform, with a rule that will change a specific node to add the new child. First, write a new transformer class:
但是,您可以将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
}
}
Then, use it like this:
然后,像这样使用它:
val newXML = new RuleTransformer(new AddChildrenTo(parentName, newChild)).transform(oldXML).head
On Scala 2.7, replace headwith first.
在 Scala 2.7 上,替换head为first.
Example on Scala 2.7:
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>
You could make it more complex to get the right element, if just the parent isn't enough. However, if you need to add the child to a parent with a common name of a specific index, then you probably need to go the way of zippers.
如果只有父元素还不够,您可以更复杂地获取正确的元素。但是,如果您需要将子项添加到具有特定索引的通用名称的父项,那么您可能需要走拉链的方式。
For instance, if you have <books><book/><book/></books>, and you want to add <author/>to the second, that would be difficult to do with rule transformer. You'd need a RewriteRule against books, which would then get its child(which really should have been named children), find the nthbookin them, add the new child to that, and then recompose the children and build the new node. Doable, but zippers might be easier if you have to do that too much.
例如,如果您有<books><book/><book/></books>,并且您想添加<author/>到第二个,那么规则转换器将很难做到。你需要一个 RewriteRule against books,然后它会得到它的child(它真的应该被命名为children),在它们中找到第 n个book,将新的子节点添加到它,然后重新组合子节点并构建新节点。可行,但如果您必须这样做太多,拉链可能会更容易。
回答by huynhjl
In Scala xml nodes are immutable, but can do this:
在 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>)
It rewrites a new xml, by making a copy of the old one and adding your node as a child.
它通过复制旧的 xml 并将您的节点添加为子节点来重写一个新的 xml。
Edit: Brian provided more info and I figured a different to match.
编辑:Brian 提供了更多信息,我想出了一个不同的匹配。
To add a child to an arbitrary node in 2.8 you can do:
要将子节点添加到 2.8 中的任意节点,您可以执行以下操作:
def add(n:Node,c:Node):Node = n match { case e:Elem => e.copy(child=e.child++c) }
That will return a new copy of parent node with the child added. Assuming you've stacked your children nodes as they became available:
这将返回添加了子节点的父节点的新副本。假设您已经在子节点可用时堆叠了它们:
scala> val stack = new Stack[Node]()
stack: scala.collection.mutable.Stack[scala.xml.Node] = Stack()
Once you've figured you're done with retrieving children, you can make a call on the parent to add all children in the stack like this:
一旦您确定已完成检索子项,您可以调用父项以将所有子项添加到堆栈中,如下所示:
stack.foldRight(<parent/>:Node){(c:Node,n:Node) => add(n,c)}
I have no idea about the performance implication of using Stackand foldRightso depending on how many children you've stacked, you may have to tinker... Then you may need to call stack.cleartoo. Hopefully this takes care of the immutable nature of Nodebut also your process as you go need.
我不知道使用的性能含义的想法Stack和foldRight所以这取决于你有多少孩子堆叠,你可能要补锅匠......然后,你可能需要调用stack.clear了。希望这可以照顾到不可变的性质,Node但也可以根据需要处理您的流程。
回答by Murdix
Since scala 2.10.0 the instance constructor of Elem has changed, if you want use naive solution written by @Daniel C. Sobral, it should be:
从 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
}
For me, it works very good.
对我来说,它的效果非常好。
回答by Patrick
Since XMLare immutable, you have to create a new one each time you want to append a node, you can use Pattern matchingto add your new node:
由于XMLare 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>)
回答by Confusion
In the usual Scala fashion, all Node, Elem, etc. instances are immutable. You can work it the other way around:
在通常的 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>
See http://sites.google.com/site/burakemir/scalaxbook.docbk.htmlfor more information.
有关详细信息,请参阅http://sites.google.com/site/burakemir/scalaxbook.docbk.html。
回答by Travis Stevens
I agree that you have to work with XML "the other way around". Keep in mind you don't have to have the entire XML document available when information becomes available, you only need to compose the XML when the application needs to read it.
我同意您必须“以相反的方式”使用 XML。请记住,当信息可用时,您不必让整个 XML 文档可用,您只需在应用程序需要读取 XML 时编写它。
Keep your subsection state however you want to, when you need the XML, wrap it all together.
保持你的小节状态,当你需要 XML 时,将它全部包装在一起。
val subsections : List[Elem]
def wrapInModel(f : => Elem) = {
<model>{f}</model>
}
wrapInModel(subsections)
or
或者
def wrapInModel(f : => Elem) = {
<model>{f}</model>
}
wrapInModel(<subsection>content</subsection>)
回答by Chris
Scales Xmlallows for simple in place changes via folding over XPaths, adding in children to a particular sub node fits right into this approach.
Scales Xml允许通过折叠 XPath 进行简单的就地更改,将子节点添加到特定子节点适合这种方法。
See In-Place Transformationsfor more details.
有关更多详细信息,请参阅就地转换。
回答by Andrei Zhaleznichenka
I implement my 'appendChild' method in the following way:
我通过以下方式实现我的“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)
}
The method appends children to your nodes in recursive manner. In the 'if' statement it simply calls 'copy' method of Elem class to produce new instances of affected children (those may be plural). Then in 'else' statement recursive calls to 'appendChild' method verify resulting XML will be rebuilt. Before 'if-else' there are sequence which is built from non-affected children. At the end, we need to copy this sequence to origin element.
该方法以递归方式将子节点附加到您的节点。在'if' 语句中,它只是调用Elem 类的'copy' 方法来生成受影响子项的新实例(它们可能是复数)。然后在 'else' 语句中递归调用 'appendChild' 方法验证生成的 XML 将被重建。在“if-else”之前,有由未受影响的孩子构建的序列。最后,我们需要将此序列复制到原点元素。
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()))
Results:
结果:
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>
回答by Andrew Norman
your root definition is actually an Elem object, a subclass of node, so if you drop your unnecessary Node typing (which hides its implementation) you could actually do a ++ on it since the Elem class has this method.
你的根定义实际上是一个 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>
Since every Elem and every Node is a NodeSeq you can add these pretty effectively even if what you are appending is an unknown sequence:
由于每个 Elem 和每个节点都是一个 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>

