| 'use strict' |
| |
| module.exports = fromMarkdown |
| |
| // These three are compiled away in the `dist/` |
| |
| var decode = require('parse-entities/decode-entity') |
| var toString = require('mdast-util-to-string') |
| var own = require('micromark/dist/constant/has-own-property') |
| var normalizeIdentifier = require('micromark/dist/util/normalize-identifier') |
| var safeFromInt = require('micromark/dist/util/safe-from-int') |
| var parser = require('micromark/dist/parse') |
| var preprocessor = require('micromark/dist/preprocess') |
| var postprocess = require('micromark/dist/postprocess') |
| |
| function fromMarkdown(value, encoding, options) { |
| if (typeof encoding !== 'string') { |
| options = encoding |
| encoding = undefined |
| } |
| |
| return compiler(options)( |
| postprocess( |
| parser(options).document().write(preprocessor()(value, encoding, true)) |
| ) |
| ) |
| } |
| |
| // Note this compiler only understand complete buffering, not streaming. |
| function compiler(options) { |
| var settings = options || {} |
| var config = configure( |
| { |
| canContainEols: [ |
| 'emphasis', |
| 'fragment', |
| 'heading', |
| 'paragraph', |
| 'strong' |
| ], |
| |
| enter: { |
| autolink: opener(link), |
| autolinkProtocol: onenterdata, |
| autolinkEmail: onenterdata, |
| atxHeading: opener(heading), |
| blockQuote: opener(blockQuote), |
| characterEscape: onenterdata, |
| characterReference: onenterdata, |
| codeFenced: opener(codeFlow), |
| codeFencedFenceInfo: buffer, |
| codeFencedFenceMeta: buffer, |
| codeIndented: opener(codeFlow, buffer), |
| codeText: opener(codeText, buffer), |
| codeTextData: onenterdata, |
| data: onenterdata, |
| codeFlowValue: onenterdata, |
| definition: opener(definition), |
| definitionDestinationString: buffer, |
| definitionLabelString: buffer, |
| definitionTitleString: buffer, |
| emphasis: opener(emphasis), |
| hardBreakEscape: opener(hardBreak), |
| hardBreakTrailing: opener(hardBreak), |
| htmlFlow: opener(html, buffer), |
| htmlFlowData: onenterdata, |
| htmlText: opener(html, buffer), |
| htmlTextData: onenterdata, |
| image: opener(image), |
| label: buffer, |
| link: opener(link), |
| listItem: opener(listItem), |
| listItemValue: onenterlistitemvalue, |
| listOrdered: opener(list, onenterlistordered), |
| listUnordered: opener(list), |
| paragraph: opener(paragraph), |
| reference: onenterreference, |
| referenceString: buffer, |
| resourceDestinationString: buffer, |
| resourceTitleString: buffer, |
| setextHeading: opener(heading), |
| strong: opener(strong), |
| thematicBreak: opener(thematicBreak) |
| }, |
| |
| exit: { |
| atxHeading: closer(), |
| atxHeadingSequence: onexitatxheadingsequence, |
| autolink: closer(), |
| autolinkEmail: onexitautolinkemail, |
| autolinkProtocol: onexitautolinkprotocol, |
| blockQuote: closer(), |
| characterEscapeValue: onexitdata, |
| characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker, |
| characterReferenceMarkerNumeric: onexitcharacterreferencemarker, |
| characterReferenceValue: closer(onexitcharacterreferencevalue), |
| codeFenced: closer(onexitcodefenced), |
| codeFencedFence: onexitcodefencedfence, |
| codeFencedFenceInfo: onexitcodefencedfenceinfo, |
| codeFencedFenceMeta: onexitcodefencedfencemeta, |
| codeFlowValue: onexitdata, |
| codeIndented: closer(onexitcodeindented), |
| codeText: closer(onexitcodetext), |
| codeTextData: onexitdata, |
| data: onexitdata, |
| definition: closer(), |
| definitionDestinationString: onexitdefinitiondestinationstring, |
| definitionLabelString: onexitdefinitionlabelstring, |
| definitionTitleString: onexitdefinitiontitlestring, |
| emphasis: closer(), |
| hardBreakEscape: closer(onexithardbreak), |
| hardBreakTrailing: closer(onexithardbreak), |
| htmlFlow: closer(onexithtmlflow), |
| htmlFlowData: onexitdata, |
| htmlText: closer(onexithtmltext), |
| htmlTextData: onexitdata, |
| image: closer(onexitimage), |
| label: onexitlabel, |
| labelText: onexitlabeltext, |
| lineEnding: onexitlineending, |
| link: closer(onexitlink), |
| listItem: closer(), |
| listOrdered: closer(), |
| listUnordered: closer(), |
| paragraph: closer(), |
| referenceString: onexitreferencestring, |
| resourceDestinationString: onexitresourcedestinationstring, |
| resourceTitleString: onexitresourcetitlestring, |
| resource: onexitresource, |
| setextHeading: closer(onexitsetextheading), |
| setextHeadingLineSequence: onexitsetextheadinglinesequence, |
| setextHeadingText: onexitsetextheadingtext, |
| strong: closer(), |
| thematicBreak: closer() |
| } |
| }, |
| |
| settings.mdastExtensions || [] |
| ) |
| |
| var data = {} |
| |
| return compile |
| |
| function compile(events) { |
| var stack = [{type: 'root', children: []}] |
| var index = -1 |
| var listStack = [] |
| var length |
| var handler |
| var listStart |
| var event |
| |
| while (++index < events.length) { |
| event = events[index] |
| |
| // We preprocess lists to add `listItem` tokens, and to infer whether |
| // items the list itself are spread out. |
| if ( |
| event[1].type === 'listOrdered' || |
| event[1].type === 'listUnordered' |
| ) { |
| if (event[0] === 'enter') { |
| listStack.push(index) |
| } else { |
| listStart = listStack.pop(index) |
| index = prepareList(events, listStart, index) |
| } |
| } |
| } |
| |
| index = -1 |
| length = events.length |
| |
| while (++index < length) { |
| handler = config[events[index][0]] |
| |
| if (own.call(handler, events[index][1].type)) { |
| handler[events[index][1].type].call( |
| { |
| stack: stack, |
| config: config, |
| enter: enter, |
| exit: exit, |
| buffer: buffer, |
| resume: resume, |
| sliceSerialize: events[index][2].sliceSerialize, |
| setData: setData, |
| getData: getData |
| }, |
| |
| events[index][1] |
| ) |
| } |
| } |
| |
| // Figure out `root` position. |
| stack[0].position = { |
| start: point( |
| length ? events[0][1].start : {line: 1, column: 1, offset: 0} |
| ), |
| |
| end: point( |
| length |
| ? events[events.length - 2][1].end |
| : {line: 1, column: 1, offset: 0} |
| ) |
| } |
| |
| return stack[0] |
| } |
| |
| function prepareList(events, start, length) { |
| var index = start - 1 |
| var containerBalance = -1 |
| var listSpread = false |
| var listItem |
| var tailIndex |
| var lineIndex |
| var tailEvent |
| var event |
| var firstBlankLineIndex |
| var atMarker |
| |
| while (++index <= length) { |
| event = events[index] |
| |
| if ( |
| event[1].type === 'listUnordered' || |
| event[1].type === 'listOrdered' || |
| event[1].type === 'blockQuote' |
| ) { |
| if (event[0] === 'enter') { |
| containerBalance++ |
| } else { |
| containerBalance-- |
| } |
| |
| atMarker = undefined |
| } else if (event[1].type === 'lineEndingBlank') { |
| if (event[0] === 'enter') { |
| if ( |
| listItem && |
| !atMarker && |
| !containerBalance && |
| !firstBlankLineIndex |
| ) { |
| firstBlankLineIndex = index |
| } |
| |
| atMarker = undefined |
| } |
| } else if ( |
| event[1].type === 'linePrefix' || |
| event[1].type === 'listItemValue' || |
| event[1].type === 'listItemMarker' || |
| event[1].type === 'listItemPrefix' || |
| event[1].type === 'listItemPrefixWhitespace' |
| ) { |
| // Empty. |
| } else { |
| atMarker = undefined |
| } |
| |
| if ( |
| (!containerBalance && |
| event[0] === 'enter' && |
| event[1].type === 'listItemPrefix') || |
| (containerBalance === -1 && |
| event[0] === 'exit' && |
| (event[1].type === 'listUnordered' || |
| event[1].type === 'listOrdered')) |
| ) { |
| if (listItem) { |
| tailIndex = index |
| lineIndex = undefined |
| |
| while (tailIndex--) { |
| tailEvent = events[tailIndex] |
| |
| if ( |
| tailEvent[1].type === 'lineEnding' || |
| tailEvent[1].type === 'lineEndingBlank' |
| ) { |
| if (tailEvent[0] === 'exit') continue |
| |
| if (lineIndex) { |
| events[lineIndex][1].type = 'lineEndingBlank' |
| listSpread = true |
| } |
| |
| tailEvent[1].type = 'lineEnding' |
| lineIndex = tailIndex |
| } else if ( |
| tailEvent[1].type === 'linePrefix' || |
| tailEvent[1].type === 'blockQuotePrefix' || |
| tailEvent[1].type === 'blockQuotePrefixWhitespace' || |
| tailEvent[1].type === 'blockQuoteMarker' || |
| tailEvent[1].type === 'listItemIndent' |
| ) { |
| // Empty |
| } else { |
| break |
| } |
| } |
| |
| if ( |
| firstBlankLineIndex && |
| (!lineIndex || firstBlankLineIndex < lineIndex) |
| ) { |
| listItem._spread = true |
| } |
| |
| // Fix position. |
| listItem.end = point( |
| lineIndex ? events[lineIndex][1].start : event[1].end |
| ) |
| |
| events.splice(lineIndex || index, 0, ['exit', listItem, event[2]]) |
| index++ |
| length++ |
| } |
| |
| // Create a new list item. |
| if (event[1].type === 'listItemPrefix') { |
| listItem = { |
| type: 'listItem', |
| _spread: false, |
| start: point(event[1].start) |
| } |
| |
| events.splice(index, 0, ['enter', listItem, event[2]]) |
| index++ |
| length++ |
| firstBlankLineIndex = undefined |
| atMarker = true |
| } |
| } |
| } |
| |
| events[start][1]._spread = listSpread |
| return length |
| } |
| |
| function setData(key, value) { |
| data[key] = value |
| } |
| |
| function getData(key) { |
| return data[key] |
| } |
| |
| function point(d) { |
| return {line: d.line, column: d.column, offset: d.offset} |
| } |
| |
| function opener(create, and) { |
| return open |
| |
| function open(token) { |
| enter.call(this, create(token), token) |
| if (and) and.call(this, token) |
| } |
| } |
| |
| function buffer() { |
| this.stack.push({type: 'fragment', children: []}) |
| } |
| |
| function enter(node, token) { |
| this.stack[this.stack.length - 1].children.push(node) |
| this.stack.push(node) |
| node.position = {start: point(token.start)} |
| return node |
| } |
| |
| function closer(and) { |
| return close |
| |
| function close(token) { |
| if (and) and.call(this, token) |
| exit.call(this, token) |
| } |
| } |
| |
| function exit(token) { |
| var node = this.stack.pop() |
| node.position.end = point(token.end) |
| return node |
| } |
| |
| function resume() { |
| var value = toString(this.stack.pop()) |
| return value |
| } |
| |
| // |
| // Handlers. |
| // |
| |
| function onenterlistordered() { |
| setData('expectingFirstListItemValue', true) |
| } |
| |
| function onenterlistitemvalue(token) { |
| if (getData('expectingFirstListItemValue')) { |
| this.stack[this.stack.length - 2].start = parseInt( |
| this.sliceSerialize(token), |
| 10 |
| ) |
| |
| setData('expectingFirstListItemValue') |
| } |
| } |
| |
| function onexitcodefencedfenceinfo() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].lang = data |
| } |
| |
| function onexitcodefencedfencemeta() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].meta = data |
| } |
| |
| function onexitcodefencedfence() { |
| // Exit if this is the closing fence. |
| if (getData('flowCodeInside')) return |
| this.buffer() |
| setData('flowCodeInside', true) |
| } |
| |
| function onexitcodefenced() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].value = data.replace( |
| /^(\r?\n|\r)|(\r?\n|\r)$/g, |
| '' |
| ) |
| |
| setData('flowCodeInside') |
| } |
| |
| function onexitcodeindented() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].value = data |
| } |
| |
| function onexitdefinitionlabelstring(token) { |
| // Discard label, use the source content instead. |
| var label = this.resume() |
| this.stack[this.stack.length - 1].label = label |
| this.stack[this.stack.length - 1].identifier = normalizeIdentifier( |
| this.sliceSerialize(token) |
| ).toLowerCase() |
| } |
| |
| function onexitdefinitiontitlestring() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].title = data |
| } |
| |
| function onexitdefinitiondestinationstring() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].url = data |
| } |
| |
| function onexitatxheadingsequence(token) { |
| if (!this.stack[this.stack.length - 1].depth) { |
| this.stack[this.stack.length - 1].depth = this.sliceSerialize( |
| token |
| ).length |
| } |
| } |
| |
| function onexitsetextheadingtext() { |
| setData('setextHeadingSlurpLineEnding', true) |
| } |
| |
| function onexitsetextheadinglinesequence(token) { |
| this.stack[this.stack.length - 1].depth = |
| this.sliceSerialize(token).charCodeAt(0) === 61 ? 1 : 2 |
| } |
| |
| function onexitsetextheading() { |
| setData('setextHeadingSlurpLineEnding') |
| } |
| |
| function onenterdata(token) { |
| var siblings = this.stack[this.stack.length - 1].children |
| var tail = siblings[siblings.length - 1] |
| |
| if (!tail || tail.type !== 'text') { |
| // Add a new text node. |
| tail = text() |
| tail.position = {start: point(token.start)} |
| this.stack[this.stack.length - 1].children.push(tail) |
| } |
| |
| this.stack.push(tail) |
| } |
| |
| function onexitdata(token) { |
| var tail = this.stack.pop() |
| tail.value += this.sliceSerialize(token) |
| tail.position.end = point(token.end) |
| } |
| |
| function onexitlineending(token) { |
| var context = this.stack[this.stack.length - 1] |
| |
| // If we’re at a hard break, include the line ending in there. |
| if (getData('atHardBreak')) { |
| context.children[context.children.length - 1].position.end = point( |
| token.end |
| ) |
| |
| setData('atHardBreak') |
| return |
| } |
| |
| if (getData('setextHeadingSlurpLineEnding')) { |
| return |
| } |
| |
| if (config.canContainEols.indexOf(context.type) !== -1) { |
| onenterdata.call(this, token) |
| onexitdata.call(this, token) |
| } |
| } |
| |
| function onexithardbreak() { |
| setData('atHardBreak', true) |
| } |
| |
| function onexithtmlflow() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].value = data |
| } |
| |
| function onexithtmltext() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].value = data |
| } |
| |
| function onexitcodetext() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].value = data |
| } |
| |
| function onexitlink() { |
| var context = this.stack[this.stack.length - 1] |
| |
| // To do: clean. |
| if (getData('inReference')) { |
| context.type += 'Reference' |
| context.referenceType = getData('referenceType') || 'shortcut' |
| delete context.url |
| delete context.title |
| } else { |
| delete context.identifier |
| delete context.label |
| delete context.referenceType |
| } |
| |
| setData('referenceType') |
| } |
| |
| function onexitimage() { |
| var context = this.stack[this.stack.length - 1] |
| |
| // To do: clean. |
| if (getData('inReference')) { |
| context.type += 'Reference' |
| context.referenceType = getData('referenceType') || 'shortcut' |
| delete context.url |
| delete context.title |
| } else { |
| delete context.identifier |
| delete context.label |
| delete context.referenceType |
| } |
| |
| setData('referenceType') |
| } |
| |
| function onexitlabeltext(token) { |
| this.stack[this.stack.length - 2].identifier = normalizeIdentifier( |
| this.sliceSerialize(token) |
| ).toLowerCase() |
| } |
| |
| function onexitlabel() { |
| var fragment = this.stack[this.stack.length - 1] |
| var value = this.resume() |
| |
| this.stack[this.stack.length - 1].label = value |
| |
| // Assume a reference. |
| setData('inReference', true) |
| |
| if (this.stack[this.stack.length - 1].type === 'link') { |
| this.stack[this.stack.length - 1].children = fragment.children |
| } else { |
| this.stack[this.stack.length - 1].alt = value |
| } |
| } |
| |
| function onexitresourcedestinationstring() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].url = data |
| } |
| |
| function onexitresourcetitlestring() { |
| var data = this.resume() |
| this.stack[this.stack.length - 1].title = data |
| } |
| |
| function onexitresource() { |
| setData('inReference') |
| } |
| |
| function onenterreference() { |
| setData('referenceType', 'collapsed') |
| } |
| |
| function onexitreferencestring(token) { |
| var label = this.resume() |
| this.stack[this.stack.length - 1].label = label |
| this.stack[this.stack.length - 1].identifier = normalizeIdentifier( |
| this.sliceSerialize(token) |
| ).toLowerCase() |
| setData('referenceType', 'full') |
| } |
| |
| function onexitcharacterreferencemarker(token) { |
| setData('characterReferenceType', token.type) |
| } |
| |
| function onexitcharacterreferencevalue(token) { |
| var data = this.sliceSerialize(token) |
| var type = getData('characterReferenceType') |
| var value |
| |
| if (type) { |
| value = safeFromInt( |
| data, |
| type === 'characterReferenceMarkerNumeric' ? 10 : 16 |
| ) |
| |
| setData('characterReferenceType') |
| } else { |
| value = decode(data) |
| } |
| |
| this.stack[this.stack.length - 1].value += value |
| } |
| |
| function onexitautolinkprotocol(token) { |
| onexitdata.call(this, token) |
| this.stack[this.stack.length - 1].url = this.sliceSerialize(token) |
| } |
| |
| function onexitautolinkemail(token) { |
| onexitdata.call(this, token) |
| this.stack[this.stack.length - 1].url = |
| 'mailto:' + this.sliceSerialize(token) |
| } |
| |
| // |
| // Creaters. |
| // |
| |
| function blockQuote() { |
| return {type: 'blockquote', children: []} |
| } |
| |
| function codeFlow() { |
| return {type: 'code', lang: null, meta: null, value: ''} |
| } |
| |
| function codeText() { |
| return {type: 'inlineCode', value: ''} |
| } |
| |
| function definition() { |
| return { |
| type: 'definition', |
| identifier: '', |
| label: null, |
| title: null, |
| url: '' |
| } |
| } |
| |
| function emphasis() { |
| return {type: 'emphasis', children: []} |
| } |
| |
| function heading() { |
| return {type: 'heading', depth: undefined, children: []} |
| } |
| |
| function hardBreak() { |
| return {type: 'break'} |
| } |
| |
| function html() { |
| return {type: 'html', value: ''} |
| } |
| |
| function image() { |
| return {type: 'image', title: null, url: '', alt: null} |
| } |
| |
| function link() { |
| return {type: 'link', title: null, url: '', children: []} |
| } |
| |
| function list(token) { |
| return { |
| type: 'list', |
| ordered: token.type === 'listOrdered', |
| start: null, |
| spread: token._spread, |
| children: [] |
| } |
| } |
| |
| function listItem(token) { |
| return { |
| type: 'listItem', |
| spread: token._spread, |
| checked: null, |
| children: [] |
| } |
| } |
| |
| function paragraph() { |
| return {type: 'paragraph', children: []} |
| } |
| |
| function strong() { |
| return {type: 'strong', children: []} |
| } |
| |
| function text() { |
| return {type: 'text', value: ''} |
| } |
| |
| function thematicBreak() { |
| return {type: 'thematicBreak'} |
| } |
| } |
| |
| function configure(config, extensions) { |
| var length = extensions.length |
| var index = -1 |
| |
| while (++index < length) { |
| extension(config, extensions[index]) |
| } |
| |
| return config |
| } |
| |
| function extension(config, extension) { |
| var key |
| var left |
| var right |
| |
| for (key in extension) { |
| left = own.call(config, key) ? config[key] : (config[key] = {}) |
| right = extension[key] |
| |
| if (key === 'canContainEols') { |
| config[key] = [].concat(left, right) |
| } else { |
| Object.assign(left, right) |
| } |
| } |
| } |