Strip evil svn:keywords
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / XML / SQLFairy.pm
CommitLineData
0a1ec87a 1package SQL::Translator::Parser::XML::SQLFairy;
2
3# -------------------------------------------------------------------
d4f84dd1 4# $Id: SQLFairy.pm 1440 2009-01-17 16:31:57Z jawnsy $
0a1ec87a 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
23=head1 NAME
24
8571d198 25SQL::Translator::Parser::XML::SQLFairy - parser for SQL::Translator's XML.
0a1ec87a 26
27=head1 SYNOPSIS
28
29 use SQL::Translator;
0a1ec87a 30
85633bfb 31 my $translator = SQL::Translator->new( show_warnings => 1 );
0a1ec87a 32
85633bfb 33 my $out = $obj->translate(
34 from => 'XML-SQLFairy',
35 to => 'MySQL',
36 filename => 'schema.xml',
37 ) or die $translator->error;
38
39 print $out;
0a1ec87a 40
41=head1 DESCRIPTION
42
43This parser handles the flavor of XML used natively by the SQLFairy
91f28468 44project (L<SQL::Translator>). The XML must be in the namespace
0a1ec87a 45"http://sqlfairy.sourceforge.net/sqlfairy.xml."
8571d198 46See L<SQL::Translator::Producer::XML::SQLFairy> for details of this format.
0a1ec87a 47
8571d198 48You do not need to specify every attribute of the Schema objects as any missing
49from the XML will be set to their default values. e.g. A field could be written
50using only;
0a1ec87a 51
8571d198 52 <sqlf:field name="email" data_type="varchar" size="255" />
0a1ec87a 53
8571d198 54Instead of the full;
0a1ec87a 55
8571d198 56 <sqlf:field name="email" data_type="varchar" size="255" is_nullable="1"
57 is_auto_increment="0" is_primary_key="0" is_foreign_key="0" order="4">
58 <sqlf:comments></sqlf:comments>
59 </sqlf:field>
0a1ec87a 60
8571d198 61If you do not explicitly set the order of items using order attributes on the
62tags then the order the tags appear in the XML will be used.
0a1ec87a 63
64=head2 default_value
65
91f28468 66Leave the attribute out all together to use the default in L<Schema::Field>.
67Use empty quotes or 'EMPTY_STRING' for a zero lenth string. 'NULL' for an
a5e624ac 68explicit null (currently sets default_value to undef in the
69Schema::Field obj).
0a1ec87a 70
91f28468 71 <sqlf:field default_value="" /> <!-- Empty string -->
72 <sqlf:field default_value="EMPTY_STRING" /> <!-- Empty string -->
73 <sqlf:field default_value="NULL" /> <!-- NULL -->
0a1ec87a 74
0a1ec87a 75=head2 ARGS
76
77Doesn't take any extra parser args at the moment.
78
4a268a6c 79=head1 LEGACY FORMAT
80
81The previous version of the SQLFairy XML allowed the attributes of the the
82schema objects to be written as either xml attributes or as data elements, in
83any combination. While this allows for lots of flexibility in writing the XML
84the result is a great many possible XML formats, not so good for DTD writing,
85XPathing etc! So we have moved to a fixed version described in
86L<SQL::Translator::Producer::XML::SQLFairy>.
87
88This version of the parser will still parse the old formats and emmit warnings
91f28468 89when it sees them being used but they should be considered B<heavily
90depreciated>.
4a268a6c 91
91f28468 92To convert your old format files simply pass them through the translator :)
4a268a6c 93
91f28468 94 $ sqlt -f XML-SQLFairy -t XML-SQLFairy schema-old.xml > schema-new.xml
4a268a6c 95
0a1ec87a 96=cut
97
98# -------------------------------------------------------------------
99
100use strict;
101
478f608d 102use vars qw[ $DEBUG @EXPORT_OK ];
0a1ec87a 103$DEBUG = 0 unless defined $DEBUG;
104
105use Data::Dumper;
106use Exporter;
107use base qw(Exporter);
108@EXPORT_OK = qw(parse);
109
110use base qw/SQL::Translator::Parser/; # Doesnt do anything at the mo!
a5e624ac 111use SQL::Translator::Utils 'debug';
0a1ec87a 112use XML::XPath;
113use XML::XPath::XMLParser;
114
0a1ec87a 115sub parse {
116 my ( $translator, $data ) = @_;
a5e624ac 117 my $schema = $translator->schema;
118 local $DEBUG = $translator->debug;
119 my $xp = XML::XPath->new(xml => $data);
0a1ec87a 120
0a1ec87a 121 $xp->set_namespace("sqlf", "http://sqlfairy.sourceforge.net/sqlfairy.xml");
122
a5e624ac 123 #
0a1ec87a 124 # Work our way through the tables
125 #
87c5565e 126 my @nodes = $xp->findnodes(
127 '/sqlf:schema/sqlf:table|/sqlf:schema/sqlf:tables/sqlf:table'
128 );
0a1ec87a 129 for my $tblnode (
8571d198 130 sort {
53a533ef 131 ("".$xp->findvalue('sqlf:order|@order',$a) || 0)
8571d198 132 <=>
53a533ef 133 ("".$xp->findvalue('sqlf:order|@order',$b) || 0)
a5e624ac 134 } @nodes
0a1ec87a 135 ) {
136 debug "Adding table:".$xp->findvalue('sqlf:name',$tblnode);
a5e624ac 137
0a1ec87a 138 my $table = $schema->add_table(
b1789409 139 get_tagfields($xp, $tblnode, "sqlf:" => qw/name order extra/)
0a1ec87a 140 ) or die $schema->error;
141
a5e624ac 142 #
0a1ec87a 143 # Fields
144 #
145 my @nodes = $xp->findnodes('sqlf:fields/sqlf:field',$tblnode);
146 foreach (
8571d198 147 sort {
a5e624ac 148 ("".$xp->findvalue('sqlf:order',$a) || 0)
8571d198 149 <=>
150 ("".$xp->findvalue('sqlf:order',$b) || 0)
a5e624ac 151 } @nodes
0a1ec87a 152 ) {
153 my %fdata = get_tagfields($xp, $_, "sqlf:",
94ed484b 154 qw/name data_type size default_value is_nullable extra
a5e624ac 155 is_auto_increment is_primary_key is_foreign_key comments/
156 );
157
158 if (
8571d198 159 exists $fdata{'default_value'} and
a5e624ac 160 defined $fdata{'default_value'}
161 ) {
162 if ( $fdata{'default_value'} =~ /^\s*NULL\s*$/ ) {
163 $fdata{'default_value'}= undef;
0a1ec87a 164 }
a5e624ac 165 elsif ( $fdata{'default_value'} =~ /^\s*EMPTY_STRING\s*$/ ) {
166 $fdata{'default_value'} = "";
0a1ec87a 167 }
168 }
a5e624ac 169
19922fbc 170 my $field = $table->add_field( %fdata ) or die $table->error;
a5e624ac 171
172 $table->primary_key( $field->name ) if $fdata{'is_primary_key'};
173
174 #
175 # TODO:
8571d198 176 # - We should be able to make the table obj spot this when
a5e624ac 177 # we use add_field.
a5e624ac 178 #
0a1ec87a 179 }
180
a5e624ac 181 #
0a1ec87a 182 # Constraints
183 #
184 @nodes = $xp->findnodes('sqlf:constraints/sqlf:constraint',$tblnode);
185 foreach (@nodes) {
186 my %data = get_tagfields($xp, $_, "sqlf:",
a5e624ac 187 qw/name type table fields reference_fields reference_table
100684f3 188 match_type on_delete on_update extra/
a5e624ac 189 );
19922fbc 190 $table->add_constraint( %data ) or die $table->error;
0a1ec87a 191 }
192
a5e624ac 193 #
0a1ec87a 194 # Indexes
195 #
196 @nodes = $xp->findnodes('sqlf:indices/sqlf:index',$tblnode);
197 foreach (@nodes) {
198 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 199 qw/name type fields options extra/);
19922fbc 200 $table->add_index( %data ) or die $table->error;
0a1ec87a 201 }
202
7c71eaab 203
204 #
205 # Comments
206 #
207 @nodes = $xp->findnodes('sqlf:comments/sqlf:comment',$tblnode);
208 foreach (@nodes) {
209 my $data = $_->string_value;
210 $table->comments( $data );
211 }
212
0a1ec87a 213 } # tables loop
214
19922fbc 215 #
216 # Views
217 #
87c5565e 218 @nodes = $xp->findnodes(
219 '/sqlf:schema/sqlf:view|/sqlf:schema/sqlf:views/sqlf:view'
220 );
19922fbc 221 foreach (@nodes) {
222 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 223 qw/name sql fields order extra/
19922fbc 224 );
225 $schema->add_view( %data ) or die $schema->error;
226 }
8571d198 227
19922fbc 228 #
229 # Triggers
230 #
87c5565e 231 @nodes = $xp->findnodes(
232 '/sqlf:schema/sqlf:trigger|/sqlf:schema/sqlf:triggers/sqlf:trigger'
233 );
19922fbc 234 foreach (@nodes) {
b1789409 235 my %data = get_tagfields($xp, $_, "sqlf:", qw/
236 name perform_action_when database_event fields on_table action order
237 extra
238 /);
19922fbc 239 $schema->add_trigger( %data ) or die $schema->error;
240 }
8571d198 241
19922fbc 242 #
243 # Procedures
244 #
87c5565e 245 @nodes = $xp->findnodes(
246 '/sqlf:schema/sqlf:procedure|/sqlf:schema/sqlf:procedures/sqlf:procedure'
247 );
19922fbc 248 foreach (@nodes) {
249 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 250 qw/name sql parameters owner comments order extra/
19922fbc 251 );
252 $schema->add_procedure( %data ) or die $schema->error;
253 }
8571d198 254
0a1ec87a 255 return 1;
256}
257
a5e624ac 258# -------------------------------------------------------------------
259sub get_tagfields {
260#
8571d198 261# get_tagfields XP, NODE, NAMESPACE => qw/TAGNAMES/;
0a1ec87a 262# get_tagfields $node, "sqlf:" => qw/name type fields reference/;
263#
8571d198 264# Returns hash of data.
265# TODO - Add handling of an explicit NULL value.
a5e624ac 266#
267
0a1ec87a 268 my ($xp, $node, @names) = @_;
269 my (%data, $ns);
270 foreach (@names) {
271 if ( m/:$/ ) { $ns = $_; next; } # Set def namespace
272 my $thisns = (s/(^.*?:)// ? $1 : $ns);
a5e624ac 273
94ed484b 274 my $is_attrib = m/^(sql|comments|action|extra)$/ ? 0 : 1;
a5e624ac 275
8571d198 276 my $attrib_path = "\@$thisns$_";
277 my $tag_path = "$thisns$_";
278 if ( $xp->exists($attrib_path,$node) ) {
279 $data{$_} = "".$xp->findvalue($attrib_path,$node);
280 warn "Use of '$_' as an attribute is depricated."
281 ." Use a child tag instead."
282 ." To convert your file to the new version see the Docs.\n"
283 unless $is_attrib;
284 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
285 }
286 elsif ( $xp->exists($tag_path,$node) ) {
94ed484b 287 if ($_ eq "extra") {
288 my %extra;
289 my $extra_nodes = $xp->find($tag_path,$node);
290 foreach ( $extra_nodes->pop->getAttributes ) {
291 $extra{$_->getName} = $_->getData;
292 }
293 $data{$_} = \%extra;
294 }
295 else {
296 $data{$_} = "".$xp->findvalue($tag_path,$node);
297 }
8571d198 298 warn "Use of '$_' as a child tag is depricated."
299 ." Use an attribute instead."
300 ." To convert your file to the new version see the Docs.\n"
301 if $is_attrib;
a5e624ac 302 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
0a1ec87a 303 }
304 }
a5e624ac 305
0a1ec87a 306 return wantarray ? %data : \%data;
307}
308
3091;
310
311# -------------------------------------------------------------------
312
313=pod
314
315=head1 BUGS
316
8571d198 317Ignores the order attribute for Constraints, Views, Indices,
318Views, Triggers and Procedures, using the tag order instead. (This is the order
319output by the SQLFairy XML producer).
0a1ec87a 320
91f28468 321=head1 SEE ALSO
322
323L<perl>, L<SQL::Translator>, L<SQL::Translator::Producer::XML::SQLFairy>,
324L<SQL::Translator::Schema>.
325
0a1ec87a 326=head1 TODO
327
328=over 4
329
8571d198 330=item *
a5e624ac 331
94ed484b 332Support options attribute.
a5e624ac 333
8571d198 334=item *
0a1ec87a 335
8571d198 336Test foreign keys are parsed ok.
0a1ec87a 337
8571d198 338=item *
0a1ec87a 339
91f28468 340Control over defaulting.
0a1ec87a 341
342=back
343
344=head1 AUTHOR
345
346Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
347
0a1ec87a 348=cut