一尘不染

Scala XML 构建:向现有节点添加子节点

xml

我有一个 XML 节点,我想随着时间的推移向其中添加子节点:

val root: Node = <model></model>

但是我看不到诸如addChild()之类的方法,因为我想写一些类似的东西:

def addToModel() = {
    root.addChild(<subsection>content</subsection>)
}

因此,在一次调用此方法后,根 xml 将是:

<model><subsection>content</subsection></model>

我能看到的唯一能够附加节点的类是 NodeBuffer。我在这里错过了一些基本的东西吗?


阅读 165

收藏
2022-06-14

共1个答案

一尘不染

那么从这个开始:

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]

现在,这并没有改变任何东西,因为 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
  }
}

然后,像这样使用它:

val newXML = new RuleTransformer(new AddChildrenTo(parentName, newChild)).transform(oldXML).head

在 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>

如果仅父级还不够,则可以使获取正确元素变得更加复杂。但是,如果您需要将子项添加到具有特定索引的通用名称的父项中,那么您可能需要走拉链的方式。

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

2022-06-14