Cogs and Levers A blog full of technical stuff

XML literals in scala

A really handy feature that has been included in the Scala programming language is xml literals. The xml literals feature allows you to declare blocks of xml directly into your Scala code. As you’ll see below, you’re not limited to static xml blocks and you’re also given the full higher-order function architecture to navigate and process your xml data.

Definition and creation

You can create an xml literal very simply inside of your Scala code:

val people = 
	<people>
		<person firstName="John" 
				lastName="Smith" 
				age="25" 
				gender="M" />
		<person firstName="Mary" 
				lastName="Brown" 
				age="23" 
				gender="F" />
		<person firstName="Jan" 
				lastName="Green" 
				age="31" 
				gender="F" />
		<person firstName="Peter" 
				lastName="Jones" 
				age="23" 
				gender="M" />
	</people>

Scala then creates a variable of type Elem for us.

Xml literals can also be constructed or generated from variable sources

val values = <values>{(1 to 10).map(x => <value number={x.toString} />)}</values>

Take note that the value of x needs to be converted to a string in order to be used in an xml literal.

Another form of generation can be accomplished with a for comprehension:

val names = 
	<names>
	{for (name <- List("Sam", "Peter", "Bill")) yield <name>{name}</name>}
	</names>

Working with literals

Once you have defined your xml literal, you can start to interact with the data just like any other Seq typed structure.

val peopleCount = (people \ "person").length
val menNodes = (people \ "person").filter(x => (x \ "@gender").text == "M")
val mensNames = menNodes.map(_ \ "@firstName")

println(s"Number of people: $peopleCount")
println(s"Mens names: $mensNames")

The usage of map and filter certainly provide a very familiar environment to query your xml data packets.

Transform with RewriteRule

The scala.xml.transform package include a class called RewriteRule. Using this class, you can transform (or re-write) parts of your xml document.

Taking the sample person data at the top of this post, we can write a transform to remove all of the men out of the set:

val removeMen = new RewriteRule {
	override def transform(n: Node): NodeSeq = n match {
		case e: Elem if (e \ "@gender").text == "M" => NodeSeq.Empty
		case n => n
	}
}

We test if the gender attribute contains an “M”, and if so we empty out the node. To apply this transform to the source data, we use the RuleTransformer class.

val noMen = new RuleTransformer(removeMen).transform(people)

Another rule we can write, would be to remove any person who was over the age of 30:

val removeOver30s = new RewriteRule {
	override def transform(n: Node): NodeSeq = n match {
		case e: Elem if e.label == "person" && (e \ "@age").text.toInt > 30 => NodeSeq.Empty
		case n => n
	}
}

Pretty much the same. The only extra complexity is ensuring that we have an age attribute and getting it casted to an integer for us to perform arithmetic testing.

The RuleTransformer class accommodates if we want to use these two transforms in conjunction with each other.

val noMenAndOver30s = new RuleTransformer(removeMen, removeOver30s).transform(people)