9a5bc3da321cc4fbfcc2ce81a644e19abf06b57f
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Producer / HTML.pm
1 package SQL::Translator::Producer::HTML;
2
3 # -------------------------------------------------------------------
4 # $Id: HTML.pm 1445 2009-02-07 17:50:03Z ashberlin $
5 # -------------------------------------------------------------------
6 # Copyright (C) 2002-2009 SQLFairy Authors
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 use strict;
24 use Data::Dumper;
25 use vars qw($VERSION $NOWRAP $NOLINKTABLE $NAME);
26
27 $VERSION = '1.99';
28
29 $NAME = __PACKAGE__;
30 $NOWRAP = 0 unless defined $NOWRAP;
31 $NOLINKTABLE = 0 unless defined $NOLINKTABLE;
32
33 # Emit XHTML by default
34 $CGI::XHTML = $CGI::XHTML = 42;
35
36 use SQL::Translator::Schema::Constants;
37
38 # -------------------------------------------------------------------
39 # Main entry point.  Returns a string containing HTML.
40 # -------------------------------------------------------------------
41 sub produce {
42     my $t           = shift;
43     my $args        = $t->producer_args;
44     my $schema      = $t->schema;
45     my $schema_name = $schema->name || 'Schema';
46     my $title       = $args->{'title'} || "Description of $schema_name";
47     my $wrap        = ! (defined $args->{'nowrap'}
48                                ? $args->{'nowrap'}
49                                : $NOWRAP);
50     my $linktable   = ! (defined $args->{'nolinktable'}
51                                ? $args->{'nolinktable'}
52                                : $NOLINKTABLE);
53     my %stylesheet  = defined $args->{'stylesheet'}
54                     ? ( -style => { src => $args->{'stylesheet'} } )
55                     : ( );
56     my @html;
57     my $q           = defined $args->{'pretty'}
58                     ? do { require CGI::Pretty;
59                             import CGI::Pretty;
60                                    CGI::Pretty->new }
61                     : do { require CGI;
62                             import CGI; 
63                                    CGI->new };
64     my ($table, @table_names);
65
66     if ($wrap) {
67         push @html,
68             $q->start_html({
69                 -title => $title,
70                 %stylesheet,
71                 -meta => { generator => $NAME },
72             }),
73             $q->h1({ -class => 'SchemaDescription' }, $title),
74             $q->hr;
75     }
76
77     @table_names = grep { length $_->name } $schema->get_tables; 
78
79     if ($linktable) {
80         # Generate top menu, with links to full table information
81         my $count = scalar(@table_names);
82         $count = sprintf "%d table%s", $count, $count == 1 ? '' : 's';
83
84         # Leading table of links
85         push @html, 
86             $q->comment("Table listing ($count)"),
87             $q->a({ -name => 'top' }),
88             $q->start_table({ -width => '100%', -class => 'LinkTable'}),
89
90             # XXX This needs to be colspan="$#{$table->fields}" class="LinkTableHeader"
91             $q->Tr(
92                 $q->td({ -class => 'LinkTableCell' },
93                     $q->h2({ -class => 'LinkTableTitle' },
94                         'Tables'
95                     ),
96                 ),
97             );
98
99         for my $table (@table_names) {
100             my $table_name = $table->name;
101             push @html, 
102                 $q->comment("Start link to table '$table_name'"),
103                 $q->Tr({ -class => 'LinkTableRow' },
104                     $q->td({ -class => 'LinkTableCell' },
105                         qq[<a id="${table_name}-link" href="#$table_name">$table_name</a>]
106                     )
107                 ),
108                 $q->comment("End link to table '$table_name'");
109         }
110         push @html, $q->end_table;
111     }
112
113     for my $table ($schema->get_tables) {
114         my $table_name = $table->name       or next;
115         my @fields     = $table->get_fields or next;
116         push @html,
117             $q->comment("Starting table '$table_name'"),
118             $q->a({ -name => $table_name }),
119             $q->table({ -class => 'TableHeader', -width => '100%' },
120                 $q->Tr({ -class => 'TableHeaderRow' },
121                     $q->td({ -class => 'TableHeaderCell' }, $q->h3($table_name)),
122                         qq[<a name="$table_name">],
123                     $q->td({ -class => 'TableHeaderCell', -align => 'right' },
124                         qq[<a href="#top">Top</a>]
125                     )
126                 )
127             );
128
129         if ( my @comments = map { $_ ? $_ : () } $table->comments ) {
130             push @html,
131                 $q->b("Comments:"),
132                     $q->br,
133                     $q->em(map { $q->br, $_ } @comments);
134         }
135
136         #
137         # Fields
138         #
139         push @html,
140             $q->start_table({ -border => 1 }),
141                 $q->Tr(
142                     $q->th({ -class => 'FieldHeader' },
143                            [ 
144                             'Field Name', 
145                             'Data Type', 
146                             'Size', 
147                             'Default Value', 
148                             'Other', 
149                             'Foreign Key' 
150                            ]
151                     ) 
152                 );
153
154         my $i = 0;
155         for my $field ( @fields ) {
156             my $name      = $field->name      || '';
157                $name      = qq[<a name="$table_name-$name">$name</a>];
158             my $data_type = $field->data_type || '';
159             my $size      = defined $field->size ? $field->size : '';
160             my $default   = defined $field->default_value 
161                             ? $field->default_value : '';
162             my $comment   = $field->comments  || '';
163             my $fk        = '';
164
165             if ($field->is_foreign_key) {
166                 my $c         = $field->foreign_key_reference;
167                 my $ref_table = $c->reference_table       || '';
168                 my $ref_field = ($c->reference_fields)[0] || '';
169                 $fk           = 
170                 qq[<a href="#$ref_table-$ref_field">$ref_table.$ref_field</a>];
171             }
172
173             my @other = ();
174             push @other, 'PRIMARY KEY' if $field->is_primary_key;
175             push @other, 'UNIQUE'      if $field->is_unique;
176             push @other, 'NOT NULL'    unless $field->is_nullable;
177             push @other, $comment      if $comment;
178             my $class = $i++ % 2 ? 'even' : 'odd';
179             push @html,
180                 $q->Tr(
181                     { -class => "tr-$class" },
182                     $q->td({ -class => "FieldCellName" }, $name),
183                     $q->td({ -class => "FieldCellType" }, $data_type),
184                     $q->td({ -class => "FieldCellSize" }, $size),
185                     $q->td({ -class => "FieldCellDefault" }, $default),
186                     $q->td({ -class => "FieldCellOther" }, join(', ', @other)),
187                     $q->td({ -class => "FieldCellFK" }, $fk),
188                 );
189         }
190         push @html, $q->end_table;
191
192         #
193         # Indices
194         #
195         if ( my @indices = $table->get_indices ) {
196             push @html, 
197                 $q->h3('Indices'),
198                 $q->start_table({ -border => 1 }),
199                     $q->Tr({ -class => 'IndexRow' },
200                         $q->th([ 'Name', 'Fields' ]) 
201                     );
202
203             for my $index ( @indices ) {
204                 my $name   = $index->name || '';
205                 my $fields = join( ', ', $index->fields ) || '';
206
207                 push @html,
208                     $q->Tr({ -class => 'IndexCell' },
209                         $q->td( [ $name, $fields ] )
210                     );
211             }
212
213             push @html, $q->end_table;
214         }
215
216         #
217         # Constraints
218         #
219         my @constraints = 
220             grep { $_->type ne PRIMARY_KEY } $table->get_constraints;
221         if ( @constraints ) {
222             push @html, 
223                 $q->h3('Constraints'),
224                 $q->start_table({ -border => 1 }),
225                     $q->Tr({ -class => 'IndexRow' },
226                         $q->th([ 'Type', 'Fields' ]) 
227                     );
228
229             for my $c ( @constraints ) {
230                 my $type   = $c->type || '';
231                 my $fields = join( ', ', $c->fields ) || '';
232
233                 push @html,
234                     $q->Tr({ -class => 'IndexCell' },
235                         $q->td( [ $type, $fields ] )
236                     );
237             }
238
239             push @html, $q->end_table;
240         }
241
242         push @html, $q->hr;
243     }
244
245     my $sqlt_version = $t->version;
246     if ($wrap) {
247         push @html,
248             qq[Created by <a href="http://sqlfairy.sourceforge.net">],
249             qq[SQL::Translator $sqlt_version</a>],
250             $q->end_html;
251     }
252
253
254     return join "\n", @html;
255 }
256
257 1;
258
259 # -------------------------------------------------------------------
260 # Always be ready to speak your mind,
261 # and a base man will avoid you.
262 # William Blake
263 # -------------------------------------------------------------------
264
265 =head1 NAME
266
267 SQL::Translator::Producer::HTML - HTML producer for SQL::Translator
268
269 =head1 SYNOPSIS
270
271   use SQL::Translator::Producer::HTML;
272
273 =head1 DESCRIPTION
274
275 Creates an HTML document describing the tables.
276
277 The HTML produced is composed of a number of tables:
278
279 =over 4
280
281 =item Links
282
283 A link table sits at the top of the output, and contains anchored
284 links to elements in the rest of the document.
285
286 If the I<nolinktable> producer arg is present, then this table is not
287 produced.
288
289 =item Tables
290
291 Each table in the schema has its own HTML table.  The top row is a row
292 of E<lt>thE<gt> elements, with a class of B<FieldHeader>; these
293 elements are I<Field Name>, I<Data Type>, I<Size>, I<Default Value>,
294 I<Other> and I<Foreign Key>.  Each successive row describes one field
295 in the table, and has a class of B<FieldCell$item>, where $item id
296 corresponds to the label of the column.  For example:
297
298     <tr>
299         <td class="FieldCellName"><a name="random-id">id</a></td>
300         <td class="FieldCellType">int</td>
301         <td class="FieldCellSize">11</td>
302         <td class="FieldCellDefault"></td>
303         <td class="FieldCellOther">PRIMARY KEY, NOT NULL</td>
304         <td class="FieldCellFK"></td>
305     </tr>
306
307     <tr>
308         <td class="FieldCellName"><a name="random-foo">foo</a></td>
309         <td class="FieldCellType">varchar</td>
310         <td class="FieldCellSize">255</td>
311         <td class="FieldCellDefault"></td>
312         <td class="FieldCellOther">NOT NULL</td>
313         <td class="FieldCellFK"></td>
314     </tr>
315
316     <tr>
317         <td class="FieldCellName"><a name="random-updated">updated</a></td>
318         <td class="FieldCellType">timestamp</td>
319         <td class="FieldCellSize">0</td>
320         <td class="FieldCellDefault"></td>
321         <td class="FieldCellOther"></td>
322         <td class="FieldCellFK"></td>
323     </tr>
324
325 =back
326
327 Unless the I<nowrap> producer arg is present, the HTML will be
328 enclosed in a basic HTML header and footer.
329
330 If the I<pretty> producer arg is present, the generated HTML will be
331 nicely spaced and human-readable.  Otherwise, it will have very little
332 insignificant whitespace and be generally smaller.
333
334
335 =head1 AUTHORS
336
337 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
338 Darren Chamberlain E<lt>darren@cpan.orgE<gt>.
339
340 =cut