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,13 +464,10 @@
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
  if (shouldUseActiveX()) {
592
473
  Parser.prototype.parseFromString = function (string) {
@@ -595,7 +476,7 @@
595
476
  doc.open();
596
477
  doc.write(string);
597
478
  doc.close();
598
- return doc
479
+ return doc;
599
480
  };
600
481
  } else {
601
482
  Parser.prototype.parseFromString = function (string) {
@@ -603,82 +484,82 @@
603
484
  doc.open();
604
485
  doc.write(string);
605
486
  doc.close();
606
- return doc
487
+ return doc;
607
488
  };
608
489
  }
609
490
  }
610
- return Parser
491
+ return Parser;
611
492
  }
612
-
613
- function shouldUseActiveX () {
493
+ function shouldUseActiveX() {
614
494
  var useActiveX = false;
615
495
  try {
616
496
  document.implementation.createHTMLDocument('').open();
617
497
  } catch (e) {
618
498
  if (root.ActiveXObject) useActiveX = true;
619
499
  }
620
- return useActiveX
500
+ return useActiveX;
621
501
  }
622
-
623
502
  var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
624
503
 
625
- function RootNode (input, options) {
504
+ function RootNode(input, options) {
626
505
  var root;
627
506
  if (typeof input === 'string') {
628
507
  var doc = htmlParser().parseFromString(
629
- // DOM parsers arrange elements in the <head> and <body>.
630
- // Wrapping in a custom element ensures elements are reliably arranged in
631
- // a single element.
632
- '<x-turndown id="turndown-root">' + input + '</x-turndown>',
633
- 'text/html'
634
- );
508
+ // DOM parsers arrange elements in the <head> and <body>.
509
+ // Wrapping in a custom element ensures elements are reliably arranged in
510
+ // a single element.
511
+ '<x-turndown id="turndown-root">' + input + '</x-turndown>', 'text/html');
635
512
  root = doc.getElementById('turndown-root');
636
513
  } else {
637
514
  root = input.cloneNode(true);
638
515
  }
516
+ normalizePre(root);
639
517
  collapseWhitespace({
640
518
  element: root,
641
519
  isBlock: isBlock,
642
520
  isVoid: isVoid,
643
521
  isPre: options.preformattedCode ? isPreOrCode : null
644
522
  });
645
-
646
- return root
523
+ return root;
647
524
  }
648
-
649
525
  var _htmlParser;
650
- function htmlParser () {
526
+ function htmlParser() {
651
527
  _htmlParser = _htmlParser || new HTMLParser();
652
- return _htmlParser
528
+ return _htmlParser;
653
529
  }
654
-
655
- function isPreOrCode (node) {
656
- return node.nodeName === 'PRE' || node.nodeName === 'CODE'
530
+ function isPreOrCode(node) {
531
+ return node.nodeName === 'PRE' || node.nodeName === 'CODE';
532
+ }
533
+ function normalizePre(root) {
534
+ if (!root.getElementsByTagName) {
535
+ return; // unsupported DOM method
536
+ }
537
+ var preNodes = root.getElementsByTagName('PRE');
538
+ for (var i = 0; i < preNodes.length; i++) {
539
+ var brNodes = preNodes[i].getElementsByTagName('BR');
540
+ while (brNodes.length > 0) {
541
+ brNodes[0].parentNode.replaceChild(brNodes[0].ownerDocument.createTextNode('\n'), brNodes[0]);
542
+ }
543
+ }
657
544
  }
658
545
 
659
- function Node (node, options) {
546
+ function Node(node, options) {
660
547
  node.isBlock = isBlock(node);
661
548
  node.isCode = node.nodeName === 'CODE' || node.parentNode.isCode;
662
549
  node.isBlank = isBlank(node);
663
550
  node.flankingWhitespace = flankingWhitespace(node, options);
664
- return node
551
+ return node;
665
552
  }
666
-
667
- function isBlank (node) {
668
- return (
669
- !isVoid(node) &&
670
- !isMeaningfulWhenBlank(node) &&
671
- /^\s*$/i.test(node.textContent) &&
672
- !hasVoid(node) &&
673
- !hasMeaningfulWhenBlank(node)
674
- )
553
+ function isBlank(node) {
554
+ return !isVoid(node) && !isMeaningfulWhenBlank(node) && /^\s*$/i.test(node.textContent) && !hasVoid(node) && !hasMeaningfulWhenBlank(node);
675
555
  }
676
-
677
- function flankingWhitespace (node, options) {
678
- if (node.isBlock || (options.preformattedCode && node.isCode)) {
679
- return { leading: '', trailing: '' }
556
+ function flankingWhitespace(node, options) {
557
+ if (node.isBlock || options.preformattedCode && node.isCode) {
558
+ return {
559
+ leading: '',
560
+ trailing: ''
561
+ };
680
562
  }
681
-
682
563
  var edges = edgeWhitespace(node.textContent);
683
564
 
684
565
  // abandon leading ASCII WS if left-flanked by ASCII WS
@@ -690,27 +571,28 @@
690
571
  if (edges.trailingAscii && isFlankedByWhitespace('right', node, options)) {
691
572
  edges.trailing = edges.trailingNonAscii;
692
573
  }
693
-
694
- return { leading: edges.leading, trailing: edges.trailing }
574
+ return {
575
+ leading: edges.leading,
576
+ trailing: edges.trailing
577
+ };
695
578
  }
696
-
697
- function edgeWhitespace (string) {
579
+ function edgeWhitespace(string) {
698
580
  var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/);
699
581
  return {
700
- leading: m[1], // whole string for whitespace-only strings
582
+ leading: m[1],
583
+ // whole string for whitespace-only strings
701
584
  leadingAscii: m[2],
702
585
  leadingNonAscii: m[3],
703
- trailing: m[4], // empty for whitespace-only strings
586
+ trailing: m[4],
587
+ // empty for whitespace-only strings
704
588
  trailingNonAscii: m[5],
705
589
  trailingAscii: m[6]
706
- }
590
+ };
707
591
  }
708
-
709
- function isFlankedByWhitespace (side, node, options) {
592
+ function isFlankedByWhitespace(side, node, options) {
710
593
  var sibling;
711
594
  var regExp;
712
595
  var isFlanked;
713
-
714
596
  if (side === 'left') {
715
597
  sibling = node.previousSibling;
716
598
  regExp = / $/;
@@ -718,7 +600,6 @@
718
600
  sibling = node.nextSibling;
719
601
  regExp = /^ /;
720
602
  }
721
-
722
603
  if (sibling) {
723
604
  if (sibling.nodeType === 3) {
724
605
  isFlanked = regExp.test(sibling.nodeValue);
@@ -728,29 +609,12 @@
728
609
  isFlanked = regExp.test(sibling.textContent);
729
610
  }
730
611
  }
731
- return isFlanked
612
+ return isFlanked;
732
613
  }
733
614
 
734
615
  var reduce = Array.prototype.reduce;
735
- var escapes = [
736
- [/\\/g, '\\\\'],
737
- [/\*/g, '\\*'],
738
- [/^-/g, '\\-'],
739
- [/^\+ /g, '\\+ '],
740
- [/^(=+)/g, '\\$1'],
741
- [/^(#{1,6}) /g, '\\$1 '],
742
- [/`/g, '\\`'],
743
- [/^~~~/g, '\\~~~'],
744
- [/\[/g, '\\['],
745
- [/\]/g, '\\]'],
746
- [/^>/g, '\\>'],
747
- [/_/g, '\\_'],
748
- [/^(\d+)\. /g, '$1\\. ']
749
- ];
750
-
751
- function TurndownService (options) {
752
- if (!(this instanceof TurndownService)) return new TurndownService(options)
753
-
616
+ function TurndownService(options) {
617
+ if (!(this instanceof TurndownService)) return new TurndownService(options);
754
618
  var defaults = {
755
619
  rules: rules,
756
620
  headingStyle: 'setext',
@@ -765,19 +629,18 @@
765
629
  br: ' ',
766
630
  preformattedCode: false,
767
631
  blankReplacement: function (content, node) {
768
- return node.isBlock ? '\n\n' : ''
632
+ return node.isBlock ? '\n\n' : '';
769
633
  },
770
634
  keepReplacement: function (content, node) {
771
- return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML
635
+ return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML;
772
636
  },
773
637
  defaultReplacement: function (content, node) {
774
- return node.isBlock ? '\n\n' + content + '\n\n' : content
638
+ return node.isBlock ? '\n\n' + content + '\n\n' : content;
775
639
  }
776
640
  };
777
641
  this.options = extend({}, defaults, options);
778
642
  this.rules = new Rules(this.options);
779
643
  }
780
-
781
644
  TurndownService.prototype = {
782
645
  /**
783
646
  * The entry point for converting a string or DOM node to Markdown
@@ -789,17 +652,12 @@
789
652
 
790
653
  turndown: function (input) {
791
654
  if (!canConvert(input)) {
792
- throw new TypeError(
793
- input + ' is not a string, or an element/document/fragment node.'
794
- )
655
+ throw new TypeError(input + ' is not a string, or an element/document/fragment node.');
795
656
  }
796
-
797
- if (input === '') return ''
798
-
657
+ if (input === '') return '';
799
658
  var output = process.call(this, new RootNode(input, this.options));
800
- return postProcess.call(this, output)
659
+ return postProcess.call(this, output);
801
660
  },
802
-
803
661
  /**
804
662
  * Add one or more plugins
805
663
  * @public
@@ -814,11 +672,10 @@
814
672
  } else if (typeof plugin === 'function') {
815
673
  plugin(this);
816
674
  } else {
817
- throw new TypeError('plugin must be a Function or an Array of Functions')
675
+ throw new TypeError('plugin must be a Function or an Array of Functions');
818
676
  }
819
- return this
677
+ return this;
820
678
  },
821
-
822
679
  /**
823
680
  * Adds a rule
824
681
  * @public
@@ -830,9 +687,8 @@
830
687
 
831
688
  addRule: function (key, rule) {
832
689
  this.rules.add(key, rule);
833
- return this
690
+ return this;
834
691
  },
835
-
836
692
  /**
837
693
  * Keep a node (as HTML) that matches the filter
838
694
  * @public
@@ -843,9 +699,8 @@
843
699
 
844
700
  keep: function (filter) {
845
701
  this.rules.keep(filter);
846
- return this
702
+ return this;
847
703
  },
848
-
849
704
  /**
850
705
  * Remove a node that matches the filter
851
706
  * @public
@@ -856,9 +711,8 @@
856
711
 
857
712
  remove: function (filter) {
858
713
  this.rules.remove(filter);
859
- return this
714
+ return this;
860
715
  },
861
-
862
716
  /**
863
717
  * Escapes Markdown syntax
864
718
  * @public
@@ -868,9 +722,7 @@
868
722
  */
869
723
 
870
724
  escape: function (string) {
871
- return escapes.reduce(function (accumulator, escape) {
872
- return accumulator.replace(escape[0], escape[1])
873
- }, string)
725
+ return escapeMarkdown(string);
874
726
  }
875
727
  };
876
728
 
@@ -882,20 +734,18 @@
882
734
  * @type String
883
735
  */
884
736
 
885
- function process (parentNode) {
737
+ function process(parentNode) {
886
738
  var self = this;
887
739
  return reduce.call(parentNode.childNodes, function (output, node) {
888
740
  node = new Node(node, self.options);
889
-
890
741
  var replacement = '';
891
742
  if (node.nodeType === 3) {
892
743
  replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue);
893
744
  } else if (node.nodeType === 1) {
894
745
  replacement = replacementForNode.call(self, node);
895
746
  }
896
-
897
- return join(output, replacement)
898
- }, '')
747
+ return join(output, replacement);
748
+ }, '');
899
749
  }
900
750
 
901
751
  /**
@@ -906,15 +756,14 @@
906
756
  * @type String
907
757
  */
908
758
 
909
- function postProcess (output) {
759
+ function postProcess(output) {
910
760
  var self = this;
911
761
  this.rules.forEach(function (rule) {
912
762
  if (typeof rule.append === 'function') {
913
763
  output = join(output, rule.append(self.options));
914
764
  }
915
765
  });
916
-
917
- return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '')
766
+ return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '');
918
767
  }
919
768
 
920
769
  /**
@@ -925,16 +774,12 @@
925
774
  * @type String
926
775
  */
927
776
 
928
- function replacementForNode (node) {
777
+ function replacementForNode(node) {
929
778
  var rule = this.rules.forNode(node);
930
779
  var content = process.call(this, node);
931
780
  var whitespace = node.flankingWhitespace;
932
781
  if (whitespace.leading || whitespace.trailing) content = content.trim();
933
- return (
934
- whitespace.leading +
935
- rule.replacement(content, node, this.options) +
936
- whitespace.trailing
937
- )
782
+ return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
938
783
  }
939
784
 
940
785
  /**
@@ -946,13 +791,12 @@
946
791
  * @type String
947
792
  */
948
793
 
949
- function join (output, replacement) {
794
+ function join(output, replacement) {
950
795
  var s1 = trimTrailingNewlines(output);
951
796
  var s2 = trimLeadingNewlines(replacement);
952
797
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
953
798
  var separator = '\n\n'.substring(0, nls);
954
-
955
- return s1 + separator + s2
799
+ return s1 + separator + s2;
956
800
  }
957
801
 
958
802
  /**
@@ -963,17 +807,10 @@
963
807
  * @type String|Object|Array|Boolean|Number
964
808
  */
965
809
 
966
- function canConvert (input) {
967
- return (
968
- input != null && (
969
- typeof input === 'string' ||
970
- (input.nodeType && (
971
- input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11
972
- ))
973
- )
974
- )
810
+ function canConvert(input) {
811
+ return input != null && (typeof input === 'string' || input.nodeType && (input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11));
975
812
  }
976
813
 
977
814
  return TurndownService;
978
815
 
979
- })));
816
+ }));