'use strict'; /** * Local dependencies */ var Ruler = require('./ruler'); var StateBlock = require('./rules_block/state_block'); /** * Parser rules */ var _rules = [ [ 'code', require('./rules_block/code') ], [ 'fences', require('./rules_block/fences'), [ 'paragraph', 'blockquote', 'list' ] ], [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'blockquote', 'list' ] ], [ 'hr', require('./rules_block/hr'), [ 'paragraph', 'blockquote', 'list' ] ], [ 'list', require('./rules_block/list'), [ 'paragraph', 'blockquote' ] ], [ 'footnote', require('./rules_block/footnote'), [ 'paragraph' ] ], [ 'heading', require('./rules_block/heading'), [ 'paragraph', 'blockquote' ] ], [ 'lheading', require('./rules_block/lheading') ], [ 'htmlblock', require('./rules_block/htmlblock'), [ 'paragraph', 'blockquote' ] ], [ 'table', require('./rules_block/table'), [ 'paragraph' ] ], [ 'deflist', require('./rules_block/deflist'), [ 'paragraph' ] ], [ 'paragraph', require('./rules_block/paragraph') ] ]; /** * Block Parser class * * @api private */ function ParserBlock() { this.ruler = new Ruler(); for (var i = 0; i < _rules.length; i++) { this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); } } /** * Generate tokens for the given input range. * * @param {Object} `state` Has properties like `src`, `parser`, `options` etc * @param {Number} `startLine` * @param {Number} `endLine` * @api private */ ParserBlock.prototype.tokenize = function (state, startLine, endLine) { var rules = this.ruler.getRules(''); var len = rules.length; var line = startLine; var hasEmptyLines = false; var ok, i; while (line < endLine) { state.line = line = state.skipEmptyLines(line); if (line >= endLine) { break; } // Termination condition for nested calls. // Nested calls currently used for blockquotes & lists if (state.tShift[line] < state.blkIndent) { break; } // Try all possible rules. // On success, rule should: // // - update `state.line` // - update `state.tokens` // - return true for (i = 0; i < len; i++) { ok = rules[i](state, line, endLine, false); if (ok) { break; } } // set state.tight iff we had an empty line before current tag // i.e. latest empty line should not count state.tight = !hasEmptyLines; // paragraph might "eat" one newline after it in nested lists if (state.isEmpty(state.line - 1)) { hasEmptyLines = true; } line = state.line; if (line < endLine && state.isEmpty(line)) { hasEmptyLines = true; line++; // two empty lines should stop the parser in list mode if (line < endLine && state.parentType === 'list' && state.isEmpty(line)) { break; } state.line = line; } } }; var TABS_SCAN_RE = /[\n\t]/g; var NEWLINES_RE = /\r[\n\u0085]|[\u2424\u2028\u0085]/g; var SPACES_RE = /\u00a0/g; /** * Tokenize the given `str`. * * @param {String} `str` Source string * @param {Object} `options` * @param {Object} `env` * @param {Array} `outTokens` * @api private */ ParserBlock.prototype.parse = function (str, options, env, outTokens) { var state, lineStart = 0, lastTabPos = 0; if (!str) { return []; } // Normalize spaces str = str.replace(SPACES_RE, ' '); // Normalize newlines str = str.replace(NEWLINES_RE, '\n'); // Replace tabs with proper number of spaces (1..4) if (str.indexOf('\t') >= 0) { str = str.replace(TABS_SCAN_RE, function (match, offset) { var result; if (str.charCodeAt(offset) === 0x0A) { lineStart = offset + 1; lastTabPos = 0; return match; } result = ' '.slice((offset - lineStart - lastTabPos) % 4); lastTabPos = offset - lineStart + 1; return result; }); } state = new StateBlock(str, this, options, env, outTokens); this.tokenize(state, state.line, state.lineMax); }; /** * Expose `ParserBlock` */ module.exports = ParserBlock;