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 |
|
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+/, '')
|
148 | .replace(/\n+$/, '\n')
|
149 | .replace(/\n/gm, '\n ');
|
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 = [];
|
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 |
|
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 |
|
428 |
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 | |
454 |
|
455 |
|
456 |
|
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) {
|
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 |
|
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) {
|
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 |
|
502 | prevText = null;
|
503 | keepLeadingWs = true;
|
504 | } else if (prevText) {
|
505 |
|
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 |
|
528 |
|
529 |
|
530 |
|
531 |
|
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 |
|
543 |
|
544 |
|
545 |
|
546 |
|
547 |
|
548 |
|
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 |
|
560 |
|
561 |
|
562 | var root = (typeof window !== 'undefined' ? window : {});
|
563 |
|
564 | |
565 |
|
566 |
|
567 |
|
568 | function canParseHTMLNatively () {
|
569 | var Parser = root.DOMParser;
|
570 | var canParse = false;
|
571 |
|
572 |
|
573 |
|
574 | try {
|
575 |
|
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';
|
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 (window.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 |
|
627 |
|
628 |
|
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.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 |
|
682 | if (edges.leadingAscii && isFlankedByWhitespace('left', node, options)) {
|
683 | edges.leading = edges.leadingNonAscii;
|
684 | }
|
685 |
|
686 |
|
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*?)([ \t\r\n]*))$/);
|
696 | return {
|
697 | leading: m[1],
|
698 | leadingAscii: m[2],
|
699 | leadingNonAscii: m[3],
|
700 | trailing: m[4],
|
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 |
|
781 |
|
782 |
|
783 |
|
784 |
|
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 |
|
802 |
|
803 |
|
804 |
|
805 |
|
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 |
|
821 |
|
822 |
|
823 |
|
824 |
|
825 |
|
826 |
|
827 |
|
828 | addRule: function (key, rule) {
|
829 | this.rules.add(key, rule);
|
830 | return this
|
831 | },
|
832 |
|
833 | |
834 |
|
835 |
|
836 |
|
837 |
|
838 |
|
839 |
|
840 |
|
841 | keep: function (filter) {
|
842 | this.rules.keep(filter);
|
843 | return this
|
844 | },
|
845 |
|
846 | |
847 |
|
848 |
|
849 |
|
850 |
|
851 |
|
852 |
|
853 |
|
854 | remove: function (filter) {
|
855 | this.rules.remove(filter);
|
856 | return this
|
857 | },
|
858 |
|
859 | |
860 |
|
861 |
|
862 |
|
863 |
|
864 |
|
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 |
|
876 |
|
877 |
|
878 |
|
879 |
|
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 |
|
900 |
|
901 |
|
902 |
|
903 |
|
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 |
|
919 |
|
920 |
|
921 |
|
922 |
|
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 |
|
939 |
|
940 |
|
941 |
|
942 |
|
943 |
|
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 |
|
957 |
|
958 |
|
959 |
|
960 |
|
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 | })));
|