my $schema = XML::Compile::Schema->new(...); my $code = $schema->compile(WRITER => ...);
The translator understands schemas, but does not encode that into actions. This module implements those actions to translate from a (nested) Perl HASH structure onto XML.
Complex elements can define any
(element) and anyAttribute
components,
with unpredictable content. In this case, you are quite on your own in
processing those constructs. The use of both schema components should
be avoided: please specify your data-structures explicit by clean type
extensions.
The procedure for the XmlWriter is simple: add key-value pairs to your
hash, in which the value is a fully prepared XML::LibXML::Attr
or XML::LibXML::Element. The keys have the form {namespace}type
.
The namespace component is important, because only spec conformant
namespaces will be used. The elements and attributes are added in
random order.
use XML::Compile::Util qw/pack_type/; my $attr = $doc->createAttributeNS($somens, $sometype, 42); my $h = { a => 12 # normal element or attribute , "{$somens}$sometype" => $attr # anyAttribute , pack_type($somens, $mytype) => $attr # nicer };
[Available since 0.79]
ComplexType and ComplexContent components can be declared with the
<mixed="true"
> attribute.
XML::Compile does not have a way to express these mixtures of information and text as Perl data-structures; the only way you can use those to the full extend, is by juggling with XML::LibXML nodes yourself.
You may provide a XML::LibXML::Element, which is complete, or a HASH which contains attributes values and an XML node with key '_'. When '_' contains a string, it will be translated into an XML text node.
All writer hooks behave differently. Be warned that the user values can be a SCALAR or a HASH, dependent on the type. You can intervene on higher data-structure levels, to repair lower levels, if you want to.
The before
hook gives you the opportunity to fix the user
supplied data structure. The XML generator will complain about
missing, superfluous, and erroneous values which you probably
want to avoid.
The before
hook returns new values. Just must not interfere
with the user provided data. When undef
is returned, the whole
node will be cancelled.
On the moment, the only predefined before
hook is PRINT_PATH
.
sub before_on_complex($$$) { my ($doc, $values, $path) = @_; my %copy = %$values; $copy{extra} = 42; delete $copy{superfluous}; $copy{count} =~ s/\D//g; # only digits \%copy; }
sub before_on_simple($$$) { my ($doc, $value, $path) = @_; $value *= 100; # convert euro to euro-cents }
sub before_on_object($$$) { my ($doc, $obj, $path) = @_; +{ name => $obj->name , price => $obj->euro , currency => 'EUR' }; }
Only one replace
hook can be defined. It must return a
XML::LibXML::Node or undef
. The hook must use the
XML::LibXML::Document
node (which is provided as first
argument) to create a node.
On the moment, the only predefined replace
hook is SKIP
.
sub replace($$$) { my ($doc, $values, $path, $tag) = @_ my $node = $doc->createElement($tag); $node->appendText($values->{text}); $node; }
The after
hooks, will each get a chance to modify the
produced XML node, for instance to encapsulate it. Each time,
the new XML node has to be returned.
On the moment, the only predefined after
hook is PRINT_PATH
.
sub after($$$$) { my ($doc, $node, $path) = @_; my $child = $doc->createAttributeNS($myns, earth => 42); $node->addChild($child); $node; }
When a schema makes a mess out of things, we can fix that with hooks. Also, when you need things that XML::Compile does not support (yet).
{ my $text; sub before($$$) { my ($doc, $values, $path) = @_; my %copy = %$values; $text = delete $copy{text}; \%copy; } sub after($$$) { my ($doc, $node, $path) = @_; $node->addChild($doc->createTextNode($text)); $node; } $schema->addHook ( type => 'mixed' , before => \&before , after => \&after ); }
In a typemap, a relation between an XML element type and a Perl class (or object) is made. Each translator back-end will implement this a little differently. This section is about how the writer handles typemaps.
Usually, an XML type will be mapped on a Perl class. The Perl class
implements the toXML
method as serializer. That method should
either return a data structure which fits that of the specific type,
or an XML::LibXML::Element.
When translating the data-structure to XML, the process may encounter objects. Only if these objects appear at locations where a typemap is defined, they are treated smartly. When some other data than an objects is found on a location which has a typemap definition, it will be used as such; objects are optional.
The object (of present) will be checked to be of the expected class.
It will be a compile-time error when the class does not implement the
toXML
method.
$schema->typemap($sometype => 'My::Perl::Class'); package My::Perl::Class; ... sub toXML { my ($self, $xmltype, $doc) = @_; ... { a => { b => 42 }, c => 'aaa' }; }
The $self
is the object found in the data-structure provided by the
user. $doc
can be used to create your own XML::LibXML::Element.
It is possible to use the same object on locations for different types:
in this case, the toXML method can distiguisk what kind of data to return
based on the $xmltype
.
In this case, some helper object arranges the serialization of the provided object. This is especially useful when the provided object does not have the toXML implemented, for instance because it is an implementation not under your control. The helper object works like an interface.
my $object = My::Perl::Class->new(...); $schema->typemap($sometype => $object); package My::Perl::Class; sub toXML { my ($self, $object, $xmltype, $doc) = @_; ... }
The toXML will only be called then $object
is blessed. If you wish
to have access to some data-type in any case, then use a simple "before"
hook.
The light version of an interface object uses CODE references. The CODE reference is only called if a blessed value is found in the user provided data. It cannot be checked automatically whether it is blessed according to the expectation.
$schema->typemap($t1 => \&myhandler); sub myhandler { my ($backend, $object, $xmltype, $doc) = @_; ... }
The typemap for the writer is implemented as a 'before' hook: just before the writer wants to start.
Of course, it could have been implemented by accepting an object anywhere in the input data. However, this would mean that all the (many) internal parser constructs would need to be extended. That would slow-down the writer considerably.