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