Permalink
Browse files

Initial table support.

  • Loading branch information...
notriddle committed Jul 12, 2015
1 parent dd7054e commit e05400529727387ec4457c12bff1d586cdbe6bde
Showing with 277 additions and 10 deletions.
  1. +24 −0 src/html.rs
  2. +105 −10 src/parse.rs
  3. +43 −0 src/scanners.rs
  4. +105 −0 table_spec.txt
@@ -73,6 +73,18 @@ impl<'a, 'b, I: Iterator<Item=Event<'a>>> Ctx<'b, I> {
self.buf.push((b'0' + level as u8) as char);
self.buf.push('>');
}
Tag::Table(_) => {
self.buf.push_str("<table>");
}
Tag::TableHead => {
self.buf.push_str("<thead><tr>");
}
Tag::TableRow => {
self.buf.push_str("<tr>");
}
Tag::TableCell => {
self.buf.push_str("<td>");
}
Tag::BlockQuote => {
self.fresh_line();
self.buf.push_str("<blockquote>\n");
@@ -139,6 +151,18 @@ impl<'a, 'b, I: Iterator<Item=Event<'a>>> Ctx<'b, I> {
self.buf.push((b'0' + level as u8) as char);
self.buf.push_str(">\n");
}
Tag::Table(_) => {
self.buf.push_str("</table>\n");
}
Tag::TableHead => {
self.buf.push_str("</tr></thead>\n");
}
Tag::TableRow => {
self.buf.push_str("</tr>\n");
}
Tag::TableCell => {
self.buf.push_str("</td>");
}
Tag::BlockQuote => self.buf.push_str("</blockquote>\n"),
Tag::CodeBlock(_) => self.buf.push_str("</code></pre>\n"),
Tag::List(Some(_)) => self.buf.push_str("</ol>\n"),
@@ -32,6 +32,9 @@ enum State {
StartBlock,
InContainers,
Inline,
TableHead(usize, usize), // limit, next
TableBody,
TableRow,
CodeLineStart,
Code,
InlineCode,
@@ -85,6 +88,12 @@ pub enum Tag<'a> {
List(Option<usize>), // TODO: add delim and tight for ast (not needed for html)
Item,

// tables
Table(i32),
TableHead,
TableRow,
TableCell,

// span-level tags
Emphasis,
Strong,
@@ -199,14 +208,18 @@ impl<'a> RawParser<'a> {
}

// block level tags
Tag::Paragraph | Tag::Header(_) | Tag::Rule | Tag::CodeBlock(_) => {
Tag::Paragraph | Tag::Header(_) | Tag::Rule | Tag::CodeBlock(_) | Tag::Table(_) => {
self.state = State::StartBlock;
// TODO: skip blank lines (for cleaner source maps)
}

// tables
Tag::TableCell => self.state = State::TableRow,
Tag::TableRow | Tag::TableHead => self.state = State::TableBody,

// inline
Tag::Code => self.state = State::Inline,
_ => ()
_ => (),
}
if next != 0 { self.off = next; }

@@ -466,20 +479,31 @@ impl<'a> RawParser<'a> {
None
}

// can start a paragraph or a setext header, as they start similarly
// can start a paragraph, a setext header, or a table, as they start similarly
fn start_paragraph(&mut self) -> Event<'a> {
let mut i = self.off + scan_nextline(&self.text[self.off..]);

if let (n, true, space) = self.scan_containers(&self.text[i..]) {
i += n;
let (n, level) = scan_setext_header(&self.text[i..]);
if space <= 3 && n != 0 {
let next = i + n;
while i > self.off && is_ascii_whitespace(self.text.as_bytes()[i - 1]) {
i -= 1;
if space <= 3 {
let (n, level) = scan_setext_header(&self.text[i..]);
if n != 0 {
let next = i + n;
while i > self.off && is_ascii_whitespace(self.text.as_bytes()[i - 1]) {
i -= 1;
}
self.state = State::Inline;
return self.start(Tag::Header(level), i, next);
}
let (n, cols) = scan_table_head(&self.text[i..]);
if n != 0 {
let next = i + n;
while i > self.off && is_ascii_whitespace(self.text.as_bytes()[i - 1]) {
i -= 1;
}
self.state = State::TableHead(i, next);
return self.start(Tag::Table(cols), self.text.len(), 0);
}
self.state = State::Inline;
return self.start(Tag::Header(level), i, next);
}
}

@@ -488,6 +512,32 @@ impl<'a> RawParser<'a> {
self.start(Tag::Paragraph, size, 0)
}

fn start_table_head(&mut self) -> Event<'a> {
if let State::TableHead(limit, next) = self.state {
self.state = State::TableRow;
return self.start(Tag::TableHead, limit, next);
} else {
panic!();
}
}

fn start_table_body(&mut self) -> Event<'a> {
let (off, _) = match self.scan_containers(&self.text[self.off ..]) {
(n, true, space) => (self.off + n, space),
_ => {
return self.end();
}
};
let n = scan_blank_line(&self.text[off..]);
if n != 0 {
self.off = off + n;
return self.end();
}
self.state = State::TableRow;
self.off = off;
return self.start(Tag::TableRow, self.text.len(), 0);
}

fn start_hrule(&mut self) -> Event<'a> {
let limit = self.off; // body of hrule is empty
self.state = State::Inline; // handy state for producing correct end tag
@@ -810,6 +860,48 @@ impl<'a> RawParser<'a> {
self.is_html_block(data))
}

fn next_table_cell(&mut self) -> Event<'a> {
let bytes = self.text.as_bytes();
let mut beg = self.off + scan_whitespace_no_nl(&self.text[self.off ..]);
let mut i = beg;
let limit = self.limit();
if i < limit && bytes[i] == b'|' {
i += 1;
beg += 1;
self.off += 1;
}
if i >= limit {
self.off = limit;
return self.end();
}
let mut n = 0;
while i < limit {
let c = bytes[i];
if c == b'\\' && i + 1 < limit && bytes[i + 1] == b'|' {
i += 2;
continue;
} else if c == b'|' {
n = 1;
break;
}
n = scan_blank_line(&self.text[i..]);
if n != 0 {
if i > beg {
n = 0;
}
break;
}
i += 1;
}
if i > beg {
self.state = State::Inline;
self.start(Tag::TableCell, i, i + n)
} else {
self.off = i + n;
self.end()
}
}

fn next_inline(&mut self) -> Event<'a> {
let bytes = self.text.as_bytes();
let beg = self.off;
@@ -1493,6 +1585,9 @@ impl<'a> Iterator for RawParser<'a> {
}
}
State::Inline => return Some(self.next_inline()),
State::TableHead(_, _) => return Some(self.start_table_head()),
State::TableBody => return Some(self.start_table_body()),
State::TableRow => return Some(self.next_table_cell()),
State::CodeLineStart => return Some(self.next_code_line_start()),
State::Code => return Some(self.next_code()),
State::InlineCode => return Some(self.next_inline_code()),
@@ -290,6 +290,49 @@ pub fn scan_setext_header(data: &str) -> (usize, i32) {
(i, level)
}

// returns number of bytes in line (including trailing newline) and column count
// TODO: alignment
pub fn scan_table_head(data: &str) -> (usize, i32) {
let (mut i, spaces) = calc_indent(data, 4);
if spaces > 3 || i == data.len() {
return (0, 0);
}
let mut cols = 0;
let mut start_col = true;
if data.as_bytes()[i] == b'|' {
i += 1;
}
for c in data.as_bytes()[i..].iter() {
let eol = scan_eol(&data[i..]);
if eol.1 {
i += eol.0;
break;
}
match *c {
b' ' | b':' => (),
b'-' => {
if start_col {
cols += 1;
start_col = false;
}
},
b'|' => {
start_col = true;
},
_ => {
cols = 0;
break;
},
}
i += 1;
}
if cols < 2 {
(0, 0)
} else {
(i, cols)
}
}

// returns: number of bytes scanned, char
pub fn scan_code_fence(data: &str) -> (usize, u8) {
let c = data.as_bytes()[0];
@@ -0,0 +1,105 @@
False match
===========

.
Test header
-----------
.
<h2>Test header</h2>
.


True match
==========

.
Test|Table
----|-----
.
<table><thead><tr><td>Test</td><td>Table</td></tr></thead>
</table>
.


Actual rows in it
=================

.
Test|Table
----|-----
Test row
Test|2

Test ending
.
<table><thead><tr><td>Test</td><td>Table</td></tr></thead>
<tr><td>Test row</td></tr>
<tr><td>Test</td><td>2</td></tr>
</table>
<p>Test ending</p>
.


Test with quote
===============

.
> Test | Table
> ------|------
> Row 1 | Every
> Row 2 | Day
>
> Paragraph
.
<blockquote>
<table><thead><tr><td>Test </td><td> Table</td></tr></thead>
<tr><td>Row 1 </td><td> Every</td></tr>
<tr><td>Row 2 </td><td> Day</td></tr>
</table>
<p>Paragraph</p>
</blockquote>
.


Test with list
==============

.
1. First entry
2. Second entry

Col 1|Col 2
-|-
Row 1|Part 2
Row 2|Part 2
.
<ol>
<li>
<p>First entry</p>
</li>
<li>
<p>Second entry</p>
<table><thead><tr><td>Col 1</td><td>Col 2</td></tr></thead>
<tr><td>Row 1</td><td>Part 2</td></tr>
<tr><td>Row 2</td><td>Part 2</td></tr>
</table>
</li>
</ol>
.


Test with border
================

.
|Col 1|Col 2|
|-----|-----|
|R1C1 |R1C2 |
|R2C1 |R2C2 |
.
<table><thead><tr><td>Col 1</td><td>Col 2</td></tr></thead>
<tr><td>R1C1 </td><td>R1C2 </td></tr>
<tr><td>R2C1 </td><td>R2C2 </td></tr>
</table>
.

0 comments on commit e054005

Please sign in to comment.