Skip to content
mandubian edited this page Jul 13, 2012 · 3 revisions

XML API

EXML object

object EXML {
  def toXML[T](t: T, base: xml.NodeSeq = xml.NodeSeq.Empty)(implicit w: XMLWriter[T]): xml.NodeSeq = [...]
  def fromXML[T](x: xml.NodeSeq)(implicit r: XMLReader[T]): Option[T] = [...]   
}

The XML formatters

XML readers

trait XMLReader[T] {
  // No error management for the time being... maybe later
  def read(x: xml.NodeSeq): Option[T]
}

It returns an Option[T] which is not satisfying for error management. This future feature will be added but it will also change this API. Anyway, it's foreseen...

XML writers

trait XMLWriter[-T] {
  def write(t: T, base: xml.NodeSeq): xml.NodeSeq
}
  • the base field is helpful when you need to pass a context node from the parent writer so it is copied/modified in the child writer. This mechanism is used in Option/List/Map writers
  • XMLWriter[T] is contravariant: if B inherits A then XMLWriter[A] inherits XMLWriter[B] meaning you can write a List[T] with a XMLWriter[Seq[T]]

For ex:

val seqw = XMLWriter[Seq[Int]]
seqw.write(List(1, 2, 3)) -> OK

XML Formatters mixing reader/writer traits

trait XMLFormatter[T] extends XMLReader[T] with XMLWriter[T]

This class is useful when you define your implicit converters since you can define both read/write.

Implicit XML formatters

All default implicits readers/writers

import play2.tools.xml.DefaultImplicits._

Basic Implicit Readers/Writers

import play2.tools.xml.BasicReaders._
import play2.tools.xml.BasicWriters._

NOTE that we don't use the standard schema xsd:typeand just put value in tags. I don't want to introduce too many standards as their are not supported everywhere...

It provides implicits for following types:

  • String
  • Int
  • Long
  • Short
  • Float
  • Double
  • Boolean

Special Implicit Readers/Writers

import play2.tools.xml.SpecialReaders._
import play2.tools.xml.SpecialWriters._

it provides implicits for following types:

  • Option[T]
  • Traversable[T] (all collections)
  • Map[K, V]

Option[T] reader/writer

The XML Schema standard seems to say (yes it's always blurred around SOAP standards and their implementations) that a nillable XML field should be written in XML:

  • if not null: <myfield>myvalue</myfield>
  • if null: <myfield xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" />

We apply this principle to the safer Option[T] provided by Scala and null is replaced by None.

Why choosing to apply the standard here? Because it fits my needs ;)

Note that default XMLWriter[Option[T]] uses the base field as it must either return a classic XML tag, either the same tag with nil="true" attribute.

So when writing custom XMLWriters with Option[T] fields, you mustn't use XML literals but the following syntax:

case class Foo(..., opt: Option[String], ...)
implicit object FooXML extends XMLWriter[Foo] {
  def write(f: Foo, base: xml.NodeSeq): xml.NodeSeq = {
    <foo>
    ...
    { EXML.toXML(f.opt, <opt/>) }
    ...
    </foo>
  }
}

Collection reader/writer

XML collection elements are the following :

<foo>
  ...
  <list>
     <item>alpha</item>
     <item>beta</item>
  </list>
  ...
</foo>

Once again here the standards propose things and people/frameworks do as they want around that so we can't really implement something working in all case and this is just a way to do it

XMLReader[Traversable[T]]

When writing custom readers with collection fields, you must read down to the leaf nodes of the list list/item:

case class Foo(... list: List[Int]...)
implicit object FooXMLF extends XMLFormatter[Foo] {
  def read(x: xml.NodeSeq): Option[Foo] = {
    for(
      ...
      list <- EXML.fromXML[List[Int]](x \ "list" \ "item");
      ...
    )...
  }
}

Note if you stop at x/"list", the parser will not find the children by itself.

XMLWriter[Traversable[T]]

This uses the base parameter in write function to identify the tag used to serialize each item of the traversable. You must write it as following:

case class Foo(... list: List[Int]...)
def write(f: Foo, base: xml.NodeSeq): xml.NodeSeq = {
  <foo>
    ...
    <list>{ EXML.toXML(f.list, <item/>) }</list>
    ...
  </foo>
}

Map reader/writer

XMLReader[Map[K, V]]

XML map elements are the following :

<foo>
  ...
  <map>
     <item><key>alpha</key><value>valuea</value></item>
     <item><key>beta</key><value>valueb</value></item>
  </map>
  ...
</foo>

Note the <key/><value/> structure which is the way I chose to serialize maps. Apparently it's also a standard way to serialize maps but I've already seen other ways so this was once again a choice.

XMLReader[Map[K, V]]

When writing custom readers with map fields, you must read down to the leaf nodes of the list map/item containing <key/><value/>:

case class Foo(... map: Map[String, Int]...)
implicit object FooXMLF extends XMLFormatter[Foo] {
  def read(x: xml.NodeSeq): Option[Foo] = {
    for(
      ...
      map <- EXML.fromXML[Map[String, Int]](x \ "map" \ "item");
      ...
    )...
  }
}

XMLWriter[Map[K, V]]

This uses the base parameter in write function to identify the tag used to serialize each couple (key, value) of the map. You must write it as following:

case class Foo(... map: Map[String, Int]...)
def write(f: Foo, base: xml.NodeSeq): xml.NodeSeq = {
  <foo>
    ...
    <map>{ EXML.toXML(f.map, <item/>) }</list>
    ...
  </foo>
}