comrak 0.4.1

A 100% CommonMark-compatible GitHub Flavored Markdown parser and formatter
Documentation
use arena_tree::Node;
use nodes::{make_block, AstNode, NodeValue, TableAlignment};
use parser::Parser;
use scanners;
use std::cell::RefCell;
use std::cmp::min;
use strings::trim;

pub fn try_opening_block<'a, 'o>(
    parser: &mut Parser<'a, 'o>,
    container: &'a AstNode<'a>,
    line: &[u8],
) -> Option<(&'a AstNode<'a>, bool)> {
    let aligns = match container.data.borrow().value {
        NodeValue::Paragraph => None,
        NodeValue::Table(ref aligns) => Some(aligns.clone()),
        _ => return None,
    };

    match aligns {
        None => try_opening_header(parser, container, line),
        Some(ref aligns) => try_opening_row(parser, container, aligns, line),
    }
}

fn try_opening_header<'a, 'o>(
    parser: &mut Parser<'a, 'o>,
    container: &'a AstNode<'a>,
    line: &[u8],
) -> Option<(&'a AstNode<'a>, bool)> {
    if scanners::table_start(&line[parser.first_nonspace..]).is_none() {
        return Some((container, false));
    }

    let header_row = match row(&container.data.borrow().content) {
        Some(header_row) => header_row,
        None => return Some((container, false)),
    };

    let marker_row = row(&line[parser.first_nonspace..]).unwrap();

    if header_row.len() != marker_row.len() {
        return Some((container, false));
    }

    let mut alignments = vec![];
    for cell in marker_row {
        let left = !cell.is_empty() && cell[0] == b':';
        let right = !cell.is_empty() && cell[cell.len() - 1] == b':';
        alignments.push(if left && right {
            TableAlignment::Center
        } else if left {
            TableAlignment::Left
        } else if right {
            TableAlignment::Right
        } else {
            TableAlignment::None
        });
    }

    let child = make_block(NodeValue::Table(alignments), parser.line_number);
    let table = parser.arena.alloc(Node::new(RefCell::new(child)));
    container.append(table);

    let header = parser.add_child(table, NodeValue::TableRow(true));
    for header_str in header_row {
        let header_cell = parser.add_child(header, NodeValue::TableCell);
        header_cell.data.borrow_mut().content = header_str;
    }

    let offset = line.len() - 1 - parser.offset;
    parser.advance_offset(line, offset, false);

    Some((table, true))
}

fn try_opening_row<'a, 'o>(
    parser: &mut Parser<'a, 'o>,
    container: &'a AstNode<'a>,
    alignments: &[TableAlignment],
    line: &[u8],
) -> Option<(&'a AstNode<'a>, bool)> {
    if parser.blank {
        return None;
    }
    let this_row = row(&line[parser.first_nonspace..]).unwrap();
    let new_row = parser.add_child(container, NodeValue::TableRow(false));

    let mut i = 0;
    while i < min(alignments.len(), this_row.len()) {
        let cell = parser.add_child(new_row, NodeValue::TableCell);
        cell.data.borrow_mut().content = this_row[i].clone();
        i += 1;
    }

    while i < alignments.len() {
        parser.add_child(new_row, NodeValue::TableCell);
        i += 1;
    }

    let offset = line.len() - 1 - parser.offset;
    parser.advance_offset(line, offset, false);

    Some((new_row, false))
}

fn row(string: &[u8]) -> Option<Vec<Vec<u8>>> {
    let len = string.len();
    let mut v = vec![];
    let mut offset = 0;

    if len > 0 && string[0] == b'|' {
        offset += 1;
    }

    loop {
        let cell_matched = scanners::table_cell(&string[offset..]).unwrap_or(0);
        let mut pipe_matched =
            scanners::table_cell_end(&string[offset + cell_matched..]).unwrap_or(0);

        if cell_matched > 0 || pipe_matched > 0 {
            let mut cell = unescape_pipes(&string[offset..offset + cell_matched]);
            trim(&mut cell);
            v.push(cell);
        }

        offset += cell_matched + pipe_matched;

        if pipe_matched == 0 {
            pipe_matched = scanners::table_row_end(&string[offset..]).unwrap_or(0);
            offset += pipe_matched;
        }

        if !((cell_matched > 0 || pipe_matched > 0) && offset < len) {
            break;
        }
    }

    if offset != len || v.is_empty() {
        None
    } else {
        Some(v)
    }
}

fn unescape_pipes(string: &[u8]) -> Vec<u8> {
    let len = string.len();
    let mut v = Vec::with_capacity(len);

    for (i, &c) in string.iter().enumerate() {
        if c == b'\\' && i + 1 < len && string[i + 1] == b'|' {
            continue;
        } else {
            v.push(c);
        }
    }

    v
}

pub fn matches(line: &[u8]) -> bool {
    row(line).is_some()
}