I have a bunch of legacy modules I want to convert to being Moose-based. The modules currently have "toXML" methods, which are hand-coded using XML::LibXML.
Is there a module or technique for serializing Moose objects to XML?
I have looked at MooseX::Storage, but that handles JSON, YAML, and Storage, not XML. A google search for Moose and XML yields lots of references to XML::Rabbit, which seems to be good for parsing XML into Moose classes, but there's not a lot out there for taking Moose objects and serializing them to XML.
The 6-year-old thread at http://grokbase.com/t/perl/moose/11akp809sr/java-annotation-net-attributes-in-moose is extremely close to what I want to do, but there doesn't seem to be any followup on it.
The MooseX::Storage serializes data in JSON
by using MooseX::Storage::Format::JSON role, which is a good example for how to plug in other formats. I cannot see any roles for XML
serialization, but it is easy to write your own and the module provides a hook for it.
This minimal example shows how to write a role and consume (use) it in a class. A role is a class-like package which never gets instantiated on its own but is rather absorbed by classes that use it. It can be used by multiple classes. At the end it is shown how to hook the new role into MooseX::Storage
and thus use that in your class.
It uses XML::Dumper for serialization itself, mostly as a placeholder for custom code (if needed).
Point.pm
package Point;
use Moose;
use overload q("") => sub {
my $self = shift;
return '(' . $self->x . ', ' . $self->y . ')'
};
has 'x' => (is => 'rw', isa => 'Int', required => 1, default => 0);
has 'y' => (is => 'rw', isa => 'Int', required => 1, default => 0);
with 'SerializeXML';
sub BUILD { print "Created a Point $_[0]\n" }
__PACKAGE__->meta->make_immutable;
1;
The only specific statement here is the line with 'SerializeXML';
SerializeXML.pm
package SerializeXML;
use Moose::Role;
use XML::Dumper;
sub to_xml {
my ($self) = shift;
return XML::Dumper->new->pl2xml($self); # or use custom code
}
sub from_xml {
my ($self, $xml) = @_;
return XML::Dumper->new->xml2pl($xml);
}
no Moose::Role;
1;
The construction of an object from XML
should be done via new
, and/or as a class method.
main
use warnings;
use strict;
use Point;
my $pt = Point->new(x => 10, y => 12);
my $obj_xml = $pt->to_xml;
print "$obj_xml\n";
my $obj = $pt->from_xml($obj_xml);
print "Object via role: $obj\n";
This prints
Created a Point (10, 12)
<perldata>
<hashref blessed_package="Point" memory_address="0x1691438">
<item key="x">10</item>
<item key="y">12</item>
</hashref>
</perldata>
Object via role: (10, 12)
Methods for writing to and loading from a file can be added. But now you have a ready role which can be hooked to MooseX::Storage
, as shown below.
I don't know nor have tested how well XML::Dumper
works with Moose
. Please test and if it doesn't cut it for your needs swap calls to it with your own code that does what you need.
The remaining step is to integrate this in MooseX::Storage
, if desired.
There are two necessary small changes to make in the above code. Use role in Point
as
use MooseX::Storage;
with Storage(format => '=SerializeXML', io => 'File');
and rename to_xml
and from_xml
to freeze
and thaw
(or add these, with same code).
Then you can use store
and load
in main
in order to write to $file
and load from it
$pt->store($file);
my $pt_new = Point->load($file);
The syntax =PackageName
is for a namespace prefix other than MooseX::Storage::Format::
For roles see Moose::Manual::Roles and Moose::Cookbook::Roles:: namespace (examples).
So one of the reasons why MooseX::Storage
doesn't have a XML storage format is that as you've discovered, most people have a legacy format for XML that they need to write to. XML and Perl actually have very different ways of representing data and figuring that out the right way to translate between the two isn't actually something you can do automatically.
You're not going to find an "off the shelf" option per-se. As simbabque mentions in a comment if you have or can easily build a Schema for your data then XML::Compile
or 'XML::Pastormight work for you (
XML::Compileis better maintained). If you're willing to do some grunt work
XML::Toolkitis designed to translate an example document into a series of Moose classes that it can then later re-serialize. However I haven't touched
XML::Toolkit` in a long time so YMMV on what it needs to get running.
I think that ultimately you'll find that the simplest solution is the one you already have, hand-built toXML
methods. Moose doesn't stop you from doing that, and in fact gives you a bunch of tools to figure out how to make it more maintainable. Look at how MooseX::Storage itself is implemented to see a good path forward.