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