Heiko Jansen wrote:
> The MODS specification has seen a number of revisions already where elements 
> and attributes have been added, removed, declared (no longer) mandatory, or 
> where the list of allowed values has been changed. My goal would be to 
> support 
> a number of older versions and all future versions with minimum effort.
>
> Writing the perl classes "by hand" I'd require a version attribute and write 
> custom code in every class / accessor to check for (version-specific) correct 
> data.
>
> But how am I supposed to tackle this problem using Moose/PRANG? Changing the 
> 'isa' property of Moose attributes at runtime based on the value of another 
> attribute (holding the MODS version number) seems weird. 
> I'd be glad if someone could describe possible solutions and/or point to 
> existing code solving this kind of problem.
>   

Well, there's been a couple of solutions suggested already. The
parameterized approach sounds like it's on the right track, but I don't
think it really is quite right. Type parameters should be more types,
not values IMHO. What I do think is right about it is that each version
of XML schema ends up as a different and independent type hierarchy.

Chris' approach of simply using one set of classes and optional
attributes might be more pragmatic, if it can work out that way. This is
where you just have one super-schema which can parse any version, and
perhaps can write out one version only.

This is certainly a problem that I haven't tackled yet with PRANG, but
this is how I'd probably approach it if a single-class approach didn't work.

First, I'd probably still keep code that represents the most current
standard hand-written. Then, I'd make a visitor helper, which walks over
the Moose metamodel, and constructs an identical set of classes, but for
the next older version.

eg say you called one XML::MODS, then the next older version might be
XML::MODS::v3_3; instead of listing all of the properties explicitly,
you use metaprogramming to pull those all from the newer type, but with
changes as appropriate.

For instance, say you wrote a role which supplied the "clone_mutant"
method. It is designed to be applied to any PRANG-mixed Moose metaclass,
and what it does is a recursive clone of all the PRANG attributes, but
along the way calls callbacks which are allowed to supply extra
parameters to the eventual ->clone() call, skip objects, etc.

That might look like this, imagining that there was only one change
between 3.4 and 3.3 of the standard:

package XML::MODS::v3_3;
use XML::MODS;
use PRANG::Handwave::MutatingCloner;
# apply the MutatingCloner role to the XML::MODS metaclass
PRANG::Handwave::Cloner->setup("XML::MODS");

# do the clone
$newer_meta->clone_mutant(
mutate_class => sub {
my $metaclass = shift;
my $new_name = $metaclass->name;
$new_name =~ s{XML::MODS::}{XML::MODS::v3_3};
return (name => $new_name); # extra clone args
},
mutate_attr => sub {
my $metaattr = shift;
if ($meta->name eq "supplied") {
return "SKIP"; # magic return value
}
else {
return (); # pass through as-is
}
},
#
);

As new standards are introduced, the transforms layer and layer. You can
also coerce objects between versions by writing rules in each class,
possibly for each direction.

I can see that being slightly easier to manage than an approach where
you try and generate all of the meta-objects for all of the classes
programmatically. But that's essentially what Parametric roles are, so
if you can get that to work and it's simpler than the above, then great.

One of the nice things about having a metamodel is that you can use
approaches like this to deal with your classes - you don't have to write
lots of subtly different classes. Classes are objects, so we should be
able to use the same tricks we do with objects, on Classes.

There is also the XML Schema-driven approach. In principle, it should be
possible to write a set of PRANG classes for the XML Schema language
itself, and transform those to new PRANG classes. Then, you could load
schemas for particular versions in, transform them to Perl classes, do
the load/validate, and tackle the transformation between versions some
other way. But that's a lot of work and who knows what hurdles lie down
that path.

Finally, perhaps PRANG itself could have some direct support for some
limited types of schema versioning. What I'm thinking of is that the
'has_attr', 'has_element' etc would have another property which
describes the range of schema versions to which this property applies.
Then, the effective version being parsed is tracked in the Context
object which is passed around during parsing and used to filter the
effective attributes for marshalling. This would be very simple, perhaps
the simplest approach to write and to understand. However I have two
misgivings with this approach;

* changes other than "this attribute was dropped/added" or "this element
is now allowed/prohibited here" would each represent their own API
design challenge. This is a problem that the mutating clone above would
not suffer from; any property can be changed just fine.
* I just worry that it's muddling what are actually different types
together into one, so you don't have formal points to coerce between
versions.

Anyway, I hope this helps and feel free to ask more questions.

Sam

Reply via email to