commit be7fcc16ff4e35ad3995d773e189d6b009bc6be2
parent 77d5eee788ba27fb06ddadf727df2c1b58346737
Author: Martin Mitas <mity@morous.org>
Date: Mon, 21 Nov 2016 09:47:31 +0100
Implement tables.
Note it is implemented as an extension. To enable it, the flag MD_FLAG_TABLES
must be explicitly specified.
Diffstat:
6 files changed, 573 insertions(+), 27 deletions(-)
diff --git a/README.md b/README.md
@@ -126,7 +126,7 @@ The list below is incomplete list of extensions I see as worth of
consideration.
- **Block Extensions:**
- - [ ] Tables
+ - [x] Tables
- [ ] Header anchors: `## Chapter {#anchor}`
(allowing fragment links pointing to it, e.g. `[link text](#anchor)`)
diff --git a/md2html/md2html.c b/md2html/md2html.c
@@ -193,6 +193,20 @@ open_code_block(struct membuffer* out, const MD_BLOCK_CODE_DETAIL* det)
}
static void
+open_td_block(struct membuffer* out, const char* cell_type, const MD_BLOCK_TD_DETAIL* det)
+{
+ MEMBUF_APPEND_LITERAL(out, "<");
+ MEMBUF_APPEND_LITERAL(out, cell_type);
+
+ switch(det->align) {
+ case MD_ALIGN_LEFT: MEMBUF_APPEND_LITERAL(out, " align=\"left\">"); break;
+ case MD_ALIGN_CENTER: MEMBUF_APPEND_LITERAL(out, " align=\"center\">"); break;
+ case MD_ALIGN_RIGHT: MEMBUF_APPEND_LITERAL(out, " align=\"right\">"); break;
+ default: MEMBUF_APPEND_LITERAL(out, ">"); break;
+ }
+}
+
+static void
open_a_span(struct membuffer* out, const MD_SPAN_A_DETAIL* det)
{
MEMBUF_APPEND_LITERAL(out, "<a href=\"");
@@ -329,6 +343,12 @@ enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
case MD_BLOCK_CODE: open_code_block(out, (const MD_BLOCK_CODE_DETAIL*) detail); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: MEMBUF_APPEND_LITERAL(out, "<p>"); break;
+ case MD_BLOCK_TABLE: MEMBUF_APPEND_LITERAL(out, "<table>\n"); break;
+ case MD_BLOCK_THEAD: MEMBUF_APPEND_LITERAL(out, "<thead>\n"); break;
+ case MD_BLOCK_TBODY: MEMBUF_APPEND_LITERAL(out, "<tbody>\n"); break;
+ case MD_BLOCK_TR: MEMBUF_APPEND_LITERAL(out, "<tr>\n"); break;
+ case MD_BLOCK_TH: open_td_block(out, "th", (MD_BLOCK_TD_DETAIL*)detail); break;
+ case MD_BLOCK_TD: open_td_block(out, "td", (MD_BLOCK_TD_DETAIL*)detail); break;
}
return 0;
@@ -348,6 +368,12 @@ leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
case MD_BLOCK_CODE: MEMBUF_APPEND_LITERAL(out, "</code></pre>\n"); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: MEMBUF_APPEND_LITERAL(out, "</p>\n"); break;
+ case MD_BLOCK_TABLE: MEMBUF_APPEND_LITERAL(out, "</table>\n"); break;
+ case MD_BLOCK_THEAD: MEMBUF_APPEND_LITERAL(out, "</thead>\n"); break;
+ case MD_BLOCK_TBODY: MEMBUF_APPEND_LITERAL(out, "</tbody>\n"); break;
+ case MD_BLOCK_TR: MEMBUF_APPEND_LITERAL(out, "</tr>\n"); break;
+ case MD_BLOCK_TH: MEMBUF_APPEND_LITERAL(out, "</th>\n"); break;
+ case MD_BLOCK_TD: MEMBUF_APPEND_LITERAL(out, "</td>\n"); break;
}
return 0;
@@ -516,6 +542,7 @@ static const option cmdline_options[] = {
{ "fno-html-spans", 0, 'G', OPTION_ARG_NONE },
{ "fno-html", 0, 'H', OPTION_ARG_NONE },
{ "fcollapse-whitespace", 0, 'W', OPTION_ARG_NONE },
+ { "ftables", 0, 'T', OPTION_ARG_NONE },
{ 0 }
};
@@ -552,6 +579,7 @@ usage(void)
" --fno-html-spans\n"
" Disable raw HTML spans\n"
" --fno-html Same as --fno-html-blocks --fno-html-spans\n"
+ " --ftables Enable tables\n"
);
}
@@ -586,6 +614,7 @@ cmdline_callback(int opt, char const* value, void* data)
case 'U': renderer_flags |= MD_FLAG_PERMISSIVEURLAUTOLINKS; break;
case '@': renderer_flags |= MD_FLAG_PERMISSIVEEMAILAUTOLINKS; break;
case 'V': renderer_flags |= MD_FLAG_PERMISSIVEAUTOLINKS; break;
+ case 'T': renderer_flags |= MD_FLAG_TABLES; break;
default:
fprintf(stderr, "Illegal option: %s\n", value);
diff --git a/md4c/md4c.c b/md4c/md4c.c
@@ -159,7 +159,9 @@ enum MD_LINETYPE_tag {
MD_LINE_INDENTEDCODE,
MD_LINE_FENCEDCODE,
MD_LINE_HTML,
- MD_LINE_TEXT
+ MD_LINE_TEXT,
+ MD_LINE_TABLE,
+ MD_LINE_TABLEUNDERLINE
};
typedef struct MD_LINE_ANALYSIS_tag MD_LINE_ANALYSIS;
@@ -3435,6 +3437,126 @@ abort:
}
+/***************************
+ *** Processing Tables ***
+ ***************************/
+
+static void
+md_analyze_table_alignment(MD_CTX* ctx, OFF beg, OFF end, MD_ALIGN* align, int n_align)
+{
+ static const MD_ALIGN align_map[] = { MD_ALIGN_DEFAULT, MD_ALIGN_LEFT, MD_ALIGN_RIGHT, MD_ALIGN_CENTER };
+ OFF off = beg;
+
+ while(n_align > 0) {
+ int index = 0; /* index into align_map[] */
+
+ while(CH(off) != _T('-'))
+ off++;
+ if(off > beg && CH(off-1) == _T(':'))
+ index |= 1;
+ while(off < end && CH(off) == _T('-'))
+ off++;
+ if(off < end && CH(off) == _T(':'))
+ index |= 2;
+
+ *align = align_map[index];
+ align++;
+ n_align--;
+ }
+
+}
+
+/* Forward declaration. */
+static int md_process_normal_block_contents(MD_CTX* ctx, const MD_LINE* lines, int n_lines);
+
+static int
+md_process_table_row(MD_CTX* ctx, MD_BLOCKTYPE cell_type, OFF beg, OFF end,
+ const MD_ALIGN* align, int n_align)
+{
+ OFF off = beg;
+ OFF cell_beg, cell_end;
+ int cell_index = 0;
+ int ret = 0;
+
+ MD_ENTER_BLOCK(MD_BLOCK_TR, NULL);
+
+ if(CH(off) == _T('|'))
+ off++;
+
+ while(off < end) {
+ cell_beg = off;
+ while(off < end && CH(off) != _T('|')) {
+ if(CH(off) == _T('\\') && off+1 < end && ISPUNCT(off+1))
+ off += 2;
+ else
+ off++;
+ }
+ cell_end = off;
+
+ while(cell_beg < end && ISWHITESPACE(cell_beg))
+ cell_beg++;
+ while(cell_end > cell_beg && ISWHITESPACE(cell_end-1))
+ cell_end--;
+
+ if(cell_end > cell_beg || off < end) {
+ MD_LINE cell_line = { cell_beg, cell_end };
+ MD_BLOCK_TD_DETAIL det;
+
+ det.align = (cell_index < n_align ? align[cell_index] : MD_ALIGN_DEFAULT);
+
+ MD_ENTER_BLOCK(cell_type, &det);
+ MD_CHECK(md_process_normal_block_contents(ctx, &cell_line, 1));
+ MD_LEAVE_BLOCK(cell_type, &det);
+ cell_index++;
+ }
+
+ off++;
+ }
+
+ MD_LEAVE_BLOCK(MD_BLOCK_TR, NULL);
+
+abort:
+ return ret;
+}
+
+static int
+md_process_table_block_contents(MD_CTX* ctx, int col_count, const MD_LINE* lines, int n_lines)
+{
+ MD_ALIGN* align;
+ int i;
+ int ret = 0;
+
+ /* At least the line with column names and the table underline have to
+ * be present. */
+ MD_ASSERT(n_lines >= 2);
+
+ align = malloc(col_count * sizeof(MD_ALIGN));
+ if(align == NULL) {
+ MD_LOG("malloc() failed.");
+ ret = -1;
+ goto abort;
+ }
+
+ md_analyze_table_alignment(ctx, lines[1].beg, lines[1].end, align, col_count);
+
+ MD_ENTER_BLOCK(MD_BLOCK_THEAD, NULL);
+ MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TH,
+ lines[0].beg, lines[0].end, align, col_count));
+ MD_LEAVE_BLOCK(MD_BLOCK_THEAD, NULL);
+
+ MD_ENTER_BLOCK(MD_BLOCK_TBODY, NULL);
+ for(i = 2; i < n_lines; i++) {
+ MD_CHECK(md_process_table_row(ctx, MD_BLOCK_TD,
+ lines[i].beg, lines[i].end, align, col_count));
+ }
+ MD_LEAVE_BLOCK(MD_BLOCK_TBODY, NULL);
+
+abort:
+ free(align);
+ return ret;
+}
+
+
/*******************************
*** Processing Leaf Block ***
*******************************/
@@ -3442,8 +3564,9 @@ abort:
struct MD_BLOCK_tag {
MD_BLOCKTYPE type : 16;
- /* MD_BLOCK_H: header level (1 - 6)
- * MD_BLOCK_CODE: non-zero if fenced, zero if indented.
+ /* MD_BLOCK_H: Header level (1 - 6)
+ * MD_BLOCK_CODE: Non-zero if fenced, zero if indented.
+ * MD_BLOCK_TABLE: Column count (as determined by the table underline)
*/
unsigned data : 16;
@@ -3626,6 +3749,11 @@ md_process_block(MD_CTX* ctx, const MD_BLOCK* block)
(const MD_VERBATIMLINE*)(block + 1), block->n_lines);
break;
+ case MD_BLOCK_TABLE:
+ ret = md_process_table_block_contents(ctx, block->data,
+ (const MD_LINE*)(block + 1), block->n_lines);
+ break;
+
default:
ret = md_process_normal_block_contents(ctx,
(const MD_LINE*)(block + 1), block->n_lines);
@@ -3737,6 +3865,7 @@ md_start_new_block(MD_CTX* ctx, const MD_LINE_ANALYSIS* line)
break;
case MD_LINE_SETEXTUNDERLINE:
+ case MD_LINE_TABLEUNDERLINE:
default:
MD_UNREACHABLE();
break;
@@ -3936,6 +4065,58 @@ md_is_setext_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_level)
}
static int
+md_is_table_underline(MD_CTX* ctx, OFF beg, OFF* p_end, unsigned* p_col_count)
+{
+ OFF off = beg;
+ unsigned col_count = 0;
+
+ if(off < ctx->size && CH(off) == _T('|')) {
+ off++;
+ while(off < ctx->size && ISWHITESPACE(off))
+ off++;
+ }
+
+ while(1) {
+ OFF cell_beg;
+ int delimited = FALSE;
+
+ /* Cell underline ("-----", ":----", "----:" or ":----:") */
+ cell_beg = off;
+ if(off < ctx->size && CH(off) == _T(':'))
+ off++;
+ while(off < ctx->size && CH(off) == _T('-'))
+ off++;
+ if(off < ctx->size && CH(off) == _T(':'))
+ off++;
+ if(off - cell_beg < 3)
+ return FALSE;
+
+ col_count++;
+
+ /* Pipe delimiter (optional at the end of line). */
+ while(off < ctx->size && ISWHITESPACE(off))
+ off++;
+ if(off < ctx->size && CH(off) == _T('|')) {
+ delimited = TRUE;
+ off++;
+ while(off < ctx->size && ISWHITESPACE(off))
+ off++;
+ }
+
+ /* Success, if we reach end of line. */
+ if(off >= ctx->size || ISNEWLINE(off))
+ break;
+
+ if(!delimited)
+ return FALSE;
+ }
+
+ *p_end = off;
+ *p_col_count = col_count;
+ return TRUE;
+}
+
+static int
md_is_opening_code_fence(MD_CTX* ctx, OFF beg, OFF* p_end)
{
OFF off = beg;
@@ -4192,6 +4373,29 @@ md_is_html_block_end_condition(MD_CTX* ctx, OFF beg, OFF* p_end)
}
}
+/* Check whether there is a given unescaped char 'ch' between 'beg' and end of line. */
+static int
+md_line_contains_char(MD_CTX* ctx, OFF beg, CHAR ch, OFF* p_pos)
+{
+ OFF off = beg;
+
+ while(off < ctx->size) {
+ if(ISNEWLINE(off)) {
+ return FALSE;
+ } else if(CH(off) == _T('\\') && off+1 < ctx->size && ISPUNCT(off+1)) {
+ off += 2;
+ } else if(CH(off) == ch) {
+ if(p_pos != NULL)
+ *p_pos = off;
+ return TRUE;
+ } else {
+ off++;
+ }
+ }
+
+ return FALSE;
+}
+
/* Analyze type of the line and find some its properties. This serves as a
* main input for determining type and boundaries of a block. */
static void
@@ -4281,6 +4485,14 @@ redo_indentation_after_blockquote_mark:
goto done;
}
+ /* Check whether we are table continuation. */
+ if(pivot_line->type == MD_LINE_TABLE) {
+ if(md_line_contains_char(ctx, off, _T('|'), &off)) {
+ line->type = MD_LINE_TABLE;
+ goto done;
+ }
+ }
+
/* Check whether we are blank line.
* Note blank lines after indented code are treated as part of that block.
* If they are at the end of the block, it is discarded by caller.
@@ -4360,6 +4572,22 @@ redo_indentation_after_blockquote_mark:
}
}
+ /* Check whether we are table underline. */
+ if((ctx->r.flags & MD_FLAG_TABLES) && pivot_line->type == MD_LINE_TEXT &&
+ (CH(off) == _T('|') || CH(off) == _T('-') || CH(off) == _T(':')))
+ {
+ unsigned col_count;
+
+ if(ctx->current_block != NULL && ctx->current_block->n_lines == 1 &&
+ md_line_contains_char(ctx, pivot_line->beg, _T('|'), NULL) &&
+ md_is_table_underline(ctx, off, &off, &col_count))
+ {
+ line->data = col_count;
+ line->type = MD_LINE_TABLEUNDERLINE;
+ goto done;
+ }
+ }
+
/* By default, we are normal text line. */
line->type = MD_LINE_TEXT;
@@ -4422,7 +4650,7 @@ md_process_line(MD_CTX* ctx, const MD_LINE_ANALYSIS** p_pivot_line, const MD_LIN
return 0;
}
- /* MD_LINE_SETEXTUNDERLINE changes meaning of the previous block and ends it. */
+ /* MD_LINE_SETEXTUNDERLINE changes meaning of the current block and ends it. */
if(line->type == MD_LINE_SETEXTUNDERLINE) {
MD_ASSERT(ctx->current_block != NULL);
ctx->current_block->type = MD_BLOCK_H;
@@ -4432,6 +4660,18 @@ md_process_line(MD_CTX* ctx, const MD_LINE_ANALYSIS** p_pivot_line, const MD_LIN
return 0;
}
+ /* MD_LINE_TABLEUNDERLINE changes meaning of the current block. */
+ if(line->type == MD_LINE_TABLEUNDERLINE) {
+ MD_ASSERT(ctx->current_block != NULL);
+ MD_ASSERT(ctx->current_block->n_lines == 1);
+ ctx->current_block->type = MD_BLOCK_TABLE;
+ ctx->current_block->data = line->data;
+ MD_ASSERT(pivot_line != &md_dummy_blank_line);
+ ((MD_LINE_ANALYSIS*)pivot_line)->type = MD_LINE_TABLE;
+ MD_CHECK(md_add_line_into_current_block(ctx, line));
+ return 0;
+ }
+
/* The current block also ends if the line has different type or block quote
* level. */
if(line->type != pivot_line->type || line->quote_level != pivot_line->quote_level)
diff --git a/md4c/md4c.h b/md4c/md4c.h
@@ -66,7 +66,7 @@ enum MD_BLOCKTYPE_tag {
MD_BLOCK_HR,
/* <h1>...</h1> (for levels up to 6)
- * Detail: See structure MD_BLOCK_H_DETAIL. */
+ * Detail: Structure MD_BLOCK_H_DETAIL. */
MD_BLOCK_H,
/* <pre><code>...</code></pre>
@@ -80,7 +80,17 @@ enum MD_BLOCKTYPE_tag {
MD_BLOCK_HTML,
/* <p>...</p> */
- MD_BLOCK_P
+ MD_BLOCK_P,
+
+ /* <table>...</table> and its contents.
+ * Detail: Structure MD_BLOCK_TD_DETAIL (used with MD_BLOCK_TH and MD_BLOCK_TD)
+ * Note all of these are used only if extension MD_FLAG_TABLES is enabled. */
+ MD_BLOCK_TABLE,
+ MD_BLOCK_THEAD,
+ MD_BLOCK_TBODY,
+ MD_BLOCK_TR,
+ MD_BLOCK_TH,
+ MD_BLOCK_TD
};
/* Span represents an in-line piece of a document which should be rendered with
@@ -95,11 +105,11 @@ enum MD_SPANTYPE_tag {
MD_SPAN_STRONG,
/* <a href="xxx">...</a>
- * Detail: See structure MD_SPAN_A_DETAIL. */
+ * Detail: Structure MD_SPAN_A_DETAIL. */
MD_SPAN_A,
/* <img src="xxx">...</a>
- * Detail: See structure MD_SPAN_IMG_DETAIL. */
+ * Detail: Structure MD_SPAN_IMG_DETAIL. */
MD_SPAN_IMG,
/* <code>...</code> */
@@ -145,6 +155,40 @@ enum MD_TEXTTYPE_tag {
};
+/* Alignment enumeration. */
+typedef enum MD_ALIGN_tag MD_ALIGN;
+enum MD_ALIGN_tag {
+ MD_ALIGN_DEFAULT = 0, /* When unspecified. */
+ MD_ALIGN_LEFT,
+ MD_ALIGN_CENTER,
+ MD_ALIGN_RIGHT
+};
+
+
+/* Detailed info for MD_BLOCK_H. */
+typedef struct MD_BLOCK_H_DETAIL_tag MD_BLOCK_H_DETAIL;
+struct MD_BLOCK_H_DETAIL_tag {
+ unsigned level; /* Header level (1 - 6) */
+};
+
+/* Detailed info for MD_BLOCK_CODE. */
+typedef struct MD_BLOCK_CODE_DETAIL_tag MD_BLOCK_CODE_DETAIL;
+struct MD_BLOCK_CODE_DETAIL_tag {
+ /* Complete "info string" */
+ const MD_CHAR* info;
+ MD_SIZE info_size;
+
+ /* Language portion of the info string. */
+ const MD_CHAR* lang;
+ MD_SIZE lang_size;
+};
+
+/* Detailed info for MD_BLOCK_TH and MD_BLOCK_TD. */
+typedef struct MD_BLOCK_TD_DETAIL_tag MD_BLOCK_TD_DETAIL;
+struct MD_BLOCK_TD_DETAIL_tag {
+ MD_ALIGN align;
+};
+
/* Detailed info for MD_SPAN_A. */
typedef struct MD_SPAN_A_DETAIL_tag MD_SPAN_A_DETAIL;
struct MD_SPAN_A_DETAIL_tag {
@@ -168,24 +212,6 @@ struct MD_SPAN_IMG_DETAIL_tag {
MD_SIZE title_size;
};
-/* Detailed info for MD_BLOCK_H. */
-typedef struct MD_BLOCK_H_DETAIL_tag MD_BLOCK_H_DETAIL;
-struct MD_BLOCK_H_DETAIL_tag {
- unsigned level; /* Header level (1 - 6) */
-};
-
-/* Detailed info for MD_BLOCK_CODE. */
-typedef struct MD_BLOCK_CODE_DETAIL_tag MD_BLOCK_CODE_DETAIL;
-struct MD_BLOCK_CODE_DETAIL_tag {
- /* Complete "info string" */
- const MD_CHAR* info;
- MD_SIZE info_size;
-
- /* Language portion of the info string. */
- const MD_CHAR* lang;
- MD_SIZE lang_size;
-};
-
/* Flags specifying Markdown dialect.
*
@@ -201,6 +227,7 @@ struct MD_BLOCK_CODE_DETAIL_tag {
#define MD_FLAG_NOHTMLBLOCKS 0x0020 /* Disable raw HTML blocks. */
#define MD_FLAG_NOHTMLSPANS 0x0040 /* Disable raw HTML (inline). */
#define MD_FLAG_NOHTML (MD_FLAG_NOHTMLBLOCKS | MD_FLAG_NOHTMLSPANS)
+#define MD_FLAG_TABLES 0x0100 /* Enable tables extension. */
/* Caller-provided callbacks.
*
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
@@ -30,3 +30,4 @@ $PYTHON "$TEST_DIR/spec_tests.py" -s "$TEST_DIR/spec.txt" -p "$PROGRAM"
# Test various extensions and deviations from the specifications:
$PYTHON "$TEST_DIR/spec_tests.py" -s "$TEST_DIR/permissive-email-autolinks.txt" -p "$PROGRAM --fpermissive-email-autolinks"
$PYTHON "$TEST_DIR/spec_tests.py" -s "$TEST_DIR/permissive-url-autolinks.txt" -p "$PROGRAM --fpermissive-url-autolinks"
+$PYTHON "$TEST_DIR/spec_tests.py" -s "$TEST_DIR/tables.txt" -p "$PROGRAM --ftables"
diff --git a/test/tables.txt b/test/tables.txt
@@ -0,0 +1,249 @@
+
+# Tables
+
+With the flag `MD_FLAG_TABLES`, MD4C enables extension for recognition of
+tables.
+
+Basic table example of a table with two columns and three lines (when not
+counting the header) is as follows:
+
+```````````````````````````````` example
+| Column 1 | Column 2 |
+|----------|----------|
+| foo | bar |
+| baz | qux |
+| quux | quuz |
+.
+<table>
+<thead>
+<tr><th>Column 1</th><th>Column 2</th></tr>
+</thead>
+<tbody>
+<tr><td>foo</td><td>bar</td></tr>
+<tr><td>baz</td><td>qux</td></tr>
+<tr><td>quux</td><td>quuz</td></tr>
+</tbody>
+</table>
+````````````````````````````````
+
+The leading and succeeding pipe characters (`|`) on each line are optional:
+
+```````````````````````````````` example
+Column 1 | Column 2 |
+---------|--------- |
+foo | bar |
+baz | qux |
+quux | quuz |
+.
+<table>
+<thead>
+<tr><th>Column 1</th><th>Column 2</th></tr>
+</thead>
+<tbody>
+<tr><td>foo</td><td>bar</td></tr>
+<tr><td>baz</td><td>qux</td></tr>
+<tr><td>quux</td><td>quuz</td></tr>
+</tbody>
+</table>
+````````````````````````````````
+
+```````````````````````````````` example
+| Column 1 | Column 2
+|----------|---------
+| foo | bar
+| baz | qux
+| quux | quuz
+.
+<table>
+<thead>
+<tr><th>Column 1</th><th>Column 2</th></tr>
+</thead>
+<tbody>
+<tr><td>foo</td><td>bar</td></tr>
+<tr><td>baz</td><td>qux</td></tr>
+<tr><td>quux</td><td>quuz</td></tr>
+</tbody>
+</table>
+````````````````````````````````
+
+```````````````````````````````` example
+Column 1 | Column 2
+---------|---------
+foo | bar
+baz | qux
+quux | quuz
+.
+<table>
+<thead>
+<tr><th>Column 1</th><th>Column 2</th></tr>
+</thead>
+<tbody>
+<tr><td>foo</td><td>bar</td></tr>
+<tr><td>baz</td><td>qux</td></tr>
+<tr><td>quux</td><td>quuz</td></tr>
+</tbody>
+</table>
+````````````````````````````````
+
+However for one-column table, at least one of those has to be used, otherwise
+it would be parsed as a Setext title followed by paragraph.
+
+```````````````````````````````` example
+Column 1
+--------
+foo
+baz
+quux
+.
+<h2>Column 1</h2>
+<p>foo
+baz
+quux</p>
+````````````````````````````````
+
+Leading and trailing whitespace in a table cell is ignored and the columns do
+not need to be aligned.
+
+```````````````````````````````` example
+Column 1 |Column 2
+---|---
+foo | bar
+baz| qux
+quux|quuz
+.
+<table>
+<thead>
+<tr><th>Column 1</th><th>Column 2</th></tr>
+</thead>
+<tbody>
+<tr><td>foo</td><td>bar</td></tr>
+<tr><td>baz</td><td>qux</td></tr>
+<tr><td>quux</td><td>quuz</td></tr>
+</tbody>
+</table>
+````````````````````````````````
+
+The table cannot interrupt a paragraph.
+
+```````````````````````````````` example
+Lorem ipsum dolor sit amet.
+| Column 1 | Column 2
+| ---------|---------
+| foo | bar
+| baz | qux
+| quux | quuz
+.
+<p>Lorem ipsum dolor sit amet.
+| Column 1 | Column 2
+| ---------|---------
+| foo | bar
+| baz | qux
+| quux | quuz</p>
+````````````````````````````````
+
+But paragraph or other block can interrupt a table as a line without any pipe
+ends the table.
+
+```````````````````````````````` example
+Column 1 | Column 2
+---------|---------
+foo | bar
+baz | qux
+quux | quuz
+Lorem ipsum dolor sit amet.
+.
+<table>
+<thead>
+<tr><th>Column 1</th><th>Column 2</th></tr>
+</thead>
+<tbody>
+<tr><td>foo</td><td>bar</td></tr>
+<tr><td>baz</td><td>qux</td></tr>
+<tr><td>quux</td><td>quuz</td></tr>
+</tbody>
+</table>
+<p>Lorem ipsum dolor sit amet.</p>
+````````````````````````````````
+
+The ruling line between head and body of the table must include the same amount
+of cells as the line with column names, and each cell has to consist of three
+dash (`-`) characters. However first, last or both dashes may be replaced with
+colon to denote column alignment.
+
+Thus this is not a table because there are too few dashes for Column 2.
+
+```````````````````````````````` example
+| Column 1 | Column 2
+| ---------|--
+| foo | bar
+| baz | qux
+| quux | quuz
+.
+<p>| Column 1 | Column 2
+| ---------|--
+| foo | bar
+| baz | qux
+| quux | quuz</p>
+````````````````````````````````
+
+And this is a table where each of the four column uses different alignment.
+
+```````````````````````````````` example
+| Column 1 | Column 2 | Column 3 | Column 4 |
+|----------|:---------|:--------:|---------:|
+| default | left | center | right |
+.
+<table>
+<thead>
+<tr><th>Column 1</th><th align="left">Column 2</th><th align="center">Column 3</th><th align="right">Column 4</th></tr>
+</thead>
+<tbody>
+<tr><td>default</td><td align="left">left</td><td align="center">center</td><td align="right">right</td></tr>
+</tbody>
+</table>
+````````````````````````````````
+
+To include a literal pipe character in any cell, it has to be escaped.
+
+```````````````````````````````` example
+Column 1 | Column 2
+---------|---------
+foo | bar
+baz | qux \| xyzzy
+quux | quuz
+.
+<table>
+<thead>
+<tr><th>Column 1</th><th>Column 2</th></tr>
+</thead>
+<tbody>
+<tr><td>foo</td><td>bar</td></tr>
+<tr><td>baz</td><td>qux | xyzzy</td></tr>
+<tr><td>quux</td><td>quuz</td></tr>
+</tbody>
+</table>
+````````````````````````````````
+
+Contents of each cell is parsed as an inline text which may contents any
+inline Markdown spans like emphasis, strong emphasis, links etc.
+
+```````````````````````````````` example
+Column 1 | Column 2
+---------|---------
+*foo* | bar
+**baz** | [qux]
+quux | [quuz](/url2)
+
+[qux]: /url
+.
+<table>
+<thead>
+<tr><th>Column 1</th><th>Column 2</th></tr>
+</thead>
+<tbody>
+<tr><td><em>foo</em></td><td>bar</td></tr>
+<tr><td><strong>baz</strong></td><td><a href="/url">qux</a></td></tr>
+<tr><td>quux</td><td><a href="/url2">quuz</a></td></tr>
+</tbody>
+</table>
+````````````````````````````````