foobar Blah blah blah

Scala: change NodeSeq

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 bar s attributes. I'm doing now:

  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 work recursively on all nodes, and if I call the conversion inside the for loop, I cannot replace them with new values, as they become immutable. How can i solve this?

+6
xml scala
source share
4 answers

Here is a simplified version of your own solution (using the Daniel option for 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 main differences between this and your source code are that it processes nodes from the outside, as shown here, where I 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> 

So far, your source code is working 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 methods will give different results.

Another difference is that the matching code is somewhat more verbose: you need one case for the actual conversion of the corresponding element and one case for the recursive processing of subnodes. The example shown can probably be reorganized a bit.

+3
source share

This bad boy did the job:

 def updateVersion( node : Node ) : Node = node match { case <foo>{ ch @ _* }</foo> => <foo>{ ch.map(updateVersion )}</foo> case <baz>{ ch @ _* }</baz> => <baz>{ ch.map(updateVersion ) }</baz> case Elem(prefix, "bar", attribs, scope, content@ _*) => Elem(prefix, "bar", attribs append Attribute(None, "key3", Text("value3"), scala.xml.Null) , scope, content:_*) case other @ _ => other } 
+1
source share

The source code seems correct. The problem is not that it does not work recursively (it is), but the strange problem that occurs when there is only one existing attribute.

Take a look at the following, which is basically identical to your code, except that I added some debugging instructions:

 val rule = new RewriteRule() { override def transform(node: Node): Seq[Node] = { println("transforming '" + node + "'") val result = node match { case elem @ Elem(prefix, label @ "bar", attribs, scope, children @ _*) => Elem(prefix, label, attribs append Attribute(None, "newKey", Text("newValue"), Null), scope, children: _*) case other => other } println("result: '" + result + "'") result } } object xf extends RuleTransformer(rule) 

Now we test it:

 scala> xf { <bar/> } transforming '<bar></bar>' result: '<bar newKey="newValue"></bar>' transforming '<bar></bar>' result: '<bar newKey="newValue"></bar>' res0: scala.xml.Node = <bar newKey="newValue"></bar> 

We see that for an element without attributes, the transformation adds a new attribute, as well as the return result. (I do not know why the conversion occurs twice.)

However, if an existing attribute exists:

 scala> xf { <bar key1="value1"/> } transforming '<bar key1="value1"></bar>' result: '<bar key1="value1" newKey="newValue"></bar>' res1: scala.xml.Node = <bar key1="value1"></bar> 

The conversion result is correct, but it does not apply to the final result!

But when there are two (or more) existing attributes, everything is fine:

 scala> xf { <bar key1="value1" key2="value2"/> } transforming '<bar key1="value1" key2="value2"></bar>' result: '<bar key2="value2" key1="value1" newKey="newValue"></bar>' transforming '<bar key1="value1" key2="value2"></bar>' result: '<bar key2="value2" key1="value1" newKey="newValue"></bar>' res2: scala.xml.Node = <bar key2="value2" key1="value1" newKey="newValue"></bar> 

It seems to me that this is a mistake in the library.

+1
source share

Try

  val rule = new RewriteRule() { override def transform(node: Node): Seq[Node] = { node match { case elem : Elem if elem.label == "bar" => (elem copy (child = this transform child)) % Attribute(None, "newKey", Text("newValue"), scala.xml.Null) case elem : Elem => elem copy (child = this transform child) case other => other } } } 
0
source share

All Articles