From fc0d49fbc2178b04acb46243289eb58e4e48396f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C4=8C=C3=AD=C5=BEek?= Date: Wed, 1 Apr 2020 13:15:51 +0200 Subject: [PATCH] Add support for interpreting as a preformatted inline element and rendering Markdown accordingly. Fix #318. --- README.md | 1 + src/collapse-whitespace.js | 15 +++++++++------ src/node.js | 20 ++++++++++++-------- src/root-node.js | 9 +++++++-- src/turndown.js | 5 +++-- test/index.html | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 66 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9f98c22e..4c238fd2 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ var turndownService = new TurndownService({ option: 'value' }) | `strongDelimiter` | `**` or `__` | `**` | | `linkStyle` | `inlined` or `referenced` | `inlined` | | `linkReferenceStyle` | `full`, `collapsed`, or `shortcut` | `full` | +| `preformattedCode` | `false` or [`true`](https://github.com/lucthev/collapse-whitespace/issues/16) | `false` | ### Advanced Options diff --git a/src/collapse-whitespace.js b/src/collapse-whitespace.js index 5176031a..653d08c1 100644 --- a/src/collapse-whitespace.js +++ b/src/collapse-whitespace.js @@ -41,7 +41,7 @@ function collapseWhitespace (options) { if (!element.firstChild || isPre(element)) return var prevText = null - var prevVoid = false + var keepLeadingWs = false var prev = null var node = next(prev, element, isPre) @@ -51,7 +51,7 @@ function collapseWhitespace (options) { var text = node.data.replace(/[ \r\n\t]+/g, ' ') if ((!prevText || / $/.test(prevText.data)) && - !prevVoid && text[0] === ' ') { + !keepLeadingWs && text[0] === ' ') { text = text.substr(1) } @@ -71,11 +71,14 @@ function collapseWhitespace (options) { } prevText = null - prevVoid = false - } else if (isVoid(node)) { - // Avoid trimming space around non-block, non-BR void elements. + keepLeadingWs = false + } else if (isVoid(node) || isPre(node)) { + // Avoid trimming space around non-block, non-BR void elements and inline PRE. prevText = null - prevVoid = true + keepLeadingWs = true + } else if (prevText) { + // Drop protection if set previously. + keepLeadingWs = false } } else { node = remove(node) diff --git a/src/node.js b/src/node.js index e50aabe5..9a2a1f19 100644 --- a/src/node.js +++ b/src/node.js @@ -1,10 +1,10 @@ import { isBlock, isVoid, hasVoid, isMeaningfulWhenBlank, hasMeaningfulWhenBlank } from './utilities' -export default function Node (node) { +export default function Node (node, options) { node.isBlock = isBlock(node) - node.isCode = node.nodeName.toLowerCase() === 'code' || node.parentNode.isCode + node.isCode = node.nodeName === 'CODE' || node.parentNode.isCode node.isBlank = isBlank(node) - node.flankingWhitespace = flankingWhitespace(node) + node.flankingWhitespace = flankingWhitespace(node, options) return node } @@ -18,18 +18,20 @@ function isBlank (node) { ) } -function flankingWhitespace (node) { - if (node.isBlock) return { leading: '', trailing: '' } +function flankingWhitespace (node, options) { + if (node.isBlock || (options.preformattedCode && node.isCode)) { + return { leading: '', trailing: '' } + } var edges = edgeWhitespace(node.textContent) // abandon leading ASCII WS if left-flanked by ASCII WS - if (edges.leadingAscii && isFlankedByWhitespace('left', node)) { + if (edges.leadingAscii && isFlankedByWhitespace('left', node, options)) { edges.leading = edges.leadingNonAscii } // abandon trailing ASCII WS if right-flanked by ASCII WS - if (edges.trailingAscii && isFlankedByWhitespace('right', node)) { + if (edges.trailingAscii && isFlankedByWhitespace('right', node, options)) { edges.trailing = edges.trailingNonAscii } @@ -48,7 +50,7 @@ function edgeWhitespace (string) { } } -function isFlankedByWhitespace (side, node) { +function isFlankedByWhitespace (side, node, options) { var sibling var regExp var isFlanked @@ -64,6 +66,8 @@ function isFlankedByWhitespace (side, node) { if (sibling) { if (sibling.nodeType === 3) { isFlanked = regExp.test(sibling.nodeValue) + } else if (options.preformattedCode && sibling.nodeName === 'CODE') { + isFlanked = false } else if (sibling.nodeType === 1 && !isBlock(sibling)) { isFlanked = regExp.test(sibling.textContent) } diff --git a/src/root-node.js b/src/root-node.js index 62a04bc6..7deaa8a8 100644 --- a/src/root-node.js +++ b/src/root-node.js @@ -2,7 +2,7 @@ import collapseWhitespace from './collapse-whitespace' import HTMLParser from './html-parser' import { isBlock, isVoid } from './utilities' -export default function RootNode (input) { +export default function RootNode (input, options) { var root if (typeof input === 'string') { var doc = htmlParser().parseFromString( @@ -19,7 +19,8 @@ export default function RootNode (input) { collapseWhitespace({ element: root, isBlock: isBlock, - isVoid: isVoid + isVoid: isVoid, + isPre: options.preformattedCode ? isPreOrCode : null }) return root @@ -30,3 +31,7 @@ function htmlParser () { _htmlParser = _htmlParser || new HTMLParser() return _htmlParser } + +function isPreOrCode (node) { + return node.nodeName === 'PRE' || node.nodeName === 'CODE' +} diff --git a/src/turndown.js b/src/turndown.js index bcd4d60b..d65fc348 100644 --- a/src/turndown.js +++ b/src/turndown.js @@ -37,6 +37,7 @@ export default function TurndownService (options) { linkStyle: 'inlined', linkReferenceStyle: 'full', br: ' ', + preformattedCode: false, blankReplacement: function (content, node) { return node.isBlock ? '\n\n' : '' }, @@ -69,7 +70,7 @@ TurndownService.prototype = { if (input === '') return '' - var output = process.call(this, new RootNode(input)) + var output = process.call(this, new RootNode(input, this.options)) return postProcess.call(this, output) }, @@ -158,7 +159,7 @@ TurndownService.prototype = { function process (parentNode) { var self = this return reduce.call(parentNode.childNodes, function (output, node) { - node = new Node(node) + node = new Node(node, self.options) var replacement = '' if (node.nodeType === 3) { diff --git a/test/index.html b/test/index.html index e28a7488..26fa586c 100644 --- a/test/index.html +++ b/test/index.html @@ -1042,6 +1042,40 @@

This is a header.

foo  _bar_
+ +
+
Four spaces make an indented code block in Markdown
+
Four spaces `    make an indented code block in Markdown`
+
+ +
+
A line break note the spaces
+
`A line break  ` **note the spaces**
+
+ +
+
tightcodewrap
+
**tight**`code`**wrap**
+
+ +
+
not so tight code wrap
+
**not so tight** `code` **wrap**
+
+ + +
+
+ + + nasty +code + + +
+
`    nasty code   `
+
+