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, therefor we do all the lookups 1st.
296 foreach my $spec ( values %$specs ) {
297 foreach ( @{$spec->{kids}} ) {
298 $_->{get_method} = "get_".$specs->{$_->{class}}{plural};
304 # Generate get_* subs to dispach the calls to the subs held in $me->{xmi_get_}
305 sub mk_get_dispatch {
306 foreach ( values %{$SPECS->{"1.2"}} ) {
307 my $name = $_->{plural};
311 *{"get_$name"} = sub {
313 #$me->{xmi_get_}{$name}->($me,@_);
314 $_[0]->{xmi_get_}{$name}->(@_);
321 my $class = ref($proto) || $proto;
325 # Create the XML::XPath object
326 # TODO Docs recommend we only use 1 XPath object per application
328 foreach (qw/filename xml ioref/) {
330 $xp = XML::XPath->new( $_ => $args{$_});
331 $xp->set_namespace("UML", "org.omg.xmi.namespace.UML");
335 $me = { xml_xpath => $xp };
337 # Work out the version of XMI we have and generate the get subs to parse it
338 my $xmiv = "".$xp->findvalue('/XMI/@xmi.version')
339 or die "Can't find XMI version";
340 $me->{xmi_get_} = mk_gets($SPECS->{$xmiv});
342 return bless $me, $class;
346 # Returns hashref of get subs from set of specs e.g. $SPECS->{"1.2"}
349 # * Add a memoize so we don't keep regenerating the subs for every use.
353 foreach ( values %$specs ) {
354 # Clone from specs and sort out the lookups into it so we get a
355 # self contained spec to use as a proper closure.
356 my $spec = dclone($_);
359 $gets->{$spec->{plural}} = mk_get($spec);
367 # Generates and returns a get_ sub for the spec given. e.g. give it
368 # $SPECS->{"1.2"}->{classes} to get the code for xmi 1.2 get_classes. So, if
369 # you want to change how the get methods work do it here!
371 # The get methods made have the args described in the docs and 2 private args
372 # used internally, to call other get methods from paths in the spec.
374 # NB: DO NOT use publicly as you will break the version independance. e.g. When
375 # using _xpath you need to know which version of XMI to use. This is handled by
376 # the use of different paths in the specs.
378 # _context => The context node to use, if not given starts from root.
380 # _xpath => The xpath to use for finding stuff.
386 # get_* closure using $spec
388 my ($me, %args) = @_;
389 my $xp = delete $args{_context} || $me->{xml_xpath};
392 my $xpath = $args{_xpath} ||= $spec->{default_path};
393 #warn "Searching for $spec->{plural} using:$xpath\n";
395 my @nodes = $xp->findnodes($xpath);
396 return unless @nodes;
398 for my $node (@nodes) {
400 # my $thing = { xpNode => $node };
402 # Get the Tag attributes
403 foreach ( @{$spec->{attrib_data}} ) {
404 $thing->{$_} = $node->getAttribute($_);
408 foreach ( @{$spec->{path_data}} ) {
409 #warn "Searching for $spec->{plural} - $_->{name} using:$_->{path}\n";
410 my @nodes = $node->findnodes($_->{path});
411 $thing->{$_->{name}} = @nodes ? $nodes[0]->getData
412 : (exists $_->{default} ? $_->{default} : undef);
415 # Run any filters set
417 # Should we do this after the kids as we may want to test them?
418 # e.g. test for number of attribs
419 if ( my $filter = $args{filter} ) {
421 next unless $filter->($thing);
426 foreach ( @{$spec->{kids}} ) {
428 my $meth = $_->{get_method};
429 $data = $me->$meth( _context => $node, _xpath => $_->{path},
430 filter => $args{"filter_$_->{name}"} );
432 if ( $_->{multiplicity} eq "1" ) {
433 $thing->{$_->{name}} = shift @$data;
436 $thing->{$_->{name}} = $data || [];
440 push @$things, $thing;
442 return wantarray ? @$things : $things;
447 1; #===========================================================================
450 package XML::XPath::Function;
453 # May need to look at doing deref on all paths just to be on the safe side!
455 # Will also want some caching as these calls are expensive as the whole doc
456 # is used but the same ref will likley be requested lots of times.
460 my ($node, @params) = @_;
462 die "xmiDeref() function takes one or no parameters\n";
465 my $nodeset = shift(@params);
466 return $nodeset unless $nodeset->size;
467 $node = $nodeset->get_node(1);
469 die "xmiDeref() needs an Element node."
470 unless $node->isa("XML::XPath::Node::Element");
472 my $id = $node->getAttribute("xmi.idref") or return $node;
473 return $node->getRootNode->find('//*[@xmi.id="'.$id.'"]');
484 use SQL::Translator::XMI::Parser;
485 my $xmip = SQL::Translator::XMI::Parser->new( xml => $xml );
486 my $classes = $xmip->get_classes();
490 Parses XMI files (XML version of UML diagrams) to perl data structures and
491 provides hooks to filter the data down to what you want.
495 Pass in name/value arg of either filename, xml or ioref for the XMI data you
500 Doc below is for classes method, all the other calls follow this form.
502 =head2 get_classes( ARGS )
504 ARGS - Name/Value list of args.
506 filter => A sub to filter the node to see if we want it. Has the nodes data,
507 before kids are added, referenced to $_. Should return true if you
508 want it, false otherwise.
510 e.g. To find only classes with a "Foo" stereotype.
512 filter => sub { return $_->{stereotype} eq "Foo"; }
514 filter_attributes => A filter sub to pass onto get_attributes.
516 filter_operations => A filter sub to pass onto get_operations.
518 Returns a perl data structure including all the kids. e.g.
522 'visibility' => 'public',
523 'isActive' => 'false',
524 'isAbstract' => 'false',
525 'isSpecification' => 'false',
526 'stereotype' => 'Table',
532 'stereotype' => 'PK',
534 'ownerScope' => 'instance',
535 'visibility' => 'public',
536 'initialValue' => undef,
537 'isSpecification' => 'false',
542 'datatype' => 'varchar'
543 'ownerScope' => 'instance',
544 'visibility' => 'public',
545 'initialValue' => '',
546 'isSpecification' => 'false',
552 'isQuery' => 'false',
553 'ownerScope' => 'instance',
554 'visibility' => 'public',
555 'isSpecification' => 'false',
557 'isAbstract' => 'false',
560 'concurrency' => 'sequential'
564 'isSpecification' => 'false',
571 'isSpecification' => 'false',
578 'isSpecification' => 'false',
588 =head1 XMI XPath Functions
590 The Parser adds the following extra XPath functions for use in the SPECS.
594 Deals with xmi.id/xmi.idref pairs of attributes. You give it an
595 xPath e.g 'UML:ModelElement.stereotype/UML:stereotype' if the the
596 tag it points at has an xmi.idref it looks up the tag with that
597 xmi.id and returns it.
599 If it doesn't have an xmi.id, the path is returned as normal.
603 <UML:ModelElement.stereotype>
604 <UML:Stereotype xmi.idref = 'stTable'/>
605 </UML:ModelElement.stereotype>
607 <UML:Stereotype xmi.id='stTable' name='Table' visibility='public'
608 isAbstract='false' isSpecification='false' isRoot='false' isLeaf='false'>
609 <UML:Stereotype.baseClass>Class</UML:Stereotype.baseClass>
612 Using xmideref(//UML:ModelElement.stereotype/UML:stereotype) would return the
613 <UML:Stereotype xmi.id = '3b4b1e:f762a35f6b:-7fb6' ...> tag.
615 Using xmideref(//UML:ModelElement.stereotype/UML:stereotype)/@name would give
626 =head1 VERSION HISTORY
630 grommit <mark.addison@itn.co.uk>
634 This package is free software and is provided "as is" without express or
635 implied warranty. It may be used, redistributed and/or modified under the
638 a) the Perl Artistic License.
640 See F<http://www.perl.com/perl/misc/Artistic.html>
642 b) the terms of the GNU General Public License as published by the Free Software
643 Foundation; either version 1, or (at your option) any later version.