// Process *this* and _that_ // 'use strict'; var isWhiteSpace = require('../common/utils').isWhiteSpace; var isPunctChar = require('../common/utils').isPunctChar; var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; function isAlphaNum(code) { return (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) || (code >= 0x41 /* A */ && code <= 0x5A /* Z */) || (code >= 0x61 /* a */ && code <= 0x7A /* z */); } // parse sequence of emphasis markers, // "start" should point at a valid marker function scanDelims(state, start) { var pos = start, lastChar, nextChar, count, isLastWhiteSpace, isLastPunctChar, isNextWhiteSpace, isNextPunctChar, can_open = true, can_close = true, max = state.posMax, marker = state.src.charCodeAt(start); lastChar = start > 0 ? state.src.charCodeAt(start - 1) : -1; while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; } if (pos >= max) { can_open = false; } count = pos - start; nextChar = pos < max ? state.src.charCodeAt(pos) : -1; isLastPunctChar = lastChar >= 0 && (isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar))); isNextPunctChar = nextChar >= 0 && (isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar))); isLastWhiteSpace = lastChar >= 0 && isWhiteSpace(lastChar); isNextWhiteSpace = nextChar >= 0 && isWhiteSpace(nextChar); if (isNextWhiteSpace) { can_open = false; } else if (isNextPunctChar) { if (!(isLastWhiteSpace || isLastPunctChar || lastChar === -1)) { can_open = false; } } if (isLastWhiteSpace) { can_close = false; } else if (isLastPunctChar) { if (!(isNextWhiteSpace || isNextPunctChar || nextChar === -1)) { can_close = false; } } if (marker === 0x5F /* _ */) { // check if we aren't inside the word if (isAlphaNum(lastChar)) { can_open = false; } if (isAlphaNum(nextChar)) { can_close = false; } } 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, 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) { state.push({ type: 'strong_open', level: state.level++ }); } if (count % 2) { state.push({ type: 'em_open', level: state.level++ }); } state.md.inline.tokenize(state); if (count % 2) { state.push({ type: 'em_close', level: --state.level }); } for (count = startCount; count > 1; count -= 2) { state.push({ type: 'strong_close', level: --state.level }); } state.pos = state.posMax + startCount; state.posMax = max; return true; };