File Coverage

blib/lib/SQL/Translator/Producer/XML/SQLFairy.pm
Criterion Covered Total %
statement 83 84 98.8
branch 20 26 76.9
condition 9 13 69.2
subroutine 9 9 100.0
pod 0 3 0.0
total 121 135 89.6


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