// Process *this* and _that_ // 'use strict'; var isWhiteSpace = require('../common/utils').isWhiteSpace; var isPunctChar = require('../common/utils').isPunctChar; var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; // parse sequence of emphasis markers, // "start" should point at a valid marker function scanDelims(state, start) { var pos = start, lastChar, nextChar, count, can_open, can_close, isLastWhiteSpace, isLastPunctChar, isNextWhiteSpace, isNextPunctChar, left_flanking = true, right_flanking = true, max = state.posMax, marker = state.src.charCodeAt(start); // treat beginning of the line as a whitespace lastChar = start > 0 ? state.src.charCodeAt(start - 1) : 0x20; while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; } count = pos - start; // treat end of the line as a whitespace nextChar = pos < max ? state.src.charCodeAt(pos) : 0x20; isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); isLastWhiteSpace = isWhiteSpace(lastChar); isNextWhiteSpace = isWhiteSpace(nextChar); if (isNextWhiteSpace) { left_flanking = false; } else if (isNextPunctChar) { if (!(isLastWhiteSpace || isLastPunctChar)) { left_flanking = false; } } if (isLastWhiteSpace) { right_flanking = false; } else if (isLastPunctChar) { if (!(isNextWhiteSpace || isNextPunctChar)) { right_flanking = false; } } if (marker === 0x5F /* _ */) { // "_" inside a word can neither open nor close an emphasis can_open = left_flanking && (!right_flanking || isLastPunctChar); can_close = right_flanking && (!left_flanking || isNextPunctChar); } else { can_open = left_flanking; can_close = right_flanking; } return { can_open: can_open, can_close: can_close, delims: count }; } module.exports = function emphasis(state, silent) { var startCount, count, found, oldCount, newCount, stack, res, token, max = state.posMax, start = state.pos, marker = state.src.charCodeAt(start); if (marker !== 0x5F/* _ */ && marker !== 0x2A /* * */) { return false; } if (silent) { return false; } // don't run any pairs in validation mode res = scanDelims(state, start); startCount = res.delims; if (!res.can_open) { state.pos += startCount; // Earlier we checked !silent, but this implementation does not need it state.pending += state.src.slice(start, state.pos); return true; } state.pos = start + startCount; stack = [ startCount ]; while (state.pos < max) { if (state.src.charCodeAt(state.pos) === marker) { res = scanDelims(state, state.pos); count = res.delims; if (res.can_close) { oldCount = stack.pop(); newCount = count; while (oldCount !== newCount) { if (newCount < oldCount) { stack.push(oldCount - newCount); break; } // assert(newCount > oldCount) newCount -= oldCount; if (stack.length === 0) { break; } state.pos += oldCount; oldCount = stack.pop(); } if (stack.length === 0) { startCount = oldCount; found = true; break; } state.pos += count; continue; } if (res.can_open) { stack.push(count); } state.pos += count; continue; } state.md.inline.skipToken(state); } if (!found) { // parser failed to find ending tag, so it's not valid emphasis state.pos = start; return false; } // found! state.posMax = state.pos; state.pos = start + startCount; // Earlier we checked !silent, but this implementation does not need it // we have `startCount` starting and ending markers, // now trying to serialize them into tokens for (count = startCount; count > 1; count -= 2) { token = state.push('strong_open', 'strong', 1); token.markup = String.fromCharCode(marker) + String.fromCharCode(marker); } if (count % 2) { token = state.push('em_open', 'em', 1); token.markup = String.fromCharCode(marker); } state.md.inline.tokenize(state); if (count % 2) { token = state.push('em_close', 'em', -1); token.markup = String.fromCharCode(marker); } for (count = startCount; count > 1; count -= 2) { token = state.push('strong_close', 'strong', -1); token.markup = String.fromCharCode(marker) + String.fromCharCode(marker); } state.pos = state.posMax + startCount; state.posMax = max; return true; };