package SQL::Translator::Parser::XML::XMI;

# -------------------------------------------------------------------
# $Id: XMI.pm,v 1.1 2003-09-04 15:55:47 grommit Exp $
# -------------------------------------------------------------------
# Copyright (C) 2003 Mark Addison <mark.addison@itn.co.uk>,
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# 02111-1307  USA
# -------------------------------------------------------------------

=head1 NAME

SQL::Translator::Parser::XML::XMI - Parser to create Schema from UML
Class diagrams stored in XMI format.

=cut

# -------------------------------------------------------------------

use strict;

use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
$VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/;
$DEBUG   = 0 unless defined $DEBUG;

use Data::Dumper;
use Exporter;
use base qw(Exporter);
@EXPORT_OK = qw(parse);

use base qw/SQL::Translator::Parser/;  # Doesnt do anything at the mo!
use SQL::Translator::Utils 'debug';
use XML::XPath;
use XML::XPath::XMLParser;


# Custom XPath functions
#-----------------------------------------------------------------------------

#
# 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
#-----------------------------------------------------------------------------

sub parse {
    my ( $translator, $data ) = @_;
    local $DEBUG    = $translator->debug;
    my $schema      = $translator->schema;
    my $pargs          = $translator->parser_args;
    
    my $xp          = XML::XPath->new(xml => $data);

    $xp->set_namespace("UML", "org.omg.xmi.namespace.UML");
    #
    # 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]');
    
    debug "Found ".scalar(@nodes)." Classes: ".join(", ",
        map {$_->getAttribute("name")} @nodes);
    
    for my $classnode (@nodes) {
        # Only process classes with <<Table>> and name
        next unless my $classname = $classnode->getAttribute("name");
        my $stereotype = "".$classnode->find(
            'xmideref(UML:ModelElement.stereotype/UML:Stereotype)/@name');
        next unless $stereotype eq "Table";
        
        # Add the table
        debug "Adding class: $classname as table:$classname";
        my $table = $schema->add_table(name=>$classname)
            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");
            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,
                #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;
            }

            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;

# -------------------------------------------------------------------

=pod

=head1 SYNOPSIS

  use SQL::Translator;
  use SQL::Translator::Parser::XML::XMI;

  my $translator     = SQL::Translator->new(
      from           => 'XML-XMI',
      to             => 'MySQL',
      filename       => 'schema.xmi',
      show_warnings  => 1,
      add_drop_table => 1,
  );

  print $obj->translate;

=head1 DESCRIPTION

=head2 UML Data Modeling

To tell the parser which Classes are tables give them a <<Table>> stereotype.

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
says it uses UML 2. So the current conformance is down to Poseidon's idea
of XMI!

=head1 ARGS

=over 4

=item visibility TODO

 visibilty=public|private|protected|package

What visibilty of stuff to translate. e.g when set to 'public' any private
Classes will be ignored and not turned into tables.

=item table_visibility    TODO

=item field_visibility    TODO

=item table_stereotype    Def:Table TODO 

What stereotype a class must have to turned into a table.

=item pkey_stereotype    Def:PK TODO 

=back

=head1 BUGS

=head1 TODO

Deal with field sizes. Don't think UML does this directly so may need to include
it in the datatype names.

Everything else! Relations, fkeys, constraints, indexes, etc...

=head1 AUTHOR

Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.

=head1 SEE ALSO

perl(1), SQL::Translator, XML::XPath, SQL::Translator::Producer::XML::SQLFairy,
SQL::Translator::Schema.

=cut
