"},I0.prototype.esc=$,e.HtmlRenderer=F,e.Node=m,e.Parser=function(e){return null==(e=e||{}).minimumHashtagLength&&(e.minimumHashtagLength=3),{doc:new j0,blocks:y0,blockStarts:v0,tip:this.doc,oldtip:this.doc,currentLine:"",lineNumber:0,offset:0,column:0,nextNonspace:0,nextNonspaceColumn:0,indent:0,indented:!1,blank:!1,partiallyConsumedTab:!1,allClosed:!0,lastMatchedContainer:this.doc,refmap:{},lastLineLength:0,inlineParser:new Xu(e),findNextNonspace:T0,advanceOffset:q0,advanceNextNonspace:D0,addLine:Qu,addChild:d0,changeTipType:e0,incorporateLine:N0,finalize:O0,processInlines:F0,closeUnmatchedBlocks:u0,parse:P0,options:e}},e.Renderer=O,e.XmlRenderer=I0,Object.defineProperty(e,"__esModule",{value:!0})});
diff --git a/lib/blocks.js b/lib/blocks.js
index 92f8a43a..5e796e37 100644
--- a/lib/blocks.js
+++ b/lib/blocks.js
@@ -1,7 +1,7 @@
"use strict";
import Node from "./node.js";
-import { unescapeString, OPENTAG, CLOSETAG } from "./common.js";
+import { unescapeString, OPENTAG, CLOSETAG, ESCAPABLE } from "./common.js";
import InlineParser from "./inlines.js";
var CODE_INDENT = 4;
@@ -55,10 +55,6 @@ var reSetextHeadingLine = /^(?:=+|-+)[ \t]*$/;
var reLineEnding = /\r\n|\n|\r/;
-var reTableRow = /^(\|?)(?:(?:\\\||[^|])*\|?)+$/;
-
-var reValidTableDelimiter = /^:?-+:?$/;
-
var MAX_AUTOCOMPLETED_CELLS = 1000;
// Returns true if string contains only space characters.
@@ -113,6 +109,18 @@ var addLine = function() {
this.tip._string_content += this.currentLine.slice(this.offset) + "\n";
};
+// Change the tip to be of type tag as long as the parent can
+// contain a block of that type. Returns whether or not the
+// type could be changed.
+var changeTipType = function(tag) {
+ const validChild = this.blocks[this.tip.parent.type].canContain(tag);
+ if (validChild) {
+ this.tip._type = tag;
+ }
+
+ return validChild;
+};
+
// Add block of type tag as a child of the tip. If the tip can't
// accept children, close and finalize it and try its parent,
// and so on til we find a block that can accept children.
@@ -459,9 +467,6 @@ var blocks = {
for (var row = block.firstChild; row; row = row.next) {
var i = 0;
for (var cell = row.firstChild; cell; cell = cell.next) {
- // copy column alignment to each cell
- cell.align = block.alignColumns[i];
-
i += 1;
// if there's more columns in a row than the header row, GitHub cuts them off
@@ -501,15 +506,8 @@ var blocks = {
return 1;
},
- finalize: function(parser, block) {
- // mark the header row since it'll have special treatment when rendering
- if (block === block.parent.firstChild) {
- block.isHeading = true;
-
- for (var cell = block.firstChild; cell; cell = cell.next) {
- cell.isHeading = true;
- }
- }
+ finalize: function() {
+ return;
},
canContain: function(t) { return (t === "table_cell"); },
acceptsLines: false
@@ -768,65 +766,104 @@ var blockStarts = [
// table
function(parser, container) {
- if (container.type !== "document") {
- // @hmhealey We should be able to have a table inside of a list item or block quote to match GitHub
- // but I'm not sure if the mobile app can render that, so let's not bother with it for now
+ // Because tables depend on two adjacent lines, the first line is read into a paragraph and then we might turn
+ // that paragraph into a table when we read the second line.
+
+ if (parser.indented || container.type !== "paragraph") {
return 0;
}
-
- if (parser.indented) {
+ if (container._tableVisited) {
return 0;
}
- if (!parser.nextLine) {
- // tables require at least two rows (header and delimiter)
+ // At this point, we're on the second line of the paragraph, so we can check to see the two lines we've read
+ // are the header row and delimiter row of the table
+
+ // Check for a delimiter first since it's stricter than the header row.
+ const delimiterCells = parseTableRow(parser.currentLine, parser.nextNonspace);
+ if (!delimiterCells || !validateDelimiterRow(delimiterCells)) {
+ // The second line of the paragraph isn't a table row, so this paragraph isn't actually a table
return 0;
}
- const nextColumn = measureNonspaceColumn(parser.nextLine);
- if (Math.abs(nextColumn - parser.column) >= CODE_INDENT) {
- // the delimiter row must be on the same indentation level as the header row
+ // container._string_content contains everything in the paragraph so far including a trailing newline, but
+ // we only want to check the last line of it for the header row
+ const lastLineMatch = matchLastLine(container._string_content);
+ const lastLine = lastLineMatch[1];
+
+ const headerCells = parseTableRow(lastLine, 0);
+ if (!headerCells) {
+ // The first line isn't a header row, so this isn't a table
return 0;
}
- parser.advanceNextNonspace();
+ if (delimiterCells.length !== headerCells.length) {
+ // The first two rows must be the same length for this to be considered a table
- // check for a delimiter first since it's stricter than the header row
- const nextLine = parser.nextLine.trim();
- const delimiterMatch = reTableRow.exec(nextLine);
- if (!delimiterMatch) {
+ // Track that we've already identified that this paragraph isn't a table, so that we don't check the same
+ // paragraph again
+ container._tableVisited = true;
return 0;
}
- const delimiterCells = parseDelimiterRow(delimiterMatch[0]);
- if (!delimiterCells) {
+ // Turn this paragraph into a table if possible
+ if (!parser.changeTipType("table")) {
return 0;
}
- const currentLine = parser.currentLine.slice(parser.nextNonspace).trim();
- var headerMatch = reTableRow.exec(currentLine);
- if (!headerMatch) {
- return 0;
+ // If there's any text before lastLine, then there's text before the table that we need to re-add as a
+ // paragraph before the table
+ if (lastLineMatch.index > 0) {
+ // Create the new paragraph node based on where we found the header row
+ const textBeforeTable = parser.tip._string_content.substring(0, lastLineMatch.index);
+ const lastLineBeforeTable = matchLastLine(textBeforeTable);
+
+ const newParagraph = new Node("paragraph", [
+ parser.tip.sourcepos[0],
+ [parser.lineNumber - 2, lastLineBeforeTable.length],
+ ]);
+ newParagraph._string_content = textBeforeTable;
+
+ // Update the parser.tip which is now the table with its new position
+ parser.tip._string_content = parser.tip._string_content.substring(lastLineMatch.index);
+ parser.tip._sourcepos[0] = [parser.lineNumber - 1, 0];
+
+ // Add the paragraph before the table
+ parser.tip.insertBefore(newParagraph);
}
- var headerCells = parseTableCells(headerMatch[0]);
+ // Store the alignments of the columns and then skip the delimiter line since we've
+ // gotten what we need from it
+ parser.tip.alignColumns = delimiterCells.map(getCellAlignment);
- if (delimiterCells.length !== headerCells.length) {
- // the first two rows must be the same length for this to be considered a table
- return 0;
- }
+ const headerRow = new Node("table_row", [
+ [parser.lineNumber - 1, parser.offset + 1],
+ [parser.lineNumber - 1, parser.offset + lastLine.length],
+ ]);
+ headerRow._string_content = container._string_content.substring(0, lastLine.length);
+ headerRow._isHeading = true;
- parser.closeUnmatchedBlocks();
+ for (let i = 0; i < headerCells.length; i++) {
+ const cell = new Node("table_cell", [
+ [parser.lineNumber - 1, headerCells[i].start],
+ [parser.lineNumber - 1, headerCells[i].end],
+ ]);
- parser.advanceNextNonspace();
- parser.addChild("table", parser.offset);
+ cell._string_content = headerCells[i].contents;
+ cell._align = parser.tip.alignColumns[i];
+ cell._isHeading = true;
- // store the alignments of the columns and then skip the delimiter line since we've
- // gotten what we need from it
- parser.tip.alignColumns = delimiterCells.map(getCellAlignment);
+ headerRow.appendChild(cell);
+ }
+
+ parser.tip.appendChild(headerRow);
- parser.skipNextLine();
+ // Mark the rest of the line as read
+ parser.advanceOffset(
+ parser.currentLine.length - parser.offset,
+ false
+ );
return 1;
},
@@ -841,75 +878,173 @@ var blockStarts = [
return 2;
}
- var rowMatch = reTableRow.exec(parser.currentLine.slice(parser.nextNonspace));
- if (!rowMatch) {
+ const cells = parseTableRow(parser.currentLine, parser.nextNonspace);
+ if (!cells) {
return 0;
}
parser.closeUnmatchedBlocks();
- parser.advanceNextNonspace();
-
- parser.addChild("table_row", parser.offset);
+ parser.addChild("table_row", parser.nextNonspace);
- // advance past leading | if one exists
- parser.advanceOffset(rowMatch[1].length, false);
+ for (let i = 0; i < cells.length; i++) {
+ const cell = new Node("table_cell", [
+ [parser.lineNumber, cells[i].start],
+ [parser.lineNumber, cells[i].end],
+ ]);
- // parse the row into cells
- var cells = parseTableCells(rowMatch[0]);
- var length = cells.length;
- for (var i = 0; i < length; i++) {
- parser.addChild("table_cell", parser.offset);
+ cell._string_content = cells[i].contents;
+ cell._align = parser.tip.parent.alignColumns[i];
- parser.tip._string_content = cells[i].trim();
-
- parser.advanceOffset(cells[i].length + 1);
+ parser.tip.appendChild(cell);
}
+ // Mark the rest of the line as read
+ parser.advanceOffset(
+ parser.currentLine.length - parser.offset,
+ false
+ );
+
return 2;
}
];
-const parseDelimiterRow = function(row) {
- if (row.indexOf("|") === -1) {
+const parseTableRow = function(line, startAt) {
+ // This is attempting to replicate row_from_string from GitHub's Commonmark fork. That function can be found here:
+ // https://github.com/github/cmark-gfm/blob/587a12bb54d95ac37241377e6ddc93ea0e45439b/extensions/table.c#L189
+
+ let cells = [];
+
+ let expectMoreCells = true;
+
+ // Start at the current parser position
+ let offset = startAt;
+
+ // Read past the optional leading pipe
+ offset += scanTableCellEnd(line, offset);
+
+ while (offset < line.length && expectMoreCells) {
+ const cellLength = scanTableCell(line, offset);
+ const pipeLength = scanTableCellEnd(line, offset + cellLength);
+
+ if (cellLength > 0 || pipeLength > 0) {
+ // We're guaranteed to have found a cell because we either found some cell content (cellLength > 0) or
+ // we found an empty cell with a pipe (cellLength == 0 && pipeLength > 0)
+ const cellContents = unescapePipes(line.substring(offset, offset + cellLength));
+
+ cells.push({
+ contents: cellContents,
+ start: offset,
+ end: offset + cellLength,
+ });
+
+ offset += cellLength + pipeLength;
+ }
+
+ if (pipeLength > 0) {
+ expectMoreCells = true;
+ } else {
+ // We've read the last cell, so check if we've reached the end of the row
+ const rowEndLength = scanTableRowEnd(line, offset);
+
+ // Unlike cmark-gfm, we don't need to try again on the next line because we only call this function with
+ // one line at a time
+
+ if (rowEndLength === -1) {
+ // There's more text after this, so this isn't a table row
+ } else {
+ offset += rowEndLength;
+ }
+
+ expectMoreCells = false;
+ }
+ }
+
+ if (offset === line.length) {
+ // We've read the whole line, so it's a valid row
+ return cells;
+ } else {
+ // There's unhandled text here, so it's not actually a table row
return null;
}
+};
+
+const reTableCell = new RegExp("^([\\\\]" + ESCAPABLE + "|[^|\r\n])+");
+const scanTableCell = function(line, offset) {
+ // Reads up until a newline or an unescaped pipe and return the number of characters read
+ const match = reTableCell.exec(line.substring(offset));
+ if (match) {
+ return match[0].length;
+ } else {
+ // If this doesn't match, it may still be valid because there's an empty table cell or we're at the end of the line
+ return 0;
+ }
+};
- const cells = parseTableCells(row).map((cell) => cell.trim());
+const scanTableCellEnd = function(line, offset) {
+ // Read an optional pipe followed by some amount of optional whitespace and return the number of characters read
+ let i = 0;
- for (const cell of cells) {
- if (!reValidTableDelimiter.test(cell)) {
- return null;
- }
+ if (line.charAt(offset + i) === "|") {
+ i += 1;
}
- return cells;
-}
+ let c = line.charAt(offset + i);
+ while (c === " " || c === "\t" || c === "\v" || c === "\f") {
+ i += 1;
+ c = line.charAt(offset + i);
+ }
+
+ return i;
+};
-var parseTableCells = function(row) {
- // remove starting pipe to make life easier
- row = row.replace(/^\|/, "");
+const scanTableRowEnd = function(line, offset) {
+ // Read any amount of optional whitespace and then ensure that we're at the end of the string
+ let i = 0;
- var reTableCell = /\||((?:\\\||[^|])+)\|?/g;
+ let c = line.charAt(offset + i);
+ while (c === " " || c === "\t" || c === "\v" || c === "\f") {
+ i += 1;
+ c = line.charAt(offset + i);
+ }
- var match;
- var cells = [];
- while (match = reTableCell.exec(row)) {
- cells.push(match[1] || "");
+ if (offset + i === line.length) {
+ // This is the end of the row
+ return i;
+ } else {
+ // There's still more after this which means this isn't actually a table row
+ return -1;
+ }
+};
+
+const reValidTableDelimiter = /^[ \t]*:?-+:?[ \t]*$/;
+const validateDelimiterRow = function(cells) {
+ for (const cell of cells) {
+ if (!reValidTableDelimiter.test(cell.contents)) {
+ return false;
+ }
}
- return cells;
+ return true;
+};
+
+const unescapePipes = function(str) {
+ return str.replace("\\|", "|");
+};
+
+const matchLastLine = function(str) {
+ return (/([^\n]*)\n$/).exec(str);
};
var getCellAlignment = function(cell) {
- cell = cell.trim();
+ const cellContents = cell.contents.trim();
- if (cell.charAt(0) === ":") {
- if (cell.charAt(cell.length - 1) === ":") {
+ if (cellContents.charAt(0) === ":") {
+ if (cellContents.charAt(cellContents.length - 1) === ":") {
return "center";
} else {
return "left";
}
- } else if (cell.endsWith(":")) {
+ } else if (cellContents.endsWith(":")) {
return "right";
} else {
return "";
@@ -974,31 +1109,10 @@ var findNextNonspace = function() {
this.indented = this.indent >= CODE_INDENT;
};
-function measureNonspaceColumn(line) {
- // This code is copied from findNextNonspace above
- var i = 0;
- var cols = 0;
- var c;
-
- while ((c = line.charAt(i)) !== "") {
- if (c === " ") {
- i++;
- cols++;
- } else if (c === "\t") {
- i++;
- cols += 4 - (cols % 4);
- } else {
- break;
- }
- }
-
- return cols;
-}
-
// Analyze a line of text and update the document appropriately.
// We parse markdown text by calling this on each line of input,
// then finalizing the document.
-var incorporateLine = function(ln, nextLn) {
+var incorporateLine = function(ln) {
var all_matched = true;
var t;
@@ -1016,7 +1130,6 @@ var incorporateLine = function(ln, nextLn) {
}
this.currentLine = ln;
- this.nextLine = nextLn;
// For each containing block, try to parse the associated line start.
// Bail out on failure: container will point to the last matching block.
@@ -1061,7 +1174,7 @@ var incorporateLine = function(ln, nextLn) {
!this.indented && // starts indented code blocks
!reMaybeSpecial.test(ln.slice(this.nextNonspace)) && // starts lists, block quotes, etc
(container.type !== "table" && container.type !== "table_row") && // start table rows
- (nextLn && !reMaybeDelimiterRow.test(nextLn.slice(this.nextNonspace))) // starts tables
+ !reMaybeDelimiterRow.test(ln.slice(this.nextNonspace)) // starts tables
) {
this.advanceNextNonspace();
break;
@@ -1156,10 +1269,6 @@ var incorporateLine = function(ln, nextLn) {
this.lastLineLength = ln.length;
};
-var skipNextLine = function() {
- this.shouldSkipNextLine = true;
-};
-
// Finalize a block. Close it and do any necessary postprocessing,
// e.g. creating string_content from strings, setting the 'tight'
// or 'loose' status of a list, and parsing the beginnings
@@ -1210,7 +1319,6 @@ var parse = function(input) {
this.column = 0;
this.lastMatchedContainer = this.doc;
this.currentLine = "";
- this.shouldSkipNextLine = false;
if (this.options.time) {
console.time("preparing input");
}
@@ -1227,11 +1335,7 @@ var parse = function(input) {
console.time("block parsing");
}
for (var i = 0; i < len; i++) {
- if (this.shouldSkipNextLine) {
- this.shouldSkipNextLine = false;
- continue;
- }
- this.incorporateLine(lines[i], lines[i + 1]);
+ this.incorporateLine(lines[i]);
}
while (this.tip) {
this.finalize(this.tip, len);
@@ -1283,8 +1387,8 @@ function Parser(options) {
advanceNextNonspace: advanceNextNonspace,
addLine: addLine,
addChild: addChild,
+ changeTipType: changeTipType,
incorporateLine: incorporateLine,
- skipNextLine: skipNextLine,
finalize: finalize,
processInlines: processInlines,
closeUnmatchedBlocks: closeUnmatchedBlocks,
diff --git a/lib/render/html.js b/lib/render/html.js
index c1a9bb43..df4fe99f 100644
--- a/lib/render/html.js
+++ b/lib/render/html.js
@@ -288,12 +288,15 @@ function table_row(node, entering) {
this.cr();
} else {
this.tag("/tr");
+ this.cr();
if (node === node.parent.firstChild) {
this.cr(); // we're not consistent about how these tags are laid out because this is what GitHub does
this.tag("/thead");
+ this.cr();
} else if (node === node.parent.lastChild) {
this.tag("/tbody");
+ this.cr();
}
}
}
diff --git a/package-lock.json b/package-lock.json
index d848a725..f8b9c545 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@mattermost/commonmark",
- "version": "0.30.1-2",
+ "version": "0.30.1-3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@mattermost/commonmark",
- "version": "0.30.1-2",
+ "version": "0.30.1-3",
"license": "BSD-2-Clause",
"dependencies": {
"entities": "~3.0.1",
diff --git a/package.json b/package.json
index 558e3652..271d5caa 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@mattermost/commonmark",
"description": "the Mattermost fork of a strongly specified, highly compatible variant of Markdown",
- "version": "0.30.1-2",
+ "version": "0.30.1-3",
"homepage": "https://commonmark.org",
"keywords": [
"markdown",
diff --git a/test/gfm_extensions.txt b/test/gfm_extensions.txt
new file mode 100644
index 00000000..d7a9267a
--- /dev/null
+++ b/test/gfm_extensions.txt
@@ -0,0 +1,529 @@
+The following is a section of
+https://github.com/github/cmark-gfm/blob/587a12bb54d95ac37241377e6ddc93ea0e45439b/test/extensions.txt
+with everything not related to tables taken out.
+
+---
+title: Extensions test
+author: Yuki Izumi
+version: 0.1
+date: '2016-08-31'
+license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)'
+...
+
+## Tables
+
+Here's a well-formed table, doing everything it should.
+
+```````````````````````````````` example
+| abc | def |
+| --- | --- |
+| ghi | jkl |
+| mno | pqr |
+.
+
+
+
+abc |
+def |
+
+
+
+
+ghi |
+jkl |
+
+
+mno |
+pqr |
+
+
+
+````````````````````````````````
+
+We're going to mix up the table now; we'll demonstrate that inline formatting
+works fine, but block elements don't. You can also have empty cells, and the
+textual alignment of the columns is shown to be irrelevant.
+
+```````````````````````````````` example
+Hello!
+
+| _abc_ | セン |
+| ----- | ---- |
+| 1. Block elements inside cells don't work. | |
+| But _**inline elements do**_. | x |
+
+Hi!
+.
+Hello!
+
+
+
+abc |
+セン |
+
+
+
+
+1. Block elements inside cells don't work. |
+ |
+
+
+But inline elements do. |
+x |
+
+
+
+Hi!
+````````````````````````````````
+
+Here we demonstrate some edge cases about what is and isn't a table.
+
+```````````````````````````````` example
+| Not enough table | to be considered table |
+
+| Not enough table | to be considered table |
+| Not enough table | to be considered table |
+
+| Just enough table | to be considered table |
+| ----------------- | ---------------------- |
+
+| ---- | --- |
+
+|x|
+|-|
+
+| xyz |
+| --- |
+.
+| Not enough table | to be considered table |
+| Not enough table | to be considered table |
+| Not enough table | to be considered table |
+
+
+
+Just enough table |
+to be considered table |
+
+
+
+| ---- | --- |
+
+
+````````````````````````````````
+
+A "simpler" table, GFM style:
+
+```````````````````````````````` example
+abc | def
+--- | ---
+xyz | ghi
+.
+
+
+
+abc |
+def |
+
+
+
+
+xyz |
+ghi |
+
+
+
+````````````````````````````````
+
+We are making the parser slighly more lax here. Here is a table with spaces at
+the end:
+
+```````````````````````````````` example
+Hello!
+
+| _abc_ | セン |
+| ----- | ---- |
+| this row has a space at the end | |
+| But _**inline elements do**_. | x |
+
+Hi!
+.
+Hello!
+
+
+
+abc |
+セン |
+
+
+
+
+this row has a space at the end |
+ |
+
+
+But inline elements do. |
+x |
+
+
+
+Hi!
+````````````````````````````````
+
+Table alignment:
+
+```````````````````````````````` example
+aaa | bbb | ccc | ddd | eee
+:-- | --- | :-: | --- | --:
+fff | ggg | hhh | iii | jjj
+.
+
+
+
+aaa |
+bbb |
+ccc |
+ddd |
+eee |
+
+
+
+
+fff |
+ggg |
+hhh |
+iii |
+jjj |
+
+
+
+````````````````````````````````
+
+### Table cell count mismatches
+
+The header and delimiter row must match.
+
+```````````````````````````````` example
+| a | b | c |
+| --- | --- |
+| this | isn't | okay |
+.
+| a | b | c |
+| --- | --- |
+| this | isn't | okay |
+````````````````````````````````
+
+But any of the body rows can be shorter. Rows longer
+than the header are truncated.
+
+```````````````````````````````` example
+| a | b | c |
+| --- | --- | ---
+| x
+| a | b
+| 1 | 2 | 3 | 4 | 5 |
+.
+
+
+
+a |
+b |
+c |
+
+
+
+
+x |
+ |
+ |
+
+
+a |
+b |
+ |
+
+
+1 |
+2 |
+3 |
+
+
+
+````````````````````````````````
+
+### Embedded pipes
+
+Tables with embedded pipes could be tricky.
+
+```````````````````````````````` example
+| a | b |
+| --- | --- |
+| Escaped pipes are \|okay\|. | Like \| this. |
+| Within `\|code\| is okay` too. |
+| _**`c\|`**_ \| complex
+| don't **\_reparse\_**
+.
+
+
+
+a |
+b |
+
+
+
+
+Escaped pipes are |okay|. |
+Like | this. |
+
+
+Within |code| is okay too. |
+ |
+
+
+c| | complex |
+ |
+
+
+don't _reparse_ |
+ |
+
+
+
+````````````````````````````````
+
+### Oddly-formatted markers
+
+This shouldn't assert.
+
+```````````````````````````````` example
+| a |
+--- |
+.
+
+````````````````````````````````
+
+### Escaping
+
+```````````````````````````````` example
+| a | b |
+| --- | --- |
+| \\ | `\\` |
+| \\\\ | `\\\\` |
+| \_ | `\_` |
+| \| | `\|` |
+| \a | `\a` |
+
+\\ `\\`
+
+\\\\ `\\\\`
+
+\_ `\_`
+
+\| `\|`
+
+\a `\a`
+.
+
+
+
+a |
+b |
+
+
+
+
+\ |
+\\ |
+
+
+\\ |
+\\\\ |
+
+
+_ |
+\_ |
+
+
+| |
+| |
+
+
+\a |
+\a |
+
+
+
+\ \\
+\\ \\\\
+_ \_
+| \|
+\a \a
+````````````````````````````````
+
+### Embedded HTML
+
+```````````````````````````````` example
+| a |
+| --- |
+| hello |
+| ok
sure |
+.
+
+
+
+a |
+
+
+
+
+hello |
+
+
+ok sure |
+
+
+
+````````````````````````````````
+
+### Reference-style links
+
+```````````````````````````````` example
+Here's a link to [Freedom Planet 2][].
+
+| Here's a link to [Freedom Planet 2][] in a table header. |
+| --- |
+| Here's a link to [Freedom Planet 2][] in a table row. |
+
+[Freedom Planet 2]: http://www.freedomplanet2.com/
+.
+Here's a link to Freedom Planet 2.
+
+````````````````````````````````
+
+### Sequential cells
+
+```````````````````````````````` example
+| a | b | c |
+| --- | --- | --- |
+| d || e |
+.
+
+
+
+a |
+b |
+c |
+
+
+
+
+d |
+ |
+e |
+
+
+
+````````````````````````````````
+
+### Interaction with emphasis
+
+```````````````````````````````` example
+| a | b |
+| --- | --- |
+|***(a)***|
+.
+
+
+
+a |
+b |
+
+
+
+
+(a) |
+ |
+
+
+
+````````````````````````````````
+
+### a table can be recognised when separated from a paragraph of text without an empty line
+
+```````````````````````````````` example
+123
+456
+| a | b |
+| ---| --- |
+d | e
+.
+123
+456
+
+
+
+a |
+b |
+
+
+
+
+d |
+e |
+
+
+
+````````````````````````````````
+
+## Interop
+
+Autolink and tables.
+
+```````````````````````````````` example
+| a | b |
+| --- | --- |
+| https://github.com www.github.com | http://pokemon.com |
+.
+
+````````````````````````````````
diff --git a/test/tables.txt b/test/tables.txt
index 7d5270c0..bfc179df 100644
--- a/test/tables.txt
+++ b/test/tables.txt
@@ -42,7 +42,8 @@ foo
foo |
-
+
+
````````````````````````````````
```````````````````````````````` example
@@ -63,7 +64,9 @@ foo
baz |
-
+
+
+
````````````````````````````````
```````````````````````````````` example
@@ -76,7 +79,8 @@ foo
foo |
aaa |
-
+
+
````````````````````````````````
```````````````````````````````` example
@@ -100,7 +104,9 @@ foo
baz |
ccc |
-
+
+
+
````````````````````````````````
### Tables without borders
@@ -115,7 +121,8 @@ foo|aaa
foo |
aaa |
-
+
+
````````````````````````````````
```````````````````````````````` example
@@ -139,7 +146,9 @@ baz|ccc
baz |
ccc |
-
+
+
+
````````````````````````````````
### Tables with mixed borders
@@ -153,7 +162,8 @@ baz|ccc
foo |
-
+
+
````````````````````````````````
```````````````````````````````` example
@@ -165,7 +175,8 @@ foo|
foo |
-
+
+
````````````````````````````````
```````````````````````````````` example
@@ -177,7 +188,8 @@ foo
foo |
-
+
+
````````````````````````````````
```````````````````````````````` example
@@ -198,7 +210,9 @@ baz
baz |
-
+
+
+
````````````````````````````````
```````````````````````````````` example
@@ -219,7 +233,9 @@ bar
baz |
-
+
+
+
````````````````````````````````
```````````````````````````````` example
@@ -240,7 +256,9 @@ baz
baz |
-
+
+
+
````````````````````````````````
```````````````````````````````` example
@@ -253,7 +271,8 @@ foo|aaa
foo |
aaa |
-
+
+
````````````````````````````````
```````````````````````````````` example
@@ -277,7 +296,9 @@ baz|ccc
baz |
ccc |
-
+
+
+
````````````````````````````````
```````````````````````````````` example
@@ -304,7 +325,9 @@ ee|baz|ff
ee |
baz |
ff |
-
+
+
+
````````````````````````````````
### Table with alignment
@@ -336,7 +359,9 @@ ee|baz|ff
3 |
2 |
1 |
-
+
+
+
````````````````````````````````
### Table with uneven rows
@@ -365,7 +390,9 @@ b | c | d | e | f
b |
c |
d |
-
+
+
+
````````````````````````````````
### Tables followed by other text
@@ -388,7 +415,9 @@ other text
aaa |
bbb |
-
+
+
+
other text
````````````````````````````````
@@ -411,7 +440,9 @@ aaa|bbb
aaa |
bbb |
-
+
+
+
- other text
- other text
@@ -436,7 +467,9 @@ aaa|bbb
aaa |
bbb |
-
+
+
+
````````````````````````````````
```````````````````````````````` example
@@ -455,7 +488,9 @@ aaa|bbb
aaa |
bbb |
-
+
+
+
````````````````````````````````
```````````````````````````````` example
@@ -474,7 +509,9 @@ aaa|bbb
aaa |
bbb |
-
+
+
+
````````````````````````````````
### Tables with preceding whitespace
@@ -501,21 +538,24 @@ indented from the delimiter row.
foo |
aaa |
-
+
+
+
+
+
+
foo|aaa
---|---
@@ -543,21 +583,24 @@ foo|aaa
foo |
aaa |
-
+
+
+
+
+
+
foo|aaa
---|---
````````````````````````````````
@@ -587,7 +630,9 @@ only break the table and not prevent it from being generated at all.
333 |
444 |
-
+
+
+
|555|666|
````````````````````````````````
@@ -618,7 +663,9 @@ The whole table can also be slightly indented.
2 |
Name |
Server |
-
+
+
+
````````````````````````````````
### Tables with escaped pipes
@@ -644,7 +691,9 @@ The whole table can also be slightly indented.
b|z |
cc| |
-
+
+
+
````````````````````````````````
```````````````````````````````` example
@@ -668,7 +717,9 @@ b\|z|cc\|
b|z |
cc| |
-
+
+
+
````````````````````````````````
@hmhealey - The following section is copied from GitHub's tests for cmark available at
@@ -699,7 +750,9 @@ Here's a well-formed table, doing everything it should.
mno |
pqr |
-
+
+
+
````````````````````````````````
We're going to mix up the table now; we'll demonstrate that inline formatting
@@ -732,7 +785,9 @@ Hi!
But inline elements do. |
x |
-
+
+
+
Hi!
````````````````````````````````
@@ -764,20 +819,23 @@ Here we demonstrate some edge cases about what is and isn't a table.
Just enough table |
to be considered table |
-
+
+
| ---- | --- |
+
+
+
+
````````````````````````````````
A "simpler" table, GFM style:
@@ -798,7 +856,9 @@ xyz | ghi
xyz |
ghi |
-
+
+
+
````````````````````````````````
We are making the parser slighly more lax here. Here is a table with spaces at
@@ -830,7 +890,9 @@ Hi!
But inline elements do. |
x |
-
+
+
+
Hi!
````````````````````````````````
@@ -858,7 +920,9 @@ fff | ggg | hhh | iii | jjj
hhh |
iii |
jjj |
-
+
+
+
````````````````````````````````
### Table cell count mismatches
@@ -908,7 +972,9 @@ than the header are truncated.
1 |
2 |
3 |
-
+
+
+
````````````````````````````````
### Embedded pipes
@@ -946,7 +1012,9 @@ Tables with embedded pipes could be tricky.
don't _reparse_ |
|
-
+
+
+
````````````````````````````````
### Oddly-formatted markers
@@ -962,7 +1030,8 @@ This shouldn't assert.
a |
-
+
+
````````````````````````````````
### Escaping
@@ -1013,7 +1082,9 @@ This shouldn't assert.
\a |
\a |
-
+
+
+
\ \\
\\ \\\\
_ \_
@@ -1041,7 +1112,9 @@ This shouldn't assert.
ok sure |
-
+
+
+
````````````````````````````````
### Reference-style links
@@ -1065,7 +1138,9 @@ Here's a link to [Freedom Planet 2][].
Here's a link to Freedom Planet 2 in a table row. |
-
+
+
+
````````````````````````````````
### Empty cells
@@ -1088,7 +1163,9 @@ Here's a link to [Freedom Planet 2][].
d |
|
e |
-
+
+
+
````````````````````````````````
### Interaction with emphasis
@@ -1109,7 +1186,9 @@ Here's a link to [Freedom Planet 2][].
(a) |
|
-
+
+
+
````````````````````````````````
### Table without space at the end
@@ -1140,7 +1219,9 @@ Here's a link to [Freedom Planet 2][].
r3c1 |
r3c2 |
-
+
+
+
````````````````````````````````
### Table with space at the end
@@ -1171,7 +1252,9 @@ Here's a link to [Freedom Planet 2][].
r3c1 |
r3c2 |
-
+
+
+
````````````````````````````````
### Table with multiple spaces at the end
@@ -1202,7 +1285,341 @@ Here's a link to [Freedom Planet 2][].
r3c1 |
r3c2 |
-
+
+
+
+````````````````````````````````
+
+### Tables inside of blockquotes
+
+This is a table in a block quote with each line starting with an angle bracket.
+
+```````````````````````````````` example
+> | aaa | bbb |
+> | --- | --- |
+> | ccc | ddd |
+> | eee | fff |
+.
+
+
+
+
+aaa |
+bbb |
+
+
+
+
+ccc |
+ddd |
+
+
+eee |
+fff |
+
+
+
+
+````````````````````````````````
+
+Tables can be alongside paragraphs in block quotes.
+
+```````````````````````````````` example
+> This is the text at the start
+>
+> | aaa | bbb |
+> | --- | --- |
+> | ccc | ddd |
+> | eee | fff |
+>
+> This is the text in the middle
+>
+> | aaa | bbb |
+> | --- | --- |
+> | ccc | ddd |
+> | eee | fff |
+>
+> This is the text at the end
+.
+
+This is the text at the start
+
+
+
+aaa |
+bbb |
+
+
+
+
+ccc |
+ddd |
+
+
+eee |
+fff |
+
+
+
+This is the text in the middle
+
+
+
+aaa |
+bbb |
+
+
+
+
+ccc |
+ddd |
+
+
+eee |
+fff |
+
+
+
+This is the text at the end
+
+````````````````````````````````
+
+At time of writing, GitHub requires that parts of the table in a quote need to
+start with an angle bracket. Any part that isn't following an angle bracket
+will be treated as plain text.
+
+```````````````````````````````` example
+> | aaa | bbb |
+| --- | --- |
+| ccc | ddd |
+| eee | fff |
+.
+
+| aaa | bbb |
+| --- | --- |
+| ccc | ddd |
+| eee | fff |
+
+````````````````````````````````
+
+```````````````````````````````` example
+> | aaa | bbb |
+ | --- | --- |
+ | ccc | ddd |
+ | eee | fff |
+.
+
+| aaa | bbb |
+| --- | --- |
+| ccc | ddd |
+| eee | fff |
+
+````````````````````````````````
+
+Once the first two rows are after angle brackets, the rest will be a plain text
+outside of the block quote
+
+```````````````````````````````` example
+> | aaa | bbb |
+> | --- | --- |
+| ccc | ddd |
+| eee | fff |
+.
+
+
+
+| ccc | ddd |
+| eee | fff |
+````````````````````````````````
+
+```````````````````````````````` example
+> | aaa | bbb |
+> | --- | --- |
+ | ccc | ddd |
+ | eee | fff |
+.
+
+
+
+| ccc | ddd |
+| eee | fff |
+````````````````````````````````
+
+```````````````````````````````` example
+> | aaa | bbb |
+> | --- | --- |
+> | ccc | ddd |
+ | eee | fff |
+.
+
+
+
+
+aaa |
+bbb |
+
+
+
+
+ccc |
+ddd |
+
+
+
+
+| eee | fff |
+````````````````````````````````
+
+### Tables inside of lists
+
+Tables can be inside of lists as long as each row of the table is indented enough.
+
+```````````````````````````````` example
+- | aaa | bbb |
+ | --- | --- |
+ | ccc | ddd |
+ | eee | fff |
+.
+
+-
+
+
+
+aaa |
+bbb |
+
+
+
+
+ccc |
+ddd |
+
+
+eee |
+fff |
+
+
+
+
+
+````````````````````````````````
+
+```````````````````````````````` example
+ - | aaa | bbb |
+ | --- | --- |
+ | ccc | ddd |
+ | eee | fff |
+.
+
+-
+
+
+
+aaa |
+bbb |
+
+
+
+
+ccc |
+ddd |
+
+
+eee |
+fff |
+
+
+
+
+
+````````````````````````````````
+
+```````````````````````````````` example
+1. | aaa | bbb |
+ | --- | --- |
+ | ccc | ddd |
+ | eee | fff |
+.
+
+-
+
+
+
+aaa |
+bbb |
+
+
+
+
+ccc |
+ddd |
+
+
+eee |
+fff |
+
+
+
+
+
+````````````````````````````````
+
+These tables aren't indented far enough.
+
+```````````````````````````````` example
+- | aaa | bbb |
+ | --- | --- |
+ | ccc | ddd |
+ | eee | fff |
+.
+
+- | aaa | bbb |
+| --- | --- |
+| ccc | ddd |
+| eee | fff |
+
+````````````````````````````````
+
+```````````````````````````````` example
+ - | aaa | bbb |
+| --- | --- |
+| ccc | ddd |
+| eee | fff |
+.
+
+| --- | --- |
+| ccc | ddd |
+| eee | fff |
+````````````````````````````````
+
+```````````````````````````````` example
+1. | aaa | bbb |
+ | --- | --- |
+ | ccc | ddd |
+ | eee | fff |
+.
+
+- | aaa | bbb |
+| --- | --- |
+| ccc | ddd |
+| eee | fff |
+
````````````````````````````````
### Invalid table (MM-55972/MM-59809)
@@ -1246,3 +1663,89 @@ dashes in it (`| |`).
| 파타 | 69.3 | | 보험 | (58.7) | 여수신‧금투 | (10.4) | 16 | |
| 하하 | 51.7 | | 금투 | (43.7) | 여수신 | (6.6) | 40 | |