| exports.tokenize = tokenizeLabelEnd |
| exports.resolveTo = resolveToLabelEnd |
| exports.resolveAll = resolveAllLabelEnd |
| |
| var assert = require('assert') |
| var codes = require('../character/codes') |
| var markdownLineEndingOrSpace = require('../character/markdown-line-ending-or-space') |
| var constants = require('../constant/constants') |
| var types = require('../constant/types') |
| var normalizeIdentifier = require('../util/normalize-identifier') |
| var chunkedSplice = require('../util/chunked-splice') |
| var resolveAll = require('../util/resolve-all') |
| var shallow = require('../util/shallow') |
| var createDestination = require('./factory-destination') |
| var createLabel = require('./factory-label') |
| var createWhitespace = require('./factory-whitespace') |
| var createTitle = require('./factory-title') |
| |
| var resource = {tokenize: tokenizeResource} |
| var fullReference = {tokenize: tokenizeFullReference} |
| var collapsedReference = {tokenize: tokenizeCollapsedReference} |
| |
| function resolveAllLabelEnd(events) { |
| var index = -1 |
| var token |
| |
| while (++index < events.length) { |
| token = events[index][1] |
| |
| if ( |
| !token._used && |
| (token.type === types.labelImage || |
| token.type === types.labelLink || |
| token.type === types.labelEnd) |
| ) { |
| // Remove the marker. |
| events.splice(index + 1, token.type === types.labelImage ? 4 : 2) |
| token.type = types.data |
| index++ |
| } |
| } |
| |
| return events |
| } |
| |
| function resolveToLabelEnd(events, context) { |
| var index = events.length |
| var offset = 0 |
| var group |
| var label |
| var text |
| var token |
| var open |
| var close |
| var media |
| |
| // Find an opening. |
| while (index--) { |
| token = events[index][1] |
| |
| if (open) { |
| // If we see another link, or inactive link label, we’ve been here before. |
| if ( |
| token.type === types.link || |
| (token.type === types.labelLink && token._inactive) |
| ) { |
| break |
| } |
| |
| // Mark other link openings as inactive, as we can’t have links in |
| // links. |
| if (events[index][0] === 'enter' && token.type === types.labelLink) { |
| token._inactive = true |
| } |
| } else if (close) { |
| if ( |
| events[index][0] === 'enter' && |
| (token.type === types.labelImage || token.type === types.labelLink) && |
| !token._balanced |
| ) { |
| open = index |
| |
| if (token.type !== types.labelLink) { |
| offset = 2 |
| break |
| } |
| } |
| } else if (token.type === types.labelEnd) { |
| close = index |
| } |
| } |
| |
| group = { |
| type: events[open][1].type === types.labelLink ? types.link : types.image, |
| start: shallow(events[open][1].start), |
| end: shallow(events[events.length - 1][1].end) |
| } |
| |
| label = { |
| type: types.label, |
| start: shallow(events[open][1].start), |
| end: shallow(events[close][1].end) |
| } |
| |
| text = { |
| type: types.labelText, |
| start: shallow(events[open + offset + 2][1].end), |
| end: shallow(events[close - 2][1].start) |
| } |
| |
| media = [ |
| ['enter', group, context], |
| ['enter', label, context] |
| ] |
| |
| // Opening marker. |
| chunkedSplice( |
| media, |
| media.length, |
| 0, |
| events.slice(open + 1, open + offset + 3) |
| ) |
| |
| // Text open. |
| chunkedSplice(media, media.length, 0, [['enter', text, context]]) |
| |
| // Between. |
| chunkedSplice( |
| media, |
| media.length, |
| 0, |
| resolveAll( |
| context.parser.constructs.insideSpan.null, |
| events.slice(open + offset + 4, close - 3), |
| context |
| ) |
| ) |
| |
| // Text close, marker close, label close. |
| chunkedSplice(media, media.length, 0, [ |
| ['exit', text, context], |
| events[close - 2], |
| events[close - 1], |
| ['exit', label, context] |
| ]) |
| |
| // Reference, resource, or so. |
| chunkedSplice(media, media.length, 0, events.slice(close + 1)) |
| |
| // Media close. |
| chunkedSplice(media, media.length, 0, [['exit', group, context]]) |
| |
| chunkedSplice(events, open, events.length, media) |
| |
| return events |
| } |
| |
| function tokenizeLabelEnd(effects, ok, nok) { |
| var self = this |
| var index = self.events.length |
| var labelStart |
| var defined |
| |
| // Find an opening. |
| while (index--) { |
| if ( |
| (self.events[index][1].type === types.labelImage || |
| self.events[index][1].type === types.labelLink) && |
| !self.events[index][1]._balanced |
| ) { |
| labelStart = self.events[index][1] |
| break |
| } |
| } |
| |
| return start |
| |
| function start(code) { |
| assert(code === codes.rightSquareBracket, 'expected `]`') |
| |
| if (!labelStart) { |
| return nok(code) |
| } |
| |
| // It’s a balanced bracket, but contains a link. |
| if (labelStart._inactive) return balanced(code) |
| defined = |
| self.parser.defined.indexOf( |
| normalizeIdentifier( |
| self.sliceSerialize({start: labelStart.end, end: self.now()}) |
| ) |
| ) > -1 |
| effects.enter(types.labelEnd) |
| effects.enter(types.labelMarker) |
| effects.consume(code) |
| effects.exit(types.labelMarker) |
| effects.exit(types.labelEnd) |
| return afterLabelEnd |
| } |
| |
| function afterLabelEnd(code) { |
| // Resource: `[asd](fgh)`. |
| if (code === codes.leftParenthesis) { |
| return effects.attempt(resource, ok, defined ? ok : balanced)(code) |
| } |
| |
| // Collapsed (`[asd][]`) or full (`[asd][fgh]`) reference? |
| if (code === codes.leftSquareBracket) { |
| return effects.attempt( |
| fullReference, |
| ok, |
| defined ? effects.attempt(collapsedReference, ok, balanced) : balanced |
| )(code) |
| } |
| |
| // Shortcut reference: `[asd]`? |
| return defined ? ok(code) : balanced(code) |
| } |
| |
| function balanced(code) { |
| labelStart._balanced = true |
| return nok(code) |
| } |
| } |
| |
| function tokenizeResource(effects, ok, nok) { |
| return start |
| |
| function start(code) { |
| assert.equal(code, codes.leftParenthesis, 'expected left paren') |
| effects.enter(types.resource) |
| effects.enter(types.resourceMarker) |
| effects.consume(code) |
| effects.exit(types.resourceMarker) |
| return createWhitespace(effects, open) |
| } |
| |
| function open(code) { |
| if (code === codes.rightParenthesis) { |
| return end(code) |
| } |
| |
| return createDestination( |
| effects, |
| destinationAfter, |
| nok, |
| types.resourceDestination, |
| types.resourceDestinationLiteral, |
| types.resourceDestinationLiteralMarker, |
| types.resourceDestinationRaw, |
| types.resourceDestinationString, |
| constants.linkResourceDestinationBalanceMax |
| )(code) |
| } |
| |
| function destinationAfter(code) { |
| return markdownLineEndingOrSpace(code) |
| ? createWhitespace(effects, between)(code) |
| : end(code) |
| } |
| |
| function between(code) { |
| if ( |
| code === codes.quotationMark || |
| code === codes.apostrophe || |
| code === codes.leftParenthesis |
| ) { |
| return createTitle( |
| effects, |
| createWhitespace(effects, end), |
| nok, |
| types.resourceTitle, |
| types.resourceTitleMarker, |
| types.resourceTitleString |
| )(code) |
| } |
| |
| return end(code) |
| } |
| |
| function end(code) { |
| if (code === codes.rightParenthesis) { |
| effects.enter(types.resourceMarker) |
| effects.consume(code) |
| effects.exit(types.resourceMarker) |
| effects.exit(types.resource) |
| return ok |
| } |
| |
| return nok(code) |
| } |
| } |
| |
| function tokenizeFullReference(effects, ok, nok) { |
| var self = this |
| |
| return start |
| |
| function start(code) { |
| assert.equal(code, codes.leftSquareBracket, 'expected left bracket') |
| return createLabel.call( |
| self, |
| effects, |
| afterLabel, |
| nok, |
| types.reference, |
| types.referenceMarker, |
| types.referenceString |
| )(code) |
| } |
| |
| function afterLabel(code) { |
| return self.parser.defined.indexOf( |
| normalizeIdentifier( |
| self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1) |
| ) |
| ) < 0 |
| ? nok(code) |
| : ok(code) |
| } |
| } |
| |
| function tokenizeCollapsedReference(effects, ok, nok) { |
| return start |
| |
| function start(code) { |
| assert.equal(code, codes.leftSquareBracket, 'expected left bracket') |
| effects.enter(types.reference) |
| effects.enter(types.referenceMarker) |
| effects.consume(code) |
| effects.exit(types.referenceMarker) |
| return open |
| } |
| |
| function open(code) { |
| if (code === codes.rightSquareBracket) { |
| effects.enter(types.referenceMarker) |
| effects.consume(code) |
| effects.exit(types.referenceMarker) |
| effects.exit(types.reference) |
| return ok |
| } |
| |
| return nok(code) |
| } |
| } |