md4c

C Markdown parser. Fast. SAX-like interface. Compliant to CommonMark specification.
git clone https://noulin.net/git/md4c.git
Log | Files | Refs | README | LICENSE

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:
MREADME.md | 2+-
Mmd2html/md2html.c | 29+++++++++++++++++++++++++++++
Mmd4c/md4c.c | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mmd4c/md4c.h | 71+++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mscripts/run-tests.sh | 1+
Atest/tables.txt | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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> +````````````````````````````````