Basic table support
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart index efab2a4..162734b 100644 --- a/pkgs/markdown/lib/src/block_parser.dart +++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -48,6 +48,9 @@ final _olPattern = new RegExp(r'^([ ]{0,3})(\d{1,9})([\.)])(([ \t])([ \t]*)(.*))?$'); +/// A line of hyphens separated by at least one pipe. +final _tablePattern = new RegExp(r'^[ ]{0,3}\|?(\-+\|)+\-*$'); + /// Maintains the internal state needed to parse a series of lines into blocks /// of Markdown suitable for further inline parsing. class BlockParser { @@ -113,8 +116,8 @@ /// /// `peek(1)` is equivalent to [next]. String peek(int linesAhead) { - if (linesAhead < 0) - throw new ArgumentError('Invalid linesAhead: $linesAhead; must be >= 0.'); + if (linesAhead < 0) throw new ArgumentError( + 'Invalid linesAhead: $linesAhead; must be >= 0.'); // Don't read past the end. if (_pos >= lines.length - linesAhead) return null; return lines[_pos + linesAhead]; @@ -706,6 +709,68 @@ const OrderedListSyntax(); } +/// Parses tables. +class TableSyntax extends BlockSyntax { + bool get canEndBlock => false; + + const TableSyntax(); + + bool canParse(BlockParser parser) { + // Note: matches *next* line, not the current one. We're looking for the + // bar separating the head row from the body rows. + return parser.matchesNext(_tablePattern); + } + + /// Parses a table into its three parts: + /// + /// * a head row of head cells (`<th>` cells) + /// * a divider of hyphens and pipes (not rendered) + /// * many body rows of body cells (`<td>` cells) + Node parse(BlockParser parser) { + var head = new Element('thead', [parseRow(parser, 'th')]); + + // Advance past the divider of hyphens. + parser.advance(); + + var rows = <Element>[]; + while (!parser.isDone && !parser.matches(_emptyPattern)) { + rows.add(parseRow(parser, 'td')); + } + var body = new Element('tbody', rows); + + return new Element('table', [head, body]); + } + + Node parseRow(BlockParser parser, String cellType) { + var line = parser.current + .replaceFirst(new RegExp(r'^\|\s*'), '') + .replaceFirst(new RegExp(r'\s*\|$'), ''); + var contents = parser.document.parseInline(line); + parser.advance(); + var row = <Node>[]; + var cellContents = <Node>[]; + var pipe = new RegExp(r'\s*\|\s*'); + + contents.forEach((Node node) { + if (node is Text) { + var cells = node.text.split(pipe); + cells.sublist(0, cells.length - 1).forEach((String cell) { + cellContents.add(new Text(cell)); + row.add(new Element(cellType, cellContents)); + cellContents = <Node>[]; + }); + cellContents.add(new Text(cells.last)); + } else { + // An Element or something else. + cellContents.add(node); + } + }); + row.add(new Element(cellType, cellContents)); + + return new Element('tr', row); + } +} + /// Parses paragraphs of regular text. class ParagraphSyntax extends BlockSyntax { static final _reflinkDefinitionStart = new RegExp(r'[ ]{0,3}\[');
diff --git a/pkgs/markdown/test/extensions/tables.unit b/pkgs/markdown/test/extensions/tables.unit new file mode 100644 index 0000000..e28def5 --- /dev/null +++ b/pkgs/markdown/test/extensions/tables.unit
@@ -0,0 +1,47 @@ +>>> basic table +head | cells +-----|------ +body | cells + +<<< +<table><thead><tr><th>head</th><th>cells</th></tr></thead><tbody><tr><td>body</td><td>cells</td></tr></tbody></table> +>>> multiple rows +head | cells +-----|------ +body | cells +more | cells + +<<< +<table><thead><tr><th>head</th><th>cells</th></tr></thead><tbody><tr><td>body</td><td>cells</td></tr><tr><td>more</td><td>cells</td></tr></tbody></table> +>>> rows wrapped in pipes +| head | cells | +|------|-------| +| body | cells | + +<<< +<table><thead><tr><th>head</th><th>cells</th></tr></thead><tbody><tr><td>body</td><td>cells</td></tr></tbody></table> +>>> cells with inline syntax +head `code` | _cells_ +------------|-------- +*text* | <span>text</span> +<<< +<table><thead><tr><th>head <code>code</code></th><th><em>cells</em></th></tr></thead><tbody><tr><td><em>text</em></td><td><span>text</span></td></tr></tbody></table> +>>> cells with inline syntax with pipes +header | _foo | bar_ +-------|------------ +text | text +<<< +<table><thead><tr><th>header</th><th><em>foo | bar</em></th></tr></thead><tbody><tr><td>text</td><td>text</td></tr></tbody></table> +>>> one column tables +head +-----|----- +body +<<< +<table><thead><tr><th>head</th></tr></thead><tbody><tr><td>body</td></tr></tbody></table> +>>> varying cells per row +head | foo | bar +-----|----- +body +row with | two cells +<<< +<table><thead><tr><th>head</th><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>body</td></tr><tr><td>row with</td><td>two cells</td></tr></tbody></table>
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart index 053e481..701ae63 100644 --- a/pkgs/markdown/test/markdown_test.dart +++ b/pkgs/markdown/test/markdown_test.dart
@@ -148,4 +148,6 @@ testFile('extensions/setext_headers_with_ids.unit', blockSyntaxes: [const SetextHeaderWithIdSyntax()]); + + testFile('extensions/tables.unit', blockSyntaxes: [const TableSyntax()]); }