代码之家  ›  专栏  ›  技术社区  ›  BefittingTheorem

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

  •  20
  • BefittingTheorem  · 技术社区  · 15 年前

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

    但我看不到这样的方法 addChild()

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

    因此,对该方法进行一次调用后,根xml将是:

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

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

    9 回复  |  直到 15 年前
        1
  •  30
  •   Daniel C. Sobral    15 年前

    我们从这个开始:

    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 ,扩展到 .

    现在,这不会改变任何事情,因为Scala中的XML是不可变的。它将生成一个新节点,并进行必要的更改。唯一的成本是创建一个新的 Elem Seq 孩子们。子节点本身不是复制的,只是引用的,这不会导致问题,因为它们是不可变的。

    但是,如果要将子节点添加到XML层次结构中较低的节点,事情会变得复杂。一种方法是使用拉链,如中所述 this blog .

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

    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
    

    head 具有 first .

    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>
    

    如果仅仅是父元素还不够,那么可以使获取正确元素的过程更加复杂。但是,如果您需要使用特定索引的通用名称将子项添加到父项,那么您可能需要使用zippers。

    例如,如果你有 <books><book/><book/></books> ,并且您要添加 <author/> books ,这将得到它的 小孩 children ),找到 th book

        2
  •  9
  •   huynhjl bhericher    15 年前

    在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并将节点作为子节点添加。

    要将子节点添加到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 所以,根据你已经堆了多少孩子,你可能需要修补。。。那么你可能需要打电话 stack.clear 也希望这能照顾到 Node 但也包括你需要的过程。

        3
  •  6
  •   Murdix    12 年前

    自从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
    }
    

        4
  •  3
  •   Patrick    15 年前

    自从 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
  •   Confusion    15 年前

    在通常的Scala方式中,所有节点、元素等实例都是不可变的。

      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 了解更多信息。

        6
  •  2
  •   Travis Stevens    14 年前

    当您需要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>)
    
        7
  •  1
  •   Chris    13 年前

    Scales Xml 允许通过折叠XPath进行简单的就地更改,将子节点添加到特定子节点正好适合这种方法。

    看见 In-Place Transformations 更多细节。

        8
  •  1
  •   Andrei Zhaleznichenka    8 年前

    我通过以下方式实现我的“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”之前,有一个由未受影响的孩子构建的序列。

    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>
    
        9
  •  1
  •   Andrew Norman    7 年前

    您的根定义实际上是一个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>
    

    因为每个元素和每个节点都是一个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)
    

    root: scala.xml.Elem = <model/>
    children: scala.xml.NodeSeq = NodeSeq(<node1><node2/></node1>, <node3/>)
    res6: scala.xml.Elem = <model><node1><node2/></node1><node3/></model>