mirror of https://github.com/lukechilds/node.git
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
400 lines
8.0 KiB
8 years ago
|
/**
|
||
|
* @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 we’re 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 don’t. 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);
|
||
|
}
|