<group> と <attributeGroup>

XML Schema Part 1 より:

モデルグループ定義を行うスキーマコンポーネントの XML 表現は<group> 要素である.これはXMLで表現された複合型定義内においてモデルグループの参照するための名前付けを行うためのものである.

<group> という機構を使うことによってスキーマ設計者は内容モデルの一部を型派生に頼ることなく再利用することができます.これは込み入ったスキーマを扱うときに便利なものです.例えば,以下のような head.misc という名前のグループと 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>

ちょっとバカバカしいぐらいですが,実際に現場で使われるスキーマはこのぐらい複雑なものもあります.例えば,上の例は XHTML 1.0 Strict 規格から持ってきたものです.以下が生成されたコードの対応部分です:

/** 
        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)
  }  
}

コンビネータ・パーサにおいて,<group ref="ipo:head.misc"/>rep(parseHeadmiscGroup) と表現されていて,これは trait HeadmiscGroup で定義されているパーサの繰り返しです.コンパイル時にこのパーサは object Head に mix-in されます.グループ定義は trait で表現されているため,異なるクラスによって再利用することができます.

属性グループ定義を行うスキーマコンポーネントの XML表現は<attributeGroup> である.これはXMLで表現された複合型定義および他の属性グループの定義内において属性宣言や属性ワイルドカードのグループを参照するための名前付けを行うためのものである.

<group> と同様に <attributeGroup> を使ってスキーマ設計者は属性の集まりを異なる複合型をまたいで再利用することができます.これはスキーマが多くの共通した属性を色々な要素で宣言している場合に特に便利です.それでは再び 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>

上記の属性グループは以下のコードを生成します:

/** 
      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
  } 
}

<group><attributeGroup> のサポートが加わったことで XML Schema や XHTML などのスキーマが取り扱えるようになり scalaxb は前進しました.このようなスキーマが処理できるようになれば,後は細かい部分を詰めていくことになります.