Class
NAME
LibXML::Class ā general purpose XML de-/serialization for Raku
SYNOPSIS
Simple Case
use LibXML::Class;
class Record1 is xml-element {
has Int:D $.id is required;
has Str $.description;
has %.meta;
}
my $rec = Record1.new(:id(1000), :description("test me"), :meta{ key1 => Ļ, key2 => "some info" });
say $rec.to-xml.Str(:format);
This would result in:
<?xml version="1.0" encoding="UTF-8"?>
<Record1 id="1000" description="test me">
<meta>
<key1>3.141592653589793e+00</key1>
<key2>some info</key2>
</meta>
</Record1>
More Complex Case
use LibXML::Class;
use Test::Async;
class Record2 is xml-element( :ns<http://my.namespace> ) {
has Int:D $.id is required is xml-attribute;
has Str:D $.description is required is xml-attribute;
has Str $.comment is xml-element(:value-attr<text>, :ns( :extra ) );
has Real:D $.amount is required is xml-element;
has DateTime $.when; # Not part of XML
}
class METAEntry is xml-element {
has Str:D $.key is required;
has Str:D $.value is required;
}
role META is xml-element {
has METAEntry @.meta-entry is xml-element('entry', :container<meta>);
}
class Registry is xml-element('registry', :ns( :extra<http://additional.namespace> )) does META {
has Record2 @.record is xml-element;
}
my $root = Registry.new;
$root.record.append:
Record2.new( :id(1001),
:description("item1"),
:comment("here comes a comment"),
:amount(42.12) ),
Record2.new( :id(1002),
:description("item2"),
:amount(0) );
$root.meta-entry.append:
METAEntry.new(:key<version>, :value<1.1a>),
METAEntry.new(:key<encoding>, :value<utf-8>);
my $xml = $root.to-xml;
diag $xml.Str(:format);
my $root-copy = Registry.from-xml: $xml.Str;
cmp-deeply $root-copy, $root, "both are the same";
The output of this would be like:
# <?xml version="1.0" encoding="UTF-8"?> # <registry xmlns:extra="http://additional.namespace"> # <record xmlns="http://my.namespace" id="1001" description="item1"> # <extra:comment text="here comes a comment"/> # <amount>42.12</amount> # </record> # <record xmlns="http://my.namespace" id="1002" description="item2"> # <amount>0</amount> # </record> # <meta> # <entry key="version" value="1.1a"/> # <entry key="encoding" value="utf-8"/> # </meta> # </registry> # ok 1 - both are the same 1..1
DESCRIPTION
Primary documentation for this module can be found in LibXML::Class::Manual. Here we would only focus on a couple of technical details.
A Quick Note On Deserialization
Whereas serialization is not that complex, after all, deserialization proves to be much trickier, espcially when it comes to implementing lazy operations. The information in this section would only prove useful if you plan to somehow "participate" in this party.
Deserialization is split into two major steps. The first one is building a profile for object constructor. At this step
we only know our outer deserialization context (think of it as if we know about our parent and how it is deserializing),
the current element we're about to deserialize, and the target class, an instance of which will represent the element
in Raku. When profile building is done (i.e. we have a %profile hash with all necessary keys) we instantiate the
class using its xml-create method:
TargetClass.xml-create: |%profile
The second step is not clearly localized in time as it might happen when the profile is being built, or later (up to never at all) if lazy operations are in effect. Either way, this step is about the actual deserialization from the XML source. It takes place individually for every entity like attribute of sequence item.
Implementation Detail Type Objects
Internally we use a couple of classes and roles that are used to produce an xml-element out of user's type object.
These must not normally be of any concern. If an XMLized object is to be distinguished from other objects one can
test it against LibXML::Class::XML role.
Yet, on a rare occasion it might be needed to distinguish an XML sequence from a non-sequence. For this purpose it is
recommended to rely on typechecking against LibXML::Class::XMLSequential role alone. Otherwise no warranties would
be given with regard to what parents and what roles an xml-element is built of.
Attributes Of xml-element Class
A few attributes that xml-element trait would introduce onto your XMLized type object that might be useful on
occasion.
Note that attributes listed here are actually private. Public access to them is provided via plain old methods.
Int:D$.xml-idUnique ID of this deserialization. Cloning creates a new ID.
LibXML::Class::Document$.xml-documentA document object. May remain undefined for manually created instances. For deserializations this is the glue which binds them all together and to the source
LibXML::Document.LibXML::Element$.xml-backingThe XML element that's been deserialized to produce this instance.
$.xml-dctxReturns deserialization context instance. When lazy operations are disabled or all lazy elements have been deserialized, resulting in dropping of the context, attribute value is undefined.
$.xml-unusedA list of LibXML AST nodes representing XML nodes not used in deserialization of this instance.
$.xml-unique-keyShortcut to
$.xml-backing.unique-key.
Dynamic Variables
The serialization and deserialization processes are, apparently, very much context-dependent. Therefore a couple of dynamic variables are used to maintain and provide current context state.
$*LIBXML-CLASS-CONFIGCurrent context configuration instance.
$*LIBXML-CLASS-CTXContext object. Currently serialization doesn't require specific context, but deserialization is using this variable. Deserialization context API would be documented later in this document.
$*LIBXML-CLASS-ELEMENTThe
LibXML::Elementinstance currently being de-/serialized. Might be useful for a custom de-/serializer code.%*LIBXML-CLASS-OVERRIDEDon't toy with this one unless you do know what are you doiing!
Only used by deserialization. Content of this hash is used to override nested context prefix definitions. Whatever prefixes are proivided with this dynamic would override anything
xml-from-elementmethod finds in its outer deserialization context, or in the defaults.
Methods Of xml-element Class
Methods available on an xml-element-traited class.
proto method to-xml(|)multi method to-xml(Str :$name, Str :ns(:namespace(:$xml-default-ns)), Str :prefix(:$xml-default-ns-pfx), :$config)multi method to-xml(LibXML::Document:D $doc, Str :$name, Str :ns(:namespace(:$xml-default-ns)), Str :prefix(:$xml-default-ns-pfx), :$config)multi method to-xml(LibXML::Element:D $elem, Str :ns(:namespace(:$xml-default-ns)), Str :prefix(:$xml-default-ns-pfx), :$config)$namewould override the target element name; not used when$elemis passed in$xml-default-nswould force the default namespace of the XML element$xml-default-ns-pfxwould force the default namespace prefix$configcan be either an instance of LibXML::Class::Config, or a hash of config parametersproto method from-xml(|)multi method from-xml(IO:D $source, *%nameds)multi method from-xml(Str:D $source-xml, *%nameds)multi method from-xml(Str:D $source-xml, LibXML::Class::Document:D :$document!, *%nameds)multi method from-xml(LibXML::Document:D $libxml-document, LibXML::Class::Config:D :$config!, *%nameds)multi method from-xml(LibXML::Element:D $elem, LibXML::Class::Document:D $document?, Str :$name, Str :ns(:namespace(:$xml-default-ns)), Str :prefix(:$xml-default-ns-pfx), :$config, :%user-profile)$nameoverrides the expected element name when the element we deserialize from has a name different from invocant's default.$xml-default-ns,$xml-default-ns-pfxoverride the namespace parameters if we know that the ones of the source element are different from class defaults.$configcan be either an instance of LibXML::Class::Config, or a hash of config parameters%user-profileis used as additional named arguments for thenewconstructor method of not the invocant class alone but for all deserializations of its child elements.method xml-name()Returns element name of the invocant. For a class/role it would be their default name.
method xml-class()Returns the actual XMLized class. This method provides easy access to the type object which provides all defaults if the invocant is a non-XMLized subclass.
method xml-config()Returns current context
LibXML::Class::Configinstance.method xml-has-lazies()Returns True if any lazy entities are undeserialized yet.
method xml-serialize-stages(),method xml-profile-stages()De-/serialization can be a multi-stage process. The simplest case is for plain XMLized classes where there is just one stage. XML sequence requires two stages to do it. These methods must return lists of method names implementing each stage. If you wish to add some custom processing then the best solution would be to inject your own stage to a list by overriding a method or both.
Note that the second method is not named
xml-deserialize-stagesbecause this is not exactly about deserialization as such.method xml-new-dctx(*%profile)Returns a new deserialization context using constructor profile in
%profile.method xml-create(*%profile)This method is used by
LibXML::Classto create and new instance of anxml-elemntclass when it's done building a constructor method%profile. The default version of it simply redirect to the standard methodnew, but LibXML::Class::Config is overriding it for manually XMLized classes to produce an instance of the original, non-XMLized, class as the result of deserialization.method xml-add-deseializarion(LibXML::Node:D $node, Mu $representation)Registers
$representationas a deserialization for theLibXML$node. This makes the representation searchable withLibXML::Class::Documentfindnodesorfind-deserializationsmethods.proto method xml-deserializations(|)multi method xml-deserializations(LibXML::Node:D $node)multi method xml-deserializations(Str:D $unique-key)proto method xml-has-deserializations(|)multi method xml-has-deserializations(LibXML::Node:D $node)multi method xml-has-deserializations(Str:D $unique-key)proto method xml-remove-deserialization(|)multi method xml-remove-deserialization(LibXML::Class::XMLObject:D $representation)multi method xml-remove-deserialization(Str:D $unique-key, Mu $representation)method xml-findnodes(|c)This method is equivalent to LibXML::Class::Document
findnodesmethod expect that the undelying LibXML method of the same name is invoked on the deserialization's backing element in$.xml-backing. If for any reason there is no backing the method returns a Failure withLibXML::Class::X::Deserialize::NoBackingexception.method xml-lazy-deserialize-context(&code)This methods invokes a code object in
&codewithin dynamic context where$*LIBXML-CLASS-CTXand$*LIBXML-CLASS-CONFIGare set. When the&codefinishes the method checks if all lazies have been deserialized already and drops the deserialization context if they have.Returns the result of
&codeinvocation.LibXML::Class::X::Deserialize::NoCtxis thrown if there is no context.method xml-deserialize-attr(Str:D :$attribute!)This method implements lazy deserialization of a Raku
$attribute. It is normally invoked by AttrX::Mooish upon accessing a lazy attribute.This is a multi-dispatch method, but the other candidates are implementation detail. It is highly recommended to only override this method in a subclass (your
xml-elementclass, normally) to do some pre- or post-processing for deserialization.proto method xml-serialize-attr(LibXML::Element:D $elem, LibXML::Class::Attr::XMLish:D $descriptor)Similarly to the
xml-deserialize-attrmethod, this one is rather an implementation detail and only recommended for pre- or post-processing in a subclass.$elemis the destination element for serialization of the value in a Raku attribute.method xml-to-element(LibXML::Element:D $elem, ... --> LibXML::Element:D),method xml-from-element(LibXML::Element:D $elem, LibXML::Class::Document:D $doc, ...)Both methods are implementation details not to be invoked directly. But they're good candidates to do pre-/post-processing as these are the final destinations methods
to-xmlandfrom-xmlcall. Basically consider the latter two as API frontends only.In both cases
$elemis the LibXML representation of ourxml-elementclass-invocant.
Serializes an xml-element class. The default without a positional argument would return full LibXML::Document
instance.
With $doc or $elem positionals an LibXML::Element would be returned. The only difference is that for $doc
case the element would be created on the document provided. Otherwise the object would serialize into $elem.
The named arguments are:
Deserialize and create an instance of the class-invocant of the method.
Named arguments are mostly the same for each multi-candidate except for the ones where LibXML::Class::Document
instance is known and where $config just doesn't make sense because we have it on the document:
Returns a list of deserializations registered for the $node, or for a node with $unique-key with
xml-add-deserialization. Returns Nil if there is nothing.
Returns True if there is at least one deserialization registered for a $node or a $unique-key.
Remove a deserialization from the registry. Normally this is only to be done when it is about to be destroyed.
Methods Available For XML Sequence Types
Sequence types provide basic methods one would expect for Positional and Iterable. If a traditional method is not implemented then either it isn't compatible with XML sequences or it could be implemented in the future; perhaps, by a request.
method xml-serialize-item(LibXML::Element:D $elem, LibXML::Class::ItemDescriptor:D $desc, Mu $value)Method serializes a sequence item
$valueinto XML sequence element$elem. The method returns serialized XML element representing the$valueand adds it to the XML sequence element as a child.method xml-deserialize-item(LibXML::Element:D $elem, LibXML::Class::ItemDescriptor:D $desc, UInt:D :$index, *%nameds)This method implements lazy deserialization of XML sequence item from XML element
$elem.$indexis the element position in the sequence.Returns deserialized value.
Exports
Traits
This module exports the following traits:
is xml-elementis xml-attributeis xml-text
They're documented in LibXML::Class::Manual.
xml-I-cant
This is just a shortcut for throwing a control exception which causes user-provided de-/serializer code to give up and let the module do the job. See more details in the Manual.
Cloning
Cloning a deserialized object isn't a trivial task, especiall when lazy operations are in effect. A custom clone
method overrides Raku's default which does some preparation before calling the parent method. When done post-clone
method gets invoked. The default post-clone procedure includes registering a new deserialization for the source XML
element.
SEE ALSO
COPYRIGHT
(c) 2023, Vadim Belman <[email protected]>
LICENSE
Artistic License 2.0
See the LICENSE|../../../../LICENSE file in this distribution.