1 package SQL::Translator::XMI::Parser;
7 SQL::Translator::XMI::Parser- Perl class for blah blah blah
13 our $VERSION = "0.01";
16 use XML::XPath::XMLParser;
17 use Storable qw/dclone/;
20 #=============================================================================
22 # Describes the 2 xmi formats 1.2 and 1.0. Neither is complete!
24 # NB The names of the data keys MUST be the same for both specs so the
25 # data structures returned are the same.
27 # There is currently no way to set the data key name for attrib_data, it just
28 # uses the attribute name from the XMI. This isn't a problem at the moment as
29 # xmi1.0 names all these things with tags so we don't need the attrib data!
30 # Also use of names seems to be consistant between the versions.
35 my $spec12 = $SPECS->{"1.2"} = {};
40 default_path => '//UML:Class[@xmi.id]',
42 [qw/name visibility isSpecification isRoot isLeaf isAbstract isActive/],
46 path => 'xmiDeref(UML:ModelElement.stereotype/UML:Stereotype)/@name',
53 # name in data returned
54 path => "UML:Classifier.feature/UML:Attribute",
56 # Points to class in spec. get_attributes() called to parse it and
57 # adds filter_attributes to the args for get_classes().
59 # How many we get back. Use '1' for 1 and '*' for lots.
60 # TODO If not set then decide depening on the return?
64 path => "UML:Classifier.feature/UML:Operation",
69 name => "taggedValues",
70 path => 'UML:ModelElement.taggedValue/UML:TaggedValue',
71 class => "taggedValue",
73 # Nice if we could say that the list should me folded into a hash
74 # on the name key. type=>"hash", hash_key=>"name" or something!
79 $spec12->{taggedValue} = {
80 name => "taggedValue",
81 plural => "taggedValues",
82 default_path => '//UML:TaggedValue[@xmi.id]',
83 attrib_data => [qw/isSpecification/],
87 path => 'UML:TaggedValue.dataValue/text()',
91 path => 'xmiDeref(UML:TaggedValue.type/UML:TagDefinition)/@name',
96 $spec12->{attribute} = {
98 plural => "attributes",
99 default_path => '//UML:Classifier.feature/UML:Attribute[@xmi.id]',
101 [qw/name visibility isSpecification ownerScope/],
104 name => "stereotype",
105 path => 'xmiDeref(UML:ModelElement.stereotype/UML:Stereotype)/@name',
110 path => 'xmiDeref(UML:StructuralFeature.type/UML:DataType)/@name',
113 name => "initialValue",
114 path => 'UML:Attribute.initialValue/UML:Expression/@body',
119 name => "taggedValues",
120 path => 'UML:ModelElement.taggedValue/UML:TaggedValue',
121 class => "taggedValue",
127 $spec12->{operation} = {
129 plural => "operations",
130 default_path => '//UML:Classifier.feature/UML:Operation[@xmi.id]',
132 [qw/name visibility isSpecification ownerScope isQuery
133 concurrency isRoot isLeaf isAbstract/],
136 name => "stereotype",
137 path => 'xmiDeref(UML:ModelElement.stereotype/UML:Stereotype)/@name',
143 name => "parameters",
144 path => "UML:BehavioralFeature.parameter/UML:Parameter",
145 class => "parameter",
149 name => "taggedValues",
150 path => 'UML:ModelElement.taggedValue/UML:TaggedValue',
151 class => "taggedValue",
157 $spec12->{parameter} = {
159 plural => "parameters",
160 default_path => '//UML:BehavioralFeature.parameter/UML:Parameter[@xmi.id]',
161 attrib_data => [qw/name isSpecification kind/],
164 name => "stereotype",
165 path => 'xmiDeref(UML:ModelElement.stereotype/UML:Stereotype)/@name',
170 path => 'xmiDeref(UML:StructuralFeature.type/UML:DataType)/@name',
175 #-----------------------------------------------------------------------------
177 my $spec10 = $SPECS->{"1.0"} = {};
182 default_path => '//Foundation.Core.Class[@xmi.id]',
187 path => 'Foundation.Core.ModelElement.name/text()',
190 name => "visibility",
191 path => 'Foundation.Core.ModelElement.visibility/@xmi.value',
194 name => "isSpecification",
195 path => 'Foundation.Core.ModelElement.isSpecification/@xmi.value',
199 path => 'Foundation.Core.GeneralizableElement.isRoot/@xmi.value',
203 path => 'Foundation.Core.GeneralizableElement.isLeaf/@xmi.value',
206 name => "isAbstract",
207 path => 'Foundation.Core.GeneralizableElement.isAbstract/@xmi.value',
211 path => 'Foundation.Core.Class.isActive/@xmi.value',
216 name => "attributes",
218 'Foundation.Core.Classifier.feature/Foundation.Core.Attribute',
219 class => "attribute",
223 # name => "operations",
224 # path => "UML:Classifier.feature/UML:Operation",
225 # class => "operation",
226 # multiplicity => "*",
231 $spec10->{attribute} = {
233 plural => "attributes",
234 default_path => '//Foundation.Core.Attribute[@xmi.id]',
238 path => 'Foundation.Core.ModelElement.name/text()',
241 name => "visibility",
242 path => 'Foundation.Core.ModelElement.visibility/@xmi.value',
245 name => "isSpecification",
246 path => 'Foundation.Core.ModelElement.isSpecification/@xmi.value',
249 name => "ownerScope",
250 path => 'Foundation.Core.Feature.ownerScope/@xmi.value',
253 name => "initialValue",
254 path => 'Foundation.Core.Attribute.initialValue/Foundation.Data_Types.Expression/Foundation.Data_Types.Expression.body/text()',
257 # name => "datatype",
258 # path => 'xmiDeref(Foundation.Core.StructuralFeature.type/Foundation.Core.Classifier)/Foundation.Core.DataType/Foundation.Core.ModelElement.name/text()',
263 #=============================================================================
269 # The parser supports xmi1.0 and xmi1.2 based on the specs above. At new() time
270 # the version is read from the XMI tag and picks out a spec e.g.
271 # $SPECS->{"1.2"} and feeds it to mk_gets() which returns a hash ref of subs
272 # (think strategy pattern), one for each entry in the specs hash. This is held
273 # in $self->{xmi_get_}.
275 # When the class is use'd it sets dispatch methods with
276 # mk_get_dispatch() that return the call using the corresponding sub in
277 # $self->{xmi_get_}. e.g.
279 # sub get_classes { $_[0]->{xmi_get_}{classes}->(@_); }
280 # sub get_attributes { $_[0]->{xmi_get_}{attributes}->(@_); }
281 # sub get_classes { $_[0]->{xmi_get_}{classes}->(@_); }
283 # The names for the data keys in the specs must match up so that we get the
284 # same data structure for each version.
288 foreach ( values %$SPECS ) { init_specs($_) };
291 # Build lookups etc. Its important that each spec item becomes self contained
292 # so we can build good closures, therefore we do all the lookups 1st.
296 foreach my $spec ( values %$specs ) {
297 # Look up for kids get method
298 foreach ( @{$spec->{kids}} ) {
299 $_->{get_method} = "get_".$specs->{$_->{class}}{plural};
302 # Add xmi.id ti all specs. Everything we want at the moment (in both
303 # versions) has an id. The tags that don't seem to be used for
305 my $attrib_data = $spec->{attrib_data} ||= [];
306 push @$attrib_data, "xmi.id";
311 # Generate get_* subs to dispach the calls to the subs held in $me->{xmi_get_}
312 sub mk_get_dispatch {
313 foreach ( values %{$SPECS->{"1.2"}} ) {
314 my $name = $_->{plural};
318 *{"get_$name"} = sub {
320 #$me->{xmi_get_}{$name}->($me,@_);
321 $_[0]->{xmi_get_}{$name}->(@_);
328 my $class = ref($proto) || $proto;
332 # Create the XML::XPath object
333 # TODO Docs recommend we only use 1 XPath object per application
335 foreach (qw/filename xml ioref/) {
337 $xp = XML::XPath->new( $_ => $args{$_});
338 $xp->set_namespace("UML", "org.omg.xmi.namespace.UML");
342 $me = { xml_xpath => $xp };
344 # Work out the version of XMI we have and generate the get subs to parse it
345 my $xmiv = $args{xmi_version}
346 || "".$xp->findvalue('/XMI/@xmi.version')
347 || die "Can't find XMI version";
348 $me->{xmi_get_} = mk_gets($SPECS->{$xmiv});
350 return bless $me, $class;
354 # Returns hashref of get subs from set of specs e.g. $SPECS->{"1.2"}
357 # * Add a memoize so we don't keep regenerating the subs for every use.
361 foreach ( values %$specs ) {
362 # Clone from specs and sort out the lookups into it so we get a
363 # self contained spec to use as a proper closure.
364 my $spec = dclone($_);
367 $gets->{$spec->{plural}} = mk_get($spec);
375 # Generates and returns a get_ sub for the spec given. e.g. give it
376 # $SPECS->{"1.2"}->{classes} to get the code for xmi 1.2 get_classes. So, if
377 # you want to change how the get methods work do it here!
379 # The get methods made have the args described in the docs and 2 private args
380 # used internally, to call other get methods from paths in the spec.
382 # NB: DO NOT use publicly as you will break the version independance. e.g. When
383 # using _xpath you need to know which version of XMI to use. This is handled by
384 # the use of different paths in the specs.
386 # _context => The context node to use, if not given starts from root.
388 # _xpath => The xpath to use for finding stuff.
394 # get_* closure using $spec
396 my ($me, %args) = @_;
397 my $xp = delete $args{_context} || $me->{xml_xpath};
400 my $xpath = $args{_xpath} ||= $spec->{default_path};
401 #warn "Searching for $spec->{plural} using:$xpath\n";
403 my @nodes = $xp->findnodes($xpath);
404 return unless @nodes;
406 for my $node (@nodes) {
408 # my $thing = { xpNode => $node };
410 # Get the Tag attributes
411 foreach ( @{$spec->{attrib_data}} ) {
412 $thing->{$_} = $node->getAttribute($_);
416 foreach ( @{$spec->{path_data}} ) {
417 #warn "Searching for $spec->{plural} - $_->{name} using:$_->{path}\n";
418 my @nodes = $node->findnodes($_->{path});
419 $thing->{$_->{name}} = @nodes ? $nodes[0]->getData
420 : (exists $_->{default} ? $_->{default} : undef);
423 # Run any filters set
425 # Should we do this after the kids as we may want to test them?
426 # e.g. test for number of attribs
427 if ( my $filter = $args{filter} ) {
429 next unless $filter->($thing);
434 foreach ( @{$spec->{kids}} ) {
436 my $meth = $_->{get_method};
437 $data = $me->$meth( _context => $node, _xpath => $_->{path},
438 filter => $args{"filter_$_->{name}"} );
440 if ( $_->{multiplicity} eq "1" ) {
441 $thing->{$_->{name}} = shift @$data;
444 $thing->{$_->{name}} = $data || [];
448 push @$things, $thing;
450 return wantarray ? @$things : $things;
455 1; #===========================================================================
458 package XML::XPath::Function;
461 # May need to look at doing deref on all paths just to be on the safe side!
463 # Will also want some caching as these calls are expensive as the whole doc
464 # is used but the same ref will likley be requested lots of times.
468 my ($node, @params) = @_;
470 die "xmiDeref() function takes one or no parameters\n";
473 my $nodeset = shift(@params);
474 return $nodeset unless $nodeset->size;
475 $node = $nodeset->get_node(1);
477 die "xmiDeref() needs an Element node."
478 unless $node->isa("XML::XPath::Node::Element");
480 my $id = $node->getAttribute("xmi.idref") or return $node;
481 return $node->getRootNode->find('//*[@xmi.id="'.$id.'"]');
492 use SQL::Translator::XMI::Parser;
493 my $xmip = SQL::Translator::XMI::Parser->new( xml => $xml );
494 my $classes = $xmip->get_classes();
498 Parses XMI files (XML version of UML diagrams) to perl data structures and
499 provides hooks to filter the data down to what you want.
503 Pass in name/value arg of either C<filename>, C<xml> or C<ioref> for the XMI
504 data you want to parse.
506 The version of XMI to use either 1.0 or 1.2 is worked out from the file. You
507 can also use a C<xmi_version> arg to set it explicitley.
511 Doc below is for classes method, all the other calls follow this form.
513 =head2 get_classes( ARGS )
515 ARGS - Name/Value list of args.
517 filter => A sub to filter the node to see if we want it. Has the nodes data,
518 before kids are added, referenced to $_. Should return true if you
519 want it, false otherwise.
521 e.g. To find only classes with a "Foo" stereotype.
523 filter => sub { return $_->{stereotype} eq "Foo"; }
525 filter_attributes => A filter sub to pass onto get_attributes.
527 filter_operations => A filter sub to pass onto get_operations.
529 Returns a perl data structure including all the kids. e.g.
533 'visibility' => 'public',
534 'isActive' => 'false',
535 'isAbstract' => 'false',
536 'isSpecification' => 'false',
537 'stereotype' => 'Table',
543 'stereotype' => 'PK',
545 'ownerScope' => 'instance',
546 'visibility' => 'public',
547 'initialValue' => undef,
548 'isSpecification' => 'false',
553 'datatype' => 'varchar'
554 'ownerScope' => 'instance',
555 'visibility' => 'public',
556 'initialValue' => '',
557 'isSpecification' => 'false',
563 'isQuery' => 'false',
564 'ownerScope' => 'instance',
565 'visibility' => 'public',
566 'isSpecification' => 'false',
568 'isAbstract' => 'false',
571 'concurrency' => 'sequential'
575 'isSpecification' => 'false',
582 'isSpecification' => 'false',
589 'isSpecification' => 'false',
599 =head1 XMI XPath Functions
601 The Parser adds the following extra XPath functions for use in the SPECS.
605 Deals with xmi.id/xmi.idref pairs of attributes. You give it an
606 xPath e.g 'UML:ModelElement.stereotype/UML:stereotype' if the the
607 tag it points at has an xmi.idref it looks up the tag with that
608 xmi.id and returns it.
610 If it doesn't have an xmi.id, the path is returned as normal.
614 <UML:ModelElement.stereotype>
615 <UML:Stereotype xmi.idref = 'stTable'/>
616 </UML:ModelElement.stereotype>
618 <UML:Stereotype xmi.id='stTable' name='Table' visibility='public'
619 isAbstract='false' isSpecification='false' isRoot='false' isLeaf='false'>
620 <UML:Stereotype.baseClass>Class</UML:Stereotype.baseClass>
623 Using xmideref(//UML:ModelElement.stereotype/UML:stereotype) would return the
624 <UML:Stereotype xmi.id = '3b4b1e:f762a35f6b:-7fb6' ...> tag.
626 Using xmideref(//UML:ModelElement.stereotype/UML:stereotype)/@name would give
637 =head1 VERSION HISTORY
641 grommit <mark.addison@itn.co.uk>
645 This package is free software and is provided "as is" without express or
646 implied warranty. It may be used, redistributed and/or modified under the
649 a) the Perl Artistic License.
651 See F<http://www.perl.com/perl/misc/Artistic.html>
653 b) the terms of the GNU General Public License as published by the Free Software
654 Foundation; either version 1, or (at your option) any later version.