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