You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
7.5 KiB
330 lines
7.5 KiB
'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
|
|
|