So, at long last, here is the code which effectively solved my problem with your help. I am so very thankful for your work, sorry it comes so late.
# Begin subroutines used in 'story' below.
# 'convert_string' finds ^words between carrots^ and matches them to k
+eys in $line_magic
# and does replacements.
sub convert_string {
my ($string, $line_magic) = @_;
$string =~ s/\^([\w\s\.\#]+)\^/exists $line_magic->{$1} ? $line_magi
+c->{$1} : $1/ge;
return $string;
}
# 'row_line' returns the array ref of a row of cells which I use in 't
+able_opts'.
sub row_line {
my ($line, $opt) = @_;
my @row = split(/\|/, $line);
my $row_data;
for my $cell (@row) {
push @{$row_data}, $cell =~ /^r(\d)\s(.+)$/ ? [inline(convert_stri
+ng($2, $opt->{'line magic'})), { 'rowspan' => $1 }] :
$cell =~ /^c(\d)\s(.+)$/ ? [inline(convert_stri
+ng($2, $opt->{'line magic'})), { 'colspan' => $1 }] :
inline(convert_string($cell, $opt->{'line magic
+'}));
}
return $row_data;
}
# 'table_opts' returns all of the options for my separate 'table' func
+tion (not included here).
# In 'story' below, I group all lines beginning with a | and them pars
+e them here.
# [ig] showed me how to group lines together in a loop which I used he
+re and in 'story'.
# || the_tables_class
# |! The table's caption
# |* The|table's|headings
# |+ A|row|which|starts|with|a|heading
# |- A|now|which|doesn't start|with|a|heading
sub table_opts {
my ($lines, $opt) = @_;
my $table_opts;
my $match = '|!*+-';
for (my $lineno = 0; $lineno < @$lines; $lineno++) {
my $line = $lines->[$lineno];
$match = substr($line, 0, 1);
if ($match eq '|') {
$line =~ s/^\| (.+)$/$1/;
$table_opts->{'class'} = $line;
}
elsif ($match eq '!') {
$line =~ s/^\! (.+)$/$1/;
$table_opts->{'caption'} = inline(convert_string($line), $opt->{
+'line magic'});
}
elsif ($match eq '*') {
$line =~ s/^\* (.+)$/$1/;
push @{$table_opts->{'rows'}}, ['header', row_line($line, $opt)]
+;
}
elsif ($match =~ /[\+-]/) {
my $start = $lineno;
my $end = $lineno;
$end++ while ($end < $#$lines and $lines->[$end + 1] =~ /^[$matc
+h]/);
my @table_rows = map {
$_ =~ s/^[\+-] (.+)/$1/;
row_line($_, $opt);
} @{$lines}[$start..$end];
my $type = $match =~ /\+/ ? 'whead' : 'data';
push @{$table_opts->{'rows'}}, [$type, \@table_rows];
$lineno = $end;
}
$match = '*+-';
}
return $table_opts;
}
# 'type' and 'get_list' are written by [hdb]. [ig] wrote a version too
+.
# 'get_list' parses the groups of lines which begin with a * or a # in
+ my data.
# The returned list is plugged into my 'list' function which is not in
+cluded here.
sub type {
my %type = ( '*' => 'u', '#' => 'o' );
$type{ substr shift, -1 }
}
sub get_list {
my ($list, $opt) = @_;
$list = [ map (inline(convert_string($_, $opt->{'line magic'})),
+ @$list) ];
my @lines = map {
/([*#]*)(\d*)\s+(.*)/;
$2 ? [$1, [$3, {value => $2}]] : [$1, $3]
} @$list;
my $maxlevel = max map { length $_->[0] } @lines;
while( $maxlevel ) {
my @indices = grep { $maxlevel == length $lines[$_]->[0] } 0..@lin
+es - 1;
while( @indices ) {
my $end = pop @indices;
my $start = $end;
$start = pop @indices while @indices and $indices[-1] == $start-
+1;
my $sublist = [
type($lines[$start]->[0]),
[ map { $_->[1] } splice @lines, $start, $end-$start + 1 ]
];
$lines[$start-1]->[1] = [ $lines[$start-1]->[1], { 'inlist' => $
+sublist} ] if $maxlevel > 1;
splice @lines, $start, 0, $sublist if $maxlevel == 1;
}
$maxlevel--;
}
@lines = grep { $_->[0] } @lines;
}
# 'heading_w_links' is something I wrote earlier.
# It adds links to wikipedia and google under the heading of a section
+.
sub heading_w_links {
my ($tab, $level, $text) = @_;
my ($heading, $wikipedia) = split(/\|/,$text);
my $article = $wikipedia ? $wikipedia : $heading;
heading($tab, $level, textify($heading), { id => idify($heading) });
paragraph($tab + 1, external_links([['Wikipedia',filify($article)],[
+'Google',searchify($heading)]]), { 'style' => 'float: right' } );
}
# End subroutines used in 'story' below.
sub story {
my ($source, $opt) = @_;
# Start table of contents and sections.
my $inc = 0;
my $cols = 0;
my @sections;
my @toc;
while (my $line = <$source>) {
chomp($line);
next if !$line;
if ($line =~ /^2 /) {
my ($number,$text) = split(/ /,$line,2);
$text =~ s/ \+$//;
push @toc, [anchor(textify($text), { href => '#'.idify($text) })
+];
}
if ($line =~ /^3 /) {
my ($number,$text) = split(/ /,$line,2);
$text =~ s/ \+$//;
$toc[$inc-1][1]->{inlist}[0] = 'u';
push @{$toc[$inc-1][1]->{inlist}[1]}, anchor(textify($text), { h
+ref => '#'.idify($text) });
#$toc[$inc-1][1]->{inlist}[2]{'style'} = 'font-size: smaller;';
}
$inc++ if $line =~ /^2 /;
$cols++ if $line =~ /^(?:2|3) /;
push @{$sections[$inc]}, $line;
}
# End table of contents and sections.
# Start parsing the sections.
my $tab = 2;
my $match = '\*#|'; # These characters denote lists or tables.
$inc = 0;
for my $section (@sections) {
if ($section) {
section($tab, sub {
$tab++;
# I'd never used for like the following before now.
for (my $lineno = 0; $lineno < @$section; $lineno++) {
my $line = $section->[$lineno];
if ($line =~ /^[$match]/) {
# $match is shortened to one character for future lines
+to match.
# It also tells the parser where to send the grouped lin
+es,
# table (|) or list (* or #).
$match = substr($line, 0, 1);
my $start = $lineno;
my $end = $lineno;
$end++ while ($end < $#$section and $section->[$end+1] =
+~ /^[$match]/);
my @list_lines = @{$section}[$start..$end];
# lines beginning with | all belong to a table and are s
+ent to 'table_opt'.
if ($match eq '|') {
my @list = map { $_ =~ s/^\|(.+)/$1/; $_; } @list_line
+s;
my $opts = table_opts(\@list, $opt);
table($tab + 1, $opts);
}
# lines beginning with a * or # belong to a list and are
+ sent to 'get_list'.
else {
my $class = $list_lines[0] =~ s/^[\*#]\| (.+)$/$1/ ? s
+hift @list_lines : undef;
my @list = get_list(\@list_lines, $opt);
$list[0][2]->{'class'} = $class ? $class : undef;
list($tab + 1, @{$list[0]});
}
$lineno = $end;
# Reset $match after a set of lines is grouped, ready fo
+r next grouping.
$match = '\*#|';
}
else {
heading_w_links($tab, $1, $2), next if $line =~ /^([1-6]
+)\s+(.+) \+$/;
heading($tab, $1, textify($2), { id => idify($2) }), nex
+t if $line =~ /^([1-6])\s+(.+)/;
div($tab, $2, { 'class' => "h$1" }), next if $line =~ /^
+([7-8])\s+(.+)/;
$opt->{'doc magic'}->{$1}->(), next if $line =~ /^&\s+(.
++)/;
line($tab + 1, $line), next if $line =~ /^<.+>/;
line($tab + 1, "<$line>"), next if $line =~ /^[bh]r$/;
blockquote($tab + 1, inline(convert_string($1, $opt->{'l
+ine magic'}))), next if $line =~ /^bq\s(.+)/;
# paragraphs
paragraph($tab + 1, inline(convert_string($1, $opt->{'li
+ne magic'})), { class => 'stanza', break => '\|' }), next if $line =~
+ /^stanza (.+)$/;
paragraph($tab + 1, inline(convert_string($2, $opt->{'li
+ne magic'})), { class => "indent$1"}), next if $line =~ /^\>(\d+) (
+.+)$/;
paragraph($tab + 1, inline(convert_string($line, $opt->{
+'line magic'})), { class => 'author' }), next if $line =~ /^(?:by|wit
+h|from|as) /;
paragraph($tab + 1, inline(convert_string($line, $opt->{
+'line magic'})));
}
}
$tab--;
});
}
my $toc_start = $opt->{'toc at'} ? $opt->{'toc at'} : 3;
if ($inc == 0 && $cols >= $toc_start) {
nav($tab, sub {
my $class = get_columns(4, $cols);
list($tab + 2, 'u', \@toc, { class => $class });
}, { heading => [2, 'Table of contents'], class => 'contents'} )
+;
}
$inc++;
}
# End parsing sections.
}