73e1a9f7eae4d7e6ec40803c1f09da4cb1fd2cc2
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / XML / SQLFairy.pm
1 package SQL::Translator::Producer::XML::SQLFairy;
2
3 # -------------------------------------------------------------------
4 # $Id: SQLFairy.pm,v 1.18 2004-08-19 20:41:32 grommit Exp $
5 # -------------------------------------------------------------------
6 # Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
7 #                    darren chamberlain <darren@cpan.org>,
8 #                    Chris Mungall <cjm@fruitfly.org>,
9 #                    Mark Addison <mark.addison@itn.co.uk>.
10 #
11 # This program is free software; you can redistribute it and/or
12 # modify it under the terms of the GNU General Public License as
13 # published by the Free Software Foundation; version 2.
14 #
15 # This program is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 # General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23 # 02111-1307  USA
24 # -------------------------------------------------------------------
25
26 =pod
27
28 =head1 NAME
29
30 SQL::Translator::Producer::XML::SQLFairy - SQLFairy's default XML format
31
32 =head1 SYNOPSIS
33
34   use SQL::Translator;
35
36   my $t              = SQL::Translator->new(
37       from           => 'MySQL',
38       to             => 'XML-SQLFairy',
39       filename       => 'schema.sql',
40       show_warnings  => 1,
41   );
42
43   print $t->translate;
44
45 =head1 DESCRIPTION
46
47 Creates XML output of a schema, in the flavor of XML used natively by the
48 SQLFairy project (L<SQL::Translator>). This format is detailed here.
49
50 The XML lives in the C<http://sqlfairy.sourceforge.net/sqlfairy.xml> namespace.
51 With a root element of <schema>.
52
53 Objects in the schema are mapped to tags of the same name as the objects class
54 (all lowercase).
55
56 The attributes of the objects (e.g. $field->name) are mapped to attributes of
57 the tag, except for sql, comments and action, which get mapped to child data
58 elements.
59
60 List valued attributes (such as the list of fields in an index)
61 get mapped to comma seperated lists of values in the attribute.
62
63 Child objects, such as a tables fields, get mapped to child tags wrapped in a
64 set of container tags using the plural of their contained classes name.
65
66 L<SQL::Translator::Schema::Field>'s extra attribute (a hash of arbitary data) is
67 mapped to a tag called extra, with the hash of data as attributes, sorted into
68 alphabetical order.
69
70 e.g.
71
72     <schema name="" database=""
73       xmlns="http://sqlfairy.sourceforge.net/sqlfairy.xml">
74
75       <tables>
76         <table name="Story" order="1">
77           <fields>
78             <field name="id" data_type="BIGINT" size="20"
79               is_nullable="0" is_auto_increment="1" is_primary_key="1"
80               is_foreign_key="0" order="3">
81               <extra ZEROFILL="1" />
82               <comments></comments>
83             </field>
84             <field name="created" data_type="datetime" size="0"
85               is_nullable="1" is_auto_increment="0" is_primary_key="0"
86               is_foreign_key="0" order="1">
87               <extra />
88               <comments></comments>
89             </field>
90             ...
91           </fields>
92           <indices>
93             <index name="foobar" type="NORMAL" fields="foo,bar" options="" />
94           </indices>
95         </table>
96       </tables>
97
98       <views>
99         <view name="email_list" fields="email" order="1">
100           <sql>SELECT email FROM Basic WHERE email IS NOT NULL</sql>
101         </view>
102       </views>
103
104     </schema>
105
106 To see a complete example of the XML translate one of your schema :)
107
108   $ sqlt -f MySQL -t XML-SQLFairy schema.sql
109
110 =head1 ARGS
111
112 =over 4
113
114 =item add_prefix
115
116 Set to true to use the default namespace prefix of 'sqlf', instead of using
117 the default namespace for
118 C<http://sqlfairy.sourceforge.net/sqlfairy.xml namespace>
119
120 e.g.
121
122  <!-- add_prefix=0 -->
123  <field name="foo" />
124
125  <!-- add_prefix=1 -->
126  <sqlf:field name="foo" />
127
128 =item prefix
129
130 Set to the namespace prefix you want to use for the
131 C<http://sqlfairy.sourceforge.net/sqlfairy.xml namespace>
132
133 e.g.
134
135  <!-- prefix='foo' -->
136  <foo:field name="foo" />
137
138 =item newlines
139
140 If true (the default) inserts newlines around the XML, otherwise the schema is
141 written on one line.
142
143 =item indent
144
145 When using newlines the number of whitespace characters to use as the indent.
146 Default is 2, set to 0 to turn off indenting.
147
148 =back
149
150 =head1 LEGACY FORMAT
151
152 The previous version of the SQLFairy XML allowed the attributes of the the
153 schema objects to be written as either xml attributes or as data elements, in
154 any combination. The old producer could produce attribute only or data element
155 only versions. While this allowed for lots of flexibility in writing the XML
156 the result is a great many possible XML formats, not so good for DTD writing,
157 XPathing etc! So we have moved to a fixed version described above.
158
159 This version of the producer will now only produce the new style XML.
160 To convert your old format files simply pass them through the translator :)
161
162  $ sqlt -f XML-SQLFairy -t XML-SQLFairy schema-old.xml > schema-new.xml
163
164 =cut
165
166 use strict;
167 use vars qw[ $VERSION @EXPORT_OK ];
168 $VERSION = sprintf "%d.%02d", q$Revision: 1.18 $ =~ /(\d+)\.(\d+)/;
169
170 use Exporter;
171 use base qw(Exporter);
172 @EXPORT_OK = qw(produce);
173
174 use IO::Scalar;
175 use SQL::Translator::Utils qw(header_comment debug);
176 BEGIN {
177     # Will someone fix XML::Writer already?
178     local $^W = 0;
179     require XML::Writer;
180     import XML::Writer;
181 }
182
183 # Which schema object attributes (methods) to write as xml elements rather than
184 # as attributes. e.g. <comments>blah, blah...</comments>
185 my @MAP_AS_ELEMENTS = qw/sql comments action extra/;
186
187
188
189 my $Namespace = 'http://sqlfairy.sourceforge.net/sqlfairy.xml';
190 my $Name      = 'sqlf';
191 my $PArgs     = {};
192
193 sub produce {
194     my $translator  = shift;
195     my $schema      = $translator->schema;
196     $PArgs          = $translator->producer_args;
197     my $newlines    = defined $PArgs->{newlines} ? $PArgs->{newlines} : 1;
198     my $indent      = defined $PArgs->{indent}   ? $PArgs->{indent}   : 2;
199     my $io          = IO::Scalar->new;
200
201     # Setup the XML::Writer and set the namespace
202     my $prefix = "";
203     $prefix    = $Name            if $PArgs->{add_prefix};
204     $prefix    = $PArgs->{prefix} if $PArgs->{prefix};
205     my $xml         = XML::Writer->new(
206         OUTPUT      => $io,
207         NAMESPACES  => 1,
208         PREFIX_MAP  => { $Namespace => $prefix },
209         DATA_MODE   => $newlines,
210         DATA_INDENT => $indent,
211     );
212
213     # Start the document
214     $xml->xmlDecl('UTF-8');
215     $xml->comment(header_comment('', ''));
216     xml_obj($xml, $schema,
217         tag => "schema", methods => [qw/name database/], end_tag => 0 );
218
219     #
220     # Table
221     #
222     $xml->startTag( [ $Namespace => "tables" ] );
223     for my $table ( $schema->get_tables ) {
224         debug "Table:",$table->name;
225         xml_obj($xml, $table,
226              tag => "table",
227              methods => [qw/name order/],
228              end_tag => 0
229          );
230
231         #
232         # Fields
233         #
234         xml_obj_children( $xml, $table,
235             tag   => 'field',
236             methods =>[qw/
237                 name data_type size is_nullable default_value is_auto_increment
238                 is_primary_key is_foreign_key extra comments order
239             /],
240         );
241
242         #
243         # Indices
244         #
245         xml_obj_children( $xml, $table,
246             tag   => 'index',
247             collection_tag => "indices",
248             methods => [qw/name type fields options/],
249         );
250
251         #
252         # Constraints
253         #
254         xml_obj_children( $xml, $table,
255             tag   => 'constraint',
256             methods => [qw/
257                 name type fields reference_table reference_fields
258                 on_delete on_update match_type expression options deferrable
259             /],
260         );
261
262         $xml->endTag( [ $Namespace => 'table' ] );
263     }
264     $xml->endTag( [ $Namespace => 'tables' ] );
265
266     #
267     # Views
268     #
269     xml_obj_children( $xml, $schema,
270         tag   => 'view',
271         methods => [qw/name sql fields order/],
272     );
273
274     #
275     # Tiggers
276     #
277     xml_obj_children( $xml, $schema,
278         tag    => 'trigger',
279         methods => [qw/name database_event action on_table perform_action_when
280             fields order/],
281     );
282
283     #
284     # Procedures
285     #
286     xml_obj_children( $xml, $schema,
287         tag   => 'procedure',
288         methods => [qw/name sql parameters owner comments order/],
289     );
290
291     $xml->endTag([ $Namespace => 'schema' ]);
292     $xml->end;
293
294     return $io;
295 }
296
297
298 #
299 # Takes and XML::Write object, Schema::* parent object, the tag name,
300 # the collection name and a list of methods (of the children) to write as XML.
301 # The collection name defaults to the name with an s on the end and is used to
302 # work out the method to get the children with. eg a name of 'foo' gives a
303 # collection of foos and gets the members using ->get_foos.
304 #
305 sub xml_obj_children {
306     my ($xml,$parent) = (shift,shift);
307     my %args = @_;
308     my ($name,$collection_name,$methods)
309         = @args{qw/tag collection_tag methods/};
310     $collection_name ||= "${name}s";
311     my $meth = "get_$collection_name";
312
313     my @kids = $parent->$meth;
314     #@kids || return;
315     $xml->startTag( [ $Namespace => $collection_name ] );
316     for my $obj ( @kids ) {
317         xml_obj($xml, $obj,
318             tag     => "$name",
319             end_tag => 1,
320             methods => $methods,
321         );
322     }
323     $xml->endTag( [ $Namespace => $collection_name ] );
324 }
325
326 #
327 # Takes an XML::Writer, Schema::* object and list of method names
328 # and writes the obect out as XML. All methods values are written as attributes
329 # except for the methods listed in @MAP_AS_ELEMENTS which get written as child
330 # data elements.
331 #
332 # The attributes/tags are written in the same order as the method names are
333 # passed.
334 #
335 # TODO
336 # - Should the Namespace be passed in instead of global? Pass in the same
337 #   as Writer ie [ NS => TAGNAME ]
338 #
339 my $elements_re = join("|", @MAP_AS_ELEMENTS);
340 $elements_re = qr/^($elements_re)$/;
341 sub xml_obj {
342     my ($xml, $obj, %args) = @_;
343     my $tag                = $args{'tag'}              || '';
344     my $end_tag            = $args{'end_tag'}          || '';
345     my @meths              = @{ $args{'methods'} };
346     my $empty_tag          = 0;
347
348     # Use array to ensure consistant (ie not hash) ordering of attribs
349     # The order comes from the meths list passed in.
350     my @tags;
351     my @attr;
352     foreach ( grep { defined $obj->$_ } @meths ) {
353         my $what = m/$elements_re/ ? \@tags : \@attr;
354         my $val = $_ eq 'extra'
355             ? { $obj->$_ }
356             : $obj->$_;
357         $val = ref $val eq 'ARRAY' ? join(',', @$val) : $val;
358         push @$what, $_ => $val;
359     };
360     my $child_tags = @tags;
361     $end_tag && !$child_tags
362         ? $xml->emptyTag( [ $Namespace => $tag ], @attr )
363         : $xml->startTag( [ $Namespace => $tag ], @attr );
364     while ( my ($name,$val) = splice @tags,0,2 ) {
365         if ( ref $val eq 'HASH' ) {
366              $xml->emptyTag( [ $Namespace => $name ],
367                  map { ($_, $val->{$_}) } sort keys %$val );
368         }
369         else {
370             $xml->dataElement( [ $Namespace => $name ], $val );
371         }
372     }
373     $xml->endTag( [ $Namespace => $tag ] ) if $child_tags && $end_tag;
374 }
375
376 1;
377
378 # -------------------------------------------------------------------
379 # The eyes of fire, the nostrils of air,
380 # The mouth of water, the beard of earth.
381 # William Blake
382 # -------------------------------------------------------------------
383
384 =pod
385
386 =head1 AUTHORS
387
388 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
389 Darren Chamberlain E<lt>darren@cpan.orgE<gt>,
390 Mark Addison E<lt>mark.addison@itn.co.ukE<gt>.
391
392 =head1 SEE ALSO
393
394 L<perl(1)>, L<SQL::Translator>, L<SQL::Translator::Parser::XML::SQLFairy>,
395 L<SQL::Translator::Schema>, L<XML::Writer>.
396
397 =cut