package Text::Textile;
use strict;
use warnings;
use base 'Exporter';
our @EXPORT_OK = qw(textile);
our $VERSION = 2.12;
our $debug = 0;
sub new {
my $class = shift;
my %options = @_;
$options{filters} ||= {};
$options{charset} ||= 'iso-8859-1';
for ( qw( char_encoding do_quotes smarty_mode ) ) {
$options{$_} = 1 unless exists $options{$_};
}
for ( qw( trim_spaces preserve_spaces head_offset disable_encode_entities ) ) {
$options{$_} = 0 unless exists $options{$_};
}
my $self = bless \%options, $class;
if (exists $options{css}) {
$self->css($options{css});
}
$options{macros} ||= $self->default_macros();
if (exists $options{flavor}) {
$self->flavor($options{flavor});
} else {
$self->flavor('xhtml1/css');
}
return $self;
}
# getter/setter methods...
sub set {
my $self = shift;
my $opt = shift;
if (ref $opt eq 'HASH') {
$self->set($_, $opt->{$_}) foreach %{$opt};
} else {
my $value = shift;
# the following options have special set methods
# that activate upon setting:
if ($opt eq 'charset') {
$self->charset($value);
} elsif ($opt eq 'css') {
$self->css($value);
} elsif ($opt eq 'flavor') {
$self->flavor($value);
} else {
$self->{$opt} = $value;
}
}
return;
}
sub get {
my $self = shift;
return $self->{shift} if @_;
return undef;
}
sub disable_html {
my $self = shift;
if (@_) {
$self->{disable_html} = shift;
}
return $self->{disable_html} || 0;
}
sub head_offset {
my $self = shift;
if (@_) {
$self->{head_offset} = shift;
}
return $self->{head_offset} || 0;
}
sub flavor {
my $self = shift;
if (@_) {
my $flavor = shift;
$self->{flavor} = $flavor;
if ($flavor =~ m/^xhtml(\d)?(\D|$)/) {
if ($1 eq '2') {
$self->{_line_open} = '
';
$self->{_blockcode_open} = '
';
$self->{_blockcode_close} = '';
$self->{css_mode} = 1;
}
} elsif ($flavor =~ m/^html/) {
$self->{_line_open} = '';
$self->{_line_close} = '';
$self->{_blockcode_close} = '';
$self->{css_mode} = $flavor =~ m/\/css/;
}
$self->_css_defaults() if $self->{css_mode} && !exists $self->{css};
}
return $self->{flavor};
}
sub css {
my $self = shift;
if (@_) {
my $css = shift;
if (ref $css eq 'HASH') {
$self->{css} = $css;
$self->{css_mode} = 1;
} else {
$self->{css_mode} = $css;
$self->_css_defaults() if $self->{css_mode} && !exists $self->{css};
}
}
return $self->{css_mode} ? $self->{css} : 0;
}
sub charset {
my $self = shift;
if (@_) {
$self->{charset} = shift;
if ($self->{charset} =~ m/^utf-?8$/i) {
$self->char_encoding(0);
} else {
$self->char_encoding(1);
}
}
return $self->{charset};
}
sub docroot {
my $self = shift;
$self->{docroot} = shift if @_;
return $self->{docroot};
}
sub trim_spaces {
my $self = shift;
$self->{trim_spaces} = shift if @_;
return $self->{trim_spaces};
}
sub filter_param {
my $self = shift;
$self->{filter_param} = shift if @_;
return $self->{filter_param};
}
sub preserve_spaces {
my $self = shift;
$self->{preserve_spaces} = shift if @_;
return $self->{preserve_spaces};
}
sub filters {
my $self = shift;
$self->{filters} = shift if @_;
return $self->{filters};
}
sub char_encoding {
my $self = shift;
$self->{char_encoding} = shift if @_;
return $self->{char_encoding};
}
sub disable_encode_entities {
my $self = shift;
$self->{disable_encode_entities} = shift if @_;
return $self->{disable_encode_entities};
}
sub handle_quotes {
my $self = shift;
$self->{do_quotes} = shift if @_;
return $self->{do_quotes};
}
# end of getter/setter methods
# a URL discovery regex. This is from Mastering Regex from O'Reilly.
# Some modifications by Brad Choate ]*)?>)(.+?)()} {"\n\n"._repl(\@repl, $1.$self->encode_html($2, 1).$3)."\n\n"}ges; # fix code tags within pre blocks we just saved. for (my $i = $pre_start; $i < scalar(@repl); $i++) { $repl[$i] =~ s{<(/?)code(.*?)>}{<$1code$2>}gs; } # preserve code blocks by default, encode contents $str =~ s{(
]+)?>)(.+?)()}
{_repl(\@repl, $1.$self->encode_html($2, 1).$3)}ges;
# encode blockcode tag (an XHTML 2 tag) and encode it's
# content by default
$str =~ s{({css_mode}) { if (($padleft || $padright) && (($alignment eq 'left') || ($alignment eq 'right'))) { $style .= ';float:'.$alignment; } else { $style .= ';text-align:'.$alignment; } $class .= ' '.$self->{css}{"class_align_$alignment"} || $alignment; } else { $pre .= qq{ align="$alignment"} if $alignment; } } $style .= qq{;padding-left:${padleft}em} if $padleft; $style .= qq{;padding-right:${padright}em} if $padright; $style .= qq{;clear:${clear}} if $clear; $class =~ s/^ // if $class; $pre .= qq{ class="$class"} if $class; $pre .= qq{ id="$id"} if $id; $style =~ s/^;// if $style; $pre .= qq{ style="$style"} if $style; $pre .= qq{ lang="$lang"} if $lang; $pre .= q{ cite="} . $self->format_url(url => $cite) . '"' if defined $cite; $pre .= '>'; $clear = undef; } $pre .= ''; } } else { $post .= '' . $block . '>'; } if ($buffer =~ m/$blocktags/) { $buffer =~ s/^\n\n//s; $out .= $buffer; } else { $buffer = $self->format_block(text => "|$filter|".$buffer, inline => 1) if defined $filter; $out .= $pre . $buffer . $post; } } if ($sticky) { if ($block eq 'bc') { # close our blockcode section $out .= $self->{_blockcode_close}; # . "\n\n"; } elsif ($block eq 'bq') { $out .= ''; # . "\n\n"; } elsif (($block eq 'table') && ($stickybuff)) { my $table_out = $self->format_table(text => $stickybuff); $out .= $table_out if defined $table_out; } elsif (($block eq 'dl') && ($stickybuff)) { my $dl_out = $self->format_deflist(text => $stickybuff); $out .= $dl_out if defined $dl_out; } } # cleanup-- restore preserved blocks my $i = scalar(@repl); $out =~ s!(?:<|<)textile#$i(?:>|>)!$_!, $i-- while local $_ = pop @repl; # scan for br, hr tags that are not closed and close them # only for xhtml! just the common ones -- don't fret over input # and the like. if ($self->{flavor} =~ m/^xhtml/i) { $out =~ s/(<(?:img|br|hr)[^>]*?(?/$1 \/>/g; } return $out; } sub format_paragraph { my $self = shift; my (%args) = @_; my $buffer = defined $args{text} ? $args{text} : ''; my @repl; $buffer =~ s{(?:^|(?<=[\s>])|([{[])) ==(.+?)== (?:$|([\]}])|(?=$punct{1,2}|\s))} {_repl(\@repl, $self->format_block(text => $2, inline => 1, pre => $1, post => $3))}gesx; my $tokens; if ($buffer =~ m/ && (!$self->{disable_html})) { # optimization -- no point in tokenizing if we # have no tags to tokenize $tokens = _tokenize($buffer); } else { $tokens = [['text', $buffer]]; } my $result = ''; foreach my $token (@{$tokens}) { my $text = $token->[1]; if ($token->[0] eq 'tag') { $text =~ s/&(?!amp;)/&/g; $result .= $text; } else { $text = $self->format_inline(text => $text); $result .= $text; } } # now, add line breaks for lines that contain plaintext my @lines = split /\n/, $result; $result = ''; my $needs_closing = 0; foreach my $line (@lines) { if (($line !~ m/($blocktags)/) && (($line =~ m/^[^<]/ || $line =~ m/>[^<]/) || ($line !~ m/'; } elsif ($block =~ m/fn(\d+)/) { my $fnum = $1; $pre .= '
{css}{class_footnote} if $self->{css}{class_footnote}; if ($align) { my $alignment = _halign($align); if ($self->{css_mode}) { if (($padleft || $padright) && (($alignment eq 'left') || ($alignment eq 'right'))) { $style .= ';float:'.$alignment; } else { $style .= ';text-align:'.$alignment; } $class .= $self->{css}{"class_align_$alignment"} || $alignment; } else { $pre .= qq{ align="$alignment"}; } } $style .= qq{;padding-left:${padleft}em} if $padleft; $style .= qq{;padding-right:${padright}em} if $padright; $style .= qq{;clear:${clear}} if $clear; $class =~ s/^ // if $class; $pre .= qq{ class="$class"} if $class; $pre .= qq{ id="}.($self->{css}{id_footnote_prefix}||'fn').$fnum.'"'; $style =~ s/^;// if $style; $pre .= qq{ style="$style"} if $style; $pre .= qq{ lang="$lang"} if $lang; $pre .= '>'; $pre .= ''.$fnum.' '; # we can close like a regular paragraph tag now $block = 'p'; $clear = undef; } else { $pre .= '<' . ($macros{$block} || $block); if ($align) { my $alignment = _halign($align); if ($self->{css_mode}) { if (($padleft || $padright) && (($alignment eq 'left') || ($alignment eq 'right'))) { $style .= ';float:'.$alignment; } else { $style .= ';text-align:'.$alignment; } $class .= ' '.$self->{css}{"class_align_$alignment"} || $alignment; } else { $pre .= qq{ align="$alignment"}; } } $style .= qq{;padding-left:${padleft}em} if $padleft; $style .= qq{;padding-right:${padright}em} if $padright; $style .= qq{;clear:${clear}} if $clear; $class =~ s/^ // if $class; $pre .= qq{ class="$class"} if $class; $pre .= qq{ id="$id"} if $id; $style =~ s/^;// if $style; $pre .= qq{ style="$style"} if $style; $pre .= qq{ lang="$lang"} if $lang; $pre .= qq{ cite="} . $self->format_url(url => $cite) . '"' if defined $cite && $block eq 'bq'; #' $pre .= '>'; $clear = undef; } $buffer = $self->format_paragraph(text => $para); if ($block eq 'bq') { $post .= '
' if $buffer !~ m/]/; if ($sticky == 0) { $post .= '
' . $code . '';
}
sub format_classstyle {
my $self = shift;
my ($clsty, $class, $style) = @_;
$style = '' if not defined $style;
$class =~ s/^ // if defined $class;
my ($lang, $padleft, $padright, $id);
if ($clsty && ($clsty =~ m/{([^}]+)}/)) {
my $_style = $1;
$_style =~ s/\n/ /g;
$style .= ';'.$_style;
$clsty =~ s/{[^}]+}//g;
}
if ($clsty && ($clsty =~ m/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/ ||
$clsty =~ m/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/)) {
if ($1 || $2) {
if ($class) {
$class = $1 . ' ' . $class;
} else {
$class = $1;
}
$id = $2;
if ($class) {
$clsty =~ s/\([A-Za-z0-9_\- ]+?(#.*?)?\)//g;
}
if ($id) {
$clsty =~ s/\(#.+?\)//g;
}
}
}
if ($clsty && ($clsty =~ m/(\(+)/)) {
$padleft = length($1);
$clsty =~ s/\(+//;
}
if ($clsty && ($clsty =~ m/(\)+)/)) {
$padright = length($1);
$clsty =~ s/\)+//;
}
if ($clsty && ($clsty =~ m/\[(.+?)\]/)) {
$lang = $1;
$clsty =~ s/\[.+?\]//g;
}
my $attrs = '';
$style .= qq{;padding-left:${padleft}em} if $padleft;
$style .= qq{;padding-right:${padright}em} if $padright;
$style =~ s/^;//;
if ( $class ) {
$class =~ s/^ //;
$class =~ s/ $//;
$attrs .= qq{ class="$class"};
}
$attrs .= qq{ id="$id"} if $id;
$attrs .= qq{ style="$style"} if $style;
$attrs .= qq{ lang="$lang"} if $lang;
$attrs =~ s/^ //;
return $attrs;
}
sub format_tag {
my $self = shift;
my (%args) = @_;
my $tagname = $args{tag};
my $text = defined $args{text} ? $args{text} : '';
my $pre = defined $args{pre} ? $args{pre} : '';
my $post = defined $args{post} ? $args{post} : '';
my $clsty = defined $args{clsty} ? $args{clsty} : '';
_strip_borders(\$pre, \$post);
my $tag = "<$tagname";
my $attr = $self->format_classstyle($clsty);
$tag .= qq{ $attr} if $attr;
$tag .= qq{>$text$tagname>};
return $pre.$tag.$post;
}
sub format_deflist {
my $self = shift;
my (%args) = @_;
my $str = defined $args{text} ? $args{text} : '';
my $clsty;
my @lines = split /\n/, $str;
if ($lines[0] =~ m/^(dl($clstyre*?)\.\.?(?:\ +|$))/) {
$clsty = $2;
$lines[0] = substr($lines[0], length($1));
}
my ($dt, $dd);
my $out = '';
foreach my $line (@lines) {
if ($line =~ m/^((?:$clstyre*)(?:[^\ ].*?)(?format_classstyle($clsty) if $clsty;
$tag .= qq{ $attr} if $attr;
$tag .= '>'."\n";
return $tag.$out."\n";
}
sub add_term {
my ($self, $dt, $dd) = @_;
my ($dtattr, $ddattr);
my $dtlang;
if ($dt =~ m/^($clstyre*)/) {
my $param = $1;
$dtattr = $self->format_classstyle($param);
if ($param =~ m/\[([A-Za-z]+?)\]/) {
$dtlang = $1;
}
$dt = substr($dt, length($param));
}
if ($dd =~ m/^($clstyre*)/) {
my $param = $1;
# if the language was specified for the term,
# then apply it to the definition as well (unless
# already specified of course)
if ($dtlang && ($param =~ m/\[([A-Za-z]+?)\]/)) {
undef $dtlang;
}
$ddattr = $self->format_classstyle(($dtlang ? "[$dtlang]" : '') . $param);
$dd = substr($dd, length($param));
}
my $out = '){$count}!$1!gs) { $str =~ s!(
){$count}!$1!gs; $str =~ s!(]*>//; $str =~ s/<\/p>\s*$//; } return $pre.$str.$post; } sub format_link { my $self = shift; my (%args) = @_; my $text = defined $args{text} ? $args{text} : ''; my $linktext = defined $args{linktext} ? $args{linktext} : ''; my $title = $args{title}; my $url = $args{url}; my $clsty = $args{clsty}; if (!defined $url || $url eq '') { return $text; } if ($self->{links} && $self->{links}{$url}) { $title ||= $self->{links}{$url}{title}; $url = $self->{links}{$url}{url}; } $linktext =~ s/ +$//; $linktext = $self->format_paragraph(text => $linktext); $url = $self->format_url(linktext => $linktext, url => $url); my $tag = qq{format_classstyle($clsty); $tag .= qq{ $attr} if $attr; if (defined $title) { $title =~ s/^\s+//; $tag .= qq{ title="$title"} if length($title); } $tag .= qq{>$linktext}; return $tag; } sub format_url { my $self = shift; my (%args) = @_; my $url = defined $args{url} ? $args{url} : ''; if ($url =~ m/^(mailto:)?([-\+\w]+\@[-\w]+(\.\w[-\w]*)+)$/) { $url = 'mailto:'.$self->mail_encode($2); } if ($url !~ m{^(/|\./|\.\./|#)}) { $url = "http://$url" if $url !~ m{^(?:https?|ftp|mailto|nntp|telnet)}; } $url =~ s/&(?!amp;)/&/g; $url =~ s/ /\+/g; $url =~ s/^((?:.+?)\?)(.+)$/$1.$self->encode_url($2)/ge; return $url; } sub format_span { my $self = shift; my (%args) = @_; my $text = defined $args{text} ? $args{text} : ''; my $pre = defined $args{pre} ? $args{pre} : ''; my $post = defined $args{post} ? $args{post} : ''; my $cite = defined $args{cite} ? $args{cite} : ''; my $align = $args{align}; my $clsty = $args{clsty}; _strip_borders(\$pre, \$post); my ($class, $style); my $tag = qq{{css_mode}) { my $alignment = _halign($align); $style .= qq{;float:$alignment} if $alignment; $class .= ' '.$self->{css}{"class_align_$alignment"} if $alignment; } else { my $alignment = _halign($align) || _valign($align); $tag .= qq{ align="$alignment"} if $alignment; } } my $attr = $self->format_classstyle($clsty, $class, $style); $tag .= qq{ $attr} if $attr; if (defined $cite) { $cite =~ s/^://; $cite = $self->format_url(url => $cite); $tag .= qq{ cite="$cite"}; } return $pre.$tag.'>'.$self->format_paragraph(text => $text).''.$post; } sub format_image { my $self = shift; my (%args) = @_; my $src = defined $args{src} ? $args{src} : ''; my $pre = defined $args{pre} ? $args{pre} : ''; my $post = defined $args{post} ? $args{post} : ''; my $extra = $args{extra}; my $align = $args{align}; my $link = $args{url}; my $clsty = $args{clsty}; _strip_borders(\$pre, \$post); return $pre.'!!'.$post if length($src) == 0; my $tag; if ($self->{flavor} =~ m/^xhtml2/) { my $type; # poor man's mime typing. need to extend this externally if ($src =~ m/(?:\.jpeg|\.jpg)$/i) { $type = 'image/jpeg'; } elsif ($src =~ m/\.gif$/i) { $type = 'image/gif'; } elsif ($src =~ m/\.png$/i) { $type = 'image/png'; } elsif ($src =~ m/\.tiff$/i) { $type = 'image/tiff'; } $tag = qq{'; } elsif ($self->{flavor} =~ m/^xhtml/) { $tag .= ' />'; } else { $tag .= '>'; } if (defined $link) { $link =~ s/^://; $link = $self->format_url(url => $link); $tag = ''.$tag.''; } return $pre.$tag.$post; } sub format_table { my $self = shift; my (%args) = @_; my $str = defined $args{text} ? $args{text} : ''; my @lines = split /\n/, $str; my @rows; my $line_count = scalar(@lines); for (my $i = 0; $i < $line_count; $i++) { if ($lines[$i] !~ m/\|\s*$/) { if ($i + 1 < $line_count) { $lines[$i+1] = $lines[$i] . "\n" . $lines[$i+1] if $i+1 <= $#lines; } else { push @rows, $lines[$i]; } } else { push @rows, $lines[$i]; } } my ($tid, $tpadl, $tpadr, $tlang); my $tclass = ''; my $tstyle = ''; my $talign = ''; if ($rows[0] =~ m/^table[^\.]/) { my $row = $rows[0]; $row =~ s/^table//; my $params = 1; # process row parameters until none are left while ($params) { if ($row =~ m/^($tblalignre)/) { # found row alignment $talign .= $1; $row = substr($row, length($1)) if $1; redo if $1; } if ($row =~ m/^($clstypadre)/) { # found a class/id/style/padding indicator my $clsty = $1; $row = substr($row, length($clsty)) if $clsty; if ($clsty =~ m/{([^}]+)}/) { $tstyle = $1; $clsty =~ s/{([^}]+)}//; redo if $tstyle; } if ($clsty =~ m/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/ || $clsty =~ m/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/) { if ($1 || $2) { $tclass = $1; $tid = $2; redo; } } $tpadl = length($1) if $clsty =~ m/(\(+)/; $tpadr = length($1) if $clsty =~ m/(\)+)/; $tlang = $1 if $clsty =~ m/\[(.+?)\]/; redo if $clsty; } $params = 0; } $row =~ s/\.\s+//; $rows[0] = $row; } my $out = ''; my @cols = split /\|/, $rows[0].' '; my (@colalign, @rowspans); foreach my $row (@rows) { my @cols = split /\|/, $row.' '; my $colcount = $#cols; pop @cols; my $colspan = 0; my $row_out = ''; my ($rowclass, $rowid, $rowalign, $rowstyle, $rowheader); $cols[0] = '' if !defined $cols[0]; if ($cols[0] =~ m/_/) { $cols[0] =~ s/_//g; $rowheader = 1; } if ($cols[0] =~ m/{([^}]+)}/) { $rowstyle = $1; $cols[0] =~ s/{[^}]+}//g; } if ($cols[0] =~ m/\(([^\#]+?)?(#(.+))?\)/) { $rowclass = $1; $rowid = $3; $cols[0] =~ s/\([^\)]+\)//g; } $rowalign = $1 if $cols[0] =~ m/($alignre)/; for (my $c = $colcount - 1; $c > 0; $c--) { if ($rowspans[$c]) { $rowspans[$c]--; next if $rowspans[$c] > 1; } my ($colclass, $colid, $header, $colparams, $colpadl, $colpadr, $collang); my $colstyle = ''; my $colalign = $colalign[$c]; my $col = pop @cols; $col ||= ''; my $attrs = ''; if ($col =~ m/^(((_|[\/\\]\d+|$alignre|$clstypadre)+)\. )/) { my $colparams = $2; $col = substr($col, length($1)); my $params = 1; # keep processing column parameters until there # are none left... while ($params) { if ($colparams =~ m/^(_|$alignre)/g) { # found alignment or heading indicator $attrs .= $1; $colparams = substr($colparams, pos($colparams)) if $1; redo if $1; } if ($colparams =~ m/^($clstypadre)/g) { # found a class/id/style/padding marker my $clsty = $1; $colparams = substr($colparams, pos($colparams)) if $clsty; if ($clsty =~ m/{([^}]+)}/) { $colstyle = $1; $clsty =~ s/{([^}]+)}//; } if ($clsty =~ m/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/ || $clsty =~ m/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/) { if ($1 || $2) { $colclass = $1; $colid = $2; if ($colclass) { $clsty =~ s/\([A-Za-z0-9_\- ]+?(#.*?)?\)//g; } elsif ($colid) { $clsty =~ s/\(#.+?\)//g; } } } if ($clsty =~ m/(\(+)/) { $colpadl = length($1); $clsty =~ s/\(+//; } if ($clsty =~ m/(\)+)/) { $colpadr = length($1); $clsty =~ s/\)+//; } if ($clsty =~ m/\[(.+?)\]/) { $collang = $1; $clsty =~ s/\[.+?\]//; } redo if $clsty; } if ($colparams =~ m/^\\(\d+)/) { $colspan = $1; $colparams = substr($colparams, length($1)+1); redo if $1; } if ($colparams =~ m/\/(\d+)/) { $rowspans[$c] = $1 if $1; $colparams = substr($colparams, length($1)+1); redo if $1; } $params = 0; } } if (length($attrs)) { $header = 1 if $attrs =~ m/_/; $colalign = '' if $attrs =~ m/($alignre)/ && length($1); # determine column alignment if ($attrs =~ m/<>/) { $colalign .= '<>'; } elsif ($attrs =~ m/) { $colalign .= '<'; } elsif ($attrs =~ m/=/) { $colalign = '='; } elsif ($attrs =~ m/>/) { $colalign = '>'; } if ($attrs =~ m/\^/) { $colalign .= '^'; } elsif ($attrs =~ m/~/) { $colalign .= '~'; } elsif ($attrs =~ m/-/) { $colalign .= '-'; } } $header = 1 if $rowheader; $colalign[$c] = $colalign if $header; $col =~ s/^ +//; $col =~ s/ +$//; if (length($col)) { # create one cell tag my $rowspan = $rowspans[$c] || 0; my $col_out = '<' . ($header ? 'th' : 'td'); if (defined $colalign) { # horizontal, vertical alignment my $halign = _halign($colalign); $col_out .= qq{ align="$halign"} if $halign; my $valign = _valign($colalign); $col_out .= qq{ valign="$valign"} if $valign; } # apply css attributes, row, column spans $colstyle .= qq{;padding-left:${colpadl}em} if $colpadl; $colstyle .= qq{;padding-right:${colpadr}em} if $colpadr; $col_out .= qq{ class="$colclass"} if $colclass; $col_out .= qq{ id="$colid"} if $colid; $colstyle =~ s/^;// if $colstyle; $col_out .= qq{ style="$colstyle"} if $colstyle; $col_out .= qq{ lang="$collang"} if $collang; $col_out .= qq{ colspan="$colspan"} if $colspan > 1; $col_out .= qq{ rowspan="$rowspan"} if ($rowspan||0) > 1; $col_out .= '>'; # if the content of this cell has newlines OR matches # our paragraph block signature, process it as a full-blown # textile document if (($col =~ m/\n\n/) || ($col =~ m/^(?:$halignre|$clstypadre*)* [\*\#] (?:$clstypadre*|$halignre)*\ /x)) { $col_out .= $self->textile($col); } else { $col_out .= $self->format_paragraph(text => $col); } $col_out .= '' . ($header ? 'th' : 'td') . '>'; $row_out = $col_out . $row_out; $colspan = 0 if $colspan; } else { $colspan = 1 if $colspan == 0; $colspan++; } } if ($colspan > 1) { # handle the spanned column if we came up short $colspan--; $row_out = q{
>>,
C<< >> or C<<