| exports.tokenize = tokenizeAttention |
| exports.resolveAll = resolveAllAttention |
| |
| var assert = require('assert') |
| var codes = require('../character/codes') |
| var constants = require('../constant/constants') |
| var types = require('../constant/types') |
| var shallow = require('../util/shallow') |
| var chunkedSplice = require('../util/chunked-splice') |
| var classifyCharacter = require('../util/classify-character') |
| var movePoint = require('../util/move-point') |
| var resolveAll = require('../util/resolve-all') |
| |
| // 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) { |
| chunkedSplice(nextEvents, nextEvents.length, 0, [ |
| ['enter', events[open][1], context], |
| ['exit', events[open][1], context] |
| ]) |
| } |
| |
| // Opening. |
| chunkedSplice(nextEvents, nextEvents.length, 0, [ |
| ['enter', group, context], |
| ['enter', openingSequence, context], |
| ['exit', openingSequence, context], |
| ['enter', text, context] |
| ]) |
| |
| // Between. |
| chunkedSplice( |
| nextEvents, |
| nextEvents.length, |
| 0, |
| resolveAll( |
| context.parser.constructs.insideSpan.null, |
| events.slice(open + 1, index), |
| context |
| ) |
| ) |
| |
| // Closing. |
| chunkedSplice(nextEvents, nextEvents.length, 0, [ |
| ['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 |
| chunkedSplice(nextEvents, nextEvents.length, 0, [ |
| ['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) |
| } |
| } |