package SQL::Translator::Parser::XML::XMI;
# -------------------------------------------------------------------
-# $Id: XMI.pm,v 1.4 2003-09-09 01:00:44 grommit Exp $
+# $Id: XMI.pm,v 1.6 2003-09-16 16:29:49 grommit Exp $
# -------------------------------------------------------------------
# Copyright (C) 2003 Mark Addison <mark.addison@itn.co.uk>,
#
=head1 DESCRIPTION
-=head2 UML Data Modeling
-
-To tell the parser which Classes are tables give them a <<Table>> stereotype.
+Currently pulls out all the Classes as tables.
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.
-Primary keys are attributes marked with <<PK>> stereotype.
-
=head2 XMI Format
-The parser has been built using XMI generated by PoseidonUML 2beta, which
+The parser has been built using XMI 1.2 generated by PoseidonUML 2beta, which
says it uses UML 2. So the current conformance is down to Poseidon's idea
of XMI!
+It should also parse XMI 1.0, such as you get from Rose, but this has had
+little testing!
+
=head1 ARGS
=over 4
use strict;
use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.4 $ =~ /(\d+)\.(\d+)/;
+$VERSION = sprintf "%d.%02d", q$Revision: 1.6 $ =~ /(\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;
-# XMI XPath parsing
-#-----------------------------------------------------------------------------
-
-#
-# get_classes( XPATHOBJ, ARGS );
-#
-# XPATHOBJ - An XML::XPath object setup and ready to use. You can also use any
-# Node to search from as this sub just calls findnodes() on the arg.
-#
-# ARGS - Name/Value list of args.
-#
-# xpath => The xpath to use for finding classes. Default is //UML:Classes
-# which will find all the classes in the XMI.
-#
-# test => An XPath predicate (ie the bit between [] ) to test the
-# classes with to decide if we should parse them. ie
-# test => '@name' would only pass out classes with a name.
-# Can also give it an array ref and it will and the tests.
-# It gets tacked onto to xpath so don't put any [] on
-# xpath if you use test as well.
-#
-# attribute_test => An XPath predicate to pass onto get_attributes.
-#
-# _add_xpath_tests $path, [qw/@name xmiVisible("public")/]; # and
-# _add_xpath_tests $path, [qw/@name xmiVisible("public")/], "or";
-sub _add_xpath_tests {
- my ($path,$tests,$join) = @_;
- return $path unless defined $tests;
- my @tests = ref($tests) ? @$tests : $tests;
- return $path unless @tests;
- $join ||= "and";
- return $path."[".join(" $join ", @tests)."]";
-}
+# SQLFairy Parser
+#-----------------------------------------------------------------------------
-sub get_stereotype {
- my ($xp) = @_;
- return "".$xp->findvalue(
- 'xmiDeref(UML:ModelElement.stereotype/UML:Stereotype)/@name');
- # TODO Test for difference between it existing or being "" ?
-}
+# is_visible - Used to check visibility in filter subs
+{
+ my %vislevel = (
+ public => 1,
+ protected => 2,
+ private => 3,
+ );
-sub get_classes {
- my ($xp,%args) = @_;
- my $classes;
-
- my $xpath = $args{xpath} ||= '//UML:Class'; # Default: all classes
- $xpath = _add_xpath_tests $xpath, $args{test};
- debug "Searching for Classes using:$xpath";
-
- my @nodes = $xp->findnodes($xpath);
- return unless @nodes;
-
- for my $classnode (@nodes) {
- my $class = {};
-
- foreach (
- qw/name visibility isSpecification
- isRoot isLeaf isAbstract isActive/
- ) {
- $class->{$_} = $classnode->getAttribute($_);
- }
- $class->{stereotype} = get_stereotype($classnode);
-
- # Class Attributes
- my $xpath = 'UML:Classifier.feature/UML:Attribute';
- $class->{attributes} = get_attributes( $classnode,
- xpath => $xpath, test => $args{attribute_test} );
-
- push @$classes, $class;
- }
- return wantarray ? @$classes : $classes;
-};
-
-sub get_attributes {
- my ($xp, %args) = @_;
-
- my $xpath = $args{xpath} ||= '//UML:Classifier.feature/UML:Attribute';
- $xpath = _add_xpath_tests $xpath, $args{test};
- debug "Searching for Attributes using:$xpath";
-
- my $attributes;
- foreach my $node ( $xp->findnodes($xpath) ) {
- my $attr = {};
-
- foreach (qw/name visibility isSpecification ownerScope/) {
- $attr->{$_} = $node->getAttribute($_);
- }
- $attr->{stereotype} = get_stereotype($node);
-
- # Get datatype name and the name body of the initial value
- $attr->{datatype} = "".$node->find(
- 'xmiDeref(UML:StructuralFeature.type/UML:DataType)/@name');
- if ( my @body = $node->findnodes(
- 'UML:Attribute.initialValue/UML:Expression/@body')
- ) {
- $attr->{initialValue} = $body[0]->getData;
- }
-
- push @$attributes, $attr;
+ sub is_visible {
+ my ($nodevis, $vis) = @_;
+ $nodevis = ref $_[0] ? $_[0]->{visibility} : $_[0];
+ return 1 unless $vis;
+ return 1 if $vislevel{$vis} >= $vislevel{$nodevis};
+ return 0;
}
- return wantarray ? @$attributes : $attributes;
}
-
-
-# SQLFairy Parser
-#-----------------------------------------------------------------------------
-
sub parse {
- eval {
-
my ( $translator, $data ) = @_;
- local $DEBUG = $translator->debug;
- my $schema = $translator->schema;
- my $pargs = $translator->parser_args;
-
+ local $DEBUG = $translator->debug;
+ my $schema = $translator->schema;
+ my $pargs = $translator->parser_args;
+
+ eval {
+
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.
-
- #
- # Build an XPath for the classes and attributes we want...
- #
- # Only classes with an id (so we don't get any refs to classes ie
- # xmi.idref classes). They also need a name to be usefull.
- my @tests = ('@xmi.id and @name');
- push @tests, "xmiVisible('$pargs->{visibility}')" if $pargs->{visibility};
-
- my $attrib_test = '@name and @xmi.id';
- $attrib_test .= " and xmiVisible('$pargs->{visibility}')"
- if $pargs->{visibility};
-
- # ...and parse them out
- my $classes = get_classes( $xp,
- xpath => "//UML:Class", test => [@tests], attribute_test => $attrib_test);
-
+
+ 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 "Classes:",Dumper($classes);
+ #print "Classes:",Dumper($classes),"\n";
#
# Turn the data from get_classes into a Schema
#
+ # TODO This is where we will applie different strategies for different UML
+ # data modeling profiles.
+ #
foreach my $class (@$classes) {
- next unless $class->{stereotype} eq "Table";
-
# Add the table
debug "Adding class: $class->{name}" if $DEBUG;
my $table = $schema->add_table( name => $class->{name} )
#
# Fields from Class attributes
#
- # name data_type size default_value is_nullable
- # is_auto_increment is_primary_key is_foreign_key comments
- #
foreach my $attr ( @{$class->{attributes}} ) {
my %data = (
name => $attr->{name},
}
} # Classes loop
+
+ };
+ print "ERROR:$@" if $@;
return 1;
-
- };
- print "ERROR: $@\n" if $@;
- return 1;
}
1;
-#=============================================================================
-#
-# XML::XPath extensions
-#
-#=============================================================================
-
-package XML::XPath::Function;
-
-=head1 XMI XPath Functions
-
-The Parser adds the following extra XPath functions.
-
-=head2 xmiDeref
-
-Deals with xmi.id/xmi.idref pairs of attributes. You give it an
-xPath e.g 'UML:ModelElement.stereotype/UML:stereotype' if the the
-tag it points at has an xmi.idref it looks up the tag with that
-xmi.id and returns it.
-
-If it doesn't have an xmi.id, the path is returned as normal.
-
-e.g. given
-
- <UML:ModelElement.stereotype>
- <UML:Stereotype xmi.idref = 'stTable'/>
- </UML:ModelElement.stereotype>
- ...
- <UML:Stereotype xmi.id='stTable' name='Table' visibility='public'
- isAbstract='false' isSpecification='false' isRoot='false' isLeaf='false'>
- <UML:Stereotype.baseClass>Class</UML:Stereotype.baseClass>
- </UML:Stereotype>
-
-Using xmideref(//UML:ModelElement.stereotype/UML:stereotype) would return the
-<UML:Stereotype xmi.id = '3b4b1e:f762a35f6b:-7fb6' ...> tag.
-
-Using xmideref(//UML:ModelElement.stereotype/UML:stereotype)/@name would give
-"Table".
-
-=head xmiVisible
-
- is_visible( VISLEVEL )
-
-Returns true or false for whether the visibility of something e.g. a Class or
-Attribute, is visible at the level given. e.g.
-
- //UML:Class[xmiVisible('public')] - Find all public classes
- //UML:Class[xmiVisible('protected')] - Find all public and protected classes
-
-Supports the 3 UML visibility levels of public, protected and private.
-
-Note: Currently any element tested that doesn't have a visibility="" attribute
-is assumed to be visible and so xmiVisible will return true. This is probably
-the wrong thing to do and is very likley to change. It is probably best to
-throw an error if we try to test something that doesn't do visibility.
-
-=cut
-
-sub 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.'"]');
-}
-
-{
- my %vislevel = (
- public => 1,
- protected => 2,
- private => 3,
- );
-
- sub xmiVisible {
- my $self = shift;
- my ($node, @params) = @_;
- if (@params < 1 or @params > 2) {
- die "xmiVisible() function takes 1 or 2 parameters\n";
- }
- elsif (@params == 2) {
- my $nodeset = shift(@params);
- return unless $nodeset->size;
- $node = $nodeset->get_node(1);
- }
- die "xmiVisible() needs an Element node."
- unless $node->isa("XML::XPath::Node::Element");
-
- my $vis = shift(@params) || return XML::XPath::Boolean->True;
- my $nodevis = $node->getAttribute("visibility")
- || return XML::XPath::Boolean->True;
- return XML::XPath::Boolean->True
- if $vislevel{$vis} >= $vislevel{$nodevis};
- return XML::XPath::Boolean->False;
- }
-}
-
-# Test of custom xpath function.
-sub hello {
- return XML::XPath::Literal->new("Hello World");
-}
-
-#=============================================================================
-package main;
-
=pod
=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