Skip to content

Commit

Permalink
Add support for preformatted code, also submitted to lucthev/collapse…
Browse files Browse the repository at this point in the history
  • Loading branch information
martincizek committed Mar 24, 2020
1 parent 9976e4b commit aa847ab
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 18 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ var turndownService = new TurndownService({ option: 'value' })
| `blankReplacement` | rule replacement function | See **Special Rules** below |
| `keepReplacement` | rule replacement function | See **Special Rules** below |
| `defaultReplacement` | rule replacement function | See **Special Rules** below |
| `preformattedCode` | `false` or [`true`](https:/lucthev/collapse-whitespace/issues/16) | `false` |

## Methods

Expand Down
15 changes: 9 additions & 6 deletions src/collapse-whitespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}

Expand All @@ -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)
Expand Down
16 changes: 9 additions & 7 deletions src/node.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { isBlock, isVoid, hasVoid } 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.isBlank = isBlank(node)
node.flankingWhitespace = flankingWhitespace(node)
node.flankingWhitespace = flankingWhitespace(node, options)
return node
}

Expand All @@ -17,28 +17,28 @@ function isBlank (node) {
)
}

function flankingWhitespace (node) {
function flankingWhitespace (node, options) {
var leading = ''
var trailing = ''

if (!node.isBlock) {
if (!node.isBlock && !(node.isCode && options.preformattedCode)) {
var hasLeading = /^\s/.test(node.textContent)
var hasTrailing = /\s$/.test(node.textContent)
var blankWithSpaces = node.isBlank && hasLeading && hasTrailing

if (hasLeading && !isFlankedByWhitespace('left', node)) {
if (hasLeading && !isFlankedByWhitespace('left', node, options)) {
leading = ' '
}

if (!blankWithSpaces && hasTrailing && !isFlankedByWhitespace('right', node)) {
if (!blankWithSpaces && hasTrailing && !isFlankedByWhitespace('right', node, options)) {
trailing = ' '
}
}

return { leading: leading, trailing: trailing }
}

function isFlankedByWhitespace (side, node) {
function isFlankedByWhitespace (side, node, options) {
var sibling
var regExp
var isFlanked
Expand All @@ -54,6 +54,8 @@ function isFlankedByWhitespace (side, node) {
if (sibling) {
if (sibling.nodeType === 3) {
isFlanked = regExp.test(sibling.nodeValue)
} else if (sibling.nodeName === 'CODE' && options.preformattedCode) {
isFlanked = false
} else if (sibling.nodeType === 1 && !isBlock(sibling)) {
isFlanked = regExp.test(sibling.textContent)
}
Expand Down
9 changes: 7 additions & 2 deletions src/root-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -30,3 +31,7 @@ function htmlParser () {
_htmlParser = _htmlParser || new HTMLParser()
return _htmlParser
}

function isPreOrCode (node) {
return node.nodeName === 'PRE' || node.nodeName === 'CODE'
}
7 changes: 4 additions & 3 deletions src/turndown.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export default function TurndownService (options) {
},
defaultReplacement: function (content, node) {
return node.isBlock ? '\n\n' + content + '\n\n' : content
}
},
preformattedCode: false
}
this.options = extend({}, defaults, options)
this.rules = new Rules(this.options)
Expand All @@ -70,7 +71,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)
},

Expand Down Expand Up @@ -145,7 +146,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 = replacementForNode.call(self, node)
return join(output, replacement)
}, '')
Expand Down
24 changes: 24 additions & 0 deletions test/turndown-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,27 @@ test('remove elements are overridden by keep', function (t) {
'Hello <del>world</del><ins>World</ins>'
)
})

test('preformattedCode option prevents whitespace collapsing', function (t) {
t.plan(4)
// Match behavior of `<code>` with CSS set as `white-space: pre-wrap;`, e.g. in GitLab
var turndownService = new TurndownService({
preformattedCode: true
})
t.equal(turndownService.turndown(
'<p>Four spaces <code> make an indented code block in Markdown</code></p>'),
'Four spaces ` make an indented code block in Markdown`'
)
t.equal(turndownService.turndown(
'<p><code>A line break </code> <b> note the spaces</b></p>'),
'`A line break ` **note the spaces**'
)
t.equal(turndownService.turndown(
'<p><b>tight</b><code>code</code><b>wrap</b></p>'),
'**tight**`code`**wrap**'
)
t.equal(turndownService.turndown(
'<p><b>not so tight </b><code>code</code><b> wrap</b></p>'),
'**not so tight** `code` **wrap**'
)
})

0 comments on commit aa847ab

Please sign in to comment.