xsi:nillable

In XML, one can omit an element to express lack of value or use an empty element.

Sometimes it is desirable to represent an unshipped item, unknown information, or inapplicable information explicitly with an element, rather than by an absent element.

The trouble with using an empty element <foo></foo> is that the emptiness would no longer matches the specified type like xs:positiveInteger. It is possible to form a xs:union of xs:positiveInteger and an xs:enumeration with only empty string in it to allow either poistive integers or empty string. However, technically speaking, an empty string is different from pure emptiness. In terms of code, it's the difference between null and "", or in Scala, None and Some("").

XML Schema resolves this issue by introducing a special attribute called xsi:nil. By writing

<price xsi:nil="true" />

XML Schema-aware parsers know that this is a special empty element, which means it's a nil value, not empty string. Note that the nillability of an element is a separate issue from the cardinality (minOccurs and maxOccurs). This means I can define minOccurs=0 and maxOccurs=1 optional element that is also nillable, or even maxOccurs="unbounded" nillable element, in which case the document could look like this:

<foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <price xsi:nil="true" />
  <price>100</price>
  <price xsi:nil="true" />
</foo>

I did not want to introduce null vs non-null into generated code more than I have to, so <price xsi:nil="true" /> produces None, which is the same value as when an element is absent in a minOccurs=0 element.

<xs:element name="NillableTest">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="price" type="xs:positiveInteger" nillable="true"/>
      <xs:element name="tax" type="xs:positiveInteger" minOccurs="0" nillable="true"/>
      <xs:element name="tag" type="xs:positiveInteger" minOccurs="0" maxOccurs="unbounded" nillable="true"/>

      <xs:element name="shipTo" type="ipo:Address" nillable="true"/>
      <xs:element name="billTo" type="ipo:Address" minOccurs="0" nillable="true" />
      <xs:element name="via" type="ipo:Address" minOccurs="0" maxOccurs="unbounded" nillable="true" />
    </xs:sequence>
  </xs:complexType>
</xs:element>

The above element produces the following code:

case class NillableTest(price: Option[Int],
  tax: Option[Int],
  tag: Seq[Option[Int]],
  shipTo: Option[Addressable],
  billTo: Option[Addressable],
  via: Seq[Option[Addressable]]) {
 
  def toXML(namespace: String, elementLabel: String, scope: scala.xml.NamespaceBinding): scala.xml.Node = {
    val prefix = scope.getPrefix(namespace)
    var attribute: scala.xml.MetaData  = scala.xml.Null
 
    scala.xml.Elem(prefix, elementLabel,
      attribute, scope,
      Seq(price match {
          case Some(x) => Seq(scala.xml.Elem(prefix, "price", scala.xml.Null, scope, scala.xml.Text(x.toString)))
          case None => Seq(rt.Helper.nilElem("http://www.example.com/IPO", "price", scope))
        },
        tax match {
          case Some(x) => Seq(scala.xml.Elem(prefix, "tax", scala.xml.Null, scope, scala.xml.Text(x.toString)))
          case None => Seq(rt.Helper.nilElem("http://www.example.com/IPO", "tax", scope))
        },
        tag.map(x => x match {
          case Some(x) => scala.xml.Elem(prefix, "tag", scala.xml.Null, scope, scala.xml.Text(x.toString))
          case None =>   rt.Helper.nilElem("http://www.example.com/IPO", "tag", scope)
        } ),
        shipTo match {
          case Some(x) => x.toXML("http://www.example.com/IPO","shipTo", scope)
          case None => Seq(rt.Helper.nilElem("http://www.example.com/IPO", "shipTo", scope))
        },
        billTo match {
          case Some(x) => x.toXML("http://www.example.com/IPO","billTo", scope)
          case None => Seq(rt.Helper.nilElem("http://www.example.com/IPO", "billTo", scope))
        },
        via.map(x => x match {
          case Some(x) => x.toXML("http://www.example.com/IPO", "via", scope)
          case None => rt.Helper.nilElem("http://www.example.com/IPO", "via", scope)
        } )).flatten: _*)
  }
}
 
object NillableTest extends rt.ElemNameParser[NillableTest] {
  val targetNamespace = "http://www.example.com/IPO"
 
  def parser(node: scala.xml.Node): Parser[NillableTest] =
    (rt.ElemName(targetNamespace, "price")) ~ 
      opt(rt.ElemName(targetNamespace, "tax")) ~ 
      rep(rt.ElemName(targetNamespace, "tag")) ~ 
      (rt.ElemName(targetNamespace, "shipTo")) ~ 
      opt(rt.ElemName(targetNamespace, "billTo")) ~ 
      rep(rt.ElemName(targetNamespace, "via")) ^^
        { case p1 ~ 
      p2 ~ 
      p3 ~ 
      p4 ~ 
      p5 ~ 
      p6 => NillableTest(if (p1.nil) None
      else Some(p1.text.toInt),
      p2 match {
        case Some(x) => if (x.nil) None
          else Some(x.text.toInt)
        case None    => None
      },
      p3.toList.map(x => if (x.nil) None
      else Some(x.text.toInt) ),
      if (p4.nil) None
      else Some(Addressable.fromXML(p4.node)),
      p5 match {
        case Some(x) => if (x.nil) None
          else Some(Addressable.fromXML(x.node))
        case None    => None
      },
      p6.toList.map(x => if (x.nil) None
      else Some(Addressable.fromXML(x.node)) )) }
}

Notice that tag element generates a sequence of options, not an optional sequence.