# Begin subroutines used in 'story' below. # 'convert_string' finds ^words between carrots^ and matches them to keys in $line_magic # and does replacements. sub convert_string { my ($string, $line_magic) = @_; $string =~ s/\^([\w\s\.\#]+)\^/exists $line_magic->{$1} ? $line_magic->{$1} : $1/ge; return $string; } # 'row_line' returns the array ref of a row of cells which I use in 'table_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_string($2, $opt->{'line magic'})), { 'rowspan' => $1 }] : $cell =~ /^c(\d)\s(.+)$/ ? [inline(convert_string($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' function (not included here). # In 'story' below, I group all lines beginning with a | and them parse them here. # [ig] showed me how to group lines together in a loop which I used here 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] =~ /^[$match]/); 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 included 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..@lines - 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), { href => '#'.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 lines, # 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 sent to 'table_opt'. if ($match eq '|') { my @list = map { $_ =~ s/^\|(.+)/$1/; $_; } @list_lines; 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/ ? shift @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 for next grouping. $match = '\*#|'; } else { heading_w_links($tab, $1, $2), next if $line =~ /^([1-6])\s+(.+) \+$/; heading($tab, $1, textify($2), { id => idify($2) }), next 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->{'line magic'}))), next if $line =~ /^bq\s(.+)/; # paragraphs paragraph($tab + 1, inline(convert_string($1, $opt->{'line magic'})), { class => 'stanza', break => '\|' }), next if $line =~ /^stanza (.+)$/; paragraph($tab + 1, inline(convert_string($2, $opt->{'line magic'})), { class => "indent$1"}), next if $line =~ /^\>(\d+) (.+)$/; paragraph($tab + 1, inline(convert_string($line, $opt->{'line magic'})), { class => 'author' }), next if $line =~ /^(?:by|with|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. }