Socket
Socket
Sign inDemoInstall

turndown

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

turndown - npm Package Compare versions

Comparing version 0.0.8 to 4.0.0-rc.1

330

dist/turndown.js

@@ -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": [

@@ -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.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc