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