round trip

I started implementation of round trip: xml document -> scala object -> back to xml document. Currently done with elements and attributes tracked in the case class. Suppose you have the following document:

val subject = <shipTo xmlns="http://www.example.com/IPO"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ipo="http://www.example.com/IPO"
    xsi:type="ipo:USAddress">
  <name>Foo</name>
  <street>1537 Paper Street</street>
  <city>Wilmington</city>
  <state>DE</state>
  <zip>19808</zip>
</shipTo>

I can turn this into object by calling fromXML():

val obj = Addressable.fromXML(subject)

Then turn it back into xml document by calling toXML():

obj match {
  case usaddress: USAddress =>
    val document = usaddress.toXML("shipTo")
    println(document)
  case _ => error("parsed object is not USAddress") 
}

This generates the following xml document:

<shipTo xsi:type="ipo:USAddress" xmlns="http://www.example.com/IPO" xmlns:ipo="http://www.example.com/IPO" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><name>Foo</name><street>1537 Paper Street</street><city>Wilmington</city><state>DE</state><zip>19808</zip></shipTo>

, which is semantically identical to the original document. Implementing the round trip reminded me that the element names carry information besides the structure of the type. For example, here's an example from XML Encryption.

<element name='ReferenceList'>
  <complexType>
    <choice minOccurs='1' maxOccurs='unbounded'>
      <element name='DataReference' type='xenc:ReferenceType'/>
      <element name='KeyReference' type='xenc:ReferenceType'/>
    </choice>
  </complexType>
</element>

Both the element DataReference and KeyReference use the type xenc:ReferenceType, so if the object only stored the type information, I wouldn't be able to turn it back into xml document again. This ambiguity present a problem even when I am just consuming the object.

My current solution is to wrap the object in a parameterized case class called DataRecord[A], which holds both the object and the element name.

case class DataRecord[A](key: String, value: A) {
  def toXML(elementLabel: String,
      scope: scala.xml.NamespaceBinding): scala.xml.Node = value match {
    case x: DataModel => x.toXML(elementLabel, scope)
    case _ => scala.xml.Elem(null, elementLabel, scala.xml.Null, scope,
      scala.xml.Text(value.toString))
  }
}

By parameterizing the class, this gave me opportunity to clean up the choice wrappers I made when I was dealing with simple types.

object Element1Option {  
  def fromXML: PartialFunction[scala.xml.NodeSeq, org.scalaxb.rt.DataRecord[Any]] = {
    case x: scala.xml.Elem if x.label == "Choice1" =>
      org.scalaxb.rt.DataRecord("Choice1", Choice1.fromXML(x))
    case x: scala.xml.Elem if x.label == "Choice2" =>
      org.scalaxb.rt.DataRecord("Choice2", x.text.toInt)
  }
}

In case of Choice1, I forward the parsing to Choice1.fromXML(), and for Choice2, I just parse the inner text into Int and put it in DataRecord, which scala seems to figure out DataRecord[Int] automagically.

Next goal is to handle <any> without ignoring it completely using this generic container. I can probably store the scala.xml.Elem object "as is" in a collection. The user of round trip probably would expect that I don't lose luggage during the flight.