It took a day or two of blog reading and scala console try outs but I managed to create my first usable Scala script.
import scala.xml._
/**
Sequence number fix script. See http://fugamusic.com/docs/ingestion/ingestion.xsd
**/
object FixSequence {
var seq: List[Pair[Int,Int]] = List()
var working_vol = 0
def main(args: Array[String]) {
val pp = new PrettyPrinter(80, 2)
val fuga = XML.loadFile(args(0))(0)
loadSeq(fuga)
println(pp.format(fixSeq(fuga)))
}
def loadSeq(xml: Node) {
for (t <- xml \ "tracks" \ "track")
seq = ((t \ "on_disc" text).toInt, (t \ "sequence_number" text).toInt) :: seq
}
def maxSeq(vol:Int) = {
seq.foldLeft(0){(s,v) => if(v._1 == vol && v._2 > s) v._2 else s}
}
def maxVol: Int = {
seq.foldLeft(0){(m,v) => if (v._1 > m) v._1 else m }
}
def baseVal(vol: Int): Int = {
(for (t <- 1 to vol-1) yield maxSeq(t)).foldLeft(0) {_+_}
}
def fixSeq(xml: Node): Node = {
def updateSeq(ns: Seq[Node]): Seq[Node] = {
for (n <- ns) yield n match {
case { disc_nr } => {
working_vol = disc_nr.text.toInt
{ disc_nr }
}
case { seq_nr } => {
{ seq_nr.text.toInt + baseVal(working_vol) }
}
case Elem(prefix, label, attribs, scope, children @ _*) =>
Elem(prefix, label, attribs, scope, updateSeq(children) : _*)
case other => other
}
}
updateSeq(xml.theSeq)(0)
}
}
FixSequence.main(args)
While working on the script I rewrote several methods to be more Scala-like as I got more feeling for some of the functional traits of Scala. For instance the method:
def baseVal(vol: Int): Int = {
var base_val = 0
for (t <- 1 to vol-1) base_val += maxSeq(t)
base_val
}
was rewritten to be var'less:
def baseVal(vol: Int): Int = {
(for (t <- 1 to vol-1) yield maxSeq(t)).foldLeft(0) {_+_}
}
(the { } can even be omitted in this case). Thanks for the foldLeft tip p3t0r!
But the hardest part was figuring out how to modify the XML structure in Scala without using DOM. Scala's XML support is quite amazing, especially the pattern matching and the ability to mix XML constructs with code!
I did cheat a bit IMHO by keeping some of the state in a var while using the recursion to walk through the XML, but it seems to work quite well. See my previous post for a problem description. Basically I need to modify the contents of the <sequence_number> based on the <on_disc> values. So first all (vol,sequence_number) pairs are parsed and based on the (max) values the <sequence_numbers> are modified.
While thinking about it some more there is an issue where the script will break down if the <on_disc> comes after the <sequence_number> but this is not the case for the XML batch that needed fixing. That could be solved by doing a element lookup in the case match of <sequence_number>. Anyway, tastes like more.. So I'll definitely be doing more Scala coding.. I've already registered scalaguru.com, so watch out!