blob: ab4d3fa3c5af54d19f408b68a7e701dbaadad64d [file] [log] [blame]
'use strict'
var whitespace = require('is-whitespace-character')
var locate = require('../locate/link')
module.exports = link
link.locator = locate
var lineFeed = '\n'
var exclamationMark = '!'
var quotationMark = '"'
var apostrophe = "'"
var leftParenthesis = '('
var rightParenthesis = ')'
var lessThan = '<'
var greaterThan = '>'
var leftSquareBracket = '['
var backslash = '\\'
var rightSquareBracket = ']'
var graveAccent = '`'
function link(eat, value, silent) {
var self = this
var subvalue = ''
var index = 0
var character = value.charAt(0)
var pedantic = self.options.pedantic
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 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 === exclamationMark) {
isImage = true
subvalue = character
character = value.charAt(++index)
}
// Eat the opening.
if (character !== leftSquareBracket) {
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 === graveAccent) {
// Inline-code in link content.
count = 1
while (value.charAt(index + 1) === graveAccent) {
subqueue += character
index++
count++
}
if (!opening) {
opening = count
} else if (count >= opening) {
opening = 0
}
} else if (character === backslash) {
// Allow brackets to be escaped.
index++
subqueue += value.charAt(index)
} else if ((!opening || gfm) && character === leftSquareBracket) {
// In GFM mode, brackets in code still count. In all other modes,
// they don’t.
depth++
} else if ((!opening || gfm) && character === rightSquareBracket) {
if (depth) {
depth--
} else {
// Allow white-space between content and url in GFM mode.
if (!pedantic) {
while (index < length) {
character = value.charAt(index + 1)
if (!whitespace(character)) {
break
}
subqueue += character
index++
}
}
if (value.charAt(index + 1) !== leftParenthesis) {
return
}
subqueue += leftParenthesis
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)
queue = ''
beforeURL = subvalue
if (character === lessThan) {
index++
beforeURL += lessThan
while (index < length) {
character = value.charAt(index)
if (character === greaterThan) {
break
}
if (commonmark && character === lineFeed) {
return
}
queue += character
index++
}
if (value.charAt(index) !== greaterThan) {
return
}
subvalue += lessThan + queue + greaterThan
url = queue
index++
} else {
character = null
subqueue = ''
while (index < length) {
character = value.charAt(index)
if (
subqueue &&
(character === quotationMark ||
character === apostrophe ||
(commonmark && character === leftParenthesis))
) {
break
}
if (whitespace(character)) {
if (!pedantic) {
break
}
subqueue += character
} else {
if (character === leftParenthesis) {
depth++
} else if (character === rightParenthesis) {
if (depth === 0) {
break
}
depth--
}
queue += subqueue
subqueue = ''
if (character === backslash) {
queue += 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 &&
(character === quotationMark ||
character === apostrophe ||
(commonmark && character === leftParenthesis))
) {
index++
subvalue += character
queue = ''
marker = character === leftParenthesis ? rightParenthesis : 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 === backslash) {
queue += 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 === rightParenthesis) {
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) !== rightParenthesis) {
return
}
/* istanbul ignore if - never used (yet) */
if (silent) {
return true
}
subvalue += rightParenthesis
url = self.decode.raw(self.unescape(url), eat(beforeURL).test().end, {
nonTerminated: false
})
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)
}