Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: modify a NodeSeq

Tags:

xml

scala

I have a NodeSeq like this:

<foo>
<baz><bar key1="value1" key2="value2">foobar</bar></baz>
Blah blah blah
<bar key1="value3">barfoo</bar>
</foo>

I want to add a new attribute to all bars' attributes. I'm currently doing:

   val rule = new RewriteRule() {
     override def transform(node: Node): Seq[Node] = {
       node match {
          case Elem(prefix, "bar", attribs, scope, content@_*)  => Elem(prefix, "bar", attribs append Attribute(None, "newKey", Text("newValue"), scala.xml.Null) , scope, content:_*)
          case other => other
       }
     }
   }

But the problem is that it only works on 1 node. I want it to recursively work on all nodes, and if I call the transform inside a for loop, I can't replace them with new values since they become immutable. How can I solve this?

like image 608
parsa Avatar asked Jan 12 '11 08:01

parsa


1 Answers

Here is a simplified version of your own solution (using Daniel's variant of the matching logic):

def updateBar(node: Node): Node = node match {
    case elem @ Elem(_, "bar", _, _, child @ _*) => elem.asInstanceOf[Elem] % Attribute(None, "newKey", Text("newValue"), Null) copy(child = child map updateBar)
    case elem @ Elem(_, _, _, _, child @ _*) => elem.asInstanceOf[Elem].copy(child = child map updateBar)
    case other => other
}

Note that the major differences between this and your original code is that this one processes the nodes from the outside in, as shown here where I've added some print statements as in my first answer:

scala> updateBar(<foo><bar>blabla</bar></foo>)
processing '<foo><bar>blabla</bar></foo>'
processing '<bar>blabla</bar>'
processing 'blabla'
result: 'blabla'
result: '<bar newKey="newValue">blabla</bar>'
result: '<foo><bar newKey="newValue">blabla</bar></foo>'
res1: scala.xml.Node = <foo><bar newKey="newValue">blabla</bar></foo>

While your original code works from the inside out (simplified example):

scala> xf { <a><b><c/></b></a> }
transforming '<c></c>'
result: '<c></c>'
transforming '<b><c></c></b>'
result: '<b><c></c></b>'
transforming '<a><b><c></c></b></a>'
result: '<a><b><c></c></b></a>'
res4: scala.xml.Node = <a><b><c></c></b></a>

There are probably cases where these two techniques will yield different results.

The other difference is that the matching code is slightly more verbose: you need one case for the actual transformation of the relevant element, and one case for recursively processing the subnodes. The example shown could probably be refactored a bit, though.

like image 156
Knut Arne Vedaa Avatar answered Sep 22 '22 23:09

Knut Arne Vedaa