Improve trigger 'scope' attribute support (RT#119997)
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / XML / SQLFairy.pm
index baa2cbb..14d98b1 100644 (file)
@@ -1,25 +1,5 @@
 package SQL::Translator::Parser::XML::SQLFairy;
 
-# -------------------------------------------------------------------
-# $Id: SQLFairy.pm,v 1.7 2004-07-08 19:34:29 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::SQLFairy - parser for SQL::Translator's XML.
@@ -27,23 +7,22 @@ SQL::Translator::Parser::XML::SQLFairy - parser for SQL::Translator's XML.
 =head1 SYNOPSIS
 
   use SQL::Translator;
-  use SQL::Translator::Parser::XML::SQLFairy;
 
-  my $translator     = SQL::Translator->new(
-      from           => 'XML-SQLFairy',
-      to             => 'MySQL',
-      filename       => 'schema.xml',
-      show_warnings  => 1,
-      add_drop_table => 1,
-  );
+  my $translator = SQL::Translator->new( show_warnings  => 1 );
 
-  print $obj->translate;
+  my $out = $obj->translate(
+      from     => 'XML-SQLFairy',
+      to       => 'MySQL',
+      filename => 'schema.xml',
+  ) or die $translator->error;
+
+  print $out;
 
 =head1 DESCRIPTION
 
 This parser handles the flavor of XML used natively by the SQLFairy
-project (SQL::Translator).  The XML must be in the namespace
-"http://sqlfairy.sourceforge.net/sqlfairy.xml."
+project (L<SQL::Translator>).  The XML must be in the XML namespace
+C<http://sqlfairy.sourceforge.net/sqlfairy.xml>.
 See L<SQL::Translator::Producer::XML::SQLFairy> for details of this format.
 
 You do not need to specify every attribute of the Schema objects as any missing
@@ -64,15 +43,14 @@ tags then the order the tags appear in the XML will be used.
 
 =head2 default_value
 
-Leave the tag out all together to use the default in Schema::Field.
-Use empty tags or 'EMPTY_STRING' for a zero lenth string. 'NULL' for an
-explicit null (currently sets default_value to undef in the
-Schema::Field obj).
+Leave the attribute out all together to use the default in
+L<SQL::Translator::Schema::Field>.  Use empty quotes or 'EMPTY_STRING'
+for a zero length string. 'NULL' for an explicit null (currently sets
+default_value to undef in the field object).
 
-  <sqlf:default_value></sqlf:default_value>             <!-- Empty string -->
-  <sqlf:default_value>EMPTY_STRING</sqlf:default_value> <!-- Empty string -->
-  <sqlf:default_value/>                                 <!-- Empty string -->
-  <sqlf:default_value>NULL</sqlf:default_value>         <!-- NULL -->
+  <sqlf:field default_value="" />                <!-- Empty string -->
+  <sqlf:field default_value="EMPTY_STRING" />    <!-- Empty string -->
+  <sqlf:field default_value="NULL" />            <!-- NULL -->
 
 =head2 ARGS
 
@@ -80,65 +58,67 @@ Doesn't take any extra parser args at the moment.
 
 =head1 LEGACY FORMAT
 
-The previous version of the SQLFairy XML allowed the attributes of the the
+The previous version of the SQLFairy XML allowed the attributes of the
 schema objects to be written as either xml attributes or as data elements, in
 any combination. While this allows for lots of flexibility in writing the XML
 the result is a great many possible XML formats, not so good for DTD writing,
 XPathing etc! So we have moved to a fixed version described in
 L<SQL::Translator::Producer::XML::SQLFairy>.
 
-This version of the parser will still parse the old formats and emmit warnings
-when it sees them being used.
-The old format is B<heavily depreciated> and B<will not> be supported in future
-versions.
+This version of the parser will still parse the old formats and emit warnings
+when it sees them being used but they should be considered B<heavily
+depreciated>.
 
-To convert your old format files simply pass them through the translator;
+To convert your old format files simply pass them through the translator :)
 
- sqlt -f XML-SQLFairy -t XML-SQLFairy schema-old.xml > schema-new.xml
+ $ sqlt -f XML-SQLFairy -t XML-SQLFairy schema-old.xml > schema-new.xml
 
 =cut
 
-# -------------------------------------------------------------------
-
 use strict;
+use warnings;
 
-use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.7 $ =~ /(\d+)\.(\d+)/;
+our ( $DEBUG, @EXPORT_OK );
+our $VERSION = '1.59';
 $DEBUG   = 0 unless defined $DEBUG;
 
 use Data::Dumper;
+use Carp::Clan qw/^SQL::Translator/;
 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;
+use XML::LibXML;
+use XML::LibXML::XPathContext;
 
 sub parse {
     my ( $translator, $data ) = @_;
     my $schema                = $translator->schema;
     local $DEBUG              = $translator->debug;
-    my $xp                    = XML::XPath->new(xml => $data);
+    my $doc                   = XML::LibXML->new->parse_string($data);
+    my $xp                    = XML::LibXML::XPathContext->new($doc);
 
-    $xp->set_namespace("sqlf", "http://sqlfairy.sourceforge.net/sqlfairy.xml");
+    $xp->registerNs("sqlf", "http://sqlfairy.sourceforge.net/sqlfairy.xml");
 
     #
     # Work our way through the tables
     #
-    my @nodes = $xp->findnodes('/sqlf:schema/sqlf:table');
+    my @nodes = $xp->findnodes(
+        '/sqlf:schema/sqlf:table|/sqlf:schema/sqlf:tables/sqlf:table'
+    );
     for my $tblnode (
         sort {
-            "".$xp->findvalue('sqlf:order|@order',$a)
+            ("".$xp->findvalue('sqlf:order|@order',$a) || 0)
             <=>
-            "".$xp->findvalue('sqlf:order|@order',$b)
+            ("".$xp->findvalue('sqlf:order|@order',$b) || 0)
         } @nodes
     ) {
         debug "Adding table:".$xp->findvalue('sqlf:name',$tblnode);
 
         my $table = $schema->add_table(
-            get_tagfields($xp, $tblnode, "sqlf:" => qw/name order/)
+            get_tagfields($xp, $tblnode, "sqlf:" => qw/name order extra/)
         ) or die $schema->error;
 
         #
@@ -153,7 +133,7 @@ sub parse {
             } @nodes
         ) {
             my %fdata = get_tagfields($xp, $_, "sqlf:",
-                qw/name data_type size default_value is_nullable 
+                qw/name data_type size default_value is_nullable extra
                 is_auto_increment is_primary_key is_foreign_key comments/
             );
 
@@ -177,7 +157,6 @@ sub parse {
             # TODO:
             # - We should be able to make the table obj spot this when
             #   we use add_field.
-            # - Deal with $field->extra
             #
         }
 
@@ -188,7 +167,7 @@ sub parse {
         foreach (@nodes) {
             my %data = get_tagfields($xp, $_, "sqlf:",
                 qw/name type table fields reference_fields reference_table
-                match_type on_delete_do on_update_do/
+                match_type on_delete on_update extra/
             );
             $table->add_constraint( %data ) or die $table->error;
         }
@@ -199,19 +178,31 @@ sub parse {
         @nodes = $xp->findnodes('sqlf:indices/sqlf:index',$tblnode);
         foreach (@nodes) {
             my %data = get_tagfields($xp, $_, "sqlf:",
-                qw/name type fields options/);
+                qw/name type fields options extra/);
             $table->add_index( %data ) or die $table->error;
         }
 
+
+        #
+        # Comments
+        #
+        @nodes = $xp->findnodes('sqlf:comments/sqlf:comment',$tblnode);
+        foreach (@nodes) {
+            my $data = $_->string_value;
+            $table->comments( $data );
+        }
+
     } # tables loop
 
     #
     # Views
     #
-    @nodes = $xp->findnodes('/sqlf:schema/sqlf:view');
+    @nodes = $xp->findnodes(
+        '/sqlf:schema/sqlf:view|/sqlf:schema/sqlf:views/sqlf:view'
+    );
     foreach (@nodes) {
         my %data = get_tagfields($xp, $_, "sqlf:",
-            qw/name sql fields order/
+            qw/name sql fields order extra/
         );
         $schema->add_view( %data ) or die $schema->error;
     }
@@ -219,21 +210,43 @@ sub parse {
     #
     # Triggers
     #
-    @nodes = $xp->findnodes('/sqlf:schema/sqlf:trigger');
+    @nodes = $xp->findnodes(
+        '/sqlf:schema/sqlf:trigger|/sqlf:schema/sqlf:triggers/sqlf:trigger'
+    );
     foreach (@nodes) {
-        my %data = get_tagfields($xp, $_, "sqlf:",
-        qw/name perform_action_when database_event fields on_table action order/
-        );
+        my %data = get_tagfields($xp, $_, "sqlf:", qw/
+            name perform_action_when database_event database_events fields
+            on_table action order extra scope
+        /);
+
+        # back compat
+        if (my $evt = $data{database_event} and $translator->{show_warnings}) {
+          carp 'The database_event tag is deprecated - please use ' .
+            'database_events (which can take one or more comma separated ' .
+            'event names)';
+          $data{database_events} = join (', ',
+            $data{database_events} || (),
+            $evt,
+          );
+        }
+
+        # split into arrayref
+        if (my $evts = $data{database_events}) {
+          $data{database_events} = [split (/\s*,\s*/, $evts) ];
+        }
+
         $schema->add_trigger( %data ) or die $schema->error;
     }
 
     #
     # Procedures
     #
-    @nodes = $xp->findnodes('/sqlf:schema/sqlf:procedure');
+    @nodes = $xp->findnodes(
+       '/sqlf:schema/sqlf:procedure|/sqlf:schema/sqlf:procedures/sqlf:procedure'
+    );
     foreach (@nodes) {
         my %data = get_tagfields($xp, $_, "sqlf:",
-        qw/name sql parameters owner comments order/
+        qw/name sql parameters owner comments order extra/
         );
         $schema->add_procedure( %data ) or die $schema->error;
     }
@@ -241,7 +254,6 @@ sub parse {
     return 1;
 }
 
-# -------------------------------------------------------------------
 sub get_tagfields {
 #
 # get_tagfields XP, NODE, NAMESPACE => qw/TAGNAMES/;
@@ -257,20 +269,29 @@ sub get_tagfields {
         if ( m/:$/ ) { $ns = $_; next; }  # Set def namespace
         my $thisns = (s/(^.*?:)// ? $1 : $ns);
 
-        my $is_attrib = m/^sql|comments|action$/ ? 0 : 1;
+        my $is_attrib = m/^(sql|comments|action|extra)$/ ? 0 : 1;
 
-        my $attrib_path = "\@$thisns$_";
+        my $attrib_path = "\@$_";
         my $tag_path    = "$thisns$_";
-        if ( $xp->exists($attrib_path,$node) ) {
-            $data{$_} = "".$xp->findvalue($attrib_path,$node);
+        if ( my $found = $xp->find($attrib_path,$node) ) {
+            $data{$_} = "".$found->to_literal;
             warn "Use of '$_' as an attribute is depricated."
                 ." Use a child tag instead."
                 ." To convert your file to the new version see the Docs.\n"
                 unless $is_attrib;
             debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
         }
-        elsif ( $xp->exists($tag_path,$node) ) {
-            $data{$_} = "".$xp->findvalue($tag_path,$node);
+        elsif ( $found = $xp->find($tag_path,$node) ) {
+            if ($_ eq "extra") {
+                my %extra;
+                foreach ( $found->pop->getAttributes ) {
+                    $extra{$_->getName} = $_->getData;
+                }
+                $data{$_} = \%extra;
+            }
+            else {
+                $data{$_} = "".$found->to_literal;
+            }
             warn "Use of '$_' as a child tag is depricated."
                 ." Use an attribute instead."
                 ." To convert your file to the new version see the Docs.\n"
@@ -284,15 +305,18 @@ sub get_tagfields {
 
 1;
 
-# -------------------------------------------------------------------
-
 =pod
 
 =head1 BUGS
 
-Ignores the order attribute for Constraints, Views, Indices,
-Views, Triggers and Procedures, using the tag order instead. (This is the order
-output by the SQLFairy XML producer).
+Ignores the order attribute for Constraints, Views, Indices, Views, Triggers
+and Procedures, using the tag order instead. (This is the order output by the
+SQLFairy XML producer).
+
+=head1 SEE ALSO
+
+L<perl>, L<SQL::Translator>, L<SQL::Translator::Producer::XML::SQLFairy>,
+L<SQL::Translator::Schema>.
 
 =head1 TODO
 
@@ -300,7 +324,7 @@ output by the SQLFairy XML producer).
 
 =item *
 
-Support options and extra attributes.
+Support options attribute.
 
 =item *
 
@@ -308,17 +332,13 @@ Test foreign keys are parsed ok.
 
 =item *
 
-Control over defaulting of non-existant tags.
+Control over defaulting.
 
 =back
 
 =head1 AUTHOR
 
-Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
-
-=head1 SEE ALSO
-
-perl(1), SQL::Translator, SQL::Translator::Producer::XML::SQLFairy,
-SQL::Translator::Schema.
+Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>,
+Jonathan Yu E<lt>frequency@cpan.orgE<gt>
 
 =cut