Reputation: 4699
I have an XML node:
<a>
<b>
<c>
foo
</c>
</b>
<b2>
bar
</b2>
</a>
I need to recursively go through every child and find a label, say in this case b
and replace it with another label with some other text say <b3>baz</b3>
, so that the final result looks like:
<a>
<b3>
baz
</b3>
<b2>
bar
</b2>
</a>
How can I make this happen?
Upvotes: 1
Views: 406
Reputation: 120
Naive recursive implementation:
import scala.xml.{Elem, Node}
def replace(xml: Node)(p: Node => Boolean)(elem: Node): Node = xml match {
case x: Node if p(x) => elem
case Elem(prefix, label, attribs, scope, child @ _*) =>
Elem(prefix, label, attribs, scope, child.map(replace(_)(p)(elem)): _*)
case x: Node => x
}
val xml = <a><k/><b><c>text</c></b></a>
replace(xml)(_.label == "c")(<z>TEXT</z>)
Upvotes: 0
Reputation: 139038
There's not really any nice way to do this with the standard library, but something like the following approach will work:
def replaceAllBs(elem: Elem): Elem = elem.copy(
child = elem.child.map {
case elem: Elem if elem.label == "b" => <b3>baz</b3>
case elem: Elem => replaceAllBs(elem)
case other => other
}
)
I.e., we descend through the tree checking whether each element is a b
, replacing it if it is, and moving on to its children if it's not.
This is a really nice use case for zippers, which are designed to make updating immutable data structures more elegant. For example, using Anti-XML (which is unfortunately no longer maintained), you could write the following:
(elem \\ "b").map(_ => <b3>baz</b3>.convert).unselect.head
You make a selection into the tree, make some changes, and then move back to the top with unselect
. If you're interested in this approach, Scales XML is actively maintained and provides another Scala XML zipper implementation (although it's syntactically a little more bulky than Anti-XML's).
Upvotes: 2