Changed debug to dump xmi model data and not just classes.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / XMI / Parser.pm
CommitLineData
f42065cb 1package SQL::Translator::XMI::Parser;
2
93f4a354 3# -------------------------------------------------------------------
4# $Id: Parser.pm,v 1.5 2003-09-29 12:02:35 grommit Exp $
5# -------------------------------------------------------------------
6# Copyright (C) 2003 Mark Addison <mark.addison@itn.co.uk>,
7#
8# This program is free software; you can redistribute it and/or
9# modify it under the terms of the GNU General Public License as
10# published by the Free Software Foundation; version 2.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15# General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20# 02111-1307 USA
21# -------------------------------------------------------------------
22
f42065cb 23=pod
24
25=head1 NAME
26
93f4a354 27SQL::Translator::XMI::Parser - XMI Parser class for use in SQL Fairy's XMI
28parser.
f42065cb 29
30=cut
31
32use strict;
33use 5.006_001;
93f4a354 34use vars qw/$VERSION/;
35$VERSION = sprintf "%d.%02d", q$Revision: 1.5 $ =~ /(\d+)\.(\d+)/;
f42065cb 36
93f4a354 37use Data::Dumper;
f42065cb 38use XML::XPath;
39use XML::XPath::XMLParser;
40use Storable qw/dclone/;
41
42# Spec
93f4a354 43#------
44# See SQL::Translator::XMI::Parser::V12 and SQL::Translator::XMI::Parser:V10
45# for examples.
f42065cb 46#
93f4a354 47# Hash ref used to describe the 2 xmi formats 1.2 and 1.0. Neither is complete!
f42065cb 48#
49# NB The names of the data keys MUST be the same for both specs so the
50# data structures returned are the same.
51#
93f4a354 52# TODO
53#
54# * There is currently no way to set the data key name for attrib_data, it just
f42065cb 55# uses the attribute name from the XMI. This isn't a problem at the moment as
56# xmi1.0 names all these things with tags so we don't need the attrib data!
57# Also use of names seems to be consistant between the versions.
58#
f42065cb 59#
93f4a354 60# XmiSpec( $spec )
f42065cb 61#
93f4a354 62# Call as class method to set up the parser from a spec (see above). This
63# generates the get_ methods for the version of XMI the spec is for. Called by
64# the sub-classes (e.g. V12 and V10) to create parsers for each version.
f42065cb 65#
93f4a354 66sub XmiSpec {
67 my ($me,$spec) = @_;
68 init_specs($spec);
69 $me->mk_gets($spec);
70}
f42065cb 71
72# Build lookups etc. Its important that each spec item becomes self contained
b4b9f867 73# so we can build good closures, therefore we do all the lookups 1st.
f42065cb 74sub init_specs {
75 my $specs = shift;
76
77 foreach my $spec ( values %$specs ) {
b4b9f867 78 # Look up for kids get method
79 foreach ( @{$spec->{kids}} ) {
f42065cb 80 $_->{get_method} = "get_".$specs->{$_->{class}}{plural};
81 }
0b3f94e0 82
b4b9f867 83 # Add xmi.id ti all specs. Everything we want at the moment (in both
84 # versions) has an id. The tags that don't seem to be used for
85 # structure.
86 my $attrib_data = $spec->{attrib_data} ||= [];
87 push @$attrib_data, "xmi.id";
f42065cb 88 }
89
90}
91
93f4a354 92# Create get methods from spec
93#
94sub mk_gets {
95 my ($proto,$specs) = @_;
96 my $class = ref($proto) || $proto;
97 foreach ( values %$specs ) {
98 # Clone from specs and sort out the lookups into it so we get a
99 # self contained spec to use as a proper closure.
100 my $spec = dclone($_);
101
102 # Create _get_* method with get_* as an alias unless the user has
103 # defined it. Allows for override. Note the alias is in this package
104 # so we can add overrides to both specs.
105 no strict "refs";
106 my $meth = "_get_$spec->{plural}";
107 *{$meth} = mk_get($spec);
108 *{__PACKAGE__."::get_$spec->{plural}"} = sub {shift->$meth(@_);}
109 unless $class->can("get_$spec->{plural}");
f42065cb 110 }
111}
112
93f4a354 113# e.g. of overriding both versions.
114#sub get_classes {
115# print "HELLO Both\n";
116# return shift->_get_classes(@_);
117#}
118
119#
120# Sets up the XML::XPath object and then checks the version of the XMI file and
121# blesses its self into either the V10 or V12 class.
122#
f42065cb 123sub new {
124 my $proto = shift;
125 my $class = ref($proto) || $proto;
126 my %args = @_;
127 my $me = {};
93f4a354 128
f42065cb 129 # Create the XML::XPath object
130 # TODO Docs recommend we only use 1 XPath object per application
131 my $xp;
132 foreach (qw/filename xml ioref/) {
133 if ($args{$_}) {
134 $xp = XML::XPath->new( $_ => $args{$_});
135 $xp->set_namespace("UML", "org.omg.xmi.namespace.UML");
136 last;
137 }
138 }
139 $me = { xml_xpath => $xp };
93f4a354 140
141 # Work out the version of XMI we have and return as that sub class
142 my $xmiv = $args{xmi_version}
b4b9f867 143 || "".$xp->findvalue('/XMI/@xmi.version')
144 || die "Can't find XMI version";
93f4a354 145 $xmiv =~ s/[.]//g;
146 $class = __PACKAGE__."::V$xmiv";
147 eval "use $class;";
148 die "Failed to load version sub class $class : $@" if $@;
f42065cb 149
93f4a354 150 return bless $me, $class;
f42065cb 151}
152
93f4a354 153#
f42065cb 154# mk_get
155#
93f4a354 156# Generates and returns a get_ sub for the spec given.
157# So, if you want to change how the get methods (e.g. get_classes) work do it
158# here!
f42065cb 159#
160# The get methods made have the args described in the docs and 2 private args
161# used internally, to call other get methods from paths in the spec.
f42065cb 162# NB: DO NOT use publicly as you will break the version independance. e.g. When
163# using _xpath you need to know which version of XMI to use. This is handled by
164# the use of different paths in the specs.
b2a00f50 165#
f42065cb 166# _context => The context node to use, if not given starts from root.
b2a00f50 167#
f42065cb 168# _xpath => The xpath to use for finding stuff.
b2a00f50 169#
f42065cb 170sub mk_get {
171 my $spec = shift;
b2a00f50 172
f42065cb 173 # get_* closure using $spec
174 return sub {
175 my ($me, %args) = @_;
176 my $xp = delete $args{_context} || $me->{xml_xpath};
177 my $things;
178
179 my $xpath = $args{_xpath} ||= $spec->{default_path};
180#warn "Searching for $spec->{plural} using:$xpath\n";
181
182 my @nodes = $xp->findnodes($xpath);
0b3f94e0 183#warn "None.\n" unless @nodes;
f42065cb 184 return unless @nodes;
185
186 for my $node (@nodes) {
0b3f94e0 187#warn " Found $spec->{name} xmi.id=".$node->getAttribute("xmi.id")." name=".$node->getAttribute("name")."\n";
f42065cb 188 my $thing = {};
189 # my $thing = { xpNode => $node };
b2a00f50 190
0b3f94e0 191 # Have we seen this before? If so just use the ref we have.
192 if ( my $id = $node->getAttribute("xmi.id") ) {
193 if ( my $foo = $me->{model}{things}{$id} ) {
194#warn " Reffing from model **********************\n";
195 push @$things, $foo;
196 next;
197 }
198 }
199
f42065cb 200 # Get the Tag attributes
201 foreach ( @{$spec->{attrib_data}} ) {
202 $thing->{$_} = $node->getAttribute($_);
203 }
b2a00f50 204
f42065cb 205 # Add the path data
206 foreach ( @{$spec->{path_data}} ) {
0b3f94e0 207#warn " $spec->{name} - $_->{name} using:$_->{path}\n";
f42065cb 208 my @nodes = $node->findnodes($_->{path});
209 $thing->{$_->{name}} = @nodes ? $nodes[0]->getData
210 : (exists $_->{default} ? $_->{default} : undef);
211 }
b2a00f50 212
213 # Run any filters set
214 #
f42065cb 215 # Should we do this after the kids as we may want to test them?
216 # e.g. test for number of attribs
217 if ( my $filter = $args{filter} ) {
218 local $_ = $thing;
219 next unless $filter->($thing);
220 }
b2a00f50 221
0b3f94e0 222 # Add anything with an id to the things lookup
223 push @$things, $thing;
224 if ( exists $thing->{"xmi.id"} and defined $thing->{"xmi.id"}
225 and my $id = $thing->{"xmi.id"}
226 ) {
227 $me->{model}{things}{$id} = $thing; }
228
f42065cb 229 # Kids
230 #
231 foreach ( @{$spec->{kids}} ) {
b2a00f50 232 my $data;
f42065cb 233 my $meth = $_->{get_method};
b2a00f50 234 my $path = $_->{path};
0b3f94e0 235
236 # Variable subs on the path from thing
237 $path =~ s/\$\{(.*?)\}/$thing->{$1}/g;
b2a00f50 238 $data = $me->$meth( _context => $node, _xpath => $path,
f42065cb 239 filter => $args{"filter_$_->{name}"} );
b2a00f50 240
f42065cb 241 if ( $_->{multiplicity} eq "1" ) {
242 $thing->{$_->{name}} = shift @$data;
243 }
244 else {
b2a00f50 245 my $kids = $thing->{$_->{name}} = $data || [];
246 if ( my $key = $_->{"map"} ) {
247 $thing->{"_map_$_->{name}"} = _mk_map($kids,$key);
248 }
f42065cb 249 }
250 }
0b3f94e0 251 }
f42065cb 252
0b3f94e0 253 if ( $spec->{isRoot} ) {
254 push(@{$me->{model}{$spec->{plural}}}, $_) foreach @$things;
f42065cb 255 }
0b3f94e0 256 return $things;
f42065cb 257} # /closure sub
258
259} # /mk_get
260
b2a00f50 261sub _mk_map {
262 my ($kids,$key) = @_;
263 my $map = {};
264 foreach (@$kids) {
265 $map->{$_->{$key}} = $_ if exists $_->{$key};
266 }
267 return $map;
268}
269
f42065cb 2701; #===========================================================================
271
272
273package XML::XPath::Function;
274
275#
276# May need to look at doing deref on all paths just to be on the safe side!
277#
278# Will also want some caching as these calls are expensive as the whole doc
279# is used but the same ref will likley be requested lots of times.
280#
281sub xmiDeref {
282 my $self = shift;
283 my ($node, @params) = @_;
284 if (@params > 1) {
285 die "xmiDeref() function takes one or no parameters\n";
286 }
287 elsif (@params) {
288 my $nodeset = shift(@params);
289 return $nodeset unless $nodeset->size;
290 $node = $nodeset->get_node(1);
291 }
292 die "xmiDeref() needs an Element node."
293 unless $node->isa("XML::XPath::Node::Element");
294
295 my $id = $node->getAttribute("xmi.idref") or return $node;
296 return $node->getRootNode->find('//*[@xmi.id="'.$id.'"]');
297}
298
299
300# compile please
3011;
302
303__END__
304
305=head1 SYNOPSIS
306
307 use SQL::Translator::XMI::Parser;
308 my $xmip = SQL::Translator::XMI::Parser->new( xml => $xml );
309 my $classes = $xmip->get_classes();
310
311=head1 DESCRIPTION
312
313Parses XMI files (XML version of UML diagrams) to perl data structures and
314provides hooks to filter the data down to what you want.
315
316=head2 new
317
b4b9f867 318Pass in name/value arg of either C<filename>, C<xml> or C<ioref> for the XMI
319data you want to parse.
320
321The version of XMI to use either 1.0 or 1.2 is worked out from the file. You
322can also use a C<xmi_version> arg to set it explicitley.
f42065cb 323
324=head2 get_* methods
325
326Doc below is for classes method, all the other calls follow this form.
327
328=head2 get_classes( ARGS )
329
330 ARGS - Name/Value list of args.
331
332 filter => A sub to filter the node to see if we want it. Has the nodes data,
333 before kids are added, referenced to $_. Should return true if you
334 want it, false otherwise.
335
336 e.g. To find only classes with a "Foo" stereotype.
337
338 filter => sub { return $_->{stereotype} eq "Foo"; }
339
340 filter_attributes => A filter sub to pass onto get_attributes.
341
342 filter_operations => A filter sub to pass onto get_operations.
343
344Returns a perl data structure including all the kids. e.g.
345
346 {
347 'name' => 'Foo',
348 'visibility' => 'public',
349 'isActive' => 'false',
350 'isAbstract' => 'false',
351 'isSpecification' => 'false',
352 'stereotype' => 'Table',
353 'isRoot' => 'false',
354 'isLeaf' => 'false',
355 'attributes' => [
356 {
357 'name' => 'fooid',
358 'stereotype' => 'PK',
359 'datatype' => 'int'
360 'ownerScope' => 'instance',
361 'visibility' => 'public',
362 'initialValue' => undef,
363 'isSpecification' => 'false',
364 },
365 {
366 'name' => 'name',
367 'stereotype' => '',
368 'datatype' => 'varchar'
369 'ownerScope' => 'instance',
370 'visibility' => 'public',
371 'initialValue' => '',
372 'isSpecification' => 'false',
373 },
374 ]
375 'operations' => [
376 {
377 'name' => 'magic',
378 'isQuery' => 'false',
379 'ownerScope' => 'instance',
380 'visibility' => 'public',
381 'isSpecification' => 'false',
382 'stereotype' => '',
383 'isAbstract' => 'false',
384 'isLeaf' => 'false',
385 'isRoot' => 'false',
386 'concurrency' => 'sequential'
387 'parameters' => [
388 {
389 'kind' => 'inout',
390 'isSpecification' => 'false',
391 'stereotype' => '',
392 'name' => 'arg1',
393 'datatype' => undef
394 },
395 {
396 'kind' => 'inout',
397 'isSpecification' => 'false',
398 'stereotype' => '',
399 'name' => 'arg2',
400 'datatype' => undef
401 },
402 {
403 'kind' => 'return',
404 'isSpecification' => 'false',
405 'stereotype' => '',
406 'name' => 'return',
407 'datatype' => undef
408 }
409 ],
410 }
411 ],
412 }
413
414=head1 XMI XPath Functions
415
93f4a354 416The Parser adds the following extra XPath functions for use in the Specs.
f42065cb 417
418=head2 xmiDeref
419
420Deals with xmi.id/xmi.idref pairs of attributes. You give it an
421xPath e.g 'UML:ModelElement.stereotype/UML:stereotype' if the the
422tag it points at has an xmi.idref it looks up the tag with that
423xmi.id and returns it.
424
425If it doesn't have an xmi.id, the path is returned as normal.
426
427e.g. given
428
429 <UML:ModelElement.stereotype>
430 <UML:Stereotype xmi.idref = 'stTable'/>
431 </UML:ModelElement.stereotype>
432 ...
433 <UML:Stereotype xmi.id='stTable' name='Table' visibility='public'
434 isAbstract='false' isSpecification='false' isRoot='false' isLeaf='false'>
435 <UML:Stereotype.baseClass>Class</UML:Stereotype.baseClass>
436 </UML:Stereotype>
437
438Using xmideref(//UML:ModelElement.stereotype/UML:stereotype) would return the
439<UML:Stereotype xmi.id = '3b4b1e:f762a35f6b:-7fb6' ...> tag.
440
441Using xmideref(//UML:ModelElement.stereotype/UML:stereotype)/@name would give
442"Table".
443
444=head1 SEE ALSO
445
446perl(1).
447
448=head1 TODO
449
450=head1 BUGS
451
452=head1 VERSION HISTORY
453
454=head1 AUTHOR
455
456grommit <mark.addison@itn.co.uk>
457
f42065cb 458=cut