<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.