且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2021-07-15 01:27:50

从这个开始:

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].

The method ++ works here because child is a Seq[Node], and newChild is a Node, which extends NodeSeq, which extends Seq[Node].

现在,这不会改变任何东西,因为 Scala 中的 XML 是不可变的.它将产生一个具有所需更改的新节点.唯一的成本是创建一个新的 Elem 对象,以及创建一个新的 Seq 子对象.子节点本身不会被复制,只是被引用,这不会导致问题,因为它们是不可变的.

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 Elem object, as well as creating a new Seq of children. The children node, themselves, are not copied, just referred to, which doesn't cause problems because they are immutable.

但是,如果您将子项添加到 XML 层次结构中向下的节点,事情会变得复杂.一种方法是使用拉链,例如 这个博客.

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.

但是,您可以使用 scala.xml.transform,其规则将更改特定节点以添加新子节点.首先,编写一个新的转换器类:

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:

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.

On Scala 2.7, replace head with first.

Scala 2.7 示例:

Example on 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.

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

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 nthbook in 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.