var attention = { name: 'attention', tokenize: tokenizeAttention, resolveAll: resolveAllAttention } export default attention import assert from 'assert' import codes from '../character/codes.mjs' import constants from '../constant/constants.mjs' import types from '../constant/types.mjs' import chunkedPush from '../util/chunked-push.mjs' import chunkedSplice from '../util/chunked-splice.mjs' import classifyCharacter from '../util/classify-character.mjs' import movePoint from '../util/move-point.mjs' import resolveAll from '../util/resolve-all.mjs' import shallow from '../util/shallow.mjs' // Take all events and resolve attention to emphasis or strong. function resolveAllAttention(events, context) { var index = -1 var open var group var text var openingSequence var closingSequence var use var nextEvents var offset // Walk through all events. // // Note: performance of this is fine on an mb of normal markdown, but it’s // a bottleneck for malicious stuff. while (++index < events.length) { // Find a token that can close. if ( events[index][0] === 'enter' && events[index][1].type === 'attentionSequence' && events[index][1]._close ) { open = index // Now walk back to find an opener. while (open--) { // Find a token that can open the closer. if ( events[open][0] === 'exit' && events[open][1].type === 'attentionSequence' && events[open][1]._open && // If the markers are the same: context.sliceSerialize(events[open][1]).charCodeAt(0) === context.sliceSerialize(events[index][1]).charCodeAt(0) ) { // If the opening can close or the closing can open, // and the close size *is not* a multiple of three, // but the sum of the opening and closing size *is* multiple of three, // then don’t match. if ( (events[open][1]._close || events[index][1]._open) && (events[index][1].end.offset - events[index][1].start.offset) % 3 && !( (events[open][1].end.offset - events[open][1].start.offset + events[index][1].end.offset - events[index][1].start.offset) % 3 ) ) { continue } // Number of markers to use from the sequence. use = events[open][1].end.offset - events[open][1].start.offset > 1 && events[index][1].end.offset - events[index][1].start.offset > 1 ? 2 : 1 openingSequence = { type: use > 1 ? types.strongSequence : types.emphasisSequence, start: movePoint(shallow(events[open][1].end), -use), end: shallow(events[open][1].end) } closingSequence = { type: use > 1 ? types.strongSequence : types.emphasisSequence, start: shallow(events[index][1].start), end: movePoint(shallow(events[index][1].start), use) } text = { type: use > 1 ? types.strongText : types.emphasisText, start: shallow(events[open][1].end), end: shallow(events[index][1].start) } group = { type: use > 1 ? types.strong : types.emphasis, start: shallow(openingSequence.start), end: shallow(closingSequence.end) } events[open][1].end = shallow(openingSequence.start) events[index][1].start = shallow(closingSequence.end) nextEvents = [] // If there are more markers in the opening, add them before. if (events[open][1].end.offset - events[open][1].start.offset) { nextEvents = chunkedPush(nextEvents, [ ['enter', events[open][1], context], ['exit', events[open][1], context] ]) } // Opening. nextEvents = chunkedPush(nextEvents, [ ['enter', group, context], ['enter', openingSequence, context], ['exit', openingSequence, context], ['enter', text, context] ]) // Between. nextEvents = chunkedPush( nextEvents, resolveAll( context.parser.constructs.insideSpan.null, events.slice(open + 1, index), context ) ) // Closing. nextEvents = chunkedPush(nextEvents, [ ['exit', text, context], ['enter', closingSequence, context], ['exit', closingSequence, context], ['exit', group, context] ]) // If there are more markers in the closing, add them after. if (events[index][1].end.offset - events[index][1].start.offset) { offset = 2 nextEvents = chunkedPush(nextEvents, [ ['enter', events[index][1], context], ['exit', events[index][1], context] ]) } else { offset = 0 } chunkedSplice(events, open - 1, index - open + 3, nextEvents) index = open + nextEvents.length - offset - 2 break } } } } // Remove remaining sequences. index = -1 while (++index < events.length) { if (events[index][1].type === 'attentionSequence') { events[index][1].type = 'data' } } return events } function tokenizeAttention(effects, ok) { var before = classifyCharacter(this.previous) var marker return start function start(code) { assert( code === codes.asterisk || code === codes.underscore, 'expected asterisk or underscore' ) effects.enter('attentionSequence') marker = code return sequence(code) } function sequence(code) { var token var after var open var close if (code === marker) { effects.consume(code) return sequence } token = effects.exit('attentionSequence') after = classifyCharacter(code) open = !after || (after === constants.characterGroupPunctuation && before) close = !before || (before === constants.characterGroupPunctuation && after) token._open = marker === codes.asterisk ? open : open && (before || !close) token._close = marker === codes.asterisk ? close : close && (after || !open) return ok(code) } }