Index: trunk/lib/Text/FormBuilder.pm
===================================================================
--- trunk/lib/Text/FormBuilder.pm	(revision 28)
+++ trunk/lib/Text/FormBuilder.pm	(revision 29)
@@ -87,27 +87,34 @@
     # expand groups
     my %groups = %{ $self->{form_spec}{groups} || {} };
-    foreach (grep { $$_[0] eq 'group' } @{ $self->{form_spec}{lines} || [] }) {
-        $$_[1]{group} =~ s/^\%//;       # strip leading % from group var name
-        
-        if (exists $groups{$$_[1]{group}}) {
-            my @fields; # fields in the group
-            push @fields, { %$_ } foreach @{ $groups{$$_[1]{group}} };
-            for my $field (@fields) {
-                $$field{label} ||= ucfirst $$field{name};
-                $$field{name} = "$$_[1]{name}_$$field{name}";                
+    
+    for my $section (@{ $self->{form_spec}{sections} || [] }) {
+##         foreach (grep { $$_[0] eq 'group' } @{ $self->{form_spec}{lines} || [] }) {
+        foreach (grep { $$_[0] eq 'group' } @{ $$section{lines} }) {
+            $$_[1]{group} =~ s/^\%//;       # strip leading % from group var name
+            
+            if (exists $groups{$$_[1]{group}}) {
+                my @fields; # fields in the group
+                push @fields, { %$_ } foreach @{ $groups{$$_[1]{group}} };
+                for my $field (@fields) {
+                    $$field{label} ||= ucfirst $$field{name};
+                    $$field{name} = "$$_[1]{name}_$$field{name}";                
+                }
+                $_ = [ 'group', { label => $$_[1]{label} || ucfirst(join(' ',split('_',$$_[1]{name}))), group => \@fields } ];
             }
-            $_ = [ 'group', { label => $$_[1]{label} || ucfirst(join(' ',split('_',$$_[1]{name}))), group => \@fields } ];
         }
     }
     
     $self->{form_spec}{fields} = [];
-    for my $line (@{ $self->{form_spec}{lines} || [] }) {
-        if ($$line[0] eq 'group') {
-            push @{ $self->{form_spec}{fields} }, $_ foreach @{ $$line[1]{group} };
-        } elsif ($$line[0] eq 'field') {
-            push @{ $self->{form_spec}{fields} }, $$line[1];
+    
+    for my $section (@{ $self->{form_spec}{sections} || [] }) {
+        #for my $line (@{ $self->{form_spec}{lines} || [] }) {
+        for my $line (@{ $$section{lines} }) {
+            if ($$line[0] eq 'group') {
+                push @{ $self->{form_spec}{fields} }, $_ foreach @{ $$line[1]{group} };
+            } elsif ($$line[0] eq 'field') {
+                push @{ $self->{form_spec}{fields} }, $$line[1];
+            }
         }
     }
-    
     
     # substitute in list names
@@ -149,7 +156,8 @@
     $$_{required} or delete $$_{required} foreach @{ $self->{form_spec}{fields} };
 
-    
+    # need to explicity set the fields so that simple text fields get picked up
     $self->{form} = CGI::FormBuilder->new(
         %DEFAULT_OPTIONS,
+        fields   => [ map { $$_{name} } @{ $self->{form_spec}{fields} } ],
         required => [ map { $$_{name} } grep { $$_{required} } @{ $self->{form_spec}{fields} } ],
         title => $self->{form_spec}{title},
@@ -163,5 +171,5 @@
             },
             data => {
-                lines       => $self->{form_spec}{lines},
+                sections    => $self->{form_spec}{sections},
                 author      => $self->{form_spec}{author},
                 description => $self->{form_spec}{description},
@@ -214,4 +222,5 @@
         title => $self->{form_spec}{title},
         text  => $self->{form_spec}{description},
+        fields   => [ map { $$_{name} } @{ $self->{form_spec}{fields} } ],
         required => [ map { $$_{name} } grep { $$_{required} } @{ $self->{form_spec}{fields} } ],
         template => {
@@ -223,5 +232,5 @@
             },
             data => {
-                lines       => $self->{form_spec}{lines},
+                sections    => $self->{form_spec}{sections},
                 author      => $self->{form_spec}{author},
                 description => $self->{form_spec}{description},
@@ -299,40 +308,44 @@
 %>
 
-<table>
-
-<% TABLE_LINE: for my $line (@lines) {
-
-    if ($$line[0] eq 'head') {
-        $OUT .= qq[  <tr><th class="sectionhead" colspan="2"><h2>$$line[1]</h2></th></tr>\n]
-    } elsif ($$line[0] eq 'field') {
-        #TODO: we only need the field names, not the full field spec in the lines strucutre
-        local $_ = $field{$$line[1]{name}};
-        # skip hidden fields in the table
-        next TABLE_LINE if $$_{type} eq 'hidden';
-        
-        $OUT .= $$_{invalid} ? qq[  <tr class="invalid">] : qq[  <tr>];
-        $OUT .= '<th class="label">' . ($$_{required} ? qq[<strong class="required">$$_{label}:</strong>] : "$$_{label}:") . '</th>';
-        if ($$_{invalid}) {
-            $OUT .= qq[<td>$$_{field} $$_{comment} Missing or invalid value.</td></tr>\n];
-        } else {
-            $OUT .= qq[<td>$$_{field} $$_{comment}</td></tr>\n];
+<%
+    SECTION: while (my $section = shift @sections) {
+        $OUT .= qq[<table id="$$section{id}">\n];
+        $OUT .= qq[  <caption><h2>$$section{head}</h2></caption>] if $$section{head};
+        TABLE_LINE: for my $line (@{ $$section{lines} }) {
+            if ($$line[0] eq 'head') {
+                $OUT .= qq[  <tr><th class="sectionhead" colspan="2"><h3>$$line[1]</h3></th></tr>\n]
+            } elsif ($$line[0] eq 'field') {
+                #TODO: we only need the field names, not the full field spec in the lines strucutre
+                local $_ = $field{$$line[1]{name}};
+                # skip hidden fields in the table
+                next TABLE_LINE if $$_{type} eq 'hidden';
+                
+                $OUT .= $$_{invalid} ? qq[  <tr class="invalid">] : qq[  <tr>];
+                $OUT .= '<th class="label">' . ($$_{required} ? qq[<strong class="required">$$_{label}:</strong>] : "$$_{label}:") . '</th>';
+                if ($$_{invalid}) {
+                    $OUT .= qq[<td>$$_{field} $$_{comment} Missing or invalid value.</td></tr>\n];
+                } else {
+                    $OUT .= qq[<td>$$_{field} $$_{comment}</td></tr>\n];
+                }
+            } elsif ($$line[0] eq 'group') {
+                my @field_names = map { $$_{name} } @{ $$line[1]{group} };
+                my @group_fields = map { $field{$_} } @field_names;
+                $OUT .= (grep { $$_{invalid} } @group_fields) ? qq[  <tr class="invalid">\n] : qq[  <tr>\n];
+                
+                $OUT .= '    <th class="label">';
+                $OUT .= (grep { $$_{required} } @group_fields) ? qq[<strong class="required">$$line[1]{label}:</strong>] : "$$line[1]{label}:";
+                $OUT .= qq[</th>\n];
+                
+                $OUT .= qq[    <td>];
+                $OUT .= join(' ', map { qq[<small class="sublabel">$$_{label}</small> $$_{field} $$_{comment}] } @group_fields);
+                $OUT .= qq[    </td>\n];
+                $OUT .= qq[  </tr>\n];
+            }   
         }
-    } elsif ($$line[0] eq 'group') {
-        my @field_names = map { $$_{name} } @{ $$line[1]{group} };
-        my @group_fields = map { $field{$_} } @field_names;
-        $OUT .= (grep { $$_{invalid} } @group_fields) ? qq[  <tr class="invalid">\n] : qq[  <tr>\n];
-        
-        $OUT .= '    <th class="label">';
-        $OUT .= (grep { $$_{required} } @group_fields) ? qq[<strong class="required">$$line[1]{label}:</strong>] : "$$line[1]{label}:";
-        $OUT .= qq[</th>\n];
-        
-        $OUT .= qq[    <td>];
-        $OUT .= join(' ', map { qq[<small class="sublabel">$$_{label}</small> $$_{field} $$_{comment}] } @group_fields);
-        $OUT .= qq[    </td>\n];
-        $OUT .= qq[  </tr>\n];
-    }   
-    
-
-} %>
+        # close the table if there are sections remaining
+        # but leave the last one open for the submit button
+        $OUT .= qq[</table>\n] if @sections;
+    }
+%>
   <tr><th></th><td style="padding-top: 1em;"><% $submit %></td></tr>
 </table>
@@ -347,7 +360,9 @@
   <title><% $title %><% $author ? ' - ' . ucfirst $author : '' %></title>
   <style type="text/css">
+    table { margin: .5em 1em; }
     #author, #footer { font-style: italic; }
+    caption h2 { padding: .125em .5em; background: #ddd; text-align: left; }
     th { text-align: left; }
-    th h2 { padding: .125em .5em; background: #eee; }
+    th h3 { padding: .125em .5em; background: #eee; }
     th.label { font-weight: normal; text-align: right; vertical-align: top; }
     td ul { list-style: none; padding-left: 0; margin-left: 0; }
@@ -475,6 +490,9 @@
     $parser->write;
 
-Calls C<render> on the FormBuilder form, and either writes the resulting HTML
-to a file, or to STDOUT if no filename is given.
+Calls C<render> on the FormBuilder form, and either writes the resulting
+HTML to a file, or to STDOUT if no filename is given.
+
+CSS Hint: to get multiple sections to all line up their fields, set a
+standard width for th.label
 
 =head2 write_module
@@ -545,4 +563,6 @@
     !list name &{ CODE }
     
+    !section id heading
+    
     !head ...
 
@@ -567,4 +587,9 @@
 A brief description of the form. Suitable for special instructions on how to
 fill out the form.
+
+=item C<!section>
+
+Starts a new section. Each section has its own heading and id, which are
+written by default into spearate tables.
 
 =item C<!head>
@@ -724,6 +749,5 @@
 Use the custom message file format for messages in the built in template
 
-C<!section> directive to split up the table into multiple tables, each
-with their own id and (optional) heading
+Custom CSS, both in addition to, and replacing the built in.
 
 Use HTML::Template instead of Text::Template for the built in template
@@ -751,5 +775,5 @@
 Thanks to eszpee for pointing out some bugs in the default value parsing,
 as well as some suggestions for i18n/l10n and splitting up long forms into
-sections (that as of this release are still on the TODO list ;-).
+sections.
 
 =head1 AUTHOR
Index: trunk/lib/Text/FormBuilder/grammar
===================================================================
--- trunk/lib/Text/FormBuilder/grammar	(revision 28)
+++ trunk/lib/Text/FormBuilder/grammar	(revision 29)
@@ -2,5 +2,8 @@
     my (
 	$context,      # line or group
-	@lines,        # master data structure
+	@sections,     # master data structure
+	$section_head,
+	$section_id,
+	@lines,        # lines in each section
 	$title,
 	$author,
@@ -23,4 +26,17 @@
 form_spec: (list_def | description_def | group_def | line)(s)
     {
+	# grab the last section, if there is any
+	if (@lines) {
+	    push @sections,
+		{
+		    id   => $section_id,
+		    head => $section_head,
+		    lines => [ @lines ],
+		};
+	}
+	
+	$section_id = $item{identifier};
+	$section_head = $item[3];
+	@lines = ();
 	$return = {
 	    title    => $title,
@@ -29,6 +45,6 @@
 	    lists    => \%lists,
 	    patterns => \%patterns,
-	    lines    => \@lines,
 	    groups   => \%groups,
+	    sections => \@sections,
 	}
     }
@@ -64,5 +80,5 @@
 
 field_line: <skip:'[ \t]*'> ( field | comment | blank ) "\n"
-line: <skip:'[ \t]*'> ( title | author | pattern_def | heading | group_field | unknown_directive | field | comment | blank ) "\n"
+line: <skip:'[ \t]*'> ( title | author | pattern_def | section_head | heading | group_field | unknown_directive | field | comment | blank ) "\n"
 
 title: '!title' /.*/
@@ -82,4 +98,23 @@
 
 pattern: /.*/
+
+section_head: '!section' identifier /.*/
+    {
+	#warn "starting section $item{identifier}\n";
+	#warn "  with heading $item[3]\n" if $item[3];
+	
+	if (@lines) {
+	    push @sections,
+		{
+		    id   => $section_id,
+		    head => $section_head,
+		    lines => [ @lines ],
+		};
+	}
+	
+	$section_id = $item{identifier};
+	$section_head = $item[3];
+	@lines = ();
+    }
 
 heading: '!head' /.*/    { push @lines, [ 'head', $item[2] ] }
