The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

PRANG::Graph::Meta::Element - metaclass metarole for XML elements

SYNOPSIS

 use PRANG::Graph;

 has_element 'somechild' =>
    is => "rw",
    isa => "Some::Type",
    xml_required => 0,
    ;

 # equivalent alternative - plays well with others!
 has 'somechild' =>
    is => "rw",
    traits => [qw/PRANG::Element/],
    isa => "Some::Type",
    xml_required => 0,
    ;

DESCRIPTION

The PRANG concept is that attributes in your classes are marked to correspond with attributes and elements in your XML. This class is for marking your class' attributes as XML elements. For marking them as XML attributes, see PRANG::Graph::Meta::Attr.

Non-trivial elements - and this means elements which contain more than a single TextNode element within - are mapped to Moose classes. The child elements that are allowed within that class correspond to the attributes marked with the PRANG::Element trait, either via has_element or the Moose traits keyword.

Where it makes sense, as much as possible is set up from the regular Moose definition of the attribute. This includes the XML node name, the type constraint, and also the predicate.

If you like, you can also set the xmlns and xml_nodeName attribute property, to override the default behaviour, which is to assume that the XML element name matches the Moose attribute name, and that the XML namespace of the element is that of the enclosing class (ie, $class->xmlns), if defined.

The order of declaring element attributes is important. They implicitly define a "sequence". To specify a "choice", you must use a union sub-type - see below. Care must be taken with bundling element attributes into roles as ordering when composing is not defined.

The predicate property of the attribute is also important. If you do not define predicate, then the attribute is considered required. This can be overridden by specifying xml_required (it must be defined to be effective).

The isa property (type constraint) you set via 'isa' is required. The behaviour for major types is described below. The module knows about sub-typing, and so if you specify a sub-type of one of these types, then the behaviour will be as for the type on this list. Only a limited subset of higher-order/parametric/structured types are permitted as described.

Bool sub-type

If the attribute is a Bool sub-type (er, or just "Bool", then the element will marshall to the empty element if true, or no element if false. The requirement that predicate be defined is relaxed for Bool sub-types.

ie, Bool will serialise to:

   <object>
     <somechild />
   </object>

For true and

   <object>
   </object>

For false.

Scalar sub-type

If it is a Scalar subtype (eg, an enum, a Str or an Int), then the value of the Moose attribute is marshalled to the value of the element as a TextNode; eg

  <somechild>somevalue</somechild>
Object sub-type

If the attribute is an Object subtype (ie, a Class), then the element is serialised according to the definition of the Class defined.

eg, with;

   {
       package CD;
       use Moose; use PRANG::Graph;
       has_element 'author' => qw( is rw isa Person );
       has_attr 'name' => qw( is rw isa Str );
   }
   {
       package Person;
       use Moose; use PRANG::Graph;
       has_attr 'group' => qw( is rw isa Bool );
       has_attr 'name' => qw( is rw isa Str );
       has_element 'deceased' => qw( is rw isa Bool );
   }

Then the object;

  CD->new(
    name => "2Pacalypse Now",
    author => Person->new(
       group => 0,
       name => "Tupac Shakur",
       deceased => 1,
       )
  );

Would serialise to (assuming that there is a PRANG::Graph document type with cd as a root element):

  <cd name="2Pacalypse Now">
    <author group="0" name="Tupac Shakur>
      <deceased />
    </author>
  </cd>
ArrayRef sub-type

An ArrayRef sub-type indicates that the element may occur multiple times at this point. Bounds may be specified directly - the xml_min and xml_max attribute properties.

Higher-order types are supported; in fact, to not specify the type of the elements of the array is a big no-no.

If xml_nodeName is specified, it refers to the items; no array container node is expected.

For example;

  has_attr 'name' =>
     is => "rw",
     isa => "Str",
     ;
  has_attr 'releases' =>
     is => "rw",
     isa => "ArrayRef[CD]",
     xml_min => 0,
     xml_nodeName => "cd",
     ;

Assuming that this property appeared in the definition for 'artist', and that CD has_attr 'title'..., it would let you parse:

  <artist>
    <name>The Headless Chickens</name>
    <cd title="Stunt Clown">...<cd>
    <cd title="Body Blow">...<cd>
    <cd title="Greedy">...<cd>
  </artist>

You cannot (currently) Union an ArrayRef type with other simple types.

Union types

Union types are special; they indicate that any one of the types indicated may be expected next. By default, the name of the element is still the name of the Moose attribute, and if the case is that a particular element may just be repeated any number of times, this is fine.

However, this can be inconvenient in the typical case where the alternation is between a set of elements which are allowed in the particular context, each corresponding to a particular Moose type. Another one is the case of mixed XML, where there may be text, then XML fragments, more text, more XML, etc.

There are two relevant questions to answer. When marshalling OUT, we want to know what element name to use for the attribute in the slot. When marshalling IN, we need to know what element names are allowable, and potentially which sub-type to expect for a particular element name.

After applying much DWIMery, the following scenarios arise;

1:1 mapping from Type to Element name

This is often the case for message containers that allow any number of a collection of classes inside. For this case, a map must be provided to the xml_nodeName function, which allows marshalling in and out to proceed.

  has_element 'message' =>
      is => "rw",
      isa => "my::unionType",
      xml_nodeName => {
          "nodename" => "TypeA",
          "somenode" => "TypeB",
      };

It is an error if types are repeated in the map. The empty string can be used as a node name for text nodes, otherwise they are not allowed.

This case is made of win because no extra attributes are required to help the marshaller; the type of the data is enough.

An example of this in practice;

  subtype "My::XML::Language::choice0"
     => as join("|", map { "My::XML::Language::$_" }
                  qw( CD Store Person ) );

  has_element 'things' =>
     is => "rw",
     isa => "ArrayRef[My::XML::Language::choice0]",
     xml_nodeName => +{ map {( lc($_) => $_ )} qw(CD Store Person) },
     ;

This would allow the enclosing class to have a 'things' property, which contains all of the elements at that point, which can be cd, store or person elements.

In this case, it may be preferrable to pass a role name as the element type, and let this module evaluate construct the xml_nodeName map itself.

more types than element names

This happens when some of the types have different XML namespaces; the type of the node is indicated by the namespace prefix.

In this case, you must supply a namespace map, too.

  has_element 'message' =>
      is => "rw",
      isa => "my::unionType",
      xml_nodeName => {
          "trumpery:nodename" => "TypeA",
          "rubble:nodename" => "TypeB",
          "claptrap:nodename" => "TypeC",
      },
      xml_nodeName_prefix => {
          "trumpery" => "uri:type:A",
          "rubble" => "uri:type:B",
          "claptrap" => "uri:type:C",
      },
      ;

FIXME: this is currently unimplemented.

more element names than types

This can happen for two reasons: one is that the schema that this element definition comes from is re-using types. Another is that you are just accepting XML without validation (eg, XMLSchema's processContents="skip" property). In this case, there needs to be another attribute which records the names of the node.

  has_element 'message' =>
      is => "rw",
      isa => "my::unionType",
      xml_nodeName => {
          "nodename" => "TypeA",
          "somenode" => "TypeB",
          "someother" => "TypeB",
      },
      xml_nodeName_attr => "message_name",
      ;

If any node name is allowed, then you can simply pass in * as an xml_nodeName value.

more namespaces than types

The principle use of this is PRANG::XMLSchema::Whatever, which converts arbitrarily namespaced XML into objects. In this case, another attribute is needed, to record the XML namespaces of the elements.

  has 'nodenames' =>
        is => "rw",
        isa => "ArrayRef[Maybe[Str]]",
        ;

  has 'nodenames_xmlns' =>
        is => "rw",
        isa => "ArrayRef[Maybe[Str]]",
        ;

  has_element 'contents' =>
      is => "rw",
      isa => "ArrayRef[PRANG::XMLSchema::Whatever|Str]",
      xml_nodeName => { "" => "Str", "*" => "PRANG::XMLSchema::Whatever" },
      xml_nodeName_attr => "nodenames",
      xmlns => "*",
      xmlns_attr => "nodenames_xmlns",
      ;

FIXME: this is currently unimplemented.

unknown/extensible element names and types

These are indicated by specifying a role. At the time that the PRANG::Graph::Node is built for the attribute, the currently available implementors of these roles are checked, which must all implement PRANG::Graph.

They Treated as if there is an xml_nodeName entry for the class, from the root_element value for the class to the type. This allows writing extensible schemas.

SEE ALSO

PRANG::Graph::Meta::Attr, PRANG::Graph::Meta::Element, PRANG::Graph::Node

AUTHOR AND LICENCE

Development commissioned by NZ Registry Services, and carried out by Catalyst IT - http://www.catalyst.net.nz/

Copyright 2009, 2010, NZ Registry Services. This module is licensed under the Artistic License v2.0, which permits relicensing under other Free Software licenses.