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