'use strict' var markdownLineEndingOrSpace = require('../character/markdown-line-ending-or-space.js') var chunkedPush = require('../util/chunked-push.js') var chunkedSplice = require('../util/chunked-splice.js') var normalizeIdentifier = require('../util/normalize-identifier.js') var resolveAll = require('../util/resolve-all.js') var shallow = require('../util/shallow.js') var factoryDestination = require('./factory-destination.js') var factoryLabel = require('./factory-label.js') var factoryTitle = require('./factory-title.js') var factoryWhitespace = require('./factory-whitespace.js') var labelEnd = { name: 'labelEnd', tokenize: tokenizeLabelEnd, resolveTo: resolveToLabelEnd, resolveAll: resolveAllLabelEnd } var resourceConstruct = { tokenize: tokenizeResource } var fullReferenceConstruct = { tokenize: tokenizeFullReference } var collapsedReferenceConstruct = { tokenize: tokenizeCollapsedReference } function resolveAllLabelEnd(events) { var index = -1 var token while (++index < events.length) { token = events[index][1] if ( !token._used && (token.type === 'labelImage' || token.type === 'labelLink' || token.type === 'labelEnd') ) { // Remove the marker. events.splice(index + 1, token.type === 'labelImage' ? 4 : 2) token.type = '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 === 'link' || (token.type === '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 === 'labelLink') { token._inactive = true } } else if (close) { if ( events[index][0] === 'enter' && (token.type === 'labelImage' || token.type === 'labelLink') && !token._balanced ) { open = index if (token.type !== 'labelLink') { offset = 2 break } } } else if (token.type === 'labelEnd') { close = index } } group = { type: events[open][1].type === 'labelLink' ? 'link' : 'image', start: shallow(events[open][1].start), end: shallow(events[events.length - 1][1].end) } label = { type: 'label', start: shallow(events[open][1].start), end: shallow(events[close][1].end) } text = { type: '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. media = chunkedPush(media, events.slice(open + 1, open + offset + 3)) // Text open. media = chunkedPush(media, [['enter', text, context]]) // Between. media = chunkedPush( media, resolveAll( context.parser.constructs.insideSpan.null, events.slice(open + offset + 4, close - 3), context ) ) // Text close, marker close, label close. media = chunkedPush(media, [ ['exit', text, context], events[close - 2], events[close - 1], ['exit', label, context] ]) // Reference, resource, or so. media = chunkedPush(media, events.slice(close + 1)) // Media close. media = chunkedPush(media, [['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 === 'labelImage' || self.events[index][1].type === 'labelLink') && !self.events[index][1]._balanced ) { labelStart = self.events[index][1] break } } return start function start(code) { 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('labelEnd') effects.enter('labelMarker') effects.consume(code) effects.exit('labelMarker') effects.exit('labelEnd') return afterLabelEnd } function afterLabelEnd(code) { // Resource: `[asd](fgh)`. if (code === 40) { return effects.attempt( resourceConstruct, ok, defined ? ok : balanced )(code) } // Collapsed (`[asd][]`) or full (`[asd][fgh]`) reference? if (code === 91) { return effects.attempt( fullReferenceConstruct, ok, defined ? effects.attempt(collapsedReferenceConstruct, 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) { effects.enter('resource') effects.enter('resourceMarker') effects.consume(code) effects.exit('resourceMarker') return factoryWhitespace(effects, open) } function open(code) { if (code === 41) { return end(code) } return factoryDestination( effects, destinationAfter, nok, 'resourceDestination', 'resourceDestinationLiteral', 'resourceDestinationLiteralMarker', 'resourceDestinationRaw', 'resourceDestinationString', 3 )(code) } function destinationAfter(code) { return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, between)(code) : end(code) } function between(code) { if (code === 34 || code === 39 || code === 40) { return factoryTitle( effects, factoryWhitespace(effects, end), nok, 'resourceTitle', 'resourceTitleMarker', 'resourceTitleString' )(code) } return end(code) } function end(code) { if (code === 41) { effects.enter('resourceMarker') effects.consume(code) effects.exit('resourceMarker') effects.exit('resource') return ok } return nok(code) } } function tokenizeFullReference(effects, ok, nok) { var self = this return start function start(code) { return factoryLabel.call( self, effects, afterLabel, nok, 'reference', 'referenceMarker', '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) { effects.enter('reference') effects.enter('referenceMarker') effects.consume(code) effects.exit('referenceMarker') return open } function open(code) { if (code === 93) { effects.enter('referenceMarker') effects.consume(code) effects.exit('referenceMarker') effects.exit('reference') return ok } return nok(code) } } module.exports = labelEnd