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