Monday, May 11, 2009

Scala and XML - Part 1

As you know, Scala has a great syntax for XML. Here are some lessons from working with it. Here is a yet another Scala XML sample which creates html.

import scala.xml.XML._
import scala.xml.NodeBuffer
import scala.xml.dtd.{DocType, PublicID}
object ScalaHtml {
val words = List("one", "two", "three")
val url = "http://www.scala-lang.org/"
def main(args : Array[String]) : Unit = {
val page =
<html>
<a href={url}>Scala</a>
<h1>My words</h1>
<ul>{listOfWords(words)}</ul>
</html>;
save("save.html", page)

val doctype = DocType("html",
PublicID("-//W3C//DTD XHTML 1.0 Strict//EN",
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"),
Nil)
saveFull("save.full.html", page, false, doctype)
}
def listOfWords(words: List[String]) = {
val result = new NodeBuffer
for(word <- words) {result &+ (<li>{word}</li>)}
result
}
}

The output is
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<a href="http://www.scala-lang.org/">Scala</a>
<h1>My words</h1>
<ul><li>one</li><li>two</li><li>three</li></ul>
</html>
Where the first line (with the DOCTYPE) will be generated when using the second save option.
Notes:
[1] When using a variable as an attribute value (see the <a> tag) you must not wrap it with double quotes. The underlying code is doing something like this:
scala.xml.MetaData $md = new UnprefixedAttribute("href", url(), $md);
Object _tmp1 = null;
NodeBuffer $buf = new NodeBuffer();
$buf.$amp$plus(new Elem(null, "a", $md, Predef$.MODULE$.$scope(), $buf));

[2] You can't create a partial node. I.e. you can't create the begin tag of a node in one command, then do some other stuff and at the end close it. Scala node creation is atomic and immutable, if you wish to embed on the fly date then it must be simple text or Node* (for example a single Node or NodeBuffer). You can't have any statements int the curly brackets though you may have a call to a method that does that in its own block.
Addendum [3] An emphasis on the string as a value in the curly brackets. Some (like myself) placed there an Int or other primitives expecting something like string concatenation. Well, it won't work and you'll get this error:
error: overloaded method constructor UnprefixedAttribute with alternatives    [scalac](String,Option[Seq[scala.xml.Node]],scala.xml.MetaData)scala.xml.UnprefixedAttribute  (String,String,scala.xml.MetaData)scala.xml.UnprefixedAttribute  (String,Seq[scala.xml.Node],scala.xml.MetaData)scala.xml.UnprefixedAttribute cannot be applied to (java.lang.String,Int,scala.xml.MetaData)
[scalac] Cluster #{summary.id}
[scalac] ^

7 comments:

Kristian Domagala May 12, 2009 at 12:06 AM  

For your list of words, you can even use the map function on List and do away with your listOfWords function:

<ul>{words.map(_ => <li>{_}</li>)}</ul>
In that case, we're using map to transform a List[String] to a List[Node].

Eishay Smith May 12, 2009 at 12:19 AM  

Yes, even better!
By the way, your sample needs a bit of a touch since it does not compile:
"error: unbound placeholder parameter"
To fix it use:
<ul>{words.map(w => <li>{w}</li>)}</ul>
But if one needs more logic of some sort (e.g. if statements) a call for a method will do it.
Thanks Kristian

Kristian Domagala May 12, 2009 at 12:30 AM  

No worries, and sorry about the error - I was going from memory as it's been too long since I've done any Scala programming on a regular basis :-(

I'm not sure what you mean by "But if one needs more logic of some sort (e.g. if statements) a call for a method will do it". Do you mean, if there is more complicated logic inside the function passed to map, you would pull it out into its own function? If that's the case, then yes, I would agree.

Anonymous May 12, 2009 at 4:44 AM  

This looks very nice - is there any syntactic sugar for parsing, rather than outputting, XML?

Eishay Smith May 12, 2009 at 8:20 AM  

Oh yea, Scala is very strong in parsing XML, with nice use of pattern matching. See http://burak.emir.googlepages.com/scalaxbook.docbk.html

Dan August 14, 2009 at 6:06 AM  

Thanks! Very helpful and exactly what I needed to build xml from a list.

Andrea Parodi June 14, 2014 at 2:10 AM  

Hi, useful article, I cited it here:http://www.parro.it/2014/06/14/gulp-jsxify-transpile-your-html-templates-to-react-jsx-files/ as an example of Scala xml

Creative Commons License This work by Eishay Smith is licensed under a Creative Commons Attribution 3.0 Unported License.