turndown 7.2.2 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,149 +2,110 @@
2
2
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
3
  typeof define === 'function' && define.amd ? define(factory) :
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TurndownService = factory());
5
- }(this, (function () { 'use strict';
5
+ })(this, (function () { 'use strict';
6
6
 
7
- function extend (destination) {
7
+ function extend(destination) {
8
8
  for (var i = 1; i < arguments.length; i++) {
9
9
  var source = arguments[i];
10
10
  for (var key in source) {
11
- if (source.hasOwnProperty(key)) destination[key] = source[key];
11
+ if (Object.prototype.hasOwnProperty.call(source, key)) destination[key] = source[key];
12
12
  }
13
13
  }
14
- return destination
14
+ return destination;
15
15
  }
16
-
17
- function repeat (character, count) {
18
- return Array(count + 1).join(character)
16
+ function repeat(character, count) {
17
+ return Array(count + 1).join(character);
19
18
  }
20
-
21
- function trimLeadingNewlines (string) {
22
- return string.replace(/^\n*/, '')
19
+ function trimLeadingNewlines(string) {
20
+ return string.replace(/^\n*/, '');
23
21
  }
24
-
25
- function trimTrailingNewlines (string) {
22
+ function trimTrailingNewlines(string) {
26
23
  // avoid match-at-end regexp bottleneck, see #370
27
24
  var indexEnd = string.length;
28
25
  while (indexEnd > 0 && string[indexEnd - 1] === '\n') indexEnd--;
29
- return string.substring(0, indexEnd)
26
+ return string.substring(0, indexEnd);
30
27
  }
31
-
32
- function trimNewlines (string) {
33
- return trimTrailingNewlines(trimLeadingNewlines(string))
28
+ function trimNewlines(string) {
29
+ return trimTrailingNewlines(trimLeadingNewlines(string));
34
30
  }
35
-
36
- var blockElements = [
37
- 'ADDRESS', 'ARTICLE', 'ASIDE', 'AUDIO', 'BLOCKQUOTE', 'BODY', 'CANVAS',
38
- 'CENTER', 'DD', 'DIR', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE',
39
- 'FOOTER', 'FORM', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER',
40
- 'HGROUP', 'HR', 'HTML', 'ISINDEX', 'LI', 'MAIN', 'MENU', 'NAV', 'NOFRAMES',
41
- 'NOSCRIPT', 'OL', 'OUTPUT', 'P', 'PRE', 'SECTION', 'TABLE', 'TBODY', 'TD',
42
- 'TFOOT', 'TH', 'THEAD', 'TR', 'UL'
43
- ];
44
-
45
- function isBlock (node) {
46
- return is(node, blockElements)
31
+ var blockElements = ['ADDRESS', 'ARTICLE', 'ASIDE', 'AUDIO', 'BLOCKQUOTE', 'BODY', 'CANVAS', 'CENTER', 'DD', 'DIR', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE', 'FOOTER', 'FORM', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER', 'HGROUP', 'HR', 'HTML', 'ISINDEX', 'LI', 'MAIN', 'MENU', 'NAV', 'NOFRAMES', 'NOSCRIPT', 'OL', 'OUTPUT', 'P', 'PRE', 'SECTION', 'TABLE', 'TBODY', 'TD', 'TFOOT', 'TH', 'THEAD', 'TR', 'UL'];
32
+ function isBlock(node) {
33
+ return is(node, blockElements);
47
34
  }
48
-
49
- var voidElements = [
50
- 'AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT',
51
- 'KEYGEN', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR'
52
- ];
53
-
54
- function isVoid (node) {
55
- return is(node, voidElements)
35
+ var voidElements = ['AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR'];
36
+ function isVoid(node) {
37
+ return is(node, voidElements);
56
38
  }
57
-
58
- function hasVoid (node) {
59
- return has(node, voidElements)
39
+ function hasVoid(node) {
40
+ return has(node, voidElements);
60
41
  }
61
-
62
- var meaningfulWhenBlankElements = [
63
- 'A', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TH', 'TD', 'IFRAME', 'SCRIPT',
64
- 'AUDIO', 'VIDEO'
65
- ];
66
-
67
- function isMeaningfulWhenBlank (node) {
68
- return is(node, meaningfulWhenBlankElements)
42
+ var meaningfulWhenBlankElements = ['A', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TH', 'TD', 'IFRAME', 'SCRIPT', 'AUDIO', 'VIDEO'];
43
+ function isMeaningfulWhenBlank(node) {
44
+ return is(node, meaningfulWhenBlankElements);
69
45
  }
70
-
71
- function hasMeaningfulWhenBlank (node) {
72
- return has(node, meaningfulWhenBlankElements)
46
+ function hasMeaningfulWhenBlank(node) {
47
+ return has(node, meaningfulWhenBlankElements);
73
48
  }
74
-
75
- function is (node, tagNames) {
76
- return tagNames.indexOf(node.nodeName) >= 0
49
+ function is(node, tagNames) {
50
+ return tagNames.indexOf(node.nodeName) >= 0;
77
51
  }
78
-
79
- function has (node, tagNames) {
80
- return (
81
- node.getElementsByTagName &&
82
- tagNames.some(function (tagName) {
83
- return node.getElementsByTagName(tagName).length
84
- })
85
- )
52
+ function has(node, tagNames) {
53
+ return node.getElementsByTagName && tagNames.some(function (tagName) {
54
+ return node.getElementsByTagName(tagName).length;
55
+ });
56
+ }
57
+ var markdownEscapes = [[/\\/g, '\\\\'], [/\*/g, '\\*'], [/^-/g, '\\-'], [/^\+ /g, '\\+ '], [/^(=+)/g, '\\$1'], [/^(#{1,6}) /g, '\\$1 '], [/`/g, '\\`'], [/^~~~/g, '\\~~~'], [/\[/g, '\\['], [/\]/g, '\\]'], [/^>/g, '\\>'], [/_/g, '\\_'], [/^(\d+)\. /g, '$1\\. ']];
58
+ function escapeMarkdown(string) {
59
+ return markdownEscapes.reduce(function (accumulator, escape) {
60
+ return accumulator.replace(escape[0], escape[1]);
61
+ }, string);
86
62
  }
87
63
 
88
64
  var rules = {};
89
-
90
65
  rules.paragraph = {
91
66
  filter: 'p',
92
-
93
67
  replacement: function (content) {
94
- return '\n\n' + content + '\n\n'
68
+ return '\n\n' + content + '\n\n';
95
69
  }
96
70
  };
97
-
98
71
  rules.lineBreak = {
99
72
  filter: 'br',
100
-
101
73
  replacement: function (content, node, options) {
102
- return options.br + '\n'
74
+ return options.br + '\n';
103
75
  }
104
76
  };
105
-
106
77
  rules.heading = {
107
78
  filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
108
-
109
79
  replacement: function (content, node, options) {
110
80
  var hLevel = Number(node.nodeName.charAt(1));
111
-
112
81
  if (options.headingStyle === 'setext' && hLevel < 3) {
113
- var underline = repeat((hLevel === 1 ? '=' : '-'), content.length);
114
- return (
115
- '\n\n' + content + '\n' + underline + '\n\n'
116
- )
82
+ var underline = repeat(hLevel === 1 ? '=' : '-', content.length);
83
+ return '\n\n' + content + '\n' + underline + '\n\n';
117
84
  } else {
118
- return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n'
85
+ return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n';
119
86
  }
120
87
  }
121
88
  };
122
-
123
89
  rules.blockquote = {
124
90
  filter: 'blockquote',
125
-
126
91
  replacement: function (content) {
127
92
  content = trimNewlines(content).replace(/^/gm, '> ');
128
- return '\n\n' + content + '\n\n'
93
+ return '\n\n' + content + '\n\n';
129
94
  }
130
95
  };
131
-
132
96
  rules.list = {
133
97
  filter: ['ul', 'ol'],
134
-
135
98
  replacement: function (content, node) {
136
99
  var parent = node.parentNode;
137
100
  if (parent.nodeName === 'LI' && parent.lastElementChild === node) {
138
- return '\n' + content
101
+ return '\n' + content;
139
102
  } else {
140
- return '\n\n' + content + '\n\n'
103
+ return '\n\n' + content + '\n\n';
141
104
  }
142
105
  }
143
106
  };
144
-
145
107
  rules.listItem = {
146
108
  filter: 'li',
147
-
148
109
  replacement: function (content, node, options) {
149
110
  var prefix = options.bulletListMarker + ' ';
150
111
  var parent = node.parentNode;
@@ -156,273 +117,208 @@
156
117
  var isParagraph = /\n$/.test(content);
157
118
  content = trimNewlines(content) + (isParagraph ? '\n' : '');
158
119
  content = content.replace(/\n/gm, '\n' + ' '.repeat(prefix.length)); // indent
159
- return (
160
- prefix + content + (node.nextSibling ? '\n' : '')
161
- )
120
+ return prefix + content + (node.nextSibling ? '\n' : '');
162
121
  }
163
122
  };
164
-
165
123
  rules.indentedCodeBlock = {
166
124
  filter: function (node, options) {
167
- return (
168
- options.codeBlockStyle === 'indented' &&
169
- node.nodeName === 'PRE' &&
170
- node.firstChild &&
171
- node.firstChild.nodeName === 'CODE'
172
- )
125
+ return options.codeBlockStyle === 'indented' && node.nodeName === 'PRE' && node.firstChild && node.firstChild.nodeName === 'CODE';
173
126
  },
174
-
175
127
  replacement: function (content, node, options) {
176
- return (
177
- '\n\n ' +
178
- node.firstChild.textContent.replace(/\n/g, '\n ') +
179
- '\n\n'
180
- )
128
+ return '\n\n ' + node.firstChild.textContent.replace(/\n/g, '\n ') + '\n\n';
181
129
  }
182
130
  };
183
-
184
131
  rules.fencedCodeBlock = {
185
132
  filter: function (node, options) {
186
- return (
187
- options.codeBlockStyle === 'fenced' &&
188
- node.nodeName === 'PRE' &&
189
- node.firstChild &&
190
- node.firstChild.nodeName === 'CODE'
191
- )
133
+ return options.codeBlockStyle === 'fenced' && node.nodeName === 'PRE' && node.firstChild && node.firstChild.nodeName === 'CODE';
192
134
  },
193
-
194
135
  replacement: function (content, node, options) {
195
136
  var className = node.firstChild.getAttribute('class') || '';
196
137
  var language = (className.match(/language-(\S+)/) || [null, ''])[1];
197
138
  var code = node.firstChild.textContent;
198
-
199
139
  var fenceChar = options.fence.charAt(0);
200
140
  var fenceSize = 3;
201
141
  var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm');
202
-
203
142
  var match;
204
- while ((match = fenceInCodeRegex.exec(code))) {
143
+ while (match = fenceInCodeRegex.exec(code)) {
205
144
  if (match[0].length >= fenceSize) {
206
145
  fenceSize = match[0].length + 1;
207
146
  }
208
147
  }
209
-
210
148
  var fence = repeat(fenceChar, fenceSize);
211
-
212
- return (
213
- '\n\n' + fence + language + '\n' +
214
- code.replace(/\n$/, '') +
215
- '\n' + fence + '\n\n'
216
- )
149
+ return '\n\n' + fence + language + '\n' + code.replace(/\n$/, '') + '\n' + fence + '\n\n';
217
150
  }
218
151
  };
219
-
220
152
  rules.horizontalRule = {
221
153
  filter: 'hr',
222
-
223
154
  replacement: function (content, node, options) {
224
- return '\n\n' + options.hr + '\n\n'
155
+ return '\n\n' + options.hr + '\n\n';
225
156
  }
226
157
  };
227
-
228
158
  rules.inlineLink = {
229
159
  filter: function (node, options) {
230
- return (
231
- options.linkStyle === 'inlined' &&
232
- node.nodeName === 'A' &&
233
- node.getAttribute('href')
234
- )
160
+ return options.linkStyle === 'inlined' && node.nodeName === 'A' && node.getAttribute('href');
235
161
  },
236
-
237
162
  replacement: function (content, node) {
238
- var href = node.getAttribute('href');
239
- if (href) href = href.replace(/([()])/g, '\\$1');
240
- var title = cleanAttribute(node.getAttribute('title'));
241
- if (title) title = ' "' + title.replace(/"/g, '\\"') + '"';
242
- return '[' + content + '](' + href + title + ')'
163
+ var href = escapeLinkDestination(node.getAttribute('href'));
164
+ var title = escapeLinkTitle(cleanAttribute(node.getAttribute('title')));
165
+ var titlePart = title ? ' "' + title + '"' : '';
166
+ return '[' + content + '](' + href + titlePart + ')';
243
167
  }
244
168
  };
245
-
246
169
  rules.referenceLink = {
247
170
  filter: function (node, options) {
248
- return (
249
- options.linkStyle === 'referenced' &&
250
- node.nodeName === 'A' &&
251
- node.getAttribute('href')
252
- )
171
+ return options.linkStyle === 'referenced' && node.nodeName === 'A' && node.getAttribute('href');
253
172
  },
254
-
255
173
  replacement: function (content, node, options) {
256
- var href = node.getAttribute('href');
174
+ var href = escapeLinkDestination(node.getAttribute('href'));
257
175
  var title = cleanAttribute(node.getAttribute('title'));
258
- if (title) title = ' "' + title + '"';
176
+ if (title) title = ' "' + escapeLinkTitle(title) + '"';
259
177
  var replacement;
260
178
  var reference;
261
-
262
179
  switch (options.linkReferenceStyle) {
263
180
  case 'collapsed':
264
181
  replacement = '[' + content + '][]';
265
182
  reference = '[' + content + ']: ' + href + title;
266
- break
183
+ break;
267
184
  case 'shortcut':
268
185
  replacement = '[' + content + ']';
269
186
  reference = '[' + content + ']: ' + href + title;
270
- break
187
+ break;
271
188
  default:
272
189
  var id = this.references.length + 1;
273
190
  replacement = '[' + content + '][' + id + ']';
274
191
  reference = '[' + id + ']: ' + href + title;
275
192
  }
276
-
277
193
  this.references.push(reference);
278
- return replacement
194
+ return replacement;
279
195
  },
280
-
281
196
  references: [],
282
-
283
197
  append: function (options) {
284
198
  var references = '';
285
199
  if (this.references.length) {
286
200
  references = '\n\n' + this.references.join('\n') + '\n\n';
287
201
  this.references = []; // Reset references
288
202
  }
289
- return references
203
+ return references;
290
204
  }
291
205
  };
292
-
293
206
  rules.emphasis = {
294
207
  filter: ['em', 'i'],
295
-
296
208
  replacement: function (content, node, options) {
297
- if (!content.trim()) return ''
298
- return options.emDelimiter + content + options.emDelimiter
209
+ if (!content.trim()) return '';
210
+ return options.emDelimiter + content + options.emDelimiter;
299
211
  }
300
212
  };
301
-
302
213
  rules.strong = {
303
214
  filter: ['strong', 'b'],
304
-
305
215
  replacement: function (content, node, options) {
306
- if (!content.trim()) return ''
307
- return options.strongDelimiter + content + options.strongDelimiter
216
+ if (!content.trim()) return '';
217
+ return options.strongDelimiter + content + options.strongDelimiter;
308
218
  }
309
219
  };
310
-
311
220
  rules.code = {
312
221
  filter: function (node) {
313
222
  var hasSiblings = node.previousSibling || node.nextSibling;
314
223
  var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings;
315
-
316
- return node.nodeName === 'CODE' && !isCodeBlock
224
+ return node.nodeName === 'CODE' && !isCodeBlock;
317
225
  },
318
-
319
226
  replacement: function (content) {
320
- if (!content) return ''
227
+ if (!content) return '';
321
228
  content = content.replace(/\r?\n|\r/g, ' ');
322
-
323
229
  var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : '';
324
230
  var delimiter = '`';
325
231
  var matches = content.match(/`+/gm) || [];
326
232
  while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`';
327
-
328
- return delimiter + extraSpace + content + extraSpace + delimiter
233
+ return delimiter + extraSpace + content + extraSpace + delimiter;
329
234
  }
330
235
  };
331
-
332
236
  rules.image = {
333
237
  filter: 'img',
334
-
335
238
  replacement: function (content, node) {
336
- var alt = cleanAttribute(node.getAttribute('alt'));
337
- var src = node.getAttribute('src') || '';
239
+ var alt = escapeMarkdown(cleanAttribute(node.getAttribute('alt')));
240
+ var src = escapeLinkDestination(node.getAttribute('src') || '');
338
241
  var title = cleanAttribute(node.getAttribute('title'));
339
- var titlePart = title ? ' "' + title + '"' : '';
340
- return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''
242
+ var titlePart = title ? ' "' + escapeLinkTitle(title) + '"' : '';
243
+ return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '';
341
244
  }
342
245
  };
343
-
344
- function cleanAttribute (attribute) {
345
- return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''
246
+ function cleanAttribute(attribute) {
247
+ return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : '';
248
+ }
249
+ function escapeLinkDestination(destination) {
250
+ var escaped = destination.replace(/([<>()])/g, '\\$1');
251
+ return escaped.indexOf(' ') >= 0 ? '<' + escaped + '>' : escaped;
252
+ }
253
+ function escapeLinkTitle(title) {
254
+ return title.replace(/"/g, '\\"');
346
255
  }
347
256
 
348
257
  /**
349
258
  * Manages a collection of rules used to convert HTML to Markdown
350
259
  */
351
260
 
352
- function Rules (options) {
261
+ function Rules(options) {
353
262
  this.options = options;
354
263
  this._keep = [];
355
264
  this._remove = [];
356
-
357
265
  this.blankRule = {
358
266
  replacement: options.blankReplacement
359
267
  };
360
-
361
268
  this.keepReplacement = options.keepReplacement;
362
-
363
269
  this.defaultRule = {
364
270
  replacement: options.defaultReplacement
365
271
  };
366
-
367
272
  this.array = [];
368
273
  for (var key in options.rules) this.array.push(options.rules[key]);
369
274
  }
370
-
371
275
  Rules.prototype = {
372
276
  add: function (key, rule) {
373
277
  this.array.unshift(rule);
374
278
  },
375
-
376
279
  keep: function (filter) {
377
280
  this._keep.unshift({
378
281
  filter: filter,
379
282
  replacement: this.keepReplacement
380
283
  });
381
284
  },
382
-
383
285
  remove: function (filter) {
384
286
  this._remove.unshift({
385
287
  filter: filter,
386
288
  replacement: function () {
387
- return ''
289
+ return '';
388
290
  }
389
291
  });
390
292
  },
391
-
392
293
  forNode: function (node) {
393
- if (node.isBlank) return this.blankRule
294
+ if (node.isBlank) return this.blankRule;
394
295
  var rule;
395
-
396
- if ((rule = findRule(this.array, node, this.options))) return rule
397
- if ((rule = findRule(this._keep, node, this.options))) return rule
398
- if ((rule = findRule(this._remove, node, this.options))) return rule
399
-
400
- return this.defaultRule
296
+ if (rule = findRule(this.array, node, this.options)) return rule;
297
+ if (rule = findRule(this._keep, node, this.options)) return rule;
298
+ if (rule = findRule(this._remove, node, this.options)) return rule;
299
+ return this.defaultRule;
401
300
  },
402
-
403
301
  forEach: function (fn) {
404
302
  for (var i = 0; i < this.array.length; i++) fn(this.array[i], i);
405
303
  }
406
304
  };
407
-
408
- function findRule (rules, node, options) {
305
+ function findRule(rules, node, options) {
409
306
  for (var i = 0; i < rules.length; i++) {
410
307
  var rule = rules[i];
411
- if (filterValue(rule, node, options)) return rule
308
+ if (filterValue(rule, node, options)) return rule;
412
309
  }
413
- return void 0
310
+ return undefined;
414
311
  }
415
-
416
- function filterValue (rule, node, options) {
312
+ function filterValue(rule, node, options) {
417
313
  var filter = rule.filter;
418
314
  if (typeof filter === 'string') {
419
- if (filter === node.nodeName.toLowerCase()) return true
315
+ if (filter === node.nodeName.toLowerCase()) return true;
420
316
  } else if (Array.isArray(filter)) {
421
- if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true
317
+ if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true;
422
318
  } else if (typeof filter === 'function') {
423
- if (filter.call(rule, node, options)) return true
319
+ if (filter.call(rule, node, options)) return true;
424
320
  } else {
425
- throw new TypeError('`filter` needs to be a string, array, or function')
321
+ throw new TypeError('`filter` needs to be a string, array, or function');
426
322
  }
427
323
  }
428
324
 
@@ -458,46 +354,39 @@
458
354
  *
459
355
  * @param {Object} options
460
356
  */
461
- function collapseWhitespace (options) {
357
+ function collapseWhitespace(options) {
462
358
  var element = options.element;
463
359
  var isBlock = options.isBlock;
464
360
  var isVoid = options.isVoid;
465
361
  var isPre = options.isPre || function (node) {
466
- return node.nodeName === 'PRE'
362
+ return node.nodeName === 'PRE';
467
363
  };
468
-
469
- if (!element.firstChild || isPre(element)) return
470
-
364
+ if (!element.firstChild || isPre(element)) return;
471
365
  var prevText = null;
472
366
  var keepLeadingWs = false;
473
-
474
367
  var prev = null;
475
368
  var node = next(prev, element, isPre);
476
-
477
369
  while (node !== element) {
478
- if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE
370
+ if (node.nodeType === 3 || node.nodeType === 4) {
371
+ // Node.TEXT_NODE or Node.CDATA_SECTION_NODE
479
372
  var text = node.data.replace(/[ \r\n\t]+/g, ' ');
480
-
481
- if ((!prevText || / $/.test(prevText.data)) &&
482
- !keepLeadingWs && text[0] === ' ') {
373
+ if ((!prevText || / $/.test(prevText.data)) && !keepLeadingWs && text[0] === ' ') {
483
374
  text = text.substr(1);
484
375
  }
485
376
 
486
377
  // `text` might be empty at this point.
487
378
  if (!text) {
488
379
  node = remove(node);
489
- continue
380
+ continue;
490
381
  }
491
-
492
382
  node.data = text;
493
-
494
383
  prevText = node;
495
- } else if (node.nodeType === 1) { // Node.ELEMENT_NODE
384
+ } else if (node.nodeType === 1) {
385
+ // Node.ELEMENT_NODE
496
386
  if (isBlock(node) || node.nodeName === 'BR') {
497
387
  if (prevText) {
498
388
  prevText.data = prevText.data.replace(/ $/, '');
499
389
  }
500
-
501
390
  prevText = null;
502
391
  keepLeadingWs = false;
503
392
  } else if (isVoid(node) || isPre(node)) {
@@ -510,14 +399,12 @@
510
399
  }
511
400
  } else {
512
401
  node = remove(node);
513
- continue
402
+ continue;
514
403
  }
515
-
516
404
  var nextNode = next(prev, node, isPre);
517
405
  prev = node;
518
406
  node = nextNode;
519
407
  }
520
-
521
408
  if (prevText) {
522
409
  prevText.data = prevText.data.replace(/ $/, '');
523
410
  if (!prevText.data) {
@@ -533,12 +420,10 @@
533
420
  * @param {Node} node
534
421
  * @return {Node} node
535
422
  */
536
- function remove (node) {
423
+ function remove(node) {
537
424
  var next = node.nextSibling || node.parentNode;
538
-
539
425
  node.parentNode.removeChild(node);
540
-
541
- return next
426
+ return next;
542
427
  }
543
428
 
544
429
  /**
@@ -550,25 +435,24 @@
550
435
  * @param {Function} isPre
551
436
  * @return {Node}
552
437
  */
553
- function next (prev, current, isPre) {
554
- if ((prev && prev.parentNode === current) || isPre(current)) {
555
- return current.nextSibling || current.parentNode
438
+ function next(prev, current, isPre) {
439
+ if (prev && prev.parentNode === current || isPre(current)) {
440
+ return current.nextSibling || current.parentNode;
556
441
  }
557
-
558
- return current.firstChild || current.nextSibling || current.parentNode
442
+ return current.firstChild || current.nextSibling || current.parentNode;
559
443
  }
560
444
 
561
445
  /*
562
446
  * Set up window for Node.js
563
447
  */
564
448
 
565
- var root = (typeof window !== 'undefined' ? window : {});
449
+ var root = typeof window !== 'undefined' ? window : {};
566
450
 
567
451
  /*
568
452
  * Parsing HTML strings
569
453
  */
570
454
 
571
- function canParseHTMLNatively () {
455
+ function canParseHTMLNatively() {
572
456
  var Parser = root.DOMParser;
573
457
  var canParse = false;
574
458
 
@@ -580,81 +464,79 @@
580
464
  canParse = true;
581
465
  }
582
466
  } catch (e) {}
583
-
584
- return canParse
467
+ return canParse;
585
468
  }
586
-
587
- function createHTMLParser () {
469
+ function createHTMLParser() {
588
470
  var Parser = function () {};
589
-
590
471
  {
591
472
  var domino = require('@mixmark-io/domino');
592
473
  Parser.prototype.parseFromString = function (string) {
593
- return domino.createDocument(string)
474
+ return domino.createDocument(string);
594
475
  };
595
476
  }
596
- return Parser
477
+ return Parser;
597
478
  }
598
-
599
479
  var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
600
480
 
601
- function RootNode (input, options) {
481
+ function RootNode(input, options) {
602
482
  var root;
603
483
  if (typeof input === 'string') {
604
484
  var doc = htmlParser().parseFromString(
605
- // DOM parsers arrange elements in the <head> and <body>.
606
- // Wrapping in a custom element ensures elements are reliably arranged in
607
- // a single element.
608
- '<x-turndown id="turndown-root">' + input + '</x-turndown>',
609
- 'text/html'
610
- );
485
+ // DOM parsers arrange elements in the <head> and <body>.
486
+ // Wrapping in a custom element ensures elements are reliably arranged in
487
+ // a single element.
488
+ '<x-turndown id="turndown-root">' + input + '</x-turndown>', 'text/html');
611
489
  root = doc.getElementById('turndown-root');
612
490
  } else {
613
491
  root = input.cloneNode(true);
614
492
  }
493
+ normalizePre(root);
615
494
  collapseWhitespace({
616
495
  element: root,
617
496
  isBlock: isBlock,
618
497
  isVoid: isVoid,
619
498
  isPre: options.preformattedCode ? isPreOrCode : null
620
499
  });
621
-
622
- return root
500
+ return root;
623
501
  }
624
-
625
502
  var _htmlParser;
626
- function htmlParser () {
503
+ function htmlParser() {
627
504
  _htmlParser = _htmlParser || new HTMLParser();
628
- return _htmlParser
505
+ return _htmlParser;
629
506
  }
630
-
631
- function isPreOrCode (node) {
632
- return node.nodeName === 'PRE' || node.nodeName === 'CODE'
507
+ function isPreOrCode(node) {
508
+ return node.nodeName === 'PRE' || node.nodeName === 'CODE';
509
+ }
510
+ function normalizePre(root) {
511
+ if (!root.getElementsByTagName) {
512
+ return; // unsupported DOM method
513
+ }
514
+ var preNodes = root.getElementsByTagName('PRE');
515
+ for (var i = 0; i < preNodes.length; i++) {
516
+ var brNodes = preNodes[i].getElementsByTagName('BR');
517
+ while (brNodes.length > 0) {
518
+ brNodes[0].parentNode.replaceChild(brNodes[0].ownerDocument.createTextNode('\n'), brNodes[0]);
519
+ }
520
+ }
633
521
  }
634
522
 
635
- function Node (node, options) {
523
+ function Node(node, options) {
636
524
  node.isBlock = isBlock(node);
637
525
  node.isCode = node.nodeName === 'CODE' || node.parentNode.isCode;
638
526
  node.isBlank = isBlank(node);
639
527
  node.flankingWhitespace = flankingWhitespace(node, options);
640
- return node
528
+ return node;
641
529
  }
642
-
643
- function isBlank (node) {
644
- return (
645
- !isVoid(node) &&
646
- !isMeaningfulWhenBlank(node) &&
647
- /^\s*$/i.test(node.textContent) &&
648
- !hasVoid(node) &&
649
- !hasMeaningfulWhenBlank(node)
650
- )
530
+ function isBlank(node) {
531
+ return !isVoid(node) && !isMeaningfulWhenBlank(node) && /^\s*$/i.test(node.textContent) && !hasVoid(node) && !hasMeaningfulWhenBlank(node);
651
532
  }
652
-
653
- function flankingWhitespace (node, options) {
654
- if (node.isBlock || (options.preformattedCode && node.isCode)) {
655
- return { leading: '', trailing: '' }
533
+ function flankingWhitespace(node, options) {
534
+ if (node.isBlock || options.preformattedCode && node.isCode) {
535
+ return {
536
+ leading: '',
537
+ trailing: ''
538
+ };
656
539
  }
657
-
658
540
  var edges = edgeWhitespace(node.textContent);
659
541
 
660
542
  // abandon leading ASCII WS if left-flanked by ASCII WS
@@ -666,27 +548,28 @@
666
548
  if (edges.trailingAscii && isFlankedByWhitespace('right', node, options)) {
667
549
  edges.trailing = edges.trailingNonAscii;
668
550
  }
669
-
670
- return { leading: edges.leading, trailing: edges.trailing }
551
+ return {
552
+ leading: edges.leading,
553
+ trailing: edges.trailing
554
+ };
671
555
  }
672
-
673
- function edgeWhitespace (string) {
556
+ function edgeWhitespace(string) {
674
557
  var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/);
675
558
  return {
676
- leading: m[1], // whole string for whitespace-only strings
559
+ leading: m[1],
560
+ // whole string for whitespace-only strings
677
561
  leadingAscii: m[2],
678
562
  leadingNonAscii: m[3],
679
- trailing: m[4], // empty for whitespace-only strings
563
+ trailing: m[4],
564
+ // empty for whitespace-only strings
680
565
  trailingNonAscii: m[5],
681
566
  trailingAscii: m[6]
682
- }
567
+ };
683
568
  }
684
-
685
- function isFlankedByWhitespace (side, node, options) {
569
+ function isFlankedByWhitespace(side, node, options) {
686
570
  var sibling;
687
571
  var regExp;
688
572
  var isFlanked;
689
-
690
573
  if (side === 'left') {
691
574
  sibling = node.previousSibling;
692
575
  regExp = / $/;
@@ -694,7 +577,6 @@
694
577
  sibling = node.nextSibling;
695
578
  regExp = /^ /;
696
579
  }
697
-
698
580
  if (sibling) {
699
581
  if (sibling.nodeType === 3) {
700
582
  isFlanked = regExp.test(sibling.nodeValue);
@@ -704,29 +586,12 @@
704
586
  isFlanked = regExp.test(sibling.textContent);
705
587
  }
706
588
  }
707
- return isFlanked
589
+ return isFlanked;
708
590
  }
709
591
 
710
592
  var reduce = Array.prototype.reduce;
711
- var escapes = [
712
- [/\\/g, '\\\\'],
713
- [/\*/g, '\\*'],
714
- [/^-/g, '\\-'],
715
- [/^\+ /g, '\\+ '],
716
- [/^(=+)/g, '\\$1'],
717
- [/^(#{1,6}) /g, '\\$1 '],
718
- [/`/g, '\\`'],
719
- [/^~~~/g, '\\~~~'],
720
- [/\[/g, '\\['],
721
- [/\]/g, '\\]'],
722
- [/^>/g, '\\>'],
723
- [/_/g, '\\_'],
724
- [/^(\d+)\. /g, '$1\\. ']
725
- ];
726
-
727
- function TurndownService (options) {
728
- if (!(this instanceof TurndownService)) return new TurndownService(options)
729
-
593
+ function TurndownService(options) {
594
+ if (!(this instanceof TurndownService)) return new TurndownService(options);
730
595
  var defaults = {
731
596
  rules: rules,
732
597
  headingStyle: 'setext',
@@ -741,19 +606,18 @@
741
606
  br: ' ',
742
607
  preformattedCode: false,
743
608
  blankReplacement: function (content, node) {
744
- return node.isBlock ? '\n\n' : ''
609
+ return node.isBlock ? '\n\n' : '';
745
610
  },
746
611
  keepReplacement: function (content, node) {
747
- return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML
612
+ return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML;
748
613
  },
749
614
  defaultReplacement: function (content, node) {
750
- return node.isBlock ? '\n\n' + content + '\n\n' : content
615
+ return node.isBlock ? '\n\n' + content + '\n\n' : content;
751
616
  }
752
617
  };
753
618
  this.options = extend({}, defaults, options);
754
619
  this.rules = new Rules(this.options);
755
620
  }
756
-
757
621
  TurndownService.prototype = {
758
622
  /**
759
623
  * The entry point for converting a string or DOM node to Markdown
@@ -765,17 +629,12 @@
765
629
 
766
630
  turndown: function (input) {
767
631
  if (!canConvert(input)) {
768
- throw new TypeError(
769
- input + ' is not a string, or an element/document/fragment node.'
770
- )
632
+ throw new TypeError(input + ' is not a string, or an element/document/fragment node.');
771
633
  }
772
-
773
- if (input === '') return ''
774
-
634
+ if (input === '') return '';
775
635
  var output = process.call(this, new RootNode(input, this.options));
776
- return postProcess.call(this, output)
636
+ return postProcess.call(this, output);
777
637
  },
778
-
779
638
  /**
780
639
  * Add one or more plugins
781
640
  * @public
@@ -790,11 +649,10 @@
790
649
  } else if (typeof plugin === 'function') {
791
650
  plugin(this);
792
651
  } else {
793
- throw new TypeError('plugin must be a Function or an Array of Functions')
652
+ throw new TypeError('plugin must be a Function or an Array of Functions');
794
653
  }
795
- return this
654
+ return this;
796
655
  },
797
-
798
656
  /**
799
657
  * Adds a rule
800
658
  * @public
@@ -806,9 +664,8 @@
806
664
 
807
665
  addRule: function (key, rule) {
808
666
  this.rules.add(key, rule);
809
- return this
667
+ return this;
810
668
  },
811
-
812
669
  /**
813
670
  * Keep a node (as HTML) that matches the filter
814
671
  * @public
@@ -819,9 +676,8 @@
819
676
 
820
677
  keep: function (filter) {
821
678
  this.rules.keep(filter);
822
- return this
679
+ return this;
823
680
  },
824
-
825
681
  /**
826
682
  * Remove a node that matches the filter
827
683
  * @public
@@ -832,9 +688,8 @@
832
688
 
833
689
  remove: function (filter) {
834
690
  this.rules.remove(filter);
835
- return this
691
+ return this;
836
692
  },
837
-
838
693
  /**
839
694
  * Escapes Markdown syntax
840
695
  * @public
@@ -844,9 +699,7 @@
844
699
  */
845
700
 
846
701
  escape: function (string) {
847
- return escapes.reduce(function (accumulator, escape) {
848
- return accumulator.replace(escape[0], escape[1])
849
- }, string)
702
+ return escapeMarkdown(string);
850
703
  }
851
704
  };
852
705
 
@@ -858,20 +711,18 @@
858
711
  * @type String
859
712
  */
860
713
 
861
- function process (parentNode) {
714
+ function process(parentNode) {
862
715
  var self = this;
863
716
  return reduce.call(parentNode.childNodes, function (output, node) {
864
717
  node = new Node(node, self.options);
865
-
866
718
  var replacement = '';
867
719
  if (node.nodeType === 3) {
868
720
  replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue);
869
721
  } else if (node.nodeType === 1) {
870
722
  replacement = replacementForNode.call(self, node);
871
723
  }
872
-
873
- return join(output, replacement)
874
- }, '')
724
+ return join(output, replacement);
725
+ }, '');
875
726
  }
876
727
 
877
728
  /**
@@ -882,15 +733,14 @@
882
733
  * @type String
883
734
  */
884
735
 
885
- function postProcess (output) {
736
+ function postProcess(output) {
886
737
  var self = this;
887
738
  this.rules.forEach(function (rule) {
888
739
  if (typeof rule.append === 'function') {
889
740
  output = join(output, rule.append(self.options));
890
741
  }
891
742
  });
892
-
893
- return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '')
743
+ return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '');
894
744
  }
895
745
 
896
746
  /**
@@ -901,16 +751,12 @@
901
751
  * @type String
902
752
  */
903
753
 
904
- function replacementForNode (node) {
754
+ function replacementForNode(node) {
905
755
  var rule = this.rules.forNode(node);
906
756
  var content = process.call(this, node);
907
757
  var whitespace = node.flankingWhitespace;
908
758
  if (whitespace.leading || whitespace.trailing) content = content.trim();
909
- return (
910
- whitespace.leading +
911
- rule.replacement(content, node, this.options) +
912
- whitespace.trailing
913
- )
759
+ return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
914
760
  }
915
761
 
916
762
  /**
@@ -922,13 +768,12 @@
922
768
  * @type String
923
769
  */
924
770
 
925
- function join (output, replacement) {
771
+ function join(output, replacement) {
926
772
  var s1 = trimTrailingNewlines(output);
927
773
  var s2 = trimLeadingNewlines(replacement);
928
774
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
929
775
  var separator = '\n\n'.substring(0, nls);
930
-
931
- return s1 + separator + s2
776
+ return s1 + separator + s2;
932
777
  }
933
778
 
934
779
  /**
@@ -939,17 +784,10 @@
939
784
  * @type String|Object|Array|Boolean|Number
940
785
  */
941
786
 
942
- function canConvert (input) {
943
- return (
944
- input != null && (
945
- typeof input === 'string' ||
946
- (input.nodeType && (
947
- input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11
948
- ))
949
- )
950
- )
787
+ function canConvert(input) {
788
+ return input != null && (typeof input === 'string' || input.nodeType && (input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11));
951
789
  }
952
790
 
953
791
  return TurndownService;
954
792
 
955
- })));
793
+ }));