link.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. 'use strict';
  2. var whitespace = require('is-whitespace-character');
  3. var locate = require('../locate/link');
  4. module.exports = link;
  5. link.locator = locate;
  6. var own = {}.hasOwnProperty;
  7. var C_BACKSLASH = '\\';
  8. var C_BRACKET_OPEN = '[';
  9. var C_BRACKET_CLOSE = ']';
  10. var C_PAREN_OPEN = '(';
  11. var C_PAREN_CLOSE = ')';
  12. var C_LT = '<';
  13. var C_GT = '>';
  14. var C_TICK = '`';
  15. var C_DOUBLE_QUOTE = '"';
  16. var C_SINGLE_QUOTE = '\'';
  17. /* Map of characters, which can be used to mark link
  18. * and image titles. */
  19. var LINK_MARKERS = {};
  20. LINK_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE;
  21. LINK_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE;
  22. /* Map of characters, which can be used to mark link
  23. * and image titles in commonmark-mode. */
  24. var COMMONMARK_LINK_MARKERS = {};
  25. COMMONMARK_LINK_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE;
  26. COMMONMARK_LINK_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE;
  27. COMMONMARK_LINK_MARKERS[C_PAREN_OPEN] = C_PAREN_CLOSE;
  28. function link(eat, value, silent) {
  29. var self = this;
  30. var subvalue = '';
  31. var index = 0;
  32. var character = value.charAt(0);
  33. var pedantic = self.options.pedantic;
  34. var commonmark = self.options.commonmark;
  35. var gfm = self.options.gfm;
  36. var closed;
  37. var count;
  38. var opening;
  39. var beforeURL;
  40. var beforeTitle;
  41. var subqueue;
  42. var hasMarker;
  43. var markers;
  44. var isImage;
  45. var content;
  46. var marker;
  47. var length;
  48. var title;
  49. var depth;
  50. var queue;
  51. var url;
  52. var now;
  53. var exit;
  54. var node;
  55. /* Detect whether this is an image. */
  56. if (character === '!') {
  57. isImage = true;
  58. subvalue = character;
  59. character = value.charAt(++index);
  60. }
  61. /* Eat the opening. */
  62. if (character !== C_BRACKET_OPEN) {
  63. return;
  64. }
  65. /* Exit when this is a link and we’re already inside
  66. * a link. */
  67. if (!isImage && self.inLink) {
  68. return;
  69. }
  70. subvalue += character;
  71. queue = '';
  72. index++;
  73. /* Eat the content. */
  74. length = value.length;
  75. now = eat.now();
  76. depth = 0;
  77. now.column += index;
  78. now.offset += index;
  79. while (index < length) {
  80. character = value.charAt(index);
  81. subqueue = character;
  82. if (character === C_TICK) {
  83. /* Inline-code in link content. */
  84. count = 1;
  85. while (value.charAt(index + 1) === C_TICK) {
  86. subqueue += character;
  87. index++;
  88. count++;
  89. }
  90. if (!opening) {
  91. opening = count;
  92. } else if (count >= opening) {
  93. opening = 0;
  94. }
  95. } else if (character === C_BACKSLASH) {
  96. /* Allow brackets to be escaped. */
  97. index++;
  98. subqueue += value.charAt(index);
  99. /* In GFM mode, brackets in code still count.
  100. * In all other modes, they don’t. This empty
  101. * block prevents the next statements are
  102. * entered. */
  103. } else if ((!opening || gfm) && character === C_BRACKET_OPEN) {
  104. depth++;
  105. } else if ((!opening || gfm) && character === C_BRACKET_CLOSE) {
  106. if (depth) {
  107. depth--;
  108. } else {
  109. /* Allow white-space between content and
  110. * url in GFM mode. */
  111. if (!pedantic) {
  112. while (index < length) {
  113. character = value.charAt(index + 1);
  114. if (!whitespace(character)) {
  115. break;
  116. }
  117. subqueue += character;
  118. index++;
  119. }
  120. }
  121. if (value.charAt(index + 1) !== C_PAREN_OPEN) {
  122. return;
  123. }
  124. subqueue += C_PAREN_OPEN;
  125. closed = true;
  126. index++;
  127. break;
  128. }
  129. }
  130. queue += subqueue;
  131. subqueue = '';
  132. index++;
  133. }
  134. /* Eat the content closing. */
  135. if (!closed) {
  136. return;
  137. }
  138. content = queue;
  139. subvalue += queue + subqueue;
  140. index++;
  141. /* Eat white-space. */
  142. while (index < length) {
  143. character = value.charAt(index);
  144. if (!whitespace(character)) {
  145. break;
  146. }
  147. subvalue += character;
  148. index++;
  149. }
  150. /* Eat the URL. */
  151. character = value.charAt(index);
  152. markers = commonmark ? COMMONMARK_LINK_MARKERS : LINK_MARKERS;
  153. queue = '';
  154. beforeURL = subvalue;
  155. if (character === C_LT) {
  156. index++;
  157. beforeURL += C_LT;
  158. while (index < length) {
  159. character = value.charAt(index);
  160. if (character === C_GT) {
  161. break;
  162. }
  163. if (commonmark && character === '\n') {
  164. return;
  165. }
  166. queue += character;
  167. index++;
  168. }
  169. if (value.charAt(index) !== C_GT) {
  170. return;
  171. }
  172. subvalue += C_LT + queue + C_GT;
  173. url = queue;
  174. index++;
  175. } else {
  176. character = null;
  177. subqueue = '';
  178. while (index < length) {
  179. character = value.charAt(index);
  180. if (subqueue && own.call(markers, character)) {
  181. break;
  182. }
  183. if (whitespace(character)) {
  184. if (!pedantic) {
  185. break;
  186. }
  187. subqueue += character;
  188. } else {
  189. if (character === C_PAREN_OPEN) {
  190. depth++;
  191. } else if (character === C_PAREN_CLOSE) {
  192. if (depth === 0) {
  193. break;
  194. }
  195. depth--;
  196. }
  197. queue += subqueue;
  198. subqueue = '';
  199. if (character === C_BACKSLASH) {
  200. queue += C_BACKSLASH;
  201. character = value.charAt(++index);
  202. }
  203. queue += character;
  204. }
  205. index++;
  206. }
  207. subvalue += queue;
  208. url = queue;
  209. index = subvalue.length;
  210. }
  211. /* Eat white-space. */
  212. queue = '';
  213. while (index < length) {
  214. character = value.charAt(index);
  215. if (!whitespace(character)) {
  216. break;
  217. }
  218. queue += character;
  219. index++;
  220. }
  221. character = value.charAt(index);
  222. subvalue += queue;
  223. /* Eat the title. */
  224. if (queue && own.call(markers, character)) {
  225. index++;
  226. subvalue += character;
  227. queue = '';
  228. marker = markers[character];
  229. beforeTitle = subvalue;
  230. /* In commonmark-mode, things are pretty easy: the
  231. * marker cannot occur inside the title.
  232. *
  233. * Non-commonmark does, however, support nested
  234. * delimiters. */
  235. if (commonmark) {
  236. while (index < length) {
  237. character = value.charAt(index);
  238. if (character === marker) {
  239. break;
  240. }
  241. if (character === C_BACKSLASH) {
  242. queue += C_BACKSLASH;
  243. character = value.charAt(++index);
  244. }
  245. index++;
  246. queue += character;
  247. }
  248. character = value.charAt(index);
  249. if (character !== marker) {
  250. return;
  251. }
  252. title = queue;
  253. subvalue += queue + character;
  254. index++;
  255. while (index < length) {
  256. character = value.charAt(index);
  257. if (!whitespace(character)) {
  258. break;
  259. }
  260. subvalue += character;
  261. index++;
  262. }
  263. } else {
  264. subqueue = '';
  265. while (index < length) {
  266. character = value.charAt(index);
  267. if (character === marker) {
  268. if (hasMarker) {
  269. queue += marker + subqueue;
  270. subqueue = '';
  271. }
  272. hasMarker = true;
  273. } else if (!hasMarker) {
  274. queue += character;
  275. } else if (character === C_PAREN_CLOSE) {
  276. subvalue += queue + marker + subqueue;
  277. title = queue;
  278. break;
  279. } else if (whitespace(character)) {
  280. subqueue += character;
  281. } else {
  282. queue += marker + subqueue + character;
  283. subqueue = '';
  284. hasMarker = false;
  285. }
  286. index++;
  287. }
  288. }
  289. }
  290. if (value.charAt(index) !== C_PAREN_CLOSE) {
  291. return;
  292. }
  293. /* istanbul ignore if - never used (yet) */
  294. if (silent) {
  295. return true;
  296. }
  297. subvalue += C_PAREN_CLOSE;
  298. url = self.decode.raw(self.unescape(url), eat(beforeURL).test().end, {nonTerminated: false});
  299. if (title) {
  300. beforeTitle = eat(beforeTitle).test().end;
  301. title = self.decode.raw(self.unescape(title), beforeTitle);
  302. }
  303. node = {
  304. type: isImage ? 'image' : 'link',
  305. title: title || null,
  306. url: url
  307. };
  308. if (isImage) {
  309. node.alt = self.decode.raw(self.unescape(content), now) || null;
  310. } else {
  311. exit = self.enterLink();
  312. node.children = self.tokenizeInline(content, now);
  313. exit();
  314. }
  315. return eat(subvalue)(node);
  316. }