link.js 7.1 KB

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