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.

400 lines
8.0 KiB

/**
* @author Titus Wormer
* @copyright 2015 Titus Wormer
* @license MIT
* @module remark:parse:tokenize:link
* @fileoverview Tokenise a link.
*/
'use strict';
var has = require('has');
var whitespace = require('is-whitespace-character');
var locate = require('../locate/link');
module.exports = link;
link.locator = locate;
var C_BACKSLASH = '\\';
var C_BRACKET_OPEN = '[';
var C_BRACKET_CLOSE = ']';
var C_PAREN_OPEN = '(';
var C_PAREN_CLOSE = ')';
var C_LT = '<';
var C_GT = '>';
var C_TICK = '`';
var C_DOUBLE_QUOTE = '"';
var C_SINGLE_QUOTE = '\'';
/* Map of characters, which can be used to mark link
* and image titles. */
var LINK_MARKERS = {};
LINK_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE;
LINK_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE;
/* Map of characters, which can be used to mark link
* and image titles in commonmark-mode. */
var COMMONMARK_LINK_MARKERS = {};
COMMONMARK_LINK_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE;
COMMONMARK_LINK_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE;
COMMONMARK_LINK_MARKERS[C_PAREN_OPEN] = C_PAREN_CLOSE;
/* Tokenise a link. */
function link(eat, value, silent) {
var self = this;
var subvalue = '';
var index = 0;
var character = value.charAt(0);
var commonmark = self.options.commonmark;
var gfm = self.options.gfm;
var closed;
var count;
var opening;
var beforeURL;
var beforeTitle;
var subqueue;
var hasMarker;
var markers;
var isImage;
var content;
var marker;
var length;
var title;
var depth;
var queue;
var url;
var now;
var exit;
var node;
/* Detect whether this is an image. */
if (character === '!') {
isImage = true;
subvalue = character;
character = value.charAt(++index);
}
/* Eat the opening. */
if (character !== C_BRACKET_OPEN) {
return;
}
/* Exit when this is a link and were already inside
* a link. */
if (!isImage && self.inLink) {
return;
}
subvalue += character;
queue = '';
index++;
/* Eat the content. */
length = value.length;
now = eat.now();
depth = 0;
now.column += index;
now.offset += index;
while (index < length) {
character = value.charAt(index);
subqueue = character;
if (character === C_TICK) {
/* Inline-code in link content. */
count = 1;
while (value.charAt(index + 1) === C_TICK) {
subqueue += character;
index++;
count++;
}
if (!opening) {
opening = count;
} else if (count >= opening) {
opening = 0;
}
} else if (character === C_BACKSLASH) {
/* Allow brackets to be escaped. */
index++;
subqueue += value.charAt(index);
/* In GFM mode, brackets in code still count.
* In all other modes, they dont. This empty
* block prevents the next statements are
* entered. */
} else if ((!opening || gfm) && character === C_BRACKET_OPEN) {
depth++;
} else if ((!opening || gfm) && character === C_BRACKET_CLOSE) {
if (depth) {
depth--;
} else {
/* Allow white-space between content and
* url in GFM mode. */
if (gfm) {
while (index < length) {
character = value.charAt(index + 1);
if (!whitespace(character)) {
break;
}
subqueue += character;
index++;
}
}
if (value.charAt(index + 1) !== C_PAREN_OPEN) {
return;
}
subqueue += C_PAREN_OPEN;
closed = true;
index++;
break;
}
}
queue += subqueue;
subqueue = '';
index++;
}
/* Eat the content closing. */
if (!closed) {
return;
}
content = queue;
subvalue += queue + subqueue;
index++;
/* Eat white-space. */
while (index < length) {
character = value.charAt(index);
if (!whitespace(character)) {
break;
}
subvalue += character;
index++;
}
/* Eat the URL. */
character = value.charAt(index);
markers = commonmark ? COMMONMARK_LINK_MARKERS : LINK_MARKERS;
queue = '';
beforeURL = subvalue;
if (character === C_LT) {
index++;
beforeURL += C_LT;
while (index < length) {
character = value.charAt(index);
if (character === C_GT) {
break;
}
if (commonmark && character === '\n') {
return;
}
queue += character;
index++;
}
if (value.charAt(index) !== C_GT) {
return;
}
subvalue += C_LT + queue + C_GT;
url = queue;
index++;
} else {
character = null;
subqueue = '';
while (index < length) {
character = value.charAt(index);
if (subqueue && has(markers, character)) {
break;
}
if (whitespace(character)) {
if (commonmark) {
break;
}
subqueue += character;
} else {
if (character === C_PAREN_OPEN) {
depth++;
} else if (character === C_PAREN_CLOSE) {
if (depth === 0) {
break;
}
depth--;
}
queue += subqueue;
subqueue = '';
if (character === C_BACKSLASH) {
queue += C_BACKSLASH;
character = value.charAt(++index);
}
queue += character;
}
index++;
}
subvalue += queue;
url = queue;
index = subvalue.length;
}
/* Eat white-space. */
queue = '';
while (index < length) {
character = value.charAt(index);
if (!whitespace(character)) {
break;
}
queue += character;
index++;
}
character = value.charAt(index);
subvalue += queue;
/* Eat the title. */
if (queue && has(markers, character)) {
index++;
subvalue += character;
queue = '';
marker = markers[character];
beforeTitle = subvalue;
/* In commonmark-mode, things are pretty easy: the
* marker cannot occur inside the title.
*
* Non-commonmark does, however, support nested
* delimiters. */
if (commonmark) {
while (index < length) {
character = value.charAt(index);
if (character === marker) {
break;
}
if (character === C_BACKSLASH) {
queue += C_BACKSLASH;
character = value.charAt(++index);
}
index++;
queue += character;
}
character = value.charAt(index);
if (character !== marker) {
return;
}
title = queue;
subvalue += queue + character;
index++;
while (index < length) {
character = value.charAt(index);
if (!whitespace(character)) {
break;
}
subvalue += character;
index++;
}
} else {
subqueue = '';
while (index < length) {
character = value.charAt(index);
if (character === marker) {
if (hasMarker) {
queue += marker + subqueue;
subqueue = '';
}
hasMarker = true;
} else if (!hasMarker) {
queue += character;
} else if (character === C_PAREN_CLOSE) {
subvalue += queue + marker + subqueue;
title = queue;
break;
} else if (whitespace(character)) {
subqueue += character;
} else {
queue += marker + subqueue + character;
subqueue = '';
hasMarker = false;
}
index++;
}
}
}
if (value.charAt(index) !== C_PAREN_CLOSE) {
return;
}
/* istanbul ignore if - never used (yet) */
if (silent) {
return true;
}
subvalue += C_PAREN_CLOSE;
url = self.decode.raw(self.unescape(url), eat(beforeURL).test().end);
if (title) {
beforeTitle = eat(beforeTitle).test().end;
title = self.decode.raw(self.unescape(title), beforeTitle);
}
node = {
type: isImage ? 'image' : 'link',
title: title || null,
url: url
};
if (isImage) {
node.alt = self.decode.raw(self.unescape(content), now) || null;
} else {
exit = self.enterLink();
node.children = self.tokenizeInline(content, now);
exit();
}
return eat(subvalue)(node);
}