@cloudrac3r/turndown 7.1.4

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.
@@ -0,0 +1,976 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define(factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TurndownService = factory());
5
+ }(this, (function () { 'use strict';
6
+
7
+ function extend (destination) {
8
+ for (var i = 1; i < arguments.length; i++) {
9
+ var source = arguments[i];
10
+ for (var key in source) {
11
+ if (source.hasOwnProperty(key)) destination[key] = source[key];
12
+ }
13
+ }
14
+ return destination
15
+ }
16
+
17
+ function repeat (character, count) {
18
+ return Array(count + 1).join(character)
19
+ }
20
+
21
+ function trimLeadingNewlines (string) {
22
+ return string.replace(/^\n*/, '')
23
+ }
24
+
25
+ function trimTrailingNewlines (string) {
26
+ // avoid match-at-end regexp bottleneck, see #370
27
+ var indexEnd = string.length;
28
+ while (indexEnd > 0 && string[indexEnd - 1] === '\n') indexEnd--;
29
+ return string.substring(0, indexEnd)
30
+ }
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)
43
+ }
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)
52
+ }
53
+
54
+ function hasVoid (node) {
55
+ return has(node, voidElements)
56
+ }
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)
65
+ }
66
+
67
+ function hasMeaningfulWhenBlank (node) {
68
+ return has(node, meaningfulWhenBlankElements)
69
+ }
70
+
71
+ function is (node, tagNames) {
72
+ return tagNames.indexOf(node.nodeName) >= 0
73
+ }
74
+
75
+ function has (node, tagNames) {
76
+ return (
77
+ node.getElementsByTagName &&
78
+ tagNames.some(function (tagName) {
79
+ return node.getElementsByTagName(tagName).length
80
+ })
81
+ )
82
+ }
83
+
84
+ var rules = {};
85
+
86
+ rules.paragraph = {
87
+ filter: 'p',
88
+
89
+ replacement: function (content) {
90
+ return '\n\n' + content + '\n\n'
91
+ }
92
+ };
93
+
94
+ rules.lineBreak = {
95
+ filter: 'br',
96
+
97
+ replacement: function (content, node, options) {
98
+ return options.br + '\n'
99
+ }
100
+ };
101
+
102
+ rules.heading = {
103
+ filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
104
+
105
+ replacement: function (content, node, options) {
106
+ var hLevel = Number(node.nodeName.charAt(1));
107
+
108
+ 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
+ )
113
+ } else {
114
+ return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n'
115
+ }
116
+ }
117
+ };
118
+
119
+ rules.blockquote = {
120
+ filter: 'blockquote',
121
+
122
+ replacement: function (content) {
123
+ content = content.replace(/^\n+|\n+$/g, '');
124
+ content = content.replace(/^/gm, '> ');
125
+ return '\n\n' + content + '\n\n'
126
+ }
127
+ };
128
+
129
+ rules.list = {
130
+ filter: ['ul', 'ol'],
131
+
132
+ replacement: function (content, node) {
133
+ var parent = node.parentNode;
134
+ if (parent.nodeName === 'LI' && parent.lastElementChild === node) {
135
+ return '\n' + content
136
+ } else {
137
+ return '\n\n' + content + '\n\n'
138
+ }
139
+ }
140
+ };
141
+
142
+ rules.listItem = {
143
+ filter: 'li',
144
+
145
+ replacement: function (content, node, options) {
146
+ content = content
147
+ .replace(/^\n+/, '') // remove leading newlines
148
+ .replace(/\n+$/, '\n') // replace trailing newlines with just a single one
149
+ .replace(/\n/gm, '\n '); // indent
150
+ var prefix = options.bulletListMarker + ' ';
151
+ var parent = node.parentNode;
152
+ if (parent.nodeName === 'OL') {
153
+ var start = parent.getAttribute('start');
154
+ var index = Array.prototype.indexOf.call(parent.children, node);
155
+ prefix = (start ? Number(start) + index : index + 1) + '. ';
156
+ }
157
+ return (
158
+ prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '')
159
+ )
160
+ }
161
+ };
162
+
163
+ rules.indentedCodeBlock = {
164
+ filter: function (node, options) {
165
+ return (
166
+ options.codeBlockStyle === 'indented' &&
167
+ node.nodeName === 'PRE' &&
168
+ node.firstChild &&
169
+ node.firstChild.nodeName === 'CODE'
170
+ )
171
+ },
172
+
173
+ replacement: function (content, node, options) {
174
+ return (
175
+ '\n\n ' +
176
+ node.firstChild.textContent.replace(/\n/g, '\n ') +
177
+ '\n\n'
178
+ )
179
+ }
180
+ };
181
+
182
+ rules.fencedCodeBlock = {
183
+ filter: function (node, options) {
184
+ return (
185
+ options.codeBlockStyle === 'fenced' &&
186
+ node.nodeName === 'PRE' &&
187
+ node.firstChild &&
188
+ node.firstChild.nodeName === 'CODE'
189
+ )
190
+ },
191
+
192
+ replacement: function (content, node, options) {
193
+ var className = node.firstChild.getAttribute('class') || '';
194
+ var language = (className.match(/language-(\S+)/) || [null, ''])[1];
195
+ var code = node.firstChild.textContent;
196
+
197
+ var fenceChar = options.fence.charAt(0);
198
+ var fenceSize = 3;
199
+ var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm');
200
+
201
+ var match;
202
+ while ((match = fenceInCodeRegex.exec(code))) {
203
+ if (match[0].length >= fenceSize) {
204
+ fenceSize = match[0].length + 1;
205
+ }
206
+ }
207
+
208
+ var fence = repeat(fenceChar, fenceSize);
209
+
210
+ return (
211
+ '\n\n' + fence + language + '\n' +
212
+ code.replace(/\n$/, '') +
213
+ '\n' + fence + '\n\n'
214
+ )
215
+ }
216
+ };
217
+
218
+ rules.horizontalRule = {
219
+ filter: 'hr',
220
+
221
+ replacement: function (content, node, options) {
222
+ return '\n\n' + options.hr + '\n\n'
223
+ }
224
+ };
225
+
226
+ rules.inlineLink = {
227
+ filter: function (node, options) {
228
+ return (
229
+ options.linkStyle === 'inlined' &&
230
+ node.nodeName === 'A' &&
231
+ node.getAttribute('href')
232
+ )
233
+ },
234
+
235
+ replacement: function (content, node) {
236
+ var href = node.getAttribute('href');
237
+ var title = cleanAttribute(node.getAttribute('title'));
238
+ if (title) title = ' "' + title + '"';
239
+ return '[' + content + '](' + href + title + ')'
240
+ }
241
+ };
242
+
243
+ rules.referenceLink = {
244
+ filter: function (node, options) {
245
+ return (
246
+ options.linkStyle === 'referenced' &&
247
+ node.nodeName === 'A' &&
248
+ node.getAttribute('href')
249
+ )
250
+ },
251
+
252
+ replacement: function (content, node, options) {
253
+ var href = node.getAttribute('href');
254
+ var title = cleanAttribute(node.getAttribute('title'));
255
+ if (title) title = ' "' + title + '"';
256
+ var replacement;
257
+ var reference;
258
+
259
+ switch (options.linkReferenceStyle) {
260
+ case 'collapsed':
261
+ replacement = '[' + content + '][]';
262
+ reference = '[' + content + ']: ' + href + title;
263
+ break
264
+ case 'shortcut':
265
+ replacement = '[' + content + ']';
266
+ reference = '[' + content + ']: ' + href + title;
267
+ break
268
+ default:
269
+ var id = this.references.length + 1;
270
+ replacement = '[' + content + '][' + id + ']';
271
+ reference = '[' + id + ']: ' + href + title;
272
+ }
273
+
274
+ this.references.push(reference);
275
+ return replacement
276
+ },
277
+
278
+ references: [],
279
+
280
+ append: function (options) {
281
+ var references = '';
282
+ if (this.references.length) {
283
+ references = '\n\n' + this.references.join('\n') + '\n\n';
284
+ this.references = []; // Reset references
285
+ }
286
+ return references
287
+ }
288
+ };
289
+
290
+ rules.emphasis = {
291
+ filter: ['em', 'i'],
292
+
293
+ replacement: function (content, node, options) {
294
+ if (!content.trim()) return ''
295
+ return options.emDelimiter + content + options.emDelimiter
296
+ }
297
+ };
298
+
299
+ rules.strong = {
300
+ filter: ['strong', 'b'],
301
+
302
+ replacement: function (content, node, options) {
303
+ if (!content.trim()) return ''
304
+ return options.strongDelimiter + content + options.strongDelimiter
305
+ }
306
+ };
307
+
308
+ rules.code = {
309
+ filter: function (node) {
310
+ var hasSiblings = node.previousSibling || node.nextSibling;
311
+ var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings;
312
+
313
+ return node.nodeName === 'CODE' && !isCodeBlock
314
+ },
315
+
316
+ replacement: function (content) {
317
+ if (!content) return ''
318
+ content = content.replace(/\r?\n|\r/g, ' ');
319
+
320
+ var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : '';
321
+ var delimiter = '`';
322
+ var matches = content.match(/`+/gm) || [];
323
+ while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`';
324
+
325
+ return delimiter + extraSpace + content + extraSpace + delimiter
326
+ }
327
+ };
328
+
329
+ rules.image = {
330
+ filter: 'img',
331
+
332
+ replacement: function (content, node) {
333
+ var alt = cleanAttribute(node.getAttribute('alt'));
334
+ var src = node.getAttribute('src') || '';
335
+ var title = cleanAttribute(node.getAttribute('title'));
336
+ var titlePart = title ? ' "' + title + '"' : '';
337
+ return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''
338
+ }
339
+ };
340
+
341
+ function cleanAttribute (attribute) {
342
+ return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''
343
+ }
344
+
345
+ /**
346
+ * Manages a collection of rules used to convert HTML to Markdown
347
+ */
348
+
349
+ function Rules (options) {
350
+ this.options = options;
351
+ this._keep = [];
352
+ this._remove = [];
353
+
354
+ this.blankRule = {
355
+ replacement: options.blankReplacement
356
+ };
357
+
358
+ this.keepReplacement = options.keepReplacement;
359
+
360
+ this.defaultRule = {
361
+ replacement: options.defaultReplacement
362
+ };
363
+
364
+ this.array = [];
365
+ for (var key in options.rules) this.array.push(options.rules[key]);
366
+ }
367
+
368
+ Rules.prototype = {
369
+ add: function (key, rule) {
370
+ this.array.unshift(rule);
371
+ },
372
+
373
+ keep: function (filter) {
374
+ this._keep.unshift({
375
+ filter: filter,
376
+ replacement: this.keepReplacement
377
+ });
378
+ },
379
+
380
+ remove: function (filter) {
381
+ this._remove.unshift({
382
+ filter: filter,
383
+ replacement: function () {
384
+ return ''
385
+ }
386
+ });
387
+ },
388
+
389
+ forNode: function (node) {
390
+ if (node.isBlank) return this.blankRule
391
+ 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
398
+ },
399
+
400
+ forEach: function (fn) {
401
+ for (var i = 0; i < this.array.length; i++) fn(this.array[i], i);
402
+ }
403
+ };
404
+
405
+ function findRule (rules, node, options) {
406
+ for (var i = 0; i < rules.length; i++) {
407
+ var rule = rules[i];
408
+ if (filterValue(rule, node, options)) return rule
409
+ }
410
+ return void 0
411
+ }
412
+
413
+ function filterValue (rule, node, options) {
414
+ var filter = rule.filter;
415
+ if (typeof filter === 'string') {
416
+ if (filter === node.nodeName.toLowerCase()) return true
417
+ } else if (Array.isArray(filter)) {
418
+ if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true
419
+ } else if (typeof filter === 'function') {
420
+ if (filter.call(rule, node, options)) return true
421
+ } else {
422
+ throw new TypeError('`filter` needs to be a string, array, or function')
423
+ }
424
+ }
425
+
426
+ /**
427
+ * The collapseWhitespace function is adapted from collapse-whitespace
428
+ * by Luc Thevenard.
429
+ *
430
+ * The MIT License (MIT)
431
+ *
432
+ * Copyright (c) 2014 Luc Thevenard <lucthevenard@gmail.com>
433
+ *
434
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
435
+ * of this software and associated documentation files (the "Software"), to deal
436
+ * in the Software without restriction, including without limitation the rights
437
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
438
+ * copies of the Software, and to permit persons to whom the Software is
439
+ * furnished to do so, subject to the following conditions:
440
+ *
441
+ * The above copyright notice and this permission notice shall be included in
442
+ * all copies or substantial portions of the Software.
443
+ *
444
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
445
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
446
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
447
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
448
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
449
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
450
+ * THE SOFTWARE.
451
+ */
452
+
453
+ /**
454
+ * collapseWhitespace(options) removes extraneous whitespace from an the given element.
455
+ *
456
+ * @param {Object} options
457
+ */
458
+ function collapseWhitespace (options) {
459
+ var element = options.element;
460
+ var isBlock = options.isBlock;
461
+ var isVoid = options.isVoid;
462
+ var isPre = options.isPre || function (node) {
463
+ return node.nodeName === 'PRE'
464
+ };
465
+
466
+ if (!element.firstChild || isPre(element)) return
467
+
468
+ var prevText = null;
469
+ var keepLeadingWs = false;
470
+
471
+ var prev = null;
472
+ var node = next(prev, element, isPre);
473
+
474
+ while (node !== element) {
475
+ if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE
476
+ var text = node.data.replace(/[ \r\n\t]+/g, ' ');
477
+
478
+ if ((!prevText || / $/.test(prevText.data)) &&
479
+ !keepLeadingWs && text[0] === ' ') {
480
+ text = text.substr(1);
481
+ }
482
+
483
+ // `text` might be empty at this point.
484
+ if (!text) {
485
+ node = remove(node);
486
+ continue
487
+ }
488
+
489
+ node.data = text;
490
+
491
+ prevText = node;
492
+ } else if (node.nodeType === 1) { // Node.ELEMENT_NODE
493
+ if (isBlock(node) || node.nodeName === 'BR') {
494
+ if (prevText) {
495
+ prevText.data = prevText.data.replace(/ $/, '');
496
+ }
497
+
498
+ prevText = null;
499
+ keepLeadingWs = false;
500
+ } else if (isVoid(node) || isPre(node)) {
501
+ // Avoid trimming space around non-block, non-BR void elements and inline PRE.
502
+ prevText = null;
503
+ keepLeadingWs = true;
504
+ } else if (prevText) {
505
+ // Drop protection if set previously.
506
+ keepLeadingWs = false;
507
+ }
508
+ } else {
509
+ node = remove(node);
510
+ continue
511
+ }
512
+
513
+ var nextNode = next(prev, node, isPre);
514
+ prev = node;
515
+ node = nextNode;
516
+ }
517
+
518
+ if (prevText) {
519
+ prevText.data = prevText.data.replace(/ $/, '');
520
+ if (!prevText.data) {
521
+ remove(prevText);
522
+ }
523
+ }
524
+ }
525
+
526
+ /**
527
+ * remove(node) removes the given node from the DOM and returns the
528
+ * next node in the sequence.
529
+ *
530
+ * @param {Node} node
531
+ * @return {Node} node
532
+ */
533
+ function remove (node) {
534
+ var next = node.nextSibling || node.parentNode;
535
+
536
+ node.parentNode.removeChild(node);
537
+
538
+ return next
539
+ }
540
+
541
+ /**
542
+ * next(prev, current, isPre) returns the next node in the sequence, given the
543
+ * current and previous nodes.
544
+ *
545
+ * @param {Node} prev
546
+ * @param {Node} current
547
+ * @param {Function} isPre
548
+ * @return {Node}
549
+ */
550
+ function next (prev, current, isPre) {
551
+ if ((prev && prev.parentNode === current) || isPre(current)) {
552
+ return current.nextSibling || current.parentNode
553
+ }
554
+
555
+ return current.firstChild || current.nextSibling || current.parentNode
556
+ }
557
+
558
+ /*
559
+ * Set up window for Node.js
560
+ */
561
+
562
+ var root = (typeof window !== 'undefined' ? window : {});
563
+
564
+ /*
565
+ * Parsing HTML strings
566
+ */
567
+
568
+ function canParseHTMLNatively () {
569
+ var Parser = root.DOMParser;
570
+ var canParse = false;
571
+
572
+ // Adapted from https://gist.github.com/1129031
573
+ // Firefox/Opera/IE throw errors on unsupported types
574
+ try {
575
+ // WebKit returns null on unsupported types
576
+ if (new Parser().parseFromString('', 'text/html')) {
577
+ canParse = true;
578
+ }
579
+ } catch (e) {}
580
+
581
+ return canParse
582
+ }
583
+
584
+ function createHTMLParser () {
585
+ var Parser = function () {};
586
+
587
+ {
588
+ if (shouldUseActiveX()) {
589
+ Parser.prototype.parseFromString = function (string) {
590
+ var doc = new window.ActiveXObject('htmlfile');
591
+ doc.designMode = 'on'; // disable on-page scripts
592
+ doc.open();
593
+ doc.write(string);
594
+ doc.close();
595
+ return doc
596
+ };
597
+ } else {
598
+ Parser.prototype.parseFromString = function (string) {
599
+ var doc = document.implementation.createHTMLDocument('');
600
+ doc.open();
601
+ doc.write(string);
602
+ doc.close();
603
+ return doc
604
+ };
605
+ }
606
+ }
607
+ return Parser
608
+ }
609
+
610
+ function shouldUseActiveX () {
611
+ var useActiveX = false;
612
+ try {
613
+ document.implementation.createHTMLDocument('').open();
614
+ } catch (e) {
615
+ if (root.ActiveXObject) useActiveX = true;
616
+ }
617
+ return useActiveX
618
+ }
619
+
620
+ var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
621
+
622
+ function RootNode (input, options) {
623
+ var root;
624
+ if (typeof input === 'string') {
625
+ 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
+ );
632
+ root = doc.getElementById('turndown-root');
633
+ } else {
634
+ root = input.cloneNode(true);
635
+ }
636
+ collapseWhitespace({
637
+ element: root,
638
+ isBlock: isBlock,
639
+ isVoid: isVoid,
640
+ isPre: options.preformattedCode ? isPreOrCode : null
641
+ });
642
+
643
+ return root
644
+ }
645
+
646
+ var _htmlParser;
647
+ function htmlParser () {
648
+ _htmlParser = _htmlParser || new HTMLParser();
649
+ return _htmlParser
650
+ }
651
+
652
+ function isPreOrCode (node) {
653
+ return node.nodeName === 'PRE' || node.nodeName === 'CODE'
654
+ }
655
+
656
+ function Node (node, options) {
657
+ node.isBlock = isBlock(node);
658
+ node.isCode = node.nodeName === 'CODE' || node.nodeName === 'A' || node.parentNode.isCode;
659
+ node.isBlank = isBlank(node);
660
+ node.flankingWhitespace = flankingWhitespace(node, options);
661
+ return node
662
+ }
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
+ )
672
+ }
673
+
674
+ function flankingWhitespace (node, options) {
675
+ if (node.isBlock || (options.preformattedCode && node.isCode)) {
676
+ return { leading: '', trailing: '' }
677
+ }
678
+
679
+ var edges = edgeWhitespace(node.textContent);
680
+
681
+ // abandon leading ASCII WS if left-flanked by ASCII WS
682
+ if (edges.leadingAscii && isFlankedByWhitespace('left', node, options)) {
683
+ edges.leading = edges.leadingNonAscii;
684
+ }
685
+
686
+ // abandon trailing ASCII WS if right-flanked by ASCII WS
687
+ if (edges.trailingAscii && isFlankedByWhitespace('right', node, options)) {
688
+ edges.trailing = edges.trailingNonAscii;
689
+ }
690
+
691
+ return { leading: edges.leading, trailing: edges.trailing }
692
+ }
693
+
694
+ function edgeWhitespace (string) {
695
+ var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/);
696
+ return {
697
+ leading: m[1], // whole string for whitespace-only strings
698
+ leadingAscii: m[2],
699
+ leadingNonAscii: m[3],
700
+ trailing: m[4], // empty for whitespace-only strings
701
+ trailingNonAscii: m[5],
702
+ trailingAscii: m[6]
703
+ }
704
+ }
705
+
706
+ function isFlankedByWhitespace (side, node, options) {
707
+ var sibling;
708
+ var regExp;
709
+ var isFlanked;
710
+
711
+ if (side === 'left') {
712
+ sibling = node.previousSibling;
713
+ regExp = / $/;
714
+ } else {
715
+ sibling = node.nextSibling;
716
+ regExp = /^ /;
717
+ }
718
+
719
+ if (sibling) {
720
+ if (sibling.nodeType === 3) {
721
+ isFlanked = regExp.test(sibling.nodeValue);
722
+ } else if (options.preformattedCode && sibling.nodeName === 'CODE') {
723
+ isFlanked = false;
724
+ } else if (sibling.nodeType === 1 && !isBlock(sibling)) {
725
+ isFlanked = regExp.test(sibling.textContent);
726
+ }
727
+ }
728
+ return isFlanked
729
+ }
730
+
731
+ 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
+
751
+ var defaults = {
752
+ rules: rules,
753
+ headingStyle: 'setext',
754
+ hr: '* * *',
755
+ bulletListMarker: '*',
756
+ codeBlockStyle: 'indented',
757
+ fence: '```',
758
+ emDelimiter: '_',
759
+ strongDelimiter: '**',
760
+ linkStyle: 'inlined',
761
+ linkReferenceStyle: 'full',
762
+ br: ' ',
763
+ preformattedCode: false,
764
+ blankReplacement: function (content, node) {
765
+ return node.isBlock ? '\n\n' : ''
766
+ },
767
+ keepReplacement: function (content, node) {
768
+ return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML
769
+ },
770
+ defaultReplacement: function (content, node) {
771
+ return node.isBlock ? '\n\n' + content + '\n\n' : content
772
+ }
773
+ };
774
+ this.options = extend({}, defaults, options);
775
+ this.rules = new Rules(this.options);
776
+ }
777
+
778
+ TurndownService.prototype = {
779
+ /**
780
+ * The entry point for converting a string or DOM node to Markdown
781
+ * @public
782
+ * @param {String|HTMLElement} input The string or DOM node to convert
783
+ * @returns A Markdown representation of the input
784
+ * @type String
785
+ */
786
+
787
+ turndown: function (input) {
788
+ if (!canConvert(input)) {
789
+ throw new TypeError(
790
+ input + ' is not a string, or an element/document/fragment node.'
791
+ )
792
+ }
793
+
794
+ if (input === '') return ''
795
+
796
+ var output = process.call(this, new RootNode(input, this.options));
797
+ return postProcess.call(this, output)
798
+ },
799
+
800
+ /**
801
+ * Add one or more plugins
802
+ * @public
803
+ * @param {Function|Array} plugin The plugin or array of plugins to add
804
+ * @returns The Turndown instance for chaining
805
+ * @type Object
806
+ */
807
+
808
+ use: function (plugin) {
809
+ if (Array.isArray(plugin)) {
810
+ for (var i = 0; i < plugin.length; i++) this.use(plugin[i]);
811
+ } else if (typeof plugin === 'function') {
812
+ plugin(this);
813
+ } else {
814
+ throw new TypeError('plugin must be a Function or an Array of Functions')
815
+ }
816
+ return this
817
+ },
818
+
819
+ /**
820
+ * Adds a rule
821
+ * @public
822
+ * @param {String} key The unique key of the rule
823
+ * @param {Object} rule The rule
824
+ * @returns The Turndown instance for chaining
825
+ * @type Object
826
+ */
827
+
828
+ addRule: function (key, rule) {
829
+ this.rules.add(key, rule);
830
+ return this
831
+ },
832
+
833
+ /**
834
+ * Keep a node (as HTML) that matches the filter
835
+ * @public
836
+ * @param {String|Array|Function} filter The unique key of the rule
837
+ * @returns The Turndown instance for chaining
838
+ * @type Object
839
+ */
840
+
841
+ keep: function (filter) {
842
+ this.rules.keep(filter);
843
+ return this
844
+ },
845
+
846
+ /**
847
+ * Remove a node that matches the filter
848
+ * @public
849
+ * @param {String|Array|Function} filter The unique key of the rule
850
+ * @returns The Turndown instance for chaining
851
+ * @type Object
852
+ */
853
+
854
+ remove: function (filter) {
855
+ this.rules.remove(filter);
856
+ return this
857
+ },
858
+
859
+ /**
860
+ * Escapes Markdown syntax
861
+ * @public
862
+ * @param {String} string The string to escape
863
+ * @returns A string with Markdown syntax escaped
864
+ * @type String
865
+ */
866
+
867
+ escape: function (string) {
868
+ return escapes.reduce(function (accumulator, escape) {
869
+ return accumulator.replace(escape[0], escape[1])
870
+ }, string)
871
+ }
872
+ };
873
+
874
+ /**
875
+ * Reduces a DOM node down to its Markdown string equivalent
876
+ * @private
877
+ * @param {HTMLElement} parentNode The node to convert
878
+ * @returns A Markdown representation of the node
879
+ * @type String
880
+ */
881
+
882
+ function process (parentNode) {
883
+ var self = this;
884
+ return reduce.call(parentNode.childNodes, function (output, node) {
885
+ node = new Node(node, self.options);
886
+
887
+ var replacement = '';
888
+ if (node.nodeType === 3) {
889
+ replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue);
890
+ } else if (node.nodeType === 1) {
891
+ replacement = replacementForNode.call(self, node);
892
+ }
893
+
894
+ return join(output, replacement)
895
+ }, '')
896
+ }
897
+
898
+ /**
899
+ * Appends strings as each rule requires and trims the output
900
+ * @private
901
+ * @param {String} output The conversion output
902
+ * @returns A trimmed version of the ouput
903
+ * @type String
904
+ */
905
+
906
+ function postProcess (output) {
907
+ var self = this;
908
+ this.rules.forEach(function (rule) {
909
+ if (typeof rule.append === 'function') {
910
+ output = join(output, rule.append(self.options));
911
+ }
912
+ });
913
+
914
+ return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '')
915
+ }
916
+
917
+ /**
918
+ * Converts an element node to its Markdown equivalent
919
+ * @private
920
+ * @param {HTMLElement} node The node to convert
921
+ * @returns A Markdown representation of the node
922
+ * @type String
923
+ */
924
+
925
+ function replacementForNode (node) {
926
+ var rule = this.rules.forNode(node);
927
+ var content = process.call(this, node);
928
+ var whitespace = node.flankingWhitespace;
929
+ if (whitespace.leading || whitespace.trailing) content = content.trim();
930
+ return (
931
+ whitespace.leading +
932
+ rule.replacement(content, node, this.options) +
933
+ whitespace.trailing
934
+ )
935
+ }
936
+
937
+ /**
938
+ * Joins replacement to the current output with appropriate number of new lines
939
+ * @private
940
+ * @param {String} output The current conversion output
941
+ * @param {String} replacement The string to append to the output
942
+ * @returns Joined output
943
+ * @type String
944
+ */
945
+
946
+ function join (output, replacement) {
947
+ var s1 = trimTrailingNewlines(output);
948
+ var s2 = trimLeadingNewlines(replacement);
949
+ var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
950
+ var separator = '\n\n'.substring(0, nls);
951
+
952
+ return s1 + separator + s2
953
+ }
954
+
955
+ /**
956
+ * Determines whether an input can be converted
957
+ * @private
958
+ * @param {String|HTMLElement} input Describe this parameter
959
+ * @returns Describe what it returns
960
+ * @type String|Object|Array|Boolean|Number
961
+ */
962
+
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
+ )
972
+ }
973
+
974
+ return TurndownService;
975
+
976
+ })));