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