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-id
Unique ID of this deserialization. Cloning creates a new ID.
LibXML::Class::Document
$.xml-document
A 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-backing
The XML element that's been deserialized to produce this instance.
$.xml-dctx
Returns 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-unused
A list of LibXML AST nodes representing XML nodes not used in deserialization of this instance.
$.xml-unique-key
Shortcut 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-CONFIG
Current context configuration instance.
$*LIBXML-CLASS-CTX
Context 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-ELEMENT
The
LibXML::Element
instance currently being de-/serialized. Might be useful for a custom de-/serializer code.%*LIBXML-CLASS-OVERRIDE
Don'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-element
method 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)
Serializes an
xml-element
class. The default without a positional argument would return fullLibXML::Document
instance.With
$doc
or$elem
positionals anLibXML::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:
$name
would override the target element name; not used when$elem
is passed in$xml-default-ns
would force the default namespace of the XML element$xml-default-ns-pfx
would force the default namespace prefix$config
can be either an instance of LibXML::Class::Config, or a hash of config parameters
proto 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)
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:$name
overrides the expected element name when the element we deserialize from has a name different from invocant's default.$xml-default-ns
,$xml-default-ns-pfx
override the namespace parameters if we know that the ones of the source element are different from class defaults.$config
can be either an instance of LibXML::Class::Config, or a hash of config parameters%user-profile
is used as additional named arguments for thenew
constructor 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::Config
instance.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-stages
because 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::Class
to create and new instance of anxml-elemnt
class 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
$representation
as a deserialization for theLibXML
$node
. This makes the representation searchable withLibXML::Class::Document
findnodes
orfind-deserializations
methods.proto method xml-deserializations(|)
multi method xml-deserializations(LibXML::Node:D $node)
multi method xml-deserializations(Str:D $unique-key)
Returns a list of deserializations registered for the
$node
, or for a node with$unique-key
withxml-add-deserialization
. Returns Nil if there is nothing.proto method xml-has-deserializations(|)
multi method xml-has-deserializations(LibXML::Node:D $node)
multi method xml-has-deserializations(Str:D $unique-key)
Returns True if there is at least one deserialization registered for a
$node
or a$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)
Remove a deserialization from the registry. Normally this is only to be done when it is about to be destroyed.
method xml-findnodes(|c)
This method is equivalent to LibXML::Class::Document
findnodes
method 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::NoBacking
exception.method xml-lazy-deserialize-context(&code)
This methods invokes a code object in
&code
within dynamic context where$*LIBXML-CLASS-CTX
and$*LIBXML-CLASS-CONFIG
are set. When the&code
finishes the method checks if all lazies have been deserialized already and drops the deserialization context if they have.Returns the result of
&code
invocation.LibXML::Class::X::Deserialize::NoCtx
is 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-element
class, 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-attr
method, this one is rather an implementation detail and only recommended for pre- or post-processing in a subclass.$elem
is 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-xml
andfrom-xml
call. Basically consider the latter two as API frontends only.In both cases
$elem
is the LibXML representation of ourxml-element
class-invocant.
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
$value
into XML sequence element$elem
. The method returns serialized XML element representing the$value
and 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
.$index
is the element position in the sequence.Returns deserialized value.
Exports
Traits
This module exports the following traits:
is xml-element
is xml-attribute
is 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 file in this distribution.