4

I'm writing a tool to update some xml files (pom.xml in this case) with scala because the effort it would take in java is significantly higher than (in theory) it is with scala. I can parse the xml file just fine, but I need to replace nodes in the existing xml and rewrite the result. for example:

<dependency>
    <groupId>foo</groupId>
    <artifactId>bar</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

So I want to find all nodes like this and replace them with:

<dependency>
    <groupId>foo</groupId>
    <artifactId>bar</artifactId>
    <version>1.0</version> <!-- notice the lack of -SNAPSHOT here -->
</dependency>

So, I can get all the version nodes simply enough, but how to replace them with the node that I want?

// document is already defined as the head of the xml file
nodes = for (node <- document \\ "version"; if (node.text.contains("SNAPSHOT"))) yeild node

then I want to do something like:

for (node <- nodes) {
    node.text = node.text.split("-")(0)
}

which doesn't work because node is immutable. I looked at the copy method for a Node, but it doesn't include text as a parameter.

2

3 Answers 3

12

You really should take a look at other questions on Stack Overflow about modifying XML. Look at the "Related" links to the right.

Here:

scala> <dependency>
     |     <groupId>foo</groupId>
     |     <artifactId>bar</artifactId>
     |     <version>1.0-SNAPSHOT</version>
     | </dependency>
res0: scala.xml.Elem =
<dependency>
    <groupId>foo</groupId>
    <artifactId>bar</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

scala> new scala.xml.transform.RewriteRule {
     |   override def transform(n: Node): Seq[Node] = n match {
     |     case <version>{v}</version> if v.text contains "SNAPSHOT" => <version>{v.text.split("-")(0)}</version>
     |     case elem: Elem => elem copy (child = elem.child flatMap (this transform))
     |     case other => other
     |   }
     | } transform res0
res9: Seq[scala.xml.Node] =
<dependency>
    <groupId>foo</groupId>
    <artifactId>bar</artifactId>
    <version>1.0</version>
</dependency>
Sign up to request clarification or add additional context in comments.

2 Comments

Do you have a glue what complexity in time your solution has? How costly is the match/search in the XML-tree? O(log n)?
@Themerius It used to be n squared, for depth n, if I recall correctly. There was a ticket and a patch for it, but I don't recall if it ever got fixed.
2

Text is represented as a Node inside of the Elements Node. So a bit of functional recursion will let you do a deep copy & filter:

def deepCopy(node:Node) : Node = node match {
  case e : Elem => e.copy(child = this.child.toSeq.map(deepCopy))
  case t : Text => new Text(t.text.split("-").head)
  case x => x
}

disclaimer: this code was not tested for errors

Comments

0

Using Scalate's Scuery CSS3 transforms and scala.xml.Elem#copy:

val xml =
  <dependency>
    <version>1.0-SNAPSHOT</version>
    <version>2.0</version>
  </dependency>

new Transformer {
  $("dependency > version") { node =>
    node.asInstanceOf[Elem].copy(child = Text(node.text.stripSuffix("-SNAPSHOT")))
  }
}.apply(xml)

yields

NodeSeq(<dependency><version>1.0</version><version>2.0</version></dependency>)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.