<group> and <attributeGroup>

From XML Schema Part 1:

The XML representation for a model group definition schema component is a <group> element information item. It provides for naming a model group for use by reference in the XML representation of complex type definitions and model groups.

The <group> construct allows the schema designers to reuse part of the content model without resorting to type derivation. This comes in handy when dealing with complicated schema. Suppose we have a group named head.misc and an element named head.

<group name="head.misc">
  <sequence>
    <choice minOccurs="0" maxOccurs="unbounded">
      <xs:element ref="ipo:script"/>
      <xs:element ref="ipo:style"/>
    </choice>
  </sequence>
</group>

<element name="head">
  <annotation>
    <documentation>
      content model is "head.misc" combined with a single
      title and an optional base element in any order
    </documentation>
  </annotation>
  <complexType>
    <sequence>
      <group ref="ipo:head.misc"/>
      <choice>
        <sequence>
          <xs:element ref="ipo:title"/>
          <group ref="ipo:head.misc"/>
        </sequence>
        <sequence>
          <xs:element ref="ipo:base"/>
          <group ref="ipo:head.misc"/>
          <xs:element ref="ipo:title"/>
          <group ref="ipo:head.misc"/>          
        </sequence>
      </choice>
    </sequence>
    <attributeGroup ref="ipo:i18n"/>
    <attribute name="id" type="ID"/>
    <attribute name="profile" type="anyURI"/>
  </complexType>
</element>

As ridiculous as it seems, a real-world schema tends to be that complicated. The above example for instance, was taken from <head> element in XHTML 1.0 Strict. The following is the corresponding portion in the generated code:

/** 
        content model is "head.misc" combined with a single
        title and an optional base element in any order
 
*/
case class Head(arg1: Seq[rt.DataRecord[Any]],
  arg2: rt.DataRecord[Any],
  i18n: I18n,
  id: Option[String],
  profile: Option[java.net.URI])
 
object Head extends rt.ElemNameParser[Head] with HeadmiscGroup {
  import HeadSequence2._
  import HeadSequence3._
  val targetNamespace = "http://www.example.com/IPO"
 
  def parser(node: scala.xml.Node): Parser[Head] =
    rep(parseHeadmiscGroup) ~ 
      ((((rt.ElemName(targetNamespace, "title")) ~ 
    rep(parseHeadmiscGroup)) ^^ 
      { case p1 ~ 
      p2 => rt.DataRecord(null, null, HeadSequence2(p1.text,
      p2.toList)) }) ||| 
    (((rt.ElemName(targetNamespace, "base")) ~ 
    rep(parseHeadmiscGroup) ~ 
    (rt.ElemName(targetNamespace, "title")) ~ 
    rep(parseHeadmiscGroup)) ^^ 
      { case p1 ~ 
      p2 ~ 
      p3 ~ 
      p4 => rt.DataRecord(null, null, HeadSequence3(p1.text,
      p2.toList,
      p3.text,
      p4.toList)) })) ^^
        { case p1 ~ 
      p2 => Head(p1.toList,
      p2,
      I18n.fromXML(node),
      (node \ "@id").headOption map { x => x.text },
      (node \ "@profile").headOption map { x => rt.Helper.toURI(x.text) }) }
 
  def toXML(__obj: Head, __namespace: String, __elementLabel: String, __scope: scala.xml.NamespaceBinding): scala.xml.NodeSeq = {
    val prefix = __scope.getPrefix(__namespace)
    var attribute: scala.xml.MetaData  = scala.xml.Null
    attribute = I18n.toAttribute(__obj.i18n, attribute, __scope)
    __obj.id foreach { x =>
      attribute = scala.xml.Attribute(null, "id", x.toString, attribute) }
    __obj.profile foreach { x =>
      attribute = scala.xml.Attribute(null, "profile", x.toString, attribute) }
    scala.xml.Elem(prefix, __elementLabel,
      attribute, __scope,
      Seq.concat(__obj.arg1.flatMap(x => HeadmiscOption1.toXML(x, x.namespace, x.key, __scope)),
        HeadOption.toXML(__obj.arg2, "http://www.example.com/IPO", __obj.arg2.key, __scope)): _*)
  }
 
 
}
 
trait  HeadOption
 
object HeadOption {
  def toXML(__obj: rt.DataRecord[Any], __namespace: String, __elementLabel: String,
      __scope: scala.xml.NamespaceBinding): scala.xml.NodeSeq = __obj.value match {
    case x: HeadSequence2 => HeadSequence2.toXML(__obj, __namespace, __elementLabel, __scope)
    case x: HeadSequence3 => HeadSequence3.toXML(__obj, __namespace, __elementLabel, __scope)
    case _ => rt.DataRecord.toXML(__obj, __namespace, __elementLabel, __scope)
  }  
}
 
case class HeadSequence2(title: String,
  arg6: Seq[rt.DataRecord[Any]])
 
object HeadSequence2 extends rt.ImplicitXMLWriter[HeadSequence2] {
  def toXML(__obj: rt.DataRecord[Any], __namespace: String, __elementLabel: String,
      __scope: scala.xml.NamespaceBinding): scala.xml.NodeSeq = __obj.value match {
    case x: HeadSequence2 => toXML(x, __namespace, __elementLabel, __scope)
    case _ => error("Expected HeadSequence2")      
  }
 
  def toXML(__obj: HeadSequence2, __namespace: String, __elementLabel: String,
      __scope: scala.xml.NamespaceBinding): scala.xml.NodeSeq = {
    val prefix = __scope.getPrefix(__namespace)
    var attribute: scala.xml.MetaData  = scala.xml.Null
    Seq.concat(scala.xml.Elem(prefix, "title", scala.xml.Null, __scope, scala.xml.Text(__obj.title.toString)),
        __obj.arg6.flatMap(x => HeadmiscOption1.toXML(x, x.namespace, x.key, __scope)))
  }
}
 
case class HeadSequence3(base: String,
  arg7: Seq[rt.DataRecord[Any]],
  title: String,
  arg8: Seq[rt.DataRecord[Any]])
 
object HeadSequence3 extends rt.ImplicitXMLWriter[HeadSequence3] {
  def toXML(__obj: rt.DataRecord[Any], __namespace: String, __elementLabel: String,
      __scope: scala.xml.NamespaceBinding): scala.xml.NodeSeq = __obj.value match {
    case x: HeadSequence3 => toXML(x, __namespace, __elementLabel, __scope)
    case _ => error("Expected HeadSequence3")      
  }
 
  def toXML(__obj: HeadSequence3, __namespace: String, __elementLabel: String,
      __scope: scala.xml.NamespaceBinding): scala.xml.NodeSeq = {
    val prefix = __scope.getPrefix(__namespace)
    var attribute: scala.xml.MetaData  = scala.xml.Null
    Seq.concat(scala.xml.Elem(prefix, "base", scala.xml.Null, __scope, scala.xml.Text(__obj.base.toString)),
        __obj.arg7.flatMap(x => HeadmiscOption1.toXML(x, x.namespace, x.key, __scope)),
        scala.xml.Elem(prefix, "title", scala.xml.Null, __scope, scala.xml.Text(__obj.title.toString)),
        __obj.arg8.flatMap(x => HeadmiscOption1.toXML(x, x.namespace, x.key, __scope)))
  }
}
 
 
trait HeadmiscGroup extends rt.AnyElemNameParser {
  def parseHeadmiscGroup: Parser[rt.DataRecord[Any]] =
    (((rt.ElemName(targetNamespace, "script")) ^^ 
      (x => rt.DataRecord(x.namespace, x.name, x.node.text))) | 
    ((rt.ElemName(targetNamespace, "style")) ^^ 
      (x => rt.DataRecord(x.namespace, x.name, x.node.text))))
}
 
trait  HeadmiscOption1
 
object HeadmiscOption1 {
  def toXML(__obj: rt.DataRecord[Any], __namespace: String, __elementLabel: String,
      __scope: scala.xml.NamespaceBinding): scala.xml.NodeSeq = __obj.value match {
 
    case _ => rt.DataRecord.toXML(__obj, __namespace, __elementLabel, __scope)
  }  
}

In the combinator parser, <group ref="ipo:head.misc"/> is expressed as rep(parseHeadmiscGroup), which is a repetition of parser defined in trait HeadmiscGroup. During compile time, the parser is mixed into object Head. Since the group definition is expressed as a trait, it is possible to be reused across different classes.

The XML representation for an attribute group definition schema component is an <attributeGroup> element information item. It provides for naming a group of attribute declarations and an attribute wildcard for use by reference in the XML representation of complex type definitions and other attribute group definitions.

Similar to <group>, <attributeGroup> allows the schema designers to reuse a group of attribute across different complex types. This is particularly useful if a schema defines a lot of common attributes sprinkled around different elements. The following is again from XHTML 1.0 Scrict:

<xs:attributeGroup name="i18n">
  <xs:annotation>
    <xs:documentation>
    internationalization attributes
    lang        language code (backwards compatible)
    xml:lang    language code (as per XML 1.0 spec)
    dir         direction for weak/neutral text
    </xs:documentation>
  </xs:annotation>
  <xs:attribute name="lang" type="LanguageCode"/>
  <xs:attribute ref="xml:lang"/>
  <xs:attribute name="dir">
    <xs:simpleType>
      <xs:restriction base="xs:token">
        <xs:enumeration value="ltr"/>
        <xs:enumeration value="rtl"/>
      </xs:restriction>
    </xs:simpleType>
  </xs:attribute>
</xs:attributeGroup>

The above attribute group generates the following code:

/** 
      internationalization attributes
      lang        language code (backwards compatible)
      xml:lang    language code (as per XML 1.0 spec)
      dir         direction for weak/neutral text
 
*/
case class I18n(dir: Option[String],
  xmllang: Option[String],
  lang: Option[String])
 
object I18n {
  def fromXML(node: scala.xml.Node): I18n = {
    I18n((node \ "@dir").headOption map { x => x.text },
      (node \ "@{http://www.w3.org/XML/1998/namespace}lang").headOption map { x => x.text },
      (node \ "@lang").headOption map { x => x.text })
  }
 
  def toAttribute(__obj: I18n, attr: scala.xml.MetaData, __scope: scala.xml.NamespaceBinding) = {
    var attribute: scala.xml.MetaData  = attr
    __obj.dir foreach { x =>
      attribute = scala.xml.Attribute(null, "dir", x.toString, attribute) }
    __obj.xmllang foreach { x =>
      attribute = scala.xml.Attribute(__scope.getPrefix("http://www.w3.org/XML/1998/namespace"), "lang", x.toString, attribute) }
    __obj.lang foreach { x =>
      attribute = scala.xml.Attribute(null, "lang", x.toString, attribute) }
    attribute
  } 
}

The combined support for <group> and <attributeGroup> carries scalaxb a long way, as it is able to process schemas like XML Schema and XHTML. Once these schema can be processed, it's now down to hammering out the details.