Comparing version 0.0.8 to 4.0.0-rc.1
@@ -270,76 +270,67 @@ var TurndownService = (function () { | ||
function OptionsValidator () { | ||
var _this = this; | ||
this.validOptions = {}; | ||
var validOptions = [ | ||
['headingStyle', ['setext', 'atx']], | ||
['hr', /([*-_] *){3,}/, 'needs to be a sequence of three of more characters matching -, _, or *, each followed optionally by any number of spaces, e.g. * * *'], | ||
['bulletListMarker', ['*', '-', '+']], | ||
['codeBlockStyle', ['indented', 'fenced']], | ||
['fence', ['```', `~~~`]], | ||
['emDelimiter', ['_', '*']], | ||
['strongDelimiter', ['__', '**']], | ||
['linkStyle', ['inlined', 'referenced']], | ||
['linkReferenceStyle', ['full', 'collapsed', 'shortcut']], | ||
['br', [' ', '\\']] | ||
]; | ||
/** | ||
* Manages a collection of rules used to convert HTML to Markdown | ||
*/ | ||
validOptions.forEach(function (validOption) { | ||
_this.validOptions[validOption[0]] = ( | ||
ValidOption.apply(null, validOption) | ||
); | ||
}); | ||
} | ||
function Rules (options) { | ||
this.options = options; | ||
OptionsValidator.prototype = { | ||
validate: function (options) { | ||
var messages = []; | ||
for (var key in options) { | ||
var result = this.validOptions[key].validate(options[key]); | ||
if (result !== true) messages.push(result); | ||
this.blankRule = { | ||
replacement: options.blankReplacement | ||
}; | ||
this.defaultRule = { | ||
replacement: options.defaultReplacement | ||
}; | ||
var keepRule = options.keepRule || { | ||
filter: options.keep, | ||
replacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
if (messages.length) throw new Error('\n - ' + messages.join('\n - ')) | ||
return | ||
} | ||
}; | ||
}; | ||
function ValidOption (name, values, customMessage) { | ||
if (!(this instanceof ValidOption)) { | ||
return new ValidOption(name, values, customMessage) | ||
} | ||
var removeRule = options.removeRule || { | ||
filter: options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.name = name; | ||
this.values = values; | ||
this.message = this.name + ' ' + ( | ||
customMessage || 'needs to be either: ' + toSentence(this.values) | ||
); | ||
this.array = [keepRule, removeRule]; | ||
for (var key in options.rules) this.array.push(options.rules[key]); | ||
} | ||
ValidOption.prototype = { | ||
validate: function (value) { | ||
return this.isValid(value) ? true : this.message | ||
Rules.prototype = { | ||
add: function (key, rule) { | ||
this.array.unshift(rule); | ||
}, | ||
isValid: function (value) { | ||
if (Array.isArray(this.values)) { | ||
return this.values.indexOf(value) > -1 | ||
} else if (this.values instanceof RegExp) { | ||
return this.values.test(value) | ||
} else { | ||
throw new Error('Valid') | ||
forNode: function (node) { | ||
if (node.isBlank) return this.blankRule | ||
for (var i = 0; i < this.array.length; i++) { | ||
var rule = this.array[i]; | ||
if (filterValue(rule, node, this.options)) return rule | ||
} | ||
return this.defaultRule | ||
}, | ||
forEach: function (fn) { | ||
for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); | ||
} | ||
}; | ||
function toSentence (array) { | ||
var sentence = array[0]; | ||
var length = array.length; | ||
var lastWordConnector = (length === 2) ? ' or ' : ', or '; | ||
for (var i = 1; i < length; i++) { | ||
var item = array[i]; | ||
if (i === length - 1) sentence = sentence + lastWordConnector + item; | ||
else sentence = sentence + ', ' + item; | ||
function filterValue (rule, node, options) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
return sentence | ||
} | ||
@@ -352,3 +343,3 @@ | ||
var index = { | ||
var voidElements = { | ||
"area": true, | ||
@@ -377,3 +368,3 @@ "base": true, | ||
var index$2 = [ | ||
var blockElements$1 = [ | ||
"address", | ||
@@ -417,3 +408,5 @@ "article", | ||
var voidElements = index; | ||
'use strict'; | ||
Object.keys(voidElements).forEach(function (name) { | ||
@@ -424,3 +417,3 @@ voidElements[name.toUpperCase()] = 1; | ||
var blockElements = {}; | ||
index$2.forEach(function (name) { | ||
blockElements$1.forEach(function (name) { | ||
blockElements[name.toUpperCase()] = 1; | ||
@@ -603,10 +596,5 @@ }); | ||
if (typeof document === 'undefined') { | ||
var jsdom = require('jsdom'); | ||
var JSDOM = require('jsdom').JSDOM; | ||
Parser.prototype.parseFromString = function (string) { | ||
return jsdom.jsdom(string, { | ||
features: { | ||
FetchExternalResources: [], | ||
ProcessExternalResources: false | ||
} | ||
}) | ||
return new JSDOM(string).window.document | ||
}; | ||
@@ -735,3 +723,2 @@ } else { | ||
var trailingNewLinesRegExp = /\n*$/; | ||
var optionsValidator = new OptionsValidator(); | ||
@@ -756,5 +743,2 @@ function TurndownService (options) { | ||
}, | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
}, | ||
keep: function (node) { | ||
@@ -770,23 +754,20 @@ switch (node.nodeName) { | ||
}, | ||
remove: ['head', 'link', 'meta', 'script', 'style'] | ||
}; | ||
optionsValidator.validate(options); | ||
this.options = extend({}, defaults, options); | ||
this.options.keepRule = this.options.keepRule || { | ||
filter: this.options.keep, | ||
replacement: function (content, node) { | ||
remove: ['head', 'link', 'meta', 'script', 'style'], | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
}; | ||
this.options.removeRule = this.options.removeRule || { | ||
filter: this.options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.options = extend({}, defaults, options); | ||
this.rules = new Rules(this.options); | ||
} | ||
TurndownService.prototype = { | ||
/** | ||
* The entry point for converting a string or DOM node to Markdown | ||
* @public | ||
* @param {String|HTMLElement} input The string or DOM node to convert | ||
* @returns A Markdown representation of the input | ||
* @type String | ||
*/ | ||
turndown: function (input) { | ||
@@ -801,6 +782,14 @@ if (!canConvert(input)) { | ||
var root = new RootNode(input); | ||
return this.postProcess(this.process(root)) | ||
var output = process.call(this, new RootNode(input)); | ||
return postProcess.call(this, output) | ||
}, | ||
/** | ||
* Add one or more plugins | ||
* @public | ||
* @param {Function|Array} plugin The plugin or array of plugins to add | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
use: function (plugin) { | ||
@@ -817,25 +806,14 @@ if (Array.isArray(plugin)) { | ||
addRule: function (key, rule) { | ||
this.options.rules[key] = rule; | ||
return this | ||
}, | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* Adds a rule | ||
* @public | ||
* @param {String} key The unique key of the rule | ||
* @param {Object} rule The rule | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
process: function (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = self.replacementForNode(node); | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
addRule: function (key, rule) { | ||
this.rules.add(key, rule); | ||
return this | ||
}, | ||
@@ -845,2 +823,6 @@ | ||
* Escapes Markdown syntax | ||
* @public | ||
* @param {String} string The string to escape | ||
* @returns A string with Markdown syntax escaped | ||
* @type String | ||
*/ | ||
@@ -891,67 +873,77 @@ | ||
) | ||
}, | ||
} | ||
}; | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
*/ | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* @private | ||
* @param {HTMLElement} parentNode The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
replacementForNode: function (node) { | ||
var rule = this.ruleForNode(node); | ||
var content = this.process(node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
}, | ||
function process (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
/** | ||
* Finds a rule for a given node | ||
*/ | ||
ruleForNode: function (node) { | ||
if (this.filterValue(this.options.keepRule, node)) { | ||
return this.options.keepRule | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = replacementForNode.call(self, node); | ||
} | ||
if (this.filterValue(this.options.removeRule, node)) { | ||
return this.options.removeRule | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
} | ||
if (node.isBlank) return { replacement: this.options.blankReplacement } | ||
/** | ||
* Appends strings as each rule requires and trims the output | ||
* @private | ||
* @param {String} output The conversion output | ||
* @returns A trimmed version of the ouput | ||
* @type String | ||
*/ | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (this.filterValue(rule, node)) return rule | ||
function postProcess (output) { | ||
var self = this; | ||
this.rules.forEach(function (rule) { | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(self.options)); | ||
} | ||
}); | ||
return { replacement: this.options.defaultReplacement } | ||
}, | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
filterValue: function (rule, node) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, this.options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
}, | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
* @private | ||
* @param {HTMLElement} node The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
postProcess: function (output) { | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(this.options)); | ||
} | ||
} | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
}; | ||
function replacementForNode (node) { | ||
var rule = this.rules.forNode(node); | ||
var content = process.call(this, node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
} | ||
/** | ||
* Determines the new lines between the current output and the replacement | ||
* @private | ||
* @param {String} output The current conversion output | ||
* @param {String} replacement The string to append to the output | ||
* @returns The whitespace to separate the current output and the replacement | ||
* @type String | ||
*/ | ||
function separatingNewlines (output, replacement) { | ||
@@ -978,2 +970,6 @@ var newlines = [ | ||
* Determines whether an input can be converted | ||
* @private | ||
* @param {String|HTMLElement} input Describe this parameter | ||
* @returns Describe what it returns | ||
* @type String|Object|Array|Boolean|Number | ||
*/ | ||
@@ -985,5 +981,5 @@ | ||
typeof input === 'string' || | ||
input.nodeType && ( | ||
(input.nodeType && ( | ||
input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11 | ||
) | ||
)) | ||
) | ||
@@ -990,0 +986,0 @@ ) |
@@ -269,76 +269,67 @@ 'use strict'; | ||
function OptionsValidator () { | ||
var _this = this; | ||
this.validOptions = {}; | ||
var validOptions = [ | ||
['headingStyle', ['setext', 'atx']], | ||
['hr', /([*-_] *){3,}/, 'needs to be a sequence of three of more characters matching -, _, or *, each followed optionally by any number of spaces, e.g. * * *'], | ||
['bulletListMarker', ['*', '-', '+']], | ||
['codeBlockStyle', ['indented', 'fenced']], | ||
['fence', ['```', `~~~`]], | ||
['emDelimiter', ['_', '*']], | ||
['strongDelimiter', ['__', '**']], | ||
['linkStyle', ['inlined', 'referenced']], | ||
['linkReferenceStyle', ['full', 'collapsed', 'shortcut']], | ||
['br', [' ', '\\']] | ||
]; | ||
/** | ||
* Manages a collection of rules used to convert HTML to Markdown | ||
*/ | ||
validOptions.forEach(function (validOption) { | ||
_this.validOptions[validOption[0]] = ( | ||
ValidOption.apply(null, validOption) | ||
); | ||
}); | ||
} | ||
function Rules (options) { | ||
this.options = options; | ||
OptionsValidator.prototype = { | ||
validate: function (options) { | ||
var messages = []; | ||
for (var key in options) { | ||
var result = this.validOptions[key].validate(options[key]); | ||
if (result !== true) messages.push(result); | ||
this.blankRule = { | ||
replacement: options.blankReplacement | ||
}; | ||
this.defaultRule = { | ||
replacement: options.defaultReplacement | ||
}; | ||
var keepRule = options.keepRule || { | ||
filter: options.keep, | ||
replacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
if (messages.length) throw new Error('\n - ' + messages.join('\n - ')) | ||
return | ||
} | ||
}; | ||
}; | ||
function ValidOption (name, values, customMessage) { | ||
if (!(this instanceof ValidOption)) { | ||
return new ValidOption(name, values, customMessage) | ||
} | ||
var removeRule = options.removeRule || { | ||
filter: options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.name = name; | ||
this.values = values; | ||
this.message = this.name + ' ' + ( | ||
customMessage || 'needs to be either: ' + toSentence(this.values) | ||
); | ||
this.array = [keepRule, removeRule]; | ||
for (var key in options.rules) this.array.push(options.rules[key]); | ||
} | ||
ValidOption.prototype = { | ||
validate: function (value) { | ||
return this.isValid(value) ? true : this.message | ||
Rules.prototype = { | ||
add: function (key, rule) { | ||
this.array.unshift(rule); | ||
}, | ||
isValid: function (value) { | ||
if (Array.isArray(this.values)) { | ||
return this.values.indexOf(value) > -1 | ||
} else if (this.values instanceof RegExp) { | ||
return this.values.test(value) | ||
} else { | ||
throw new Error('Valid') | ||
forNode: function (node) { | ||
if (node.isBlank) return this.blankRule | ||
for (var i = 0; i < this.array.length; i++) { | ||
var rule = this.array[i]; | ||
if (filterValue(rule, node, this.options)) return rule | ||
} | ||
return this.defaultRule | ||
}, | ||
forEach: function (fn) { | ||
for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); | ||
} | ||
}; | ||
function toSentence (array) { | ||
var sentence = array[0]; | ||
var length = array.length; | ||
var lastWordConnector = (length === 2) ? ' or ' : ', or '; | ||
for (var i = 1; i < length; i++) { | ||
var item = array[i]; | ||
if (i === length - 1) sentence = sentence + lastWordConnector + item; | ||
else sentence = sentence + ', ' + item; | ||
function filterValue (rule, node, options) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
return sentence | ||
} | ||
@@ -351,3 +342,3 @@ | ||
var index = { | ||
var voidElements = { | ||
"area": true, | ||
@@ -376,3 +367,3 @@ "base": true, | ||
var index$2 = [ | ||
var blockElements$1 = [ | ||
"address", | ||
@@ -416,3 +407,5 @@ "article", | ||
var voidElements = index; | ||
'use strict'; | ||
Object.keys(voidElements).forEach(function (name) { | ||
@@ -423,3 +416,3 @@ voidElements[name.toUpperCase()] = 1; | ||
var blockElements = {}; | ||
index$2.forEach(function (name) { | ||
blockElements$1.forEach(function (name) { | ||
blockElements[name.toUpperCase()] = 1; | ||
@@ -602,10 +595,5 @@ }); | ||
if (typeof document === 'undefined') { | ||
var jsdom = require('jsdom'); | ||
var JSDOM = require('jsdom').JSDOM; | ||
Parser.prototype.parseFromString = function (string) { | ||
return jsdom.jsdom(string, { | ||
features: { | ||
FetchExternalResources: [], | ||
ProcessExternalResources: false | ||
} | ||
}) | ||
return new JSDOM(string).window.document | ||
}; | ||
@@ -734,3 +722,2 @@ } else { | ||
var trailingNewLinesRegExp = /\n*$/; | ||
var optionsValidator = new OptionsValidator(); | ||
@@ -755,5 +742,2 @@ function TurndownService (options) { | ||
}, | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
}, | ||
keep: function (node) { | ||
@@ -769,23 +753,20 @@ switch (node.nodeName) { | ||
}, | ||
remove: ['head', 'link', 'meta', 'script', 'style'] | ||
}; | ||
optionsValidator.validate(options); | ||
this.options = extend({}, defaults, options); | ||
this.options.keepRule = this.options.keepRule || { | ||
filter: this.options.keep, | ||
replacement: function (content, node) { | ||
remove: ['head', 'link', 'meta', 'script', 'style'], | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
}; | ||
this.options.removeRule = this.options.removeRule || { | ||
filter: this.options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.options = extend({}, defaults, options); | ||
this.rules = new Rules(this.options); | ||
} | ||
TurndownService.prototype = { | ||
/** | ||
* The entry point for converting a string or DOM node to Markdown | ||
* @public | ||
* @param {String|HTMLElement} input The string or DOM node to convert | ||
* @returns A Markdown representation of the input | ||
* @type String | ||
*/ | ||
turndown: function (input) { | ||
@@ -800,6 +781,14 @@ if (!canConvert(input)) { | ||
var root = new RootNode(input); | ||
return this.postProcess(this.process(root)) | ||
var output = process.call(this, new RootNode(input)); | ||
return postProcess.call(this, output) | ||
}, | ||
/** | ||
* Add one or more plugins | ||
* @public | ||
* @param {Function|Array} plugin The plugin or array of plugins to add | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
use: function (plugin) { | ||
@@ -816,25 +805,14 @@ if (Array.isArray(plugin)) { | ||
addRule: function (key, rule) { | ||
this.options.rules[key] = rule; | ||
return this | ||
}, | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* Adds a rule | ||
* @public | ||
* @param {String} key The unique key of the rule | ||
* @param {Object} rule The rule | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
process: function (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = self.replacementForNode(node); | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
addRule: function (key, rule) { | ||
this.rules.add(key, rule); | ||
return this | ||
}, | ||
@@ -844,2 +822,6 @@ | ||
* Escapes Markdown syntax | ||
* @public | ||
* @param {String} string The string to escape | ||
* @returns A string with Markdown syntax escaped | ||
* @type String | ||
*/ | ||
@@ -890,67 +872,77 @@ | ||
) | ||
}, | ||
} | ||
}; | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
*/ | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* @private | ||
* @param {HTMLElement} parentNode The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
replacementForNode: function (node) { | ||
var rule = this.ruleForNode(node); | ||
var content = this.process(node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
}, | ||
function process (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
/** | ||
* Finds a rule for a given node | ||
*/ | ||
ruleForNode: function (node) { | ||
if (this.filterValue(this.options.keepRule, node)) { | ||
return this.options.keepRule | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = replacementForNode.call(self, node); | ||
} | ||
if (this.filterValue(this.options.removeRule, node)) { | ||
return this.options.removeRule | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
} | ||
if (node.isBlank) return { replacement: this.options.blankReplacement } | ||
/** | ||
* Appends strings as each rule requires and trims the output | ||
* @private | ||
* @param {String} output The conversion output | ||
* @returns A trimmed version of the ouput | ||
* @type String | ||
*/ | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (this.filterValue(rule, node)) return rule | ||
function postProcess (output) { | ||
var self = this; | ||
this.rules.forEach(function (rule) { | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(self.options)); | ||
} | ||
}); | ||
return { replacement: this.options.defaultReplacement } | ||
}, | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
filterValue: function (rule, node) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, this.options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
}, | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
* @private | ||
* @param {HTMLElement} node The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
postProcess: function (output) { | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(this.options)); | ||
} | ||
} | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
}; | ||
function replacementForNode (node) { | ||
var rule = this.rules.forNode(node); | ||
var content = process.call(this, node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
} | ||
/** | ||
* Determines the new lines between the current output and the replacement | ||
* @private | ||
* @param {String} output The current conversion output | ||
* @param {String} replacement The string to append to the output | ||
* @returns The whitespace to separate the current output and the replacement | ||
* @type String | ||
*/ | ||
function separatingNewlines (output, replacement) { | ||
@@ -977,2 +969,6 @@ var newlines = [ | ||
* Determines whether an input can be converted | ||
* @private | ||
* @param {String|HTMLElement} input Describe this parameter | ||
* @returns Describe what it returns | ||
* @type String|Object|Array|Boolean|Number | ||
*/ | ||
@@ -984,5 +980,5 @@ | ||
typeof input === 'string' || | ||
input.nodeType && ( | ||
(input.nodeType && ( | ||
input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11 | ||
) | ||
)) | ||
) | ||
@@ -989,0 +985,0 @@ ) |
@@ -267,76 +267,67 @@ function extend (destination) { | ||
function OptionsValidator () { | ||
var _this = this; | ||
this.validOptions = {}; | ||
var validOptions = [ | ||
['headingStyle', ['setext', 'atx']], | ||
['hr', /([*-_] *){3,}/, 'needs to be a sequence of three of more characters matching -, _, or *, each followed optionally by any number of spaces, e.g. * * *'], | ||
['bulletListMarker', ['*', '-', '+']], | ||
['codeBlockStyle', ['indented', 'fenced']], | ||
['fence', ['```', `~~~`]], | ||
['emDelimiter', ['_', '*']], | ||
['strongDelimiter', ['__', '**']], | ||
['linkStyle', ['inlined', 'referenced']], | ||
['linkReferenceStyle', ['full', 'collapsed', 'shortcut']], | ||
['br', [' ', '\\']] | ||
]; | ||
/** | ||
* Manages a collection of rules used to convert HTML to Markdown | ||
*/ | ||
validOptions.forEach(function (validOption) { | ||
_this.validOptions[validOption[0]] = ( | ||
ValidOption.apply(null, validOption) | ||
); | ||
}); | ||
} | ||
function Rules (options) { | ||
this.options = options; | ||
OptionsValidator.prototype = { | ||
validate: function (options) { | ||
var messages = []; | ||
for (var key in options) { | ||
var result = this.validOptions[key].validate(options[key]); | ||
if (result !== true) messages.push(result); | ||
this.blankRule = { | ||
replacement: options.blankReplacement | ||
}; | ||
this.defaultRule = { | ||
replacement: options.defaultReplacement | ||
}; | ||
var keepRule = options.keepRule || { | ||
filter: options.keep, | ||
replacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
if (messages.length) throw new Error('\n - ' + messages.join('\n - ')) | ||
return | ||
} | ||
}; | ||
}; | ||
function ValidOption (name, values, customMessage) { | ||
if (!(this instanceof ValidOption)) { | ||
return new ValidOption(name, values, customMessage) | ||
} | ||
var removeRule = options.removeRule || { | ||
filter: options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.name = name; | ||
this.values = values; | ||
this.message = this.name + ' ' + ( | ||
customMessage || 'needs to be either: ' + toSentence(this.values) | ||
); | ||
this.array = [keepRule, removeRule]; | ||
for (var key in options.rules) this.array.push(options.rules[key]); | ||
} | ||
ValidOption.prototype = { | ||
validate: function (value) { | ||
return this.isValid(value) ? true : this.message | ||
Rules.prototype = { | ||
add: function (key, rule) { | ||
this.array.unshift(rule); | ||
}, | ||
isValid: function (value) { | ||
if (Array.isArray(this.values)) { | ||
return this.values.indexOf(value) > -1 | ||
} else if (this.values instanceof RegExp) { | ||
return this.values.test(value) | ||
} else { | ||
throw new Error('Valid') | ||
forNode: function (node) { | ||
if (node.isBlank) return this.blankRule | ||
for (var i = 0; i < this.array.length; i++) { | ||
var rule = this.array[i]; | ||
if (filterValue(rule, node, this.options)) return rule | ||
} | ||
return this.defaultRule | ||
}, | ||
forEach: function (fn) { | ||
for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); | ||
} | ||
}; | ||
function toSentence (array) { | ||
var sentence = array[0]; | ||
var length = array.length; | ||
var lastWordConnector = (length === 2) ? ' or ' : ', or '; | ||
for (var i = 1; i < length; i++) { | ||
var item = array[i]; | ||
if (i === length - 1) sentence = sentence + lastWordConnector + item; | ||
else sentence = sentence + ', ' + item; | ||
function filterValue (rule, node, options) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
return sentence | ||
} | ||
@@ -349,3 +340,3 @@ | ||
var index = { | ||
var voidElements = { | ||
"area": true, | ||
@@ -374,3 +365,3 @@ "base": true, | ||
var index$2 = [ | ||
var blockElements$1 = [ | ||
"address", | ||
@@ -414,3 +405,5 @@ "article", | ||
var voidElements = index; | ||
'use strict'; | ||
Object.keys(voidElements).forEach(function (name) { | ||
@@ -421,3 +414,3 @@ voidElements[name.toUpperCase()] = 1; | ||
var blockElements = {}; | ||
index$2.forEach(function (name) { | ||
blockElements$1.forEach(function (name) { | ||
blockElements[name.toUpperCase()] = 1; | ||
@@ -600,10 +593,5 @@ }); | ||
if (typeof document === 'undefined') { | ||
var jsdom = require('jsdom'); | ||
var JSDOM = require('jsdom').JSDOM; | ||
Parser.prototype.parseFromString = function (string) { | ||
return jsdom.jsdom(string, { | ||
features: { | ||
FetchExternalResources: [], | ||
ProcessExternalResources: false | ||
} | ||
}) | ||
return new JSDOM(string).window.document | ||
}; | ||
@@ -732,3 +720,2 @@ } else { | ||
var trailingNewLinesRegExp = /\n*$/; | ||
var optionsValidator = new OptionsValidator(); | ||
@@ -753,5 +740,2 @@ function TurndownService (options) { | ||
}, | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
}, | ||
keep: function (node) { | ||
@@ -767,23 +751,20 @@ switch (node.nodeName) { | ||
}, | ||
remove: ['head', 'link', 'meta', 'script', 'style'] | ||
}; | ||
optionsValidator.validate(options); | ||
this.options = extend({}, defaults, options); | ||
this.options.keepRule = this.options.keepRule || { | ||
filter: this.options.keep, | ||
replacement: function (content, node) { | ||
remove: ['head', 'link', 'meta', 'script', 'style'], | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
}; | ||
this.options.removeRule = this.options.removeRule || { | ||
filter: this.options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.options = extend({}, defaults, options); | ||
this.rules = new Rules(this.options); | ||
} | ||
TurndownService.prototype = { | ||
/** | ||
* The entry point for converting a string or DOM node to Markdown | ||
* @public | ||
* @param {String|HTMLElement} input The string or DOM node to convert | ||
* @returns A Markdown representation of the input | ||
* @type String | ||
*/ | ||
turndown: function (input) { | ||
@@ -798,6 +779,14 @@ if (!canConvert(input)) { | ||
var root = new RootNode(input); | ||
return this.postProcess(this.process(root)) | ||
var output = process.call(this, new RootNode(input)); | ||
return postProcess.call(this, output) | ||
}, | ||
/** | ||
* Add one or more plugins | ||
* @public | ||
* @param {Function|Array} plugin The plugin or array of plugins to add | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
use: function (plugin) { | ||
@@ -814,25 +803,14 @@ if (Array.isArray(plugin)) { | ||
addRule: function (key, rule) { | ||
this.options.rules[key] = rule; | ||
return this | ||
}, | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* Adds a rule | ||
* @public | ||
* @param {String} key The unique key of the rule | ||
* @param {Object} rule The rule | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
process: function (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = self.replacementForNode(node); | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
addRule: function (key, rule) { | ||
this.rules.add(key, rule); | ||
return this | ||
}, | ||
@@ -842,2 +820,6 @@ | ||
* Escapes Markdown syntax | ||
* @public | ||
* @param {String} string The string to escape | ||
* @returns A string with Markdown syntax escaped | ||
* @type String | ||
*/ | ||
@@ -888,67 +870,77 @@ | ||
) | ||
}, | ||
} | ||
}; | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
*/ | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* @private | ||
* @param {HTMLElement} parentNode The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
replacementForNode: function (node) { | ||
var rule = this.ruleForNode(node); | ||
var content = this.process(node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
}, | ||
function process (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
/** | ||
* Finds a rule for a given node | ||
*/ | ||
ruleForNode: function (node) { | ||
if (this.filterValue(this.options.keepRule, node)) { | ||
return this.options.keepRule | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = replacementForNode.call(self, node); | ||
} | ||
if (this.filterValue(this.options.removeRule, node)) { | ||
return this.options.removeRule | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
} | ||
if (node.isBlank) return { replacement: this.options.blankReplacement } | ||
/** | ||
* Appends strings as each rule requires and trims the output | ||
* @private | ||
* @param {String} output The conversion output | ||
* @returns A trimmed version of the ouput | ||
* @type String | ||
*/ | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (this.filterValue(rule, node)) return rule | ||
function postProcess (output) { | ||
var self = this; | ||
this.rules.forEach(function (rule) { | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(self.options)); | ||
} | ||
}); | ||
return { replacement: this.options.defaultReplacement } | ||
}, | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
filterValue: function (rule, node) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, this.options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
}, | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
* @private | ||
* @param {HTMLElement} node The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
postProcess: function (output) { | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(this.options)); | ||
} | ||
} | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
}; | ||
function replacementForNode (node) { | ||
var rule = this.rules.forNode(node); | ||
var content = process.call(this, node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
} | ||
/** | ||
* Determines the new lines between the current output and the replacement | ||
* @private | ||
* @param {String} output The current conversion output | ||
* @param {String} replacement The string to append to the output | ||
* @returns The whitespace to separate the current output and the replacement | ||
* @type String | ||
*/ | ||
function separatingNewlines (output, replacement) { | ||
@@ -975,2 +967,6 @@ var newlines = [ | ||
* Determines whether an input can be converted | ||
* @private | ||
* @param {String|HTMLElement} input Describe this parameter | ||
* @returns Describe what it returns | ||
* @type String|Object|Array|Boolean|Number | ||
*/ | ||
@@ -982,5 +978,5 @@ | ||
typeof input === 'string' || | ||
input.nodeType && ( | ||
(input.nodeType && ( | ||
input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11 | ||
) | ||
)) | ||
) | ||
@@ -987,0 +983,0 @@ ) |
@@ -269,76 +269,67 @@ 'use strict'; | ||
function OptionsValidator () { | ||
var _this = this; | ||
this.validOptions = {}; | ||
var validOptions = [ | ||
['headingStyle', ['setext', 'atx']], | ||
['hr', /([*-_] *){3,}/, 'needs to be a sequence of three of more characters matching -, _, or *, each followed optionally by any number of spaces, e.g. * * *'], | ||
['bulletListMarker', ['*', '-', '+']], | ||
['codeBlockStyle', ['indented', 'fenced']], | ||
['fence', ['```', `~~~`]], | ||
['emDelimiter', ['_', '*']], | ||
['strongDelimiter', ['__', '**']], | ||
['linkStyle', ['inlined', 'referenced']], | ||
['linkReferenceStyle', ['full', 'collapsed', 'shortcut']], | ||
['br', [' ', '\\']] | ||
]; | ||
/** | ||
* Manages a collection of rules used to convert HTML to Markdown | ||
*/ | ||
validOptions.forEach(function (validOption) { | ||
_this.validOptions[validOption[0]] = ( | ||
ValidOption.apply(null, validOption) | ||
); | ||
}); | ||
} | ||
function Rules (options) { | ||
this.options = options; | ||
OptionsValidator.prototype = { | ||
validate: function (options) { | ||
var messages = []; | ||
for (var key in options) { | ||
var result = this.validOptions[key].validate(options[key]); | ||
if (result !== true) messages.push(result); | ||
this.blankRule = { | ||
replacement: options.blankReplacement | ||
}; | ||
this.defaultRule = { | ||
replacement: options.defaultReplacement | ||
}; | ||
var keepRule = options.keepRule || { | ||
filter: options.keep, | ||
replacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
if (messages.length) throw new Error('\n - ' + messages.join('\n - ')) | ||
return | ||
} | ||
}; | ||
}; | ||
function ValidOption (name, values, customMessage) { | ||
if (!(this instanceof ValidOption)) { | ||
return new ValidOption(name, values, customMessage) | ||
} | ||
var removeRule = options.removeRule || { | ||
filter: options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.name = name; | ||
this.values = values; | ||
this.message = this.name + ' ' + ( | ||
customMessage || 'needs to be either: ' + toSentence(this.values) | ||
); | ||
this.array = [keepRule, removeRule]; | ||
for (var key in options.rules) this.array.push(options.rules[key]); | ||
} | ||
ValidOption.prototype = { | ||
validate: function (value) { | ||
return this.isValid(value) ? true : this.message | ||
Rules.prototype = { | ||
add: function (key, rule) { | ||
this.array.unshift(rule); | ||
}, | ||
isValid: function (value) { | ||
if (Array.isArray(this.values)) { | ||
return this.values.indexOf(value) > -1 | ||
} else if (this.values instanceof RegExp) { | ||
return this.values.test(value) | ||
} else { | ||
throw new Error('Valid') | ||
forNode: function (node) { | ||
if (node.isBlank) return this.blankRule | ||
for (var i = 0; i < this.array.length; i++) { | ||
var rule = this.array[i]; | ||
if (filterValue(rule, node, this.options)) return rule | ||
} | ||
return this.defaultRule | ||
}, | ||
forEach: function (fn) { | ||
for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); | ||
} | ||
}; | ||
function toSentence (array) { | ||
var sentence = array[0]; | ||
var length = array.length; | ||
var lastWordConnector = (length === 2) ? ' or ' : ', or '; | ||
for (var i = 1; i < length; i++) { | ||
var item = array[i]; | ||
if (i === length - 1) sentence = sentence + lastWordConnector + item; | ||
else sentence = sentence + ', ' + item; | ||
function filterValue (rule, node, options) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
return sentence | ||
} | ||
@@ -351,3 +342,3 @@ | ||
var index = { | ||
var voidElements = { | ||
"area": true, | ||
@@ -376,3 +367,3 @@ "base": true, | ||
var index$2 = [ | ||
var blockElements$1 = [ | ||
"address", | ||
@@ -416,3 +407,5 @@ "article", | ||
var voidElements = index; | ||
'use strict'; | ||
Object.keys(voidElements).forEach(function (name) { | ||
@@ -423,3 +416,3 @@ voidElements[name.toUpperCase()] = 1; | ||
var blockElements = {}; | ||
index$2.forEach(function (name) { | ||
blockElements$1.forEach(function (name) { | ||
blockElements[name.toUpperCase()] = 1; | ||
@@ -602,10 +595,5 @@ }); | ||
if (typeof document === 'undefined') { | ||
var jsdom = require('jsdom'); | ||
var JSDOM = require('jsdom').JSDOM; | ||
Parser.prototype.parseFromString = function (string) { | ||
return jsdom.jsdom(string, { | ||
features: { | ||
FetchExternalResources: [], | ||
ProcessExternalResources: false | ||
} | ||
}) | ||
return new JSDOM(string).window.document | ||
}; | ||
@@ -734,3 +722,2 @@ } else { | ||
var trailingNewLinesRegExp = /\n*$/; | ||
var optionsValidator = new OptionsValidator(); | ||
@@ -755,5 +742,2 @@ function TurndownService (options) { | ||
}, | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
}, | ||
keep: function (node) { | ||
@@ -769,23 +753,20 @@ switch (node.nodeName) { | ||
}, | ||
remove: ['head', 'link', 'meta', 'script', 'style'] | ||
}; | ||
optionsValidator.validate(options); | ||
this.options = extend({}, defaults, options); | ||
this.options.keepRule = this.options.keepRule || { | ||
filter: this.options.keep, | ||
replacement: function (content, node) { | ||
remove: ['head', 'link', 'meta', 'script', 'style'], | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
}; | ||
this.options.removeRule = this.options.removeRule || { | ||
filter: this.options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.options = extend({}, defaults, options); | ||
this.rules = new Rules(this.options); | ||
} | ||
TurndownService.prototype = { | ||
/** | ||
* The entry point for converting a string or DOM node to Markdown | ||
* @public | ||
* @param {String|HTMLElement} input The string or DOM node to convert | ||
* @returns A Markdown representation of the input | ||
* @type String | ||
*/ | ||
turndown: function (input) { | ||
@@ -800,6 +781,14 @@ if (!canConvert(input)) { | ||
var root = new RootNode(input); | ||
return this.postProcess(this.process(root)) | ||
var output = process.call(this, new RootNode(input)); | ||
return postProcess.call(this, output) | ||
}, | ||
/** | ||
* Add one or more plugins | ||
* @public | ||
* @param {Function|Array} plugin The plugin or array of plugins to add | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
use: function (plugin) { | ||
@@ -816,25 +805,14 @@ if (Array.isArray(plugin)) { | ||
addRule: function (key, rule) { | ||
this.options.rules[key] = rule; | ||
return this | ||
}, | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* Adds a rule | ||
* @public | ||
* @param {String} key The unique key of the rule | ||
* @param {Object} rule The rule | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
process: function (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = self.replacementForNode(node); | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
addRule: function (key, rule) { | ||
this.rules.add(key, rule); | ||
return this | ||
}, | ||
@@ -844,2 +822,6 @@ | ||
* Escapes Markdown syntax | ||
* @public | ||
* @param {String} string The string to escape | ||
* @returns A string with Markdown syntax escaped | ||
* @type String | ||
*/ | ||
@@ -890,67 +872,77 @@ | ||
) | ||
}, | ||
} | ||
}; | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
*/ | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* @private | ||
* @param {HTMLElement} parentNode The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
replacementForNode: function (node) { | ||
var rule = this.ruleForNode(node); | ||
var content = this.process(node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
}, | ||
function process (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
/** | ||
* Finds a rule for a given node | ||
*/ | ||
ruleForNode: function (node) { | ||
if (this.filterValue(this.options.keepRule, node)) { | ||
return this.options.keepRule | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = replacementForNode.call(self, node); | ||
} | ||
if (this.filterValue(this.options.removeRule, node)) { | ||
return this.options.removeRule | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
} | ||
if (node.isBlank) return { replacement: this.options.blankReplacement } | ||
/** | ||
* Appends strings as each rule requires and trims the output | ||
* @private | ||
* @param {String} output The conversion output | ||
* @returns A trimmed version of the ouput | ||
* @type String | ||
*/ | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (this.filterValue(rule, node)) return rule | ||
function postProcess (output) { | ||
var self = this; | ||
this.rules.forEach(function (rule) { | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(self.options)); | ||
} | ||
}); | ||
return { replacement: this.options.defaultReplacement } | ||
}, | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
filterValue: function (rule, node) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, this.options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
}, | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
* @private | ||
* @param {HTMLElement} node The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
postProcess: function (output) { | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(this.options)); | ||
} | ||
} | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
}; | ||
function replacementForNode (node) { | ||
var rule = this.rules.forNode(node); | ||
var content = process.call(this, node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
} | ||
/** | ||
* Determines the new lines between the current output and the replacement | ||
* @private | ||
* @param {String} output The current conversion output | ||
* @param {String} replacement The string to append to the output | ||
* @returns The whitespace to separate the current output and the replacement | ||
* @type String | ||
*/ | ||
function separatingNewlines (output, replacement) { | ||
@@ -977,2 +969,6 @@ var newlines = [ | ||
* Determines whether an input can be converted | ||
* @private | ||
* @param {String|HTMLElement} input Describe this parameter | ||
* @returns Describe what it returns | ||
* @type String|Object|Array|Boolean|Number | ||
*/ | ||
@@ -984,5 +980,5 @@ | ||
typeof input === 'string' || | ||
input.nodeType && ( | ||
(input.nodeType && ( | ||
input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11 | ||
) | ||
)) | ||
) | ||
@@ -989,0 +985,0 @@ ) |
@@ -267,76 +267,67 @@ function extend (destination) { | ||
function OptionsValidator () { | ||
var _this = this; | ||
this.validOptions = {}; | ||
var validOptions = [ | ||
['headingStyle', ['setext', 'atx']], | ||
['hr', /([*-_] *){3,}/, 'needs to be a sequence of three of more characters matching -, _, or *, each followed optionally by any number of spaces, e.g. * * *'], | ||
['bulletListMarker', ['*', '-', '+']], | ||
['codeBlockStyle', ['indented', 'fenced']], | ||
['fence', ['```', `~~~`]], | ||
['emDelimiter', ['_', '*']], | ||
['strongDelimiter', ['__', '**']], | ||
['linkStyle', ['inlined', 'referenced']], | ||
['linkReferenceStyle', ['full', 'collapsed', 'shortcut']], | ||
['br', [' ', '\\']] | ||
]; | ||
/** | ||
* Manages a collection of rules used to convert HTML to Markdown | ||
*/ | ||
validOptions.forEach(function (validOption) { | ||
_this.validOptions[validOption[0]] = ( | ||
ValidOption.apply(null, validOption) | ||
); | ||
}); | ||
} | ||
function Rules (options) { | ||
this.options = options; | ||
OptionsValidator.prototype = { | ||
validate: function (options) { | ||
var messages = []; | ||
for (var key in options) { | ||
var result = this.validOptions[key].validate(options[key]); | ||
if (result !== true) messages.push(result); | ||
this.blankRule = { | ||
replacement: options.blankReplacement | ||
}; | ||
this.defaultRule = { | ||
replacement: options.defaultReplacement | ||
}; | ||
var keepRule = options.keepRule || { | ||
filter: options.keep, | ||
replacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
if (messages.length) throw new Error('\n - ' + messages.join('\n - ')) | ||
return | ||
} | ||
}; | ||
}; | ||
function ValidOption (name, values, customMessage) { | ||
if (!(this instanceof ValidOption)) { | ||
return new ValidOption(name, values, customMessage) | ||
} | ||
var removeRule = options.removeRule || { | ||
filter: options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.name = name; | ||
this.values = values; | ||
this.message = this.name + ' ' + ( | ||
customMessage || 'needs to be either: ' + toSentence(this.values) | ||
); | ||
this.array = [keepRule, removeRule]; | ||
for (var key in options.rules) this.array.push(options.rules[key]); | ||
} | ||
ValidOption.prototype = { | ||
validate: function (value) { | ||
return this.isValid(value) ? true : this.message | ||
Rules.prototype = { | ||
add: function (key, rule) { | ||
this.array.unshift(rule); | ||
}, | ||
isValid: function (value) { | ||
if (Array.isArray(this.values)) { | ||
return this.values.indexOf(value) > -1 | ||
} else if (this.values instanceof RegExp) { | ||
return this.values.test(value) | ||
} else { | ||
throw new Error('Valid') | ||
forNode: function (node) { | ||
if (node.isBlank) return this.blankRule | ||
for (var i = 0; i < this.array.length; i++) { | ||
var rule = this.array[i]; | ||
if (filterValue(rule, node, this.options)) return rule | ||
} | ||
return this.defaultRule | ||
}, | ||
forEach: function (fn) { | ||
for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); | ||
} | ||
}; | ||
function toSentence (array) { | ||
var sentence = array[0]; | ||
var length = array.length; | ||
var lastWordConnector = (length === 2) ? ' or ' : ', or '; | ||
for (var i = 1; i < length; i++) { | ||
var item = array[i]; | ||
if (i === length - 1) sentence = sentence + lastWordConnector + item; | ||
else sentence = sentence + ', ' + item; | ||
function filterValue (rule, node, options) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
return sentence | ||
} | ||
@@ -349,3 +340,3 @@ | ||
var index = { | ||
var voidElements = { | ||
"area": true, | ||
@@ -374,3 +365,3 @@ "base": true, | ||
var index$2 = [ | ||
var blockElements$1 = [ | ||
"address", | ||
@@ -414,3 +405,5 @@ "article", | ||
var voidElements = index; | ||
'use strict'; | ||
Object.keys(voidElements).forEach(function (name) { | ||
@@ -421,3 +414,3 @@ voidElements[name.toUpperCase()] = 1; | ||
var blockElements = {}; | ||
index$2.forEach(function (name) { | ||
blockElements$1.forEach(function (name) { | ||
blockElements[name.toUpperCase()] = 1; | ||
@@ -600,10 +593,5 @@ }); | ||
if (typeof document === 'undefined') { | ||
var jsdom = require('jsdom'); | ||
var JSDOM = require('jsdom').JSDOM; | ||
Parser.prototype.parseFromString = function (string) { | ||
return jsdom.jsdom(string, { | ||
features: { | ||
FetchExternalResources: [], | ||
ProcessExternalResources: false | ||
} | ||
}) | ||
return new JSDOM(string).window.document | ||
}; | ||
@@ -732,3 +720,2 @@ } else { | ||
var trailingNewLinesRegExp = /\n*$/; | ||
var optionsValidator = new OptionsValidator(); | ||
@@ -753,5 +740,2 @@ function TurndownService (options) { | ||
}, | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
}, | ||
keep: function (node) { | ||
@@ -767,23 +751,20 @@ switch (node.nodeName) { | ||
}, | ||
remove: ['head', 'link', 'meta', 'script', 'style'] | ||
}; | ||
optionsValidator.validate(options); | ||
this.options = extend({}, defaults, options); | ||
this.options.keepRule = this.options.keepRule || { | ||
filter: this.options.keep, | ||
replacement: function (content, node) { | ||
remove: ['head', 'link', 'meta', 'script', 'style'], | ||
defaultReplacement: function (content, node) { | ||
return node.isBlock ? '\n\n' + content + '\n\n' : content | ||
} | ||
}; | ||
this.options.removeRule = this.options.removeRule || { | ||
filter: this.options.remove, | ||
replacement: function () { | ||
return '' | ||
} | ||
}; | ||
this.options = extend({}, defaults, options); | ||
this.rules = new Rules(this.options); | ||
} | ||
TurndownService.prototype = { | ||
/** | ||
* The entry point for converting a string or DOM node to Markdown | ||
* @public | ||
* @param {String|HTMLElement} input The string or DOM node to convert | ||
* @returns A Markdown representation of the input | ||
* @type String | ||
*/ | ||
turndown: function (input) { | ||
@@ -798,6 +779,14 @@ if (!canConvert(input)) { | ||
var root = new RootNode(input); | ||
return this.postProcess(this.process(root)) | ||
var output = process.call(this, new RootNode(input)); | ||
return postProcess.call(this, output) | ||
}, | ||
/** | ||
* Add one or more plugins | ||
* @public | ||
* @param {Function|Array} plugin The plugin or array of plugins to add | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
use: function (plugin) { | ||
@@ -814,25 +803,14 @@ if (Array.isArray(plugin)) { | ||
addRule: function (key, rule) { | ||
this.options.rules[key] = rule; | ||
return this | ||
}, | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* Adds a rule | ||
* @public | ||
* @param {String} key The unique key of the rule | ||
* @param {Object} rule The rule | ||
* @returns The Turndown instance for chaining | ||
* @type Object | ||
*/ | ||
process: function (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = self.replacementForNode(node); | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
addRule: function (key, rule) { | ||
this.rules.add(key, rule); | ||
return this | ||
}, | ||
@@ -842,2 +820,6 @@ | ||
* Escapes Markdown syntax | ||
* @public | ||
* @param {String} string The string to escape | ||
* @returns A string with Markdown syntax escaped | ||
* @type String | ||
*/ | ||
@@ -888,67 +870,77 @@ | ||
) | ||
}, | ||
} | ||
}; | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
*/ | ||
/** | ||
* Reduces a DOM node down to its Markdown string equivalent | ||
* @private | ||
* @param {HTMLElement} parentNode The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
replacementForNode: function (node) { | ||
var rule = this.ruleForNode(node); | ||
var content = this.process(node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
}, | ||
function process (parentNode) { | ||
var self = this; | ||
return reduce.call(parentNode.childNodes, function (output, node) { | ||
node = new Node(node); | ||
/** | ||
* Finds a rule for a given node | ||
*/ | ||
ruleForNode: function (node) { | ||
if (this.filterValue(this.options.keepRule, node)) { | ||
return this.options.keepRule | ||
var replacement = ''; | ||
if (node.nodeType === 3) { | ||
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); | ||
} else if (node.nodeType === 1) { | ||
replacement = replacementForNode.call(self, node); | ||
} | ||
if (this.filterValue(this.options.removeRule, node)) { | ||
return this.options.removeRule | ||
} | ||
return join(output, replacement) | ||
}, '') | ||
} | ||
if (node.isBlank) return { replacement: this.options.blankReplacement } | ||
/** | ||
* Appends strings as each rule requires and trims the output | ||
* @private | ||
* @param {String} output The conversion output | ||
* @returns A trimmed version of the ouput | ||
* @type String | ||
*/ | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (this.filterValue(rule, node)) return rule | ||
function postProcess (output) { | ||
var self = this; | ||
this.rules.forEach(function (rule) { | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(self.options)); | ||
} | ||
}); | ||
return { replacement: this.options.defaultReplacement } | ||
}, | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
filterValue: function (rule, node) { | ||
var filter = rule.filter; | ||
if (typeof filter === 'string') { | ||
if (filter === node.nodeName.toLowerCase()) return true | ||
} else if (Array.isArray(filter)) { | ||
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true | ||
} else if (typeof filter === 'function') { | ||
if (filter.call(rule, node, this.options)) return true | ||
} else { | ||
throw new TypeError('`filter` needs to be a string, array, or function') | ||
} | ||
}, | ||
/** | ||
* Converts an element node to its Markdown equivalent | ||
* @private | ||
* @param {HTMLElement} node The node to convert | ||
* @returns A Markdown representation of the node | ||
* @type String | ||
*/ | ||
postProcess: function (output) { | ||
for (var key in this.options.rules) { | ||
var rule = this.options.rules[key]; | ||
if (typeof rule.append === 'function') { | ||
output = join(output, rule.append(this.options)); | ||
} | ||
} | ||
return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') | ||
} | ||
}; | ||
function replacementForNode (node) { | ||
var rule = this.rules.forNode(node); | ||
var content = process.call(this, node); | ||
var whitespace = node.flankingWhitespace; | ||
if (whitespace.leading || whitespace.trailing) content = content.trim(); | ||
return ( | ||
whitespace.leading + | ||
rule.replacement(content, node, this.options) + | ||
whitespace.trailing | ||
) | ||
} | ||
/** | ||
* Determines the new lines between the current output and the replacement | ||
* @private | ||
* @param {String} output The current conversion output | ||
* @param {String} replacement The string to append to the output | ||
* @returns The whitespace to separate the current output and the replacement | ||
* @type String | ||
*/ | ||
function separatingNewlines (output, replacement) { | ||
@@ -975,2 +967,6 @@ var newlines = [ | ||
* Determines whether an input can be converted | ||
* @private | ||
* @param {String|HTMLElement} input Describe this parameter | ||
* @returns Describe what it returns | ||
* @type String|Object|Array|Boolean|Number | ||
*/ | ||
@@ -982,5 +978,5 @@ | ||
typeof input === 'string' || | ||
input.nodeType && ( | ||
(input.nodeType && ( | ||
input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11 | ||
) | ||
)) | ||
) | ||
@@ -987,0 +983,0 @@ ) |
{ | ||
"name": "turndown", | ||
"description": "A library that converts HTML to Markdown", | ||
"version": "0.0.8", | ||
"version": "4.0.0-rc.1", | ||
"author": "Dom Christie", | ||
@@ -11,13 +11,13 @@ "browser": { | ||
"collapse-whitespace": "^1.1.2", | ||
"jsdom": "^9.9.1" | ||
"jsdom": "^11.3.0" | ||
}, | ||
"devDependencies": { | ||
"browserify": "^13.3.0", | ||
"browserify": "^14.5.0", | ||
"nodemon": "^1.10.2", | ||
"rollup": "^0.36.3", | ||
"rollup-plugin-commonjs": "^5.0.5", | ||
"rollup-plugin-node-resolve": "^2.0.0", | ||
"rollup-plugin-replace": "^1.1.1", | ||
"standard": "^8.5.0", | ||
"tape": "^4.6.3" | ||
"rollup": "^0.50.0", | ||
"rollup-plugin-commonjs": "^8.2.6", | ||
"rollup-plugin-node-resolve": "^3.0.0", | ||
"rollup-plugin-replace": "^2.0.0", | ||
"standard": "^10.0.3", | ||
"turndown-attendant": "0.0.2" | ||
}, | ||
@@ -24,0 +24,0 @@ "files": [ |
123
README.md
@@ -7,4 +7,2 @@ # Turndown | ||
**Note this is currently a work-in-progress to replace https://github.com/domchristie/to-markdown. It is pre-release software. API changes are likely.** | ||
## Installation | ||
@@ -44,3 +42,3 @@ | ||
| `codeBlockStyle` | `indented` or `fenced` | `indented` | | ||
| `fence` | <code>```</code> or `~~~` | <code>```</code> | | ||
| `fence` | ` ``` ` or `~~~` | ` ``` ` | | ||
| `emDelimiter` | `_` or `*` | `_` | | ||
@@ -51,8 +49,127 @@ | `strongDelimiter` | `**` or `__` | `**` | | ||
### Advanced Options | ||
| Option | Valid values | Default | | ||
| :-------------------- | :------------ | :------ | | ||
| `blankReplacement` | rule replacement function | See **Special Rules** below | | ||
| `keep` | rule filter | See **Special Rules** below | | ||
| `remove` | rule filter | See **Special Rules** below | | ||
| `defaultReplacement` | rule replacement function | See **Special Rules** below | | ||
| `keepRule` | rule | See **Special Rules** below | | ||
| `removeRule` | rule | See **Special Rules** below | | ||
## Methods | ||
### `addRule(key, rule)` | ||
The `key` parameter is a unique name for the rule for easy reference. Example: | ||
```js | ||
turndownService.addRule('strikethrough', { | ||
filter: ['del', 's', 'strike'], | ||
replacement: function (content) { | ||
return '~' + content + '~' | ||
} | ||
}) | ||
``` | ||
See **Extending with Rules** below. | ||
### `use(plugin|array)` | ||
Use a plugin, or an array of plugins. Example: | ||
```js | ||
// Import plugins from turndown-plugin-gfm | ||
var turndownPluginGfm = require('turndown-plugin-gfm') | ||
var gfm = turndownPluginGfm.gfm | ||
var tables = gfm.tables | ||
var strikethrough = gfm.strikethrough | ||
// Use the gfm plugin | ||
turndownService.use(gfm) | ||
// Use the table and strikethrough plugins only | ||
turndownService.use([tables, strikethrough]) | ||
``` | ||
See **Plugins** below. | ||
## Extending with Rules | ||
Turndown can be extended by adding **rules**. A rule is a plain JavaScript object with `filter` and `replacement` properties. For example, the rule for converting `<p>` elements is as follows: | ||
```js | ||
{ | ||
filter: 'p', | ||
replacement: function (content) { | ||
return '\n\n' + content + '\n\n' | ||
} | ||
} | ||
``` | ||
The filter selects `<p>` elements, and the replacement function returns the `<p>` contents separated by two new lines. | ||
### `filter` String|Array|Function | ||
The filter property determines whether or not an element should be replaced with the rule's `replacement`. DOM nodes can be selected simply using a tag name or an array of tag names: | ||
* `filter: 'p'` will select `<p>` elements | ||
* `filter: ['em', 'i']` will select `<em>` or `<i>` elements | ||
Alternatively, the filter can be a function that returns a boolean depending on whether a given node should be replaced. The function is passed a DOM node as well as the `TurndownService` options. For example, the following rule selects `<a>` elements (with an `href`) when the `linkStyle` option is `inlined`: | ||
```js | ||
filter: function (node, options) { | ||
return ( | ||
options.linkStyle === 'inlined' && | ||
node.nodeName === 'A' && | ||
node.getAttribute('href') | ||
) | ||
} | ||
``` | ||
### `replacement` Function | ||
The replacement function determines how an element should be converted. It should return the Markdown string for a given node. The function is passed the node's content, the node itself, and the `TurndownService` options. | ||
The following rule shows how `<em>` elements are converted: | ||
```js | ||
rules.emphasis = { | ||
filter: ['em', 'i'], | ||
replacement: function (content, node, options) { | ||
return options.emDelimiter + content + options.emDelimiter | ||
} | ||
} | ||
``` | ||
### Special Rules | ||
**Blank rule** determines how to handle blank elements. It overrides every rule (even those added via `addRule`). A node is blank if it only contains whitespace, and it's not an `<a>`, `<td>`,`<th>` or a void element. Its behaviour can be customised using the `blankReplacement` option. | ||
**Keep rule** determines how to handle the elements that should not be converted, i.e. rendered as HTML in the Markdown output. By default, it will keep `<table>` and plain `<pre>` elements. Block-level elements will be separated from surrounding content by blank lines. The keep rule `filter` can be customised using the `keep` option. To replace the rule, use the `keepRule` option. The keep rule may be overridden by rules added via `addRule`. | ||
**Remove rule** determines which elements to remove altogether. By default, it removes `<head>`, `<link>`, `<meta>`, `<script>`, and `<style>` elements, and replaces them with an empty string. Like the keep rule, its `filter` can be customised using the `remove` option. To replace the rule, use the `removeRule` option. The remove rule may be overridden by rules added via `addRule`. | ||
**Default rule** handles nodes which are not recognised by any other rule. By default, it outputs the node's text content (separated by blank lines if it is a block-level element). Its behaviour can be customised with the `defaultReplacement` option. | ||
### Rule Precedence | ||
Turndown iterates over the set of rules, and picks the first one that matches satifies the `filter`. The following list describes the order of precedence: | ||
1. Blank rule | ||
2. Added rules (optional) | ||
3. Keep rule | ||
4. Remove rule | ||
5. Commonmark rules | ||
6. Default rule | ||
## Plugins | ||
The plugin API provides a convenient way for developers to apply multiple extensions. A plugin is just a function that is called with the `TurndownService` instance. | ||
## License | ||
turndown is copyright © 2017+ Dom Christie and released under the MIT license. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
126123
173
4130
+ Addedabab@2.0.6(transitive)
+ Addedacorn@5.7.46.4.2(transitive)
+ Addedacorn-globals@4.3.4(transitive)
+ Addedacorn-walk@6.2.0(transitive)
+ Addedasync-limiter@1.0.1(transitive)
+ Addedbrowser-process-hrtime@1.0.0(transitive)
+ Addedcssstyle@1.4.0(transitive)
+ Addeddata-urls@1.1.0(transitive)
+ Addeddomexception@1.0.1(transitive)
+ Addedjsdom@11.12.0(transitive)
+ Addedleft-pad@1.3.0(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedlodash.sortby@4.7.0(transitive)
+ Addednwsapi@2.2.13(transitive)
+ Addedparse5@4.0.0(transitive)
+ Addedpn@1.1.0(transitive)
+ Addedrequest-promise-core@1.1.4(transitive)
+ Addedrequest-promise-native@1.0.9(transitive)
+ Addedstealthy-require@1.1.1(transitive)
+ Addedtr46@1.0.1(transitive)
+ Addedw3c-hr-time@1.0.2(transitive)
+ Addedwhatwg-mimetype@2.3.0(transitive)
+ Addedwhatwg-url@6.5.07.1.0(transitive)
+ Addedws@5.2.4(transitive)
+ Addedxml-name-validator@3.0.0(transitive)
- Removedabab@1.0.4(transitive)
- Removedacorn@4.0.13(transitive)
- Removedacorn-globals@3.1.0(transitive)
- Removedcontent-type-parser@1.0.2(transitive)
- Removedcssstyle@0.2.37(transitive)
- Removedjsdom@9.12.0(transitive)
- Removednwmatcher@1.4.4(transitive)
- Removedparse5@1.5.1(transitive)
- Removedtr46@0.0.3(transitive)
- Removedwebidl-conversions@3.0.1(transitive)
- Removedwhatwg-url@4.8.0(transitive)
- Removedxml-name-validator@2.0.1(transitive)
Updatedjsdom@^11.3.0