package SQL::Translator::Parser::XML::XMI;
# -------------------------------------------------------------------
-# $Id: XMI.pm,v 1.2 2003-09-08 12:27:29 grommit Exp $
+# $Id: XMI.pm,v 1.8 2003-09-22 11:41:07 grommit Exp $
# -------------------------------------------------------------------
# Copyright (C) 2003 Mark Addison <mark.addison@itn.co.uk>,
#
=cut
-# -------------------------------------------------------------------
-
use strict;
use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.2 $ =~ /(\d+)\.(\d+)/;
+$VERSION = sprintf "%d.%02d", q$Revision: 1.8 $ =~ /(\d+)\.(\d+)/;
$DEBUG = 0 unless defined $DEBUG;
use Data::Dumper;
use base qw/SQL::Translator::Parser/; # Doesnt do anything at the mo!
use SQL::Translator::Utils 'debug';
-use XML::XPath;
-use XML::XPath::XMLParser;
+use SQL::Translator::XMI::Parser;
-# Custom XPath functions
+# SQLFairy Parser
#-----------------------------------------------------------------------------
-#
-# Pass a nodeset. If the first node has an xmi.idref attrib then return
-# the nodeset for that id
-#
-sub XML::XPath::Function::xmideref {
- my $self = shift;
- my ($node, @params) = @_;
- if (@params > 1) {
- die "xmideref() function takes one or no parameters\n";
- }
- elsif (@params) {
- my $nodeset = shift(@params);
- return $nodeset unless $nodeset->size;
- $node = $nodeset->get_node(1);
- }
- die "xmideref() needs an Element node."
- unless $node->isa("XML::XPath::Node::Element");
-
- my $id = $node->getAttribute("xmi.idref") or return $node;
- return $node->getRootNode->find('//*[@xmi.id="'.$id.'"]');
-}
-
-sub XML::XPath::Function::hello {
- return XML::XPath::Literal->new("Hello World");
-}
-
-
-
-# Parser
-#-----------------------------------------------------------------------------
-
-#
-# is_visible( {ELEMENT|VIS_OF_THING}, VISLEVEL)
-#
-# Returns true or false for whether the visibility of something e.g. Class,
-# Attribute, is visible at the level given.
-#
+# is_visible - Used to check visibility in filter subs
{
my %vislevel = (
public => 1,
);
sub is_visible {
- my ($arg, $vis) = @_;
+ my ($nodevis, $vis) = @_;
+ $nodevis = ref $_[0] ? $_[0]->{visibility} : $_[0];
return 1 unless $vis;
- my $foo;
- die "is_visible : Needs something to test" unless $arg;
- if ( $arg->isa("XML::XPath::Node::Element") ) {
- $foo = $arg->getAttribute("visibility");
- }
- else {
- $foo = $arg;
- }
- return 1 if $vislevel{$vis} >= $vislevel{$foo};
- return 0;
+ return 1 if $vislevel{$vis} >= $vislevel{$nodevis};
+ return 0;
}
}
+my ($schema, $pargs);
+
sub parse {
my ( $translator, $data ) = @_;
- local $DEBUG = $translator->debug;
- my $schema = $translator->schema;
- my $pargs = $translator->parser_args;
+ local $DEBUG = $translator->debug;
+ $schema = $translator->schema;
+ $pargs = $translator->parser_args;
+ $pargs->{classes2schema} ||= \&classes2schema;
debug "Visibility Level:$pargs->{visibility}" if $DEBUG;
- my $xp = XML::XPath->new(xml => $data);
- $xp->set_namespace("UML", "org.omg.xmi.namespace.UML");
- #
+ my $xmip = SQL::Translator::XMI::Parser->new(xml => $data);
+
# TODO
# - Options to set the initial context node so we don't just
# blindly do all the classes. e.g. Select a diag name to do.
- #
- # Work our way through the classes, creating tables. We only
- # want class with xmi.id attributes and not the refs to them,
- # which will have xmi.idref attributes.
- #
- my @nodes = $xp->findnodes('//UML:Class[@xmi.id]');
+ my $classes = $xmip->get_classes(
+ filter => sub {
+ return unless $_->{name};
+ return unless is_visible($_, $pargs->{visibility});
+ return 1;
+ },
+ filter_attributes => sub {
+ return unless $_->{name};
+ return unless is_visible($_, $pargs->{visibility});
+ return 1;
+ },
+ );
+ debug "Found ".scalar(@$classes)." Classes: ".join(", ",
+ map {$_->{"name"}} @$classes) if $DEBUG;
+ debug "Classes:",Dumper($classes);
- debug "Found ".scalar(@nodes)." Classes: ".join(", ",
- map {$_->getAttribute("name")} @nodes) if $DEBUG;
+ #
+ # Turn the data from get_classes into a Schema
+ #
+ $pargs->{classes2schema}->($schema, $classes);
- for my $classnode (@nodes) {
- # Only process classes with <<Table>> and name
- next unless my $classname = $classnode->getAttribute("name");
- next unless !$pargs->{visibility}
- or is_visible($classnode, $pargs->{visibility});
+ return 1;
+}
+
+1;
- my $stereotype = "".$classnode->find(
- 'xmideref(UML:ModelElement.stereotype/UML:Stereotype)/@name');
- next unless $stereotype eq "Table";
+# Default conversion sub. Makes all classes into tables using all their
+# attributes.
+sub classes2schema {
+ my ($schema, $classes) = @_;
+ foreach my $class (@$classes) {
# Add the table
- debug "Adding class: $classname as table:$classname" if $DEBUG;
- my $table = $schema->add_table(name=>$classname)
+ debug "Adding class: $class->{name}";
+ my $table = $schema->add_table( name => $class->{name} )
or die "Schema Error: ".$schema->error;
#
# Fields from Class attributes
#
- # name data_type size default_value is_nullable
- # is_auto_increment is_primary_key is_foreign_key comments
- #
- foreach my $attrnode ( $classnode->findnodes(
- 'UML:Classifier.feature/UML:Attribute[@xmi.id]',)
- ) {
- next unless my $fieldname = $attrnode->getAttribute("name");
- next unless !$pargs->{visibility}
- or is_visible($attrnode, $pargs->{visibility});
-
- my $stereotype = "".$attrnode->findvalue(
- 'xmideref(UML:ModelElement.stereotype/UML:Stereotype)/@name');
- my %data = (
- name => $fieldname,
- data_type => "".$attrnode->find(
- 'xmideref(UML:StructuralFeature.type/UML:DataType)/@name'),
- is_primary_key => $stereotype eq "PK" ? 1 : 0,
+ foreach my $attr ( @{$class->{attributes}} ) {
+ my %data = (
+ name => $attr->{name},
+ is_primary_key => $attr->{stereotype} eq "PK" ? 1 : 0,
#is_foreign_key => $stereotype eq "FK" ? 1 : 0,
);
- if ( my @body = $attrnode->findnodes(
- 'UML:Attribute.initialValue/UML:Expression/@body')
- ) {
- $data{default_value} = $body[0]->getData;
- }
+ $data{default_value} = $attr->{initialValue}
+ if exists $attr->{initialValue};
+ $data{data_type} = $attr->{_map_taggedValues}{dataType}{dataValue}
+ || $attr->{datatype};
+ $data{size} = $attr->{_map_taggedValues}{size}{dataValue};
+ $data{is_nullable}=$attr->{_map_taggedValues}{nullable}{dataValue};
- debug "Adding field:",Dumper(\%data);
my $field = $table->add_field( %data ) or die $schema->error;
-
$table->primary_key( $field->name ) if $data{'is_primary_key'};
- #
- # TODO:
- # - We should be able to make the table obj spot this when
- # we use add_field.
- #
}
} # Classes loop
-
- return 1;
}
1;
-# -------------------------------------------------------------------
+__END__
=pod
=head1 DESCRIPTION
-=head2 UML Data Modeling
+Translates XMI (UML models in XML format) into Schema. This basic parser
+will just pull out all the classes as tables with fields from their attributes.
-To tell the parser which Classes are tables give them a <<Table>> stereotype.
+For more detail you will need to use a UML profile for data modelling. These are
+supported by sub parsers. See their docs for details.
-Any attributes of the class will be used as fields. The datatype of the
-attribute must be a UML datatype and not an object, with the datatype's name
-being used to set the data_type value in the schema.
+=over 4
-Primary keys are attributes marked with <<PK>> stereotype.
+=item XML::XMI::Rational
-=head2 XMI Format
+The Rational Software UML Data Modeling Profile
-The parser has been built using XMI generated by PoseidonUML 2beta, which
-says it uses UML 2. So the current conformance is down to Poseidon's idea
-of XMI!
+=back
=head1 ARGS
=back
+=head1 XMI Format
+
+Uses either XMI v1.0 or v1.2. The version to use is detected automatically
+from the <XMI> tag in the source file.
+
+The parser has been built using XMI 1.2 generated by PoseidonUML 2, which
+says it uses UML 2. So the current conformance is down to Poseidon's idea
+of XMI! 1.0 support is based on a Rose file, is less complete and has little
+testing.
+
+
=head1 BUGS
Seems to be slow. I think this is because the XMI files can get pretty
-big and complex, especially all the diagram info.
+big and complex, especially all the diagram info, and XPath needs to load the
+whole tree.
-=head1 TODO
-
-B<field sizes> Don't think UML does this directly so may need to include
-it in the datatype names.
+Deleting the diagrams from an XMI1.2 file (make a backup!) will really speed
+things up. Remove <UML:Diagram> tags and all their contents.
-B<table_visibility and field_visibility args> Seperate control over what is
-parsed, setting visibility arg will set both.
+=head1 TODO
-Everything else! Relations, fkeys, constraints, indexes, etc...
+More profiles.
=head1 AUTHOR
SQL::Translator::Schema.
=cut
+
+