I have since learned more and presented what I deem to be a superior solution in another answer. I have also fixed this one, as I noticed I was failing to account for the subnode
restriction.
Thanks for the question! I just learned some cool stuff when dealing with XML. Here is what you want:
def updateVersion(node: Node): Node = {
def updateNodes(ns: Seq[Node], mayChange: Boolean): Seq[Node] =
for(subnode <- ns) yield subnode match {
case <version>{ _ }</version> if mayChange => <version>2</version>
case Elem(prefix, "subnode", attribs, scope, children @ _*) =>
Elem(prefix, "subnode", attribs, scope, updateNodes(children, true) : _*)
case Elem(prefix, label, attribs, scope, children @ _*) =>
Elem(prefix, label, attribs, scope, updateNodes(children, mayChange) : _*)
case other => other // preserve text
}
updateNodes(node.theSeq, false)(0)
}
Now, explanation. First and last case statements should be obvious. The last one exists to catch those parts of an XML which are not elements. Or, in other words, text. Note in the first statement, though, the test against the flag to indicate whether version
may be changed or not.
The second and third case statements will use a pattern matcher against the object Elem. This will break an element into all its component parts. The last parameter, "children @ _*", will match children to a list of anything. Or, more specifically, a Seq[Node]. Then we reconstruct the element, with the parts we extracted, but pass the Seq[Node] to updateNodes, doing the recursion step. If we are matching against the element subnode
, then we change the flag mayChange to true
, enabling the change of the version.
In the last line, we use node.theSeq to generate a Seq[Node] from Node, and (0) to get the first element of the Seq[Node] returned as result. Since updateNodes is essentially a map function (for ... yield is translated into map), we know the result will only have one element. We pass a false
flag to ensure that no version
will be changed unless a subnode
element is an ancestor.
There is a slightly different way of doing it, that's more powerful but a bit more verbose and obscure:
def updateVersion(node: Node): Node = {
def updateNodes(ns: Seq[Node], mayChange: Boolean): Seq[Node] =
for(subnode <- ns) yield subnode match {
case Elem(prefix, "version", attribs, scope, Text(_)) if mayChange =>
Elem(prefix, "version", attribs, scope, Text("2"))
case Elem(prefix, "subnode", attribs, scope, children @ _*) =>
Elem(prefix, "subnode", attribs, scope, updateNodes(children, true) : _*)
case Elem(prefix, label, attribs, scope, children @ _*) =>
Elem(prefix, label, attribs, scope, updateNodes(children, mayChange) : _*)
case other => other // preserve text
}
updateNodes(node.theSeq, false)(0)
}
This version allows you to change any "version" tag, whatever it's prefix, attribs and scope.