link.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. // Process [link](<to> "stuff")
  2. 'use strict';
  3. var normalizeReference = require('../common/utils').normalizeReference;
  4. var isSpace = require('../common/utils').isSpace;
  5. module.exports = function link(state, silent) {
  6. var attrs,
  7. code,
  8. label,
  9. labelEnd,
  10. labelStart,
  11. pos,
  12. res,
  13. ref,
  14. token,
  15. href = '',
  16. title = '',
  17. oldPos = state.pos,
  18. max = state.posMax,
  19. start = state.pos,
  20. parseReference = true;
  21. if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; }
  22. labelStart = state.pos + 1;
  23. labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true);
  24. // parser failed to find ']', so it's not a valid link
  25. if (labelEnd < 0) { return false; }
  26. pos = labelEnd + 1;
  27. if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
  28. //
  29. // Inline link
  30. //
  31. // might have found a valid shortcut link, disable reference parsing
  32. parseReference = false;
  33. // [link]( <href> "title" )
  34. // ^^ skipping these spaces
  35. pos++;
  36. for (; pos < max; pos++) {
  37. code = state.src.charCodeAt(pos);
  38. if (!isSpace(code) && code !== 0x0A) { break; }
  39. }
  40. if (pos >= max) { return false; }
  41. // [link]( <href> "title" )
  42. // ^^^^^^ parsing link destination
  43. start = pos;
  44. res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax);
  45. if (res.ok) {
  46. href = state.md.normalizeLink(res.str);
  47. if (state.md.validateLink(href)) {
  48. pos = res.pos;
  49. } else {
  50. href = '';
  51. }
  52. // [link]( <href> "title" )
  53. // ^^ skipping these spaces
  54. start = pos;
  55. for (; pos < max; pos++) {
  56. code = state.src.charCodeAt(pos);
  57. if (!isSpace(code) && code !== 0x0A) { break; }
  58. }
  59. // [link]( <href> "title" )
  60. // ^^^^^^^ parsing link title
  61. res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax);
  62. if (pos < max && start !== pos && res.ok) {
  63. title = res.str;
  64. pos = res.pos;
  65. // [link]( <href> "title" )
  66. // ^^ skipping these spaces
  67. for (; pos < max; pos++) {
  68. code = state.src.charCodeAt(pos);
  69. if (!isSpace(code) && code !== 0x0A) { break; }
  70. }
  71. }
  72. }
  73. if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
  74. // parsing a valid shortcut link failed, fallback to reference
  75. parseReference = true;
  76. }
  77. pos++;
  78. }
  79. if (parseReference) {
  80. //
  81. // Link reference
  82. //
  83. if (typeof state.env.references === 'undefined') { return false; }
  84. if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {
  85. start = pos + 1;
  86. pos = state.md.helpers.parseLinkLabel(state, pos);
  87. if (pos >= 0) {
  88. label = state.src.slice(start, pos++);
  89. } else {
  90. pos = labelEnd + 1;
  91. }
  92. } else {
  93. pos = labelEnd + 1;
  94. }
  95. // covers label === '' and label === undefined
  96. // (collapsed reference link and shortcut reference link respectively)
  97. if (!label) { label = state.src.slice(labelStart, labelEnd); }
  98. ref = state.env.references[normalizeReference(label)];
  99. if (!ref) {
  100. state.pos = oldPos;
  101. return false;
  102. }
  103. href = ref.href;
  104. title = ref.title;
  105. }
  106. //
  107. // We found the end of the link, and know for a fact it's a valid link;
  108. // so all that's left to do is to call tokenizer.
  109. //
  110. if (!silent) {
  111. state.pos = labelStart;
  112. state.posMax = labelEnd;
  113. token = state.push('link_open', 'a', 1);
  114. token.attrs = attrs = [ [ 'href', href ] ];
  115. if (title) {
  116. attrs.push([ 'title', title ]);
  117. }
  118. state.linkLevel++;
  119. state.md.inline.tokenize(state);
  120. state.linkLevel--;
  121. token = state.push('link_close', 'a', -1);
  122. }
  123. state.pos = pos;
  124. state.posMax = max;
  125. return true;
  126. };