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()]);
}