Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #258: Allow escaping pipes within tables #261

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions index.compiler.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1698,6 +1698,89 @@ describe('GFM tables', () => {

`);
});

it('should handle escaped pipes inside a table', () => {
render(
compiler(
[
'| \\|Attribute\\| | \\|Type\\| |',
'| --------------- | ------------------ |',
'| pos\\|position | "left" \\| "right" |',
].join('\n')
)
);

expect(root.innerHTML).toMatchInlineSnapshot(`

<table data-reactroot>
<thead>
<tr>
<th>
|Attribute|
</th>
<th>
|Type|
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
pos|position
</td>
<td>
"left" | "right"
</td>
</tr>
</tbody>
</table>

`);
});

it('should handle pipes in code inside a table', () => {
render(
compiler(
[
'| Attribute | Type |',
'| ------------ | --------------------- |',
'| `position` | `"left" | "right"` |',
].join('\n')
)
);

expect(root.innerHTML).toMatchInlineSnapshot(`

<table data-reactroot>
<thead>
<tr>
<th>
Attribute
</th>
<th>
Type
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>
position
</code>
</td>
<td>
<code>
"left" | "right"
</code>
</td>
</tr>
</tbody>
</table>

`);
});

});

describe('arbitrary HTML', () => {
Expand Down
74 changes: 50 additions & 24 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,12 @@ const REFERENCE_LINK_R = /^\[([^\]]*)\] ?\[([^\]]*)\]/;
const SQUARE_BRACKETS_R = /(\[|\])/g;
const SHOULD_RENDER_AS_BLOCK_R = /(\n|^[-*]\s|^#|^ {2,}|^-{2,}|^>\s)/;
const TAB_R = /\t/g;
const TABLE_SEPARATOR_R = /^ *\| */;
const TABLE_TRIM_PIPES = /(^ *\||\| *$)/g;
const TABLE_CELL_END_TRIM = / *$/;
const TABLE_CENTER_ALIGN = /^ *:-+: *$/;
const TABLE_LEFT_ALIGN = /^ *:-+ *$/;
const TABLE_RIGHT_ALIGN = /^ *-+: *$/;
const TABLE_ROW_SPLIT = / *\| */;

const TEXT_BOLD_R = /^([*_])\1((?:\[.*?\][([].*?[)\]]|<.*?>(?:.*?<.*?>)?|`.*?`|~+.*?~+|.)*?)\1\1(?!\1)/;
const TEXT_EMPHASIZED_R = /^([*_])((?:\[.*?\][([].*?[)\]]|<.*?>(?:.*?<.*?>)?|`.*?`|~+.*?~+|.)*?)\1(?!\1)/;
Expand Down Expand Up @@ -287,46 +288,56 @@ function parseTableAlignCapture(alignCapture) {
return null;
}

function parseTableHeader(capture, parse, state) {
const headerText = capture[1]
.replace(TABLE_TRIM_PIPES, '')
.trim()
.split(TABLE_ROW_SPLIT);

return headerText.map(function(text) {
return parse(text, state);
function parseTableRow(source, parse, state) {
const prevInTable = state.inTable;
state.inTable = true;
const tableRow = parse(source.trim(), state);
state.inTable = prevInTable;

let cells = [[]];
tableRow.forEach(function(node, i) {
if (node.type === 'tableSeparator') {
// Filter out empty table separators at the start/end:
if (i !== 0 && i !== tableRow.length - 1) {
// Split the current row:
cells.push([]);
}
} else {
if (node.type === 'text' && (
tableRow[i + 1] == null ||
tableRow[i + 1].type === 'tableSeparator'
)) {
node.content = node.content.replace(TABLE_CELL_END_TRIM, "");
}
cells[cells.length - 1].push(node);
}
});
return cells;
}

function parseTableAlign(capture /*, parse, state*/) {
const alignText = capture[2]
function parseTableAlign(source /*, parse, state*/) {
const alignText = source
.replace(TABLE_TRIM_PIPES, '')
.trim()
.split(TABLE_ROW_SPLIT);
.split('|');

return alignText.map(parseTableAlignCapture);
}

function parseTableCells(capture, parse, state) {
const rowsText = capture[3]
function parseTableCells(source, parse, state) {
const rowsText = source
.trim()
.split('\n');

return rowsText.map(function(rowText) {
return rowText
.replace(TABLE_TRIM_PIPES, '')
.split(TABLE_ROW_SPLIT)
.map(function(text) {
return parse(text.trim(), state);
});
return parseTableRow(rowText, parse, state);
});
}

function parseTable(capture, parse, state) {
state.inline = true;
const header = parseTableHeader(capture, parse, state);
const align = parseTableAlign(capture, parse, state);
const cells = parseTableCells(capture, parse, state);
const header = parseTableRow(capture[1], parse, state);
const align = parseTableAlign(capture[2], parse, state);
const cells = parseTableCells(capture[3], parse, state);
state.inline = false;

return {
Expand Down Expand Up @@ -1413,6 +1424,21 @@ export function compiler(markdown, options) {
},
},

tableSeparator: {
match: function(source, state) {
if (!state.inTable) {
return null;
}
return TABLE_SEPARATOR_R.exec(source);
},
order: PARSE_PRIORITY_HIGH,
parse: function() {
return { type: 'tableSeparator' };
},
// These shouldn't be reached, but in case they are, be reasonable:
react() { return ' | '; }
},

text: {
// Here we look for anything followed by non-symbols,
// double newlines, or double-space-newlines
Expand Down