From 2b798a8841766631e23430fc89891bd81aebd4a1 Mon Sep 17 00:00:00 2001 From: "Kyle J. McKay" Date: Thu, 7 Dec 2017 02:36:11 -0800 Subject: [PATCH] Markdown.pl: support tables Add support for basic tables. Nested tables are not supported although tables themselves can appear within lists and blockquotes and do work properly there. The commonly used table syntax is recognized including the left/right/center alignment indicators. Inline markup within each column also works just fine. Signed-off-by: Kyle J. McKay --- Markdown.pl | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++- basics.md | 20 ++++++++++ syntax.md | 61 ++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 1 deletion(-) diff --git a/Markdown.pl b/Markdown.pl index d0c13d4..66586b8 100755 --- a/Markdown.pl +++ b/Markdown.pl @@ -111,7 +111,7 @@ BEGIN { # Table of hash values for escaped characters: my %g_escape_table; BEGIN { - foreach my $char (split //, "\\\`*_~{}[]()>#+-.!") { + foreach my $char (split //, "\\\`*_~{}[]()>#+-.!|") { $g_escape_table{$char} = block_id($char,1); } } @@ -696,6 +696,8 @@ sub _RunBlockGamut { $text = _DoBlockQuotes($text); + $text = _DoTables($text); + # We already ran _HashHTMLBlocks() before, in Markdown(), but that # was to escape raw HTML in the original Markdown source. This time, # we're escaping the markup we've just created, so that we don't wrap @@ -1798,6 +1800,98 @@ sub _DoBlockQuotes { } +my ($LEAD, $TRAIL, $LEADBAR, $LEADSP, $COLPL, $SEP); +BEGIN { + $LEAD = qr/(?>[ ]*(?:\|[ ]*)?)/o; + $TRAIL = qr/\|[ ]*/o; + $LEADBAR = qr/(?>[ ]*\|[ ]*)/o; + $LEADSP = qr/(?>[ ]*)/o; + $COLPL = qr/(?:[^\n|\\]|\\[^\n])+/o; + $SEP = qr/[ ]*:?-+:?[ ]*/o; +} + +sub _DoTables { + my $text = shift; + + $text =~ s{ + ( # Wrap whole thing to avoid $& + (?: (?<=\n\n) | \A\n? ) # Preceded by blank line or beginning of string + ^( # Header line + $LEADBAR \| [^\n]* | + $LEADBAR $COLPL [^\n]* | + $LEADSP $COLPL \| [^\n]* + )\n + ( # Separator line + $LEADBAR $SEP (?: \| $SEP )* (?: \| [ ]*)? | + $SEP (?: \| $SEP )+ (?: \| [ ]*)? | + $SEP \| [ ]* + )\n + ((?: # Rows (0+) + $LEADBAR \| [^\n]* \n | + $LEADBAR $COLPL [^\n]* \n | + $LEADSP $COLPL \| [^\n]* \n + )*) + ) + } { + my ($w, $h, $s, $rows) = ($1, $2, $3, $4); + my @heads = _SplitTableRow($h); + my @seps = _SplitTableRow($s); + if (@heads == @seps) { + my @align = map { + if (/^:-+:$/) {" align=\"center\""} + elsif (/^:/) {" align=\"left\""} + elsif (/:$/) {" align=\"right\""} + else {""} + } @seps; + my $headers = _MakeTableRow("th", \@align, @heads); + my $tab ="\n\n" . + " " . _MakeTableRow("th", \@align, @heads) . "\n"; + my $cnt = 0; + my @classes = ("class=\"$opt{style_prefix}row-even\"", "class=\"$opt{style_prefix}row-odd\""); + $tab .= " " . _MakeTableRow("td", \@align, _SplitTableRow($_)) . "\n" + foreach split(/\n/, $rows); + $tab .= "
\n\n"; + } else { + $w; + } + }egmx; + + return $text; +} + + +sub _SplitTableRow { + my $row = shift; + $row =~ s/^$LEAD//; + $row =~ s/$TRAIL$//; + $row =~ s!\\\\!$g_escape_table{'\\'}!go; # Must process escaped backslashes first. + $row =~ s!\\\|!$g_escape_table{'|'}!go; # Then do \| + my @elems = map { + s!$g_escape_table{'|'}!|!go; + s!$g_escape_table{'\\'}!\\!go; + s/^[ ]+//; + s/[ ]+$//; + $_; + } split(/[ ]*\|[ ]*/, $row, -1); + @elems or push(@elems, ""); + return @elems; +} + + +sub _MakeTableRow { + my $etype = shift; + my $align = shift; + my $row = ""; + for (my $i = 0; $i < @$align; ++$i) { + my $data = $_[$i]; + defined($data) or $data = ""; + $row .= "<" . $etype . $$align[$i] . ">" . + _RunSpanGamut($data) . ""; + } + return $row; +} + + sub _FormParagraphs { # # Params: @@ -2151,6 +2245,15 @@ div.%(base)code-bt > pre > code, div.%(base)code > pre > code { border-bottom: thin dotted; } +table.%(base)table { + margin-bottom: 0.5em; +} +table.%(base)table, table.%(base)table th, table.%(base)table td { + border-collapse: collapse; + border-spacing: 0; + border: thin solid; +} + ol.%(base)ol { counter-reset: %(base)item; } diff --git a/basics.md b/basics.md index 5ffc80f..f5925d4 100644 --- a/basics.md +++ b/basics.md @@ -198,6 +198,26 @@ Output: +~~~~~~ +Tables +~~~~~~ + +Markdown supports simple tables like so: + + | Item | Price | Description | + | ---- | -----:| ----------- | + | Nut | $1.29 | Delicious | + | Bean | $0.37 | Fiber | + +Output: + + + + + +
ItemPriceDescription
Nut$1.29Delicious
Bean$0.37Fiber
+ + ~~~~~ Links ~~~~~ diff --git a/syntax.md b/syntax.md index 54e5c2b..c31ef43 100644 --- a/syntax.md +++ b/syntax.md @@ -17,6 +17,7 @@ Markdown: Syntax * [Headers] * [Blockquotes] * [Lists] + * [Tables] * [Style Sheet] * [Code Blocks] * [Horizontal Rules] @@ -554,6 +555,65 @@ immediately followed by another line that looks like a list item (either of the same kind or of a sublist). +~~~~~~ +Tables +~~~~~~ + +Markdown supports simple tables like so: + + | Item | Price | Description | + | ---- | -----:| ----------- | + | Nut | $1.29 | Delicious | + | Bean | $0.37 | Fiber | + +Output: + + + + + +
ItemPriceDescription
Nut$1.29Delicious
Bean$0.37Fiber
+ +The leading and trailing `|` on each line are optional unless there is only +a single column in which case at least one `|` is always required -- two if +the single column contains only whitespace. + +Leading and trailing whitespace are always trimmed from each column's value +before using it. + +To include a literal `|` (veritical bar) character in a column's value, precede +it with a `\` (backslash). To include a literal `\` use `\\` (double them). + +The number of columns in the separator row must match exactly the number of +columns in the header row in order for the table to be recognized. + +Each separator in the separator line must be one or more `-` (dash) characters +optionally with a `:` (colon) on either or both ends. With no colons the +column alignment will be the default. With a colon only on the left the +alignment will be `left`. With a colon only on the right the alignment will +be `right`. And finally, with a colon on both ends the alignment will be +`center`. The alignment will be applied to the column in both header and body +rows. + +Body rows that contain fewer columns than the header row have empty columns +added. Body rows that contain more columns than the header row have the +extra columns dropped. + +The vertical bars do not need to be lined up, sloppy tables work just fine. +The above example could be rewritten like so: + + Item|Price|Description + -|-:|- + Nut|$1.29|Delicious + Bean|$0.37|Fiber + +Inline markup is recognized just fine within each column: + + |Example + |:- + |~~Strikeout~~ `code` _etc._ + + ~~~~~~~~~~~ Style Sheet ~~~~~~~~~~~ @@ -1091,3 +1151,4 @@ Markdown provides backslash escapes for the following characters: - minus sign (hyphen) . dot ! exclamation mark + | vertical bar (escape only needed/recognized in tables)