Skip to content
philcali edited this page Feb 13, 2012 · 6 revisions

One of my goals for LMXML was to be able to template output without having to write a template language.

One could easily write out to an existing template language, if they so desired.

Templates vs Template Languages

A template is something dumb, meaning it does not have any logic to output the data being passed in. A quick example would be composing a letter with a word processor.

If you want to write a letter, and choose to start with a letter template, it may create a new document formatted a certain way, but ultimately it is left up to you to fill in the data. It will most likely provide stubs where you would need to fill in contacts, your name, address information, etc.

Template languages take it one step further, and try to build all the output based on what you pass in. In the previous example, the template may have some tags that would loop through the list of contacts you provided it, and format it a certain way, and all you had to do was pass in the data.

The latter sounds better, but the template is no longer a dumb thing anymore. Embedded within is (usually) exposure about the data to be processed and other programming language constructs, namely iteration and conditional logic.

LMXML aims to try to bridge the difference by taking a piece from both pies.

Transforming Parsed Nodes

Templating in LMXML is achieved by transforming the parsed data by using built-in conversions.

I will now make a blog template as an example:

html
  head title "My Cool Blog"
  body
    div #posts
      posts
        div .post
          div .post-title post-title
          div .post-body post-body

You may notice there is no fancy iteration syntax or anything like that. It is all LMXML.

Naturally, I would like to iterate through a collection of blog posts, and apply the children nodes to format each post.

Node Processors

Node Processors are used by the Transform converter to inject logic during the conversion process.

Note: Processor's are simply Scala functions. That means control structures can be composed together like a real language.

I will now define template logic used to transform the parsed nodes into what I want.

case class Post(title: String, body: String)

val posts = List(Post(....), Post(....))

import lmxml._
import transforms._

val transform = Transform(
  "posts" -> Foreach(posts) { post => Seq (
    "post-title" -> Value(post.title),
    "post-body" -> Value(post.body)
  ) }
)

The Transform converter expects a collection of tuples (String, Processor).

Some common controls are shipped with the library.

  • Foreach for looping
  • If for branching
  • Value for setting arbitrary data to the transformer
  • Fill for working with values that are set (potentially in the future)
  • Empty which is equivalent to a null value.
val transform = Transform(
  "collection" -> Foreach(collection) { item =>
    Seq(
      "item-check" -> If(item.length > 100) { Seq(
        "item-header" -> Value(item.header),
        "item-contents" -> Value(item.contents)
      ) }.orElse { Seq(
        "item-failure" -> Value("This is bad... Here's the enemy!")
      ) },
      "sub-items" -> Foreach(item.children) { subItem =>
        //etc
      }
    )
  }
)

Putting it all together

In the conversion section, it is explained that LmxmlConverts are composable.

val posts = Post.getAll() // Imaginary data store

val transform = Transform(
  "posts" -> Foreach(posts) { post => Seq (
    "post-title" -> Value(post.title),
    "post-body" -> Value(post.body)
  ) }
)

DefaultLmxml.fromFile("index.lmxml")(transform andThen XmlConvert)

Benefits

  • Type safety
  • Compilation errors
  • Avoid template data exposure
  • Templates are simple
  • Template language is pure Scala

Clone this wiki locally