source: text-formbuilder/trunk/lib/Text/FormBuilder.pm @ 22

Last change on this file since 22 was 21, checked in by peter, 20 years ago

added !group and !field directive to the language to allow for horizontal groups of input fields
moving to a new representation of the parsed formspec based on lines as opposed to fields

File size: 18.8 KB
Line 
1package Text::FormBuilder;
2
3use strict;
4use warnings;
5
6use vars qw($VERSION);
7
8$VERSION = '0.06';
9
10use Carp;
11use Text::FormBuilder::Parser;
12use CGI::FormBuilder;
13
14sub new {
15    my $invocant = shift;
16    my $class = ref $invocant || $invocant;
17    my $self = {
18        parser => Text::FormBuilder::Parser->new,
19    };
20    return bless $self, $class;
21}
22
23sub parse {
24    my ($self, $source) = @_;
25    if (ref $source && ref $source eq 'SCALAR') {
26        $self->parse_text($$source);
27    } else {
28        $self->parse_file($source);
29    }
30}
31
32sub parse_file {
33    my ($self, $filename) = @_;
34   
35    # so it can be called as a class method
36    $self = $self->new unless ref $self;
37   
38    local $/ = undef;
39    open SRC, "< $filename";
40    my $src = <SRC>;
41    close SRC;
42   
43    return $self->parse_text($src);
44}
45
46sub parse_text {
47    my ($self, $src) = @_;
48   
49    # so it can be called as a class method
50    $self = $self->new unless ref $self;
51   
52    $self->{form_spec} = $self->{parser}->form_spec($src);
53   
54    # mark structures as not built (newly parsed text)
55    $self->{built} = 0;
56   
57    return $self;
58}
59
60sub build {
61    my ($self, %options) = @_;
62
63    # save the build options so they can be used from write_module
64    $self->{build_options} = { %options };
65   
66    # our custom %options:
67    # form_only: use only the form part of the template
68    my $form_only = $options{form_only};
69    delete $options{form_only};
70   
71    # substitute in custom pattern definitions for field validation
72    if (my %patterns = %{ $self->{form_spec}{patterns} || {} }) {
73        foreach (@{ $self->{form_spec}{fields} }) {
74            if ($$_{validate} and exists $patterns{$$_{validate}}) {
75                $$_{validate} = $patterns{$$_{validate}};
76            }
77        }
78    }
79   
80    # remove extraneous undefined values
81    for my $field (@{ $self->{form_spec}{fields} }) {
82        defined $$field{$_} or delete $$field{$_} foreach keys %{ $field };
83    }
84   
85    # so we don't get all fields required
86    foreach (@{ $self->{form_spec}{fields} }) {
87        delete $$_{validate} unless $$_{validate};
88    }
89
90    # expand groups
91    my %groups = %{ $self->{form_spec}{groups} };
92    foreach (grep { $$_[0] eq 'group' } @{ $self->{form_spec}{lines} }) {
93        $$_[1]{group} =~ s/^\%//;       # strip leading % from group var name
94       
95        if (exists $groups{$$_[1]{group}}) {
96            my @fields; # fields in the group
97            push @fields, { %$_ } foreach @{ $groups{$$_[1]{group}} };
98            for my $field (@fields) {
99                $$field{label} ||= ucfirst $$field{name};
100                $$field{name} = "$$_[1]{name}_$$field{name}";               
101            }
102            $_ = [ 'group', { label => $$_[1]{label} || ucfirst(join(' ',split('_',$$_[1]{name}))), group => \@fields } ];
103        }
104    }
105   
106    $self->{form_spec}{fields} = [];
107    for my $line (@{ $self->{form_spec}{lines} }) {
108        if ($$line[0] eq 'group') {
109            push @{ $self->{form_spec}{fields} }, $_ foreach @{ $$line[1]{group} };
110        } elsif ($$line[0] eq 'field') {
111            push @{ $self->{form_spec}{fields} }, $$line[1];
112        }
113    }
114   
115   
116    # substitute in list names
117    if (my %lists = %{ $self->{form_spec}{lists} || {} }) {
118        foreach (@{ $self->{form_spec}{fields} }) {
119            next unless $$_{list};
120           
121            $$_{list} =~ s/^\@//;   # strip leading @ from list var name
122           
123            # a hack so we don't get screwy reference errors
124            if (exists $lists{$$_{list}}) {
125                my @list;
126                push @list, { %$_ } foreach @{ $lists{$$_{list}} };
127                $$_{options} = \@list;
128            }
129        } continue {
130            delete $$_{list};
131        }
132    }
133   
134   
135   
136
137   
138    # TODO: use lines instead of fields
139    # TODO: change template to do groups
140   
141    # TODO: configurable threshold for this
142    foreach (@{ $self->{form_spec}{fields} }) {
143        $$_{ulist} = 1 if defined $$_{options} and @{ $$_{options} } >= 3;
144    }
145   
146    $self->{form} = CGI::FormBuilder->new(
147        method => 'GET',
148        javascript => 0,
149        keepextras => 1,
150        title => $self->{form_spec}{title},
151        #fields => [ map { $$_{name} } @{ $self->{form_spec}{fields} } ],
152        template => {
153            type => 'Text',
154            engine => {
155                TYPE       => 'STRING',
156                SOURCE     => $form_only ? $self->_form_template : $self->_template,
157                DELIMITERS => [ qw(<% %>) ],
158            },
159            data => {
160                lines       => $self->{form_spec}{lines},
161                headings    => $self->{form_spec}{headings},
162                author      => $self->{form_spec}{author},
163                description => $self->{form_spec}{description},
164            },
165        },
166        %options,
167    );
168    $self->{form}->field(%{ $_ }) foreach @{ $self->{form_spec}{fields} };
169   
170    # mark structures as built
171    $self->{built} = 1;
172   
173    # TEMP: dump @lines structure
174    use YAML;
175    warn YAML::Dump($self->{form_spec}->{lines}), "\n";
176   
177    return $self;
178}
179
180sub write {
181    my ($self, $outfile) = @_;
182   
183    # automatically call build if needed to
184    # allow the new->parse->write shortcut
185    $self->build unless $self->{built};
186   
187    if ($outfile) {
188        open FORM, "> $outfile";
189        print FORM $self->form->render;
190        close FORM;
191    } else {
192        print $self->form->render;
193    }
194}
195
196sub write_module {
197    my ($self, $package, $use_tidy) = @_;
198
199    croak 'Expecting a package name' unless $package;
200   
201    # automatically call build if needed to
202    # allow the new->parse->write shortcut
203    $self->build unless $self->{built};
204   
205    # conditionally use Data::Dumper
206    eval 'use Data::Dumper;';
207    die "Can't write module; need Data::Dumper. $@" if $@;
208   
209    # don't dump $VARn names
210    $Data::Dumper::Terse = 1;
211   
212    my $title       = $self->{form_spec}{title} || '';
213    my $author      = $self->{form_spec}{author} || '';
214    my $description = $self->{form_spec}{description} || '';
215   
216    my $headings    = Data::Dumper->Dump([$self->{form_spec}{headings}],['headings']);
217    my $fields      = Data::Dumper->Dump([ [ map { $$_{name} } @{ $self->{form_spec}{fields} } ] ],['fields']);
218   
219    my %options = %{ $self->{build_options} };
220    my $source = $options{form_only} ? $self->_form_template : $self->_template;
221   
222    delete $options{form_only};
223   
224    my $form_options = keys %options > 0 ? Data::Dumper->Dump([$self->{build_options}],['*options']) : '';
225   
226    my $field_setup = join(
227        "\n", 
228        map { '$cgi_form->field' . Data::Dumper->Dump([$_],['*field']) . ';' } @{ $self->{form_spec}{fields} }
229    );
230   
231    my $module = <<END;
232package $package;
233use strict;
234use warnings;
235
236use CGI::FormBuilder;
237
238sub get_form {
239    my \$cgi = shift;
240    my \$cgi_form = CGI::FormBuilder->new(
241        method => 'GET',
242        params => \$cgi,
243        javascript => 0,
244        keepextras => 1,
245        title => q[$title],
246        fields => $fields,
247        template => {
248            type => 'Text',
249            engine => {
250                TYPE       => 'STRING',
251                SOURCE     => q[$source],
252                DELIMITERS => [ qw(<% %>) ],
253            },
254            data => {
255                headings => $headings,
256                author   => q[$author],
257                description => q[$description],
258            },
259        },
260        $form_options
261    );
262   
263    $field_setup
264   
265    return \$cgi_form;
266}
267
268# module return
2691;
270END
271   
272    my $outfile = (split(/::/, $package))[-1] . '.pm';
273   
274    if ($use_tidy) {
275        # clean up the generated code, if asked
276        eval 'use Perl::Tidy';
277        die "Can't tidy the code: $@" if $@;
278        Perl::Tidy::perltidy(source => \$module, destination => $outfile);
279    } else {
280        # otherwise, just print as is
281        open FORM, "> $outfile";
282        print FORM $module;
283        close FORM;
284    }
285}
286
287sub form {
288    my $self = shift;
289   
290    # automatically call build if needed to
291    # allow the new->parse->write shortcut
292    $self->build unless $self->{built};
293
294    return $self->{form};
295}
296
297sub _form_template {
298q[<% $description ? qq[<p id="description">$description</p>] : '' %>
299<% (grep { $_->{required} } @fields) ? qq[<p id="instructions">(Required fields are marked in <strong>bold</strong>.)</p>] : '' %>
300<% $start %>
301<table>
302
303<% for my $line (@lines) {
304
305    if ($$line[0] eq 'head') {
306        $OUT .= qq[  <tr><th class="sectionhead" colspan="2"><h2>$$line[1]</h2></th></tr>\n]
307    } elsif ($$line[0] eq 'field') {
308        #TODO: we only need the field names, not the full field spec in the lines strucutre
309        local $_ = $field{$$line[1]{name}};
310        $OUT .= $$_{invalid} ? qq[  <tr class="invalid">] : qq[  <tr>];
311        $OUT .= '<th class="label">' . ($$_{required} ? qq[<strong class="required">$$_{label}:</strong>] : "$$_{label}:") . '</th>';
312        if ($$_{invalid}) {
313            $OUT .= qq[<td>$$_{field} $$_{comment} Missing or invalid value.</td></tr>\n];
314        } else {
315            $OUT .= qq[<td>$$_{field} $$_{comment}</td></tr>\n];
316        }
317    } elsif ($$line[0] eq 'group') {
318        my @field_names = map { $$_{name} } @{ $$line[1]{group} };
319        my @group_fields = map { $field{$_} } @field_names;
320        $OUT .= (grep { $$_{invalid} } @group_fields) ? qq[  <tr class="invalid">\n] : qq[  <tr>\n];
321       
322       
323        #TODO: validated but not required fields
324        # in a form spec: //EMAIL?
325       
326        #TODO: this doesn't seem to be working; all groups are getting marked as required       
327        $OUT .= '    <th class="label">';
328        $OUT .= (grep { $$_{required} } @group_fields) ? qq[<strong class="required">$$line[1]{label}:</strong>] : "$$line[1]{label}:";
329        $OUT .= qq[</th>\n];
330       
331        $OUT .= qq[    <td>];
332        $OUT .= join(' ', map { qq[<small class="sublabel">$$_{label}</small> $$_{field} $$_{comment}] } @group_fields);
333        $OUT .= qq[    </td>\n];
334        $OUT .= qq[  </tr>\n];
335    }   
336   
337
338} %>
339  <tr><th></th><td style="padding-top: 1em;"><% $submit %></td></tr>
340</table>
341<% $end %>
342];
343}
344
345sub _template {
346    my $self = shift;
347q[<html>
348<head>
349  <title><% $title %><% $author ? ' - ' . ucfirst $author : '' %></title>
350  <style type="text/css">
351    #author, #footer { font-style: italic; }
352    th { text-align: left; }
353    th h2 { padding: .125em .5em; background: #eee; }
354    th.label { font-weight: normal; text-align: right; vertical-align: top; }
355    td ul { list-style: none; padding-left: 0; margin-left: 0; }
356    .sublabel { color: #999; }
357  </style>
358</head>
359<body>
360
361<h1><% $title %></h1>
362<% $author ? qq[<p id="author">Created by $author</p>] : '' %>
363] . $self->_form_template . q[
364<hr />
365<div id="footer">
366  <p id="creator">Made with <a href="http://formbuilder.org/">CGI::FormBuilder</a> version <% CGI::FormBuilder->VERSION %>.</p>
367</div>
368</body>
369</html>
370];
371}
372
373sub dump { 
374    eval "use YAML;";
375    unless ($@) {
376        print YAML::Dump(shift->{form_spec});
377    } else {
378        warn "Can't dump form spec structure: $@";
379    }
380}
381
382
383# module return
3841;
385
386=head1 NAME
387
388Text::FormBuilder - Create CGI::FormBuilder objects from simple text descriptions
389
390=head1 SYNOPSIS
391
392    use Text::FormBuilder;
393   
394    my $parser = Text::FormBuilder->new;
395    $parser->parse($src_file);
396   
397    # returns a new CGI::FormBuilder object with
398    # the fields from the input form spec
399    my $form = $parser->form;
400   
401    # write a My::Form module to Form.pm
402    $parser->write_module('My::Form');
403
404=head1 DESCRIPTION
405
406=head2 new
407
408=head2 parse
409
410    # parse a file
411    $parser->parse($filename);
412   
413    # or pass a scalar ref for parse a literal string
414    $parser->parse(\$string);
415
416Parse the file or string. Returns the parser object.
417
418=head2 parse_file
419
420    $parser->parse_file($src_file);
421   
422    # or as a class method
423    my $parser = Text::FormBuilder->parse($src_file);
424
425=head2 parse_text
426
427    $parser->parse_text($src);
428
429Parse the given C<$src> text. Returns the parser object.
430
431=head2 build
432
433    $parser->build(%options);
434
435Builds the CGI::FormBuilder object. Options directly used by C<build> are:
436
437=over
438
439=item C<form_only>
440
441Only uses the form portion of the template, and omits the surrounding html,
442title, author, and the standard footer. This does, however, include the
443description as specified with the C<!description> directive.
444
445=back
446
447All other options given to C<build> are passed on verbatim to the
448L<CGI::FormBuilder> constructor. Any options given here override the
449defaults that this module uses.
450
451The C<form>, C<write>, and C<write_module> methods will all call
452C<build> with no options for you if you do not do so explicitly.
453This allows you to say things like this:
454
455    my $form = Text::FormBuilder->new->parse('formspec.txt')->form;
456
457However, if you need to specify options to C<build>, you must call it
458explictly after C<parse>.
459
460=head2 form
461
462    my $form = $parser->form;
463
464Returns the L<CGI::FormBuilder> object. Remember that you can modify
465this object directly, in order to (for example) dynamically populate
466dropdown lists or change input types at runtime.
467
468=head2 write
469
470    $parser->write($out_file);
471    # or just print to STDOUT
472    $parser->write;
473
474Calls C<render> on the FormBuilder form, and either writes the resulting HTML
475to a file, or to STDOUT if no filename is given.
476
477=head2 write_module
478
479    $parser->write_module($package, $use_tidy);
480
481Takes a package name, and writes out a new module that can be used by your
482CGI script to render the form. This way, you only need CGI::FormBuilder on
483your server, and you don't have to parse the form spec each time you want
484to display your form. The generated module has one function (not exported)
485called C<get_form>, that takes a CGI object as its only argument, and returns
486a CGI::FormBuilder object.
487
488First, you parse the formspec and write the module, which you can do as a one-liner:
489
490    $ perl -MText::FormBuilder -e"Text::FormBuilder->parse('formspec.txt')->write_module('My::Form')"
491
492And then, in your CGI script, use the new module:
493
494    #!/usr/bin/perl -w
495    use strict;
496   
497    use CGI;
498    use My::Form;
499   
500    my $q = CGI->new;
501    my $form = My::Form::get_form($q);
502   
503    # do the standard CGI::FormBuilder stuff
504    if ($form->submitted && $form->validate) {
505        # process results
506    } else {
507        print $q->header;
508        print $form->render;
509    }
510
511If you pass a true value as the second argument to C<write_module>, the parser
512will run L<Perl::Tidy> on the generated code before writing the module file.
513
514    # write tidier code
515    $parser->write_module('My::Form', 1);
516
517=head2 dump
518
519Uses L<YAML> to print out a human-readable representation of the parsed
520form spec.
521
522=head1 LANGUAGE
523
524    field_name[size]|descriptive label[hint]:type=default{option1[display string],...}//validate
525   
526    !title ...
527   
528    !author ...
529   
530    !description {
531        ...
532    }
533   
534    !pattern name /regular expression/
535   
536    !list name {
537        option1[display string],
538        option2[display string],
539        ...
540    }
541   
542    !list name &{ CODE }
543   
544    !head ...
545
546=head2 Directives
547
548=over
549
550=item C<!pattern>
551
552Defines a validation pattern.
553
554=item C<!list>
555
556Defines a list for use in a C<radio>, C<checkbox>, or C<select> field.
557
558=item C<!title>
559
560=item C<!author>
561
562=item C<!description>
563
564A brief description of the form. Suitable for special instructions on how to
565fill out the form.
566
567=item C<!head>
568
569Inserts a heading between two fields. There can only be one heading between
570any two fields; the parser will warn you if you try to put two headings right
571next to each other.
572
573=back
574
575=head2 Fields
576
577Form fields are each described on a single line. The simplest field is just a
578name:
579
580    color
581
582This yields a form with one text input field of the default size named `color'.
583The label for this field as generated by CGI::FormBuilder would be ``Color''.
584To add a longer or more descriptive label, use:
585
586    color|Favorite color
587
588Field names cannot contain whitespace, but the descriptive label can.
589
590To use a different input type:
591
592    color|Favorite color:select{red,blue,green}
593
594Recognized input types are the same as those used by CGI::FormBuilder:
595
596    text        # the default
597    textarea
598    select
599    radio
600    checkbox
601    static
602
603To change the size of the input field, add a bracketed subscript after the
604field name (but before the descriptive label):
605
606    # for a single line field, sets size="40"
607    title[40]:text
608   
609    # for a multiline field, sets rows="4" and cols="30"
610    description[4,30]:textarea
611
612For the input types that can have options (C<select>, C<radio>, and
613C<checkbox>), here's how you do it:
614
615    color|Favorite color:select{red,blue,green}
616
617Values are in a comma-separated list inside curly braces. Whitespace
618between values is irrelevant, although there cannot be any whitespace
619within a value.
620
621To add more descriptive display text to a vlaue in a list, add a square-bracketed
622``subscript,'' as in:
623
624    ...:select{red[Scarlet],blue[Azure],green[Olive Drab]}
625
626As you can see, spaces I<are> allowed within the display text for a value.
627
628If you have a list of options that is too long to fit comfortably on one line,
629consider using the C<!list> directive:
630
631    !list MONTHS {
632        1[January],
633        2[February],
634        3[March],
635        # and so on...
636    }
637   
638    month:select@MONTHS
639
640There is another form of the C<!list> directive: the dynamic list:
641
642    !list RANDOM &{ map { rand } (0..5) }
643
644The code inside the C<&{ ... }> is C<eval>ed by C<build>, and the results
645are stuffed into the list. The C<eval>ed code can either return a simple
646list, as the example does, or the fancier C<< ( { value1 => 'Description 1'},
647{ value2 => 'Description 2}, ... ) >> form.
648
649B<NOTE:> This feature of the language may go away unless I find a compelling
650reason for it in the next few versions. What I really wanted was lists that
651were filled in at run-time (e.g. from a database), and that can be done easily
652enough with the CGI::FormBuilder object directly.
653
654You can also supply a default value to the field. To get a default value of
655C<green> for the color field:
656
657    color|Favorite color:select=green{red,blue,green}
658
659To validate a field, include a validation type at the end of the field line:
660
661    email|Email address//EMAIL
662
663Valid validation types include any of the builtin defaults from L<CGI::FormBuilder>,
664or the name of a pattern that you define with the C<!pattern> directive elsewhere
665in your form spec:
666
667    !pattern DAY /^([1-3][0-9])|[1-9]$/
668   
669    last_day//DAY
670
671If you just want a required value, use the builtin validation type C<VALUE>:
672
673    title//VALUE
674
675=head2 Comments
676
677    # comment ...
678
679Any line beginning with a C<#> is considered a comment.
680
681=head1 TODO
682
683C<!include> directive to include external formspec files
684
685Field groups all on one line in the generated form
686
687Better tests!
688
689=head1 SEE ALSO
690
691L<CGI::FormBuilder>
692
693=head1 AUTHOR
694
695Peter Eichman <peichman@cpan.org>
696
697=head1 COPYRIGHT AND LICENSE
698
699Copyright E<copy>2004 by Peter Eichman.
700
701This program is free software; you can redistribute it and/or
702modify it under the same terms as Perl itself.
703
704=cut
Note: See TracBrowser for help on using the repository browser.