我有一个 XML 节点,我想随着时间的推移向其中添加子节点:
val root: Node = <model></model>
但是我看不到诸如addChild()之类的方法,因为我想写一些类似的东西:
def addToModel() = { root.addChild(<subsection>content</subsection>) }
因此,在一次调用此方法后,根 xml 将是:
<model><subsection>content</subsection></model>
我能看到的唯一能够附加节点的类是 NodeBuffer。我在这里错过了一些基本的东西吗?
那么从这个开始:
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是 a Seq[Node],并且newChild是 a Node,它扩展了NodeSeq,扩展了Seq[Node]。
++
child
Seq[Node]
newChild
Node
NodeSeq
现在,这并没有改变任何东西,因为 Scala 中的 XML 是不可变的。它将生成一个具有所需更改的新节点。唯一的成本是创建一个新Elem对象以及创建一个新Seq的子对象。子节点本身不会被复制,只是被引用,这不会导致问题,因为它们是不可变的。
Elem
Seq
但是,如果您将子节点添加到 XML 层次结构中的节点,事情就会变得复杂。一种方法是使用拉链,如本博客中所述。
但是,您可以将scala.xml.transform, 与将更改特定节点以添加新子节点的规则一起使用。首先,编写一个新的转换器类:
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.
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 against books,然后它会得到它child(它真的应该被命名为children),在其中找到第 n个 book,将新的子节点添加到其中,然后重新组合子节点并构建新节点。可行,但如果你必须这样做太多,拉链可能会更容易。
<books><book/><book/></books>
<author/>
books
children
book