-
Notifications
You must be signed in to change notification settings - Fork 19
XML API
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] = [...]
}
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...
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
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.
import play2.tools.xml.DefaultImplicits._
import play2.tools.xml.BasicReaders._
import play2.tools.xml.BasicWriters._
NOTE that we don't use the standard schema
xsd:type
and 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
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]
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 thebase
field as it must either return a classic XML tag, either the same tag withnil="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>
}
}
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
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.
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>
}
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.
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");
...
)...
}
}
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>
}