123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- 'use strict'
- module.exports = footnotes
- var tab = 9 // '\t'
- var lineFeed = 10 // '\n'
- var space = 32
- var exclamationMark = 33 // '!'
- var colon = 58 // ':'
- var leftSquareBracket = 91 // '['
- var backslash = 92 // '\'
- var rightSquareBracket = 93 // ']'
- var caret = 94 // '^'
- var graveAccent = 96 // '`'
- var tabSize = 4
- var maxSlice = 1024
- function footnotes(options) {
- var parser = this.Parser
- var compiler = this.Compiler
- if (isRemarkParser(parser)) {
- attachParser(parser, options)
- }
- if (isRemarkCompiler(compiler)) {
- attachCompiler(compiler)
- }
- }
- function isRemarkParser(parser) {
- return Boolean(parser && parser.prototype && parser.prototype.blockTokenizers)
- }
- function isRemarkCompiler(compiler) {
- return Boolean(compiler && compiler.prototype && compiler.prototype.visitors)
- }
- function attachParser(parser, options) {
- var settings = options || {}
- var proto = parser.prototype
- var blocks = proto.blockTokenizers
- var spans = proto.inlineTokenizers
- var blockMethods = proto.blockMethods
- var inlineMethods = proto.inlineMethods
- var originalDefinition = blocks.definition
- var originalReference = spans.reference
- var interruptors = []
- var index = -1
- var length = blockMethods.length
- var method
- // Interrupt by anything except for indented code or paragraphs.
- while (++index < length) {
- method = blockMethods[index]
- if (
- method === 'newline' ||
- method === 'indentedCode' ||
- method === 'paragraph' ||
- method === 'footnoteDefinition'
- ) {
- continue
- }
- interruptors.push([method])
- }
- interruptors.push(['footnoteDefinition'])
- // Insert tokenizers.
- if (settings.inlineNotes) {
- before(inlineMethods, 'reference', 'inlineNote')
- spans.inlineNote = footnote
- }
- before(blockMethods, 'definition', 'footnoteDefinition')
- before(inlineMethods, 'reference', 'footnoteCall')
- blocks.definition = definition
- blocks.footnoteDefinition = footnoteDefinition
- spans.footnoteCall = footnoteCall
- spans.reference = reference
- proto.interruptFootnoteDefinition = interruptors
- reference.locator = originalReference.locator
- footnoteCall.locator = locateFootnoteCall
- footnote.locator = locateFootnote
- function footnoteDefinition(eat, value, silent) {
- var self = this
- var interruptors = self.interruptFootnoteDefinition
- var offsets = self.offset
- var length = value.length + 1
- var index = 0
- var content = []
- var label
- var labelStart
- var labelEnd
- var code
- var now
- var add
- var exit
- var children
- var start
- var indent
- var contentStart
- var lines
- var line
- // Skip initial whitespace.
- while (index < length) {
- code = value.charCodeAt(index)
- if (code !== tab && code !== space) break
- index++
- }
- // Parse `[^`.
- if (value.charCodeAt(index++) !== leftSquareBracket) return
- if (value.charCodeAt(index++) !== caret) return
- // Parse label.
- labelStart = index
- while (index < length) {
- code = value.charCodeAt(index)
- // Exit on white space.
- if (
- code !== code ||
- code === lineFeed ||
- code === tab ||
- code === space
- ) {
- return
- }
- if (code === rightSquareBracket) {
- labelEnd = index
- index++
- break
- }
- index++
- }
- // Exit if we didn’t find an end, no label, or there’s no colon.
- if (
- labelEnd === undefined ||
- labelStart === labelEnd ||
- value.charCodeAt(index++) !== colon
- ) {
- return
- }
- // Found it!
- /* istanbul ignore if - never used (yet) */
- if (silent) {
- return true
- }
- label = value.slice(labelStart, labelEnd)
- // Now, to get all lines.
- now = eat.now()
- start = 0
- indent = 0
- contentStart = index
- lines = []
- while (index < length) {
- code = value.charCodeAt(index)
- if (code !== code || code === lineFeed) {
- line = {
- start: start,
- contentStart: contentStart || index,
- contentEnd: index,
- end: index
- }
- lines.push(line)
- // Prepare a new line.
- if (code === lineFeed) {
- start = index + 1
- indent = 0
- contentStart = undefined
- line.end = start
- }
- } else if (indent !== undefined) {
- if (code === space || code === tab) {
- indent += code === space ? 1 : tabSize - (indent % tabSize)
- if (indent > tabSize) {
- indent = undefined
- contentStart = index
- }
- } else {
- // If this line is not indented and it’s either preceded by a blank
- // line or starts a new block, exit.
- if (
- indent < tabSize &&
- line &&
- (line.contentStart === line.contentEnd ||
- interrupt(interruptors, blocks, self, [
- eat,
- value.slice(index, maxSlice),
- true
- ]))
- ) {
- break
- }
- indent = undefined
- contentStart = index
- }
- }
- index++
- }
- // Remove trailing lines without content.
- index = -1
- length = lines.length
- while (length > 0) {
- line = lines[length - 1]
- if (line.contentStart !== line.contentEnd) {
- break
- }
- length--
- }
- // Add all, but ignore the final line feed.
- add = eat(value.slice(0, line.contentEnd))
- // Add indent offsets and get content w/o indents.
- while (++index < length) {
- line = lines[index]
- offsets[now.line + index] =
- (offsets[now.line + index] || 0) + (line.contentStart - line.start)
- content.push(value.slice(line.contentStart, line.end))
- }
- // Parse content.
- exit = self.enterBlock()
- children = self.tokenizeBlock(content.join(''), now)
- exit()
- return add({
- type: 'footnoteDefinition',
- identifier: label.toLowerCase(),
- label: label,
- children: children
- })
- }
- // Parse a footnote call / footnote reference, such as `[^label]`
- function footnoteCall(eat, value, silent) {
- var length = value.length + 1
- var index = 0
- var label
- var labelStart
- var labelEnd
- var code
- if (value.charCodeAt(index++) !== leftSquareBracket) return
- if (value.charCodeAt(index++) !== caret) return
- labelStart = index
- while (index < length) {
- code = value.charCodeAt(index)
- if (
- code !== code ||
- code === lineFeed ||
- code === tab ||
- code === space
- ) {
- return
- }
- if (code === rightSquareBracket) {
- labelEnd = index
- index++
- break
- }
- index++
- }
- if (labelEnd === undefined || labelStart === labelEnd) {
- return
- }
- /* istanbul ignore if - never used (yet) */
- if (silent) {
- return true
- }
- label = value.slice(labelStart, labelEnd)
- return eat(value.slice(0, index))({
- type: 'footnoteReference',
- identifier: label.toLowerCase(),
- label: label
- })
- }
- // Parse an inline note / footnote, such as `^[text]`
- function footnote(eat, value, silent) {
- var self = this
- var length = value.length + 1
- var index = 0
- var balance = 0
- var now
- var code
- var contentStart
- var contentEnd
- var fenceStart
- var fenceOpenSize
- var fenceCloseSize
- if (value.charCodeAt(index++) !== caret) return
- if (value.charCodeAt(index++) !== leftSquareBracket) return
- contentStart = index
- while (index < length) {
- code = value.charCodeAt(index)
- // EOF:
- if (code !== code) {
- return
- }
- // If we’re not in code:
- if (fenceOpenSize === undefined) {
- if (code === backslash) {
- index += 2
- } else if (code === leftSquareBracket) {
- balance++
- index++
- } else if (code === rightSquareBracket) {
- if (balance === 0) {
- contentEnd = index
- index++
- break
- } else {
- balance--
- index++
- }
- } else if (code === graveAccent) {
- fenceStart = index
- fenceOpenSize = 1
- while (value.charCodeAt(fenceStart + fenceOpenSize) === graveAccent) {
- fenceOpenSize++
- }
- index += fenceOpenSize
- } else {
- index++
- }
- }
- // We’re in code:
- else {
- if (code === graveAccent) {
- fenceStart = index
- fenceCloseSize = 1
- while (
- value.charCodeAt(fenceStart + fenceCloseSize) === graveAccent
- ) {
- fenceCloseSize++
- }
- index += fenceCloseSize
- // Found it, we’re no longer in code!
- if (fenceOpenSize === fenceCloseSize) {
- fenceOpenSize = undefined
- }
- fenceCloseSize = undefined
- } else {
- index++
- }
- }
- }
- if (contentEnd === undefined) {
- return
- }
- /* istanbul ignore if - never used (yet) */
- if (silent) {
- return true
- }
- now = eat.now()
- now.column += 2
- now.offset += 2
- return eat(value.slice(0, index))({
- type: 'footnote',
- children: self.tokenizeInline(value.slice(contentStart, contentEnd), now)
- })
- }
- // Do not allow `![^` or `[^` as a normal reference, do pass all other values
- // through.
- function reference(eat, value, silent) {
- var index = 0
- if (value.charCodeAt(index) === exclamationMark) index++
- if (value.charCodeAt(index) !== leftSquareBracket) return
- if (value.charCodeAt(index + 1) === caret) return
- return originalReference.call(this, eat, value, silent)
- }
- // Do not allow `[^` as a normal definition, do pass all other values through.
- function definition(eat, value, silent) {
- var index = 0
- var code = value.charCodeAt(index)
- while (code === space || code === tab) code = value.charCodeAt(++index)
- if (code !== leftSquareBracket) return
- if (value.charCodeAt(index + 1) === caret) return
- return originalDefinition.call(this, eat, value, silent)
- }
- function locateFootnoteCall(value, from) {
- return value.indexOf('[', from)
- }
- function locateFootnote(value, from) {
- return value.indexOf('^[', from)
- }
- }
- function attachCompiler(compiler) {
- var serializers = compiler.prototype.visitors
- var indent = ' '
- serializers.footnote = footnote
- serializers.footnoteReference = footnoteReference
- serializers.footnoteDefinition = footnoteDefinition
- function footnote(node) {
- return '^[' + this.all(node).join('') + ']'
- }
- function footnoteReference(node) {
- return '[^' + (node.label || node.identifier) + ']'
- }
- function footnoteDefinition(node) {
- var lines = this.all(node).join('\n\n').split('\n')
- var index = 0
- var length = lines.length
- var line
- // Indent each line, except the first, that is not empty.
- while (++index < length) {
- line = lines[index]
- if (line === '') continue
- lines[index] = indent + line
- }
- return '[^' + (node.label || node.identifier) + ']: ' + lines.join('\n')
- }
- }
- function before(list, before, value) {
- list.splice(list.indexOf(before), 0, value)
- }
- // Mimics <https://github.com/remarkjs/remark/blob/b4c993e/packages/remark-parse/lib/util/interrupt.js>,
- // but simplified for our needs.
- function interrupt(list, tokenizers, ctx, parameters) {
- var length = list.length
- var index = -1
- while (++index < length) {
- if (tokenizers[list[index][0]].apply(ctx, parameters)) {
- return true
- }
- }
- return false
- }
|