exports.flow = { null: {tokenize: tokenizeTable, resolve: resolveTable, interruptible: true} } var createSpace = require('micromark/dist/tokenize/factory-space') var setextUnderlineMini = {tokenize: tokenizeSetextUnderlineMini, partial: true} var nextPrefixedOrBlank = {tokenize: tokenizeNextPrefixedOrBlank, partial: true} function resolveTable(events, context) { var length = events.length var index = -1 var token var inHead var inDelimiterRow var inRow var cell var content var text var contentStart var contentEnd var cellStart while (++index < length) { token = events[index][1] if (inRow) { if (token.type === 'temporaryTableCellContent') { contentStart = contentStart || index contentEnd = index } if ( // Combine separate content parts into one. (token.type === 'tableCellDivider' || token.type === 'tableRow') && contentEnd ) { content = { type: 'tableContent', start: events[contentStart][1].start, end: events[contentEnd][1].end } text = { type: 'chunkText', start: content.start, end: content.end, contentType: 'text' } events.splice( contentStart, contentEnd - contentStart + 1, ['enter', content, context], ['enter', text, context], ['exit', text, context], ['exit', content, context] ) index -= contentEnd - contentStart - 3 length = events.length contentStart = undefined contentEnd = undefined } } if ( events[index][0] === 'exit' && cellStart && cellStart + 1 < index && (token.type === 'tableCellDivider' || (token.type === 'tableRow' && (cellStart + 3 < index || events[cellStart][1].type !== 'whitespace'))) ) { cell = { type: inDelimiterRow ? 'tableDelimiter' : inHead ? 'tableHeader' : 'tableData', start: events[cellStart][1].start, end: events[index][1].end } events.splice(index + (token.type === 'tableCellDivider' ? 1 : 0), 0, [ 'exit', cell, context ]) events.splice(cellStart, 0, ['enter', cell, context]) index += 2 length = events.length cellStart = index + 1 } if (token.type === 'tableRow') { inRow = events[index][0] === 'enter' if (inRow) { cellStart = index + 1 } } if (token.type === 'tableDelimiterRow') { inDelimiterRow = events[index][0] === 'enter' if (inDelimiterRow) { cellStart = index + 1 } } if (token.type === 'tableHead') { inHead = events[index][0] === 'enter' } } return events } function tokenizeTable(effects, ok, nok) { var align = [] var tableHeaderCount = 0 var seenDelimiter var hasDash return start function start(code) { /* istanbul ignore if - used to be passed in beta micromark versions. */ if (code === null || code === -5 || code === -4 || code === -3) { return nok(code) } effects.enter('table')._align = align effects.enter('tableHead') effects.enter('tableRow') // If we start with a pipe, we open a cell marker. if (code === 124) { return cellDividerHead(code) } tableHeaderCount++ effects.enter('temporaryTableCellContent') // Can’t be space or eols at the start of a construct, so we’re in a cell. return inCellContentHead(code) } function cellDividerHead(code) { // Always a pipe. effects.enter('tableCellDivider') effects.consume(code) effects.exit('tableCellDivider') seenDelimiter = true return cellBreakHead } function cellBreakHead(code) { // EOF, CR, LF, CRLF. if (code === null || code === -5 || code === -4 || code === -3) { return atRowEndHead(code) } // HT, VS, SP. if (code === -2 || code === -1 || code === 32) { effects.enter('whitespace') effects.consume(code) return inWhitespaceHead } if (seenDelimiter) { seenDelimiter = undefined tableHeaderCount++ } // `|` if (code === 124) { return cellDividerHead(code) } // Anything else is cell content. effects.enter('temporaryTableCellContent') return inCellContentHead(code) } function inWhitespaceHead(code) { // HT, VS, SP. if (code === -2 || code === -1 || code === 32) { effects.consume(code) return inWhitespaceHead } effects.exit('whitespace') return cellBreakHead(code) } function inCellContentHead(code) { // EOF, whitespace, pipe if (code === null || code < 0 || code === 32 || code === 124) { effects.exit('temporaryTableCellContent') return cellBreakHead(code) } effects.consume(code) // `\` return code === 92 ? inCellContentEscapeHead : inCellContentHead } function inCellContentEscapeHead(code) { // `\` or `|` if (code === 92 || code === 124) { effects.consume(code) return inCellContentHead } // Anything else. return inCellContentHead(code) } function atRowEndHead(code) { if (code === null) { return nok(code) } effects.exit('tableRow') effects.exit('tableHead') // Always a line ending. effects.enter('lineEnding') effects.consume(code) effects.exit('lineEnding') // If a setext heading, exit. return effects.check( setextUnderlineMini, nok, // Support an indent before the delimiter row. createSpace(effects, rowStartDelimiter, 'linePrefix', 4) ) } function rowStartDelimiter(code) { // If there’s another space, or we’re at the EOL/EOF, exit. if (code === null || code < 0 || code === 32) { return nok(code) } effects.enter('tableDelimiterRow') return atDelimiterRowBreak(code) } function atDelimiterRowBreak(code) { // EOF, CR, LF, CRLF. if (code === null || code === -5 || code === -4 || code === -3) { return rowEndDelimiter(code) } // HT, VS, SP. if (code === -2 || code === -1 || code === 32) { effects.enter('whitespace') effects.consume(code) return inWhitespaceDelimiter } // `-` if (code === 45) { effects.enter('tableDelimiterFiller') effects.consume(code) hasDash = true align.push(null) return inFillerDelimiter } // `:` if (code === 58) { effects.enter('tableDelimiterAlignment') effects.consume(code) effects.exit('tableDelimiterAlignment') align.push('left') return afterLeftAlignment } // If we start with a pipe, we open a cell marker. if (code === 124) { effects.enter('tableCellDivider') effects.consume(code) effects.exit('tableCellDivider') return atDelimiterRowBreak } return nok(code) } function inWhitespaceDelimiter(code) { // HT, VS, SP. if (code === -2 || code === -1 || code === 32) { effects.consume(code) return inWhitespaceDelimiter } effects.exit('whitespace') return atDelimiterRowBreak(code) } function inFillerDelimiter(code) { // `-` if (code === 45) { effects.consume(code) return inFillerDelimiter } effects.exit('tableDelimiterFiller') // `:` if (code === 58) { effects.enter('tableDelimiterAlignment') effects.consume(code) effects.exit('tableDelimiterAlignment') align[align.length - 1] = align[align.length - 1] === 'left' ? 'center' : 'right' return afterRightAlignment } return atDelimiterRowBreak(code) } function afterLeftAlignment(code) { // `-` if (code === 45) { effects.enter('tableDelimiterFiller') effects.consume(code) hasDash = true return inFillerDelimiter } // Anything else is not ok. return nok(code) } function afterRightAlignment(code) { // EOF, CR, LF, CRLF. if (code === null || code === -5 || code === -4 || code === -3) { return rowEndDelimiter(code) } // HT, VS, SP. if (code === -2 || code === -1 || code === 32) { effects.enter('whitespace') effects.consume(code) return inWhitespaceDelimiter } // `|` if (code === 124) { effects.enter('tableCellDivider') effects.consume(code) effects.exit('tableCellDivider') return atDelimiterRowBreak } return nok(code) } function rowEndDelimiter(code) { effects.exit('tableDelimiterRow') // Exit if there was no dash at all, or if the header cell count is not the // delimiter cell count. if (!hasDash || tableHeaderCount !== align.length) { return nok(code) } if (code === null) { return tableClose(code) } return effects.check(nextPrefixedOrBlank, tableClose, tableContinue)(code) } function tableClose(code) { effects.exit('table') return ok(code) } function tableContinue(code) { // Always a line ending. effects.enter('lineEnding') effects.consume(code) effects.exit('lineEnding') // We checked that it’s not a prefixed or blank line, so we’re certain a // body is coming, though it may be indented. return createSpace(effects, bodyStart, 'linePrefix', 4) } function bodyStart(code) { effects.enter('tableBody') return rowStartBody(code) } function rowStartBody(code) { effects.enter('tableRow') // If we start with a pipe, we open a cell marker. if (code === 124) { return cellDividerBody(code) } effects.enter('temporaryTableCellContent') // Can’t be space or eols at the start of a construct, so we’re in a cell. return inCellContentBody(code) } function cellDividerBody(code) { // Always a pipe. effects.enter('tableCellDivider') effects.consume(code) effects.exit('tableCellDivider') return cellBreakBody } function cellBreakBody(code) { // EOF, CR, LF, CRLF. if (code === null || code === -5 || code === -4 || code === -3) { return atRowEndBody(code) } // HT, VS, SP. if (code === -2 || code === -1 || code === 32) { effects.enter('whitespace') effects.consume(code) return inWhitespaceBody } // `|` if (code === 124) { return cellDividerBody(code) } // Anything else is cell content. effects.enter('temporaryTableCellContent') return inCellContentBody(code) } function inWhitespaceBody(code) { // HT, VS, SP. if (code === -2 || code === -1 || code === 32) { effects.consume(code) return inWhitespaceBody } effects.exit('whitespace') return cellBreakBody(code) } function inCellContentBody(code) { // EOF, whitespace, pipe if (code === null || code < 0 || code === 32 || code === 124) { effects.exit('temporaryTableCellContent') return cellBreakBody(code) } effects.consume(code) // `\` return code === 92 ? inCellContentEscapeBody : inCellContentBody } function inCellContentEscapeBody(code) { // `\` or `|` if (code === 92 || code === 124) { effects.consume(code) return inCellContentBody } // Anything else. return inCellContentBody(code) } function atRowEndBody(code) { effects.exit('tableRow') if (code === null) { return tableBodyClose(code) } return effects.check( nextPrefixedOrBlank, tableBodyClose, tableBodyContinue )(code) } function tableBodyClose(code) { effects.exit('tableBody') return tableClose(code) } function tableBodyContinue(code) { // Always a line ending. effects.enter('lineEnding') effects.consume(code) effects.exit('lineEnding') // Support an optional prefix, then start a body row. return createSpace(effects, rowStartBody, 'linePrefix', 4) } } // Based on micromark, but that won’t work as we’re in a table, and that expects // content. // function tokenizeSetextUnderlineMini(effects, ok, nok) { return start function start(code) { // `-` if (code !== 45) { return nok(code) } effects.enter('setextUnderline') return sequence(code) } function sequence(code) { if (code === 45) { effects.consume(code) return sequence } return whitespace(code) } function whitespace(code) { if (code === -2 || code === -1 || code === 32) { effects.consume(code) return whitespace } if (code === null || code === -5 || code === -4 || code === -3) { return ok(code) } return nok(code) } } function tokenizeNextPrefixedOrBlank(effects, ok, nok) { var size = 0 return start function start(code) { // This is a check, so we don’t care about tokens, but we open a bogus one // so we’re valid. effects.enter('check') // EOL. effects.consume(code) return whitespace } function whitespace(code) { // VS or SP. if (code === -1 || code === 32) { effects.consume(code) size++ return size === 4 ? ok : whitespace } // EOF or whitespace if (code === null || code < 0) { return ok(code) } // Anything else. return nok(code) } }