Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,22 @@ module.exports = function downdoc (asciidoc, { attributes: initialAttrs = {} } =
inHeader = (inContainer = inPara = (indent = '') || false) || asciidoc[0] === '=' || !!~asciidoc.indexOf('\n= ')
let blockAttrs, blockTitle, chr0, grab, hardbreakNext, listStack, match, next, subs, style, verbatim
const [containerStack, nrefs, refs, skipStack, undef] = [(listStack = []).slice(), new Map(), new Map(), [], () => {}]
return lines
const passthroughContents = []

// Extract stem macros before any other processing
const processedLines = lines.map(line => {
if (InlineStemMacroRx.test(line)) {
return line.replace(InlineStemMacroRx, (_, expr) => {
const index = passthroughContents.length
passthroughContents.push(expr.replace(/\\]/g, ']'))
return `stem:[${index}]`
})
} else {
return line;
}
})

return processedLines
.reduce((accum, line, idx) => {
while ((grab = match = next = style = subs = undefined) === undefined) {
if (skipStack.length && (match = skipStack[skipStack.length - 1] || (line === 'endif::[]' && line))) {
Expand Down Expand Up @@ -291,6 +306,10 @@ module.exports = function downdoc (asciidoc, { attributes: initialAttrs = {} } =
const { title = id, reftext = title, autoId = id } = refs.get(id) || nrefs.get(id) || {}
return '[' + (text || reftext) + '](#' + autoId + ')'
})
.replace(/stem:\[(\d+)\]/g, (_, index) => {
const content = passthroughContents[parseInt(index, 10)]
return content ? `$${content}$` : `stem:[${index}]`
})
.concat(((grab = verbatim?.cap)) ? '\n' + grab : '') // prettier-ignore
}

Expand Down Expand Up @@ -326,7 +345,19 @@ function isHeading (str, acceptAll, blockAttrs, marker, title, spaceIdx = str.in

function macros (str) {
if (!~str.indexOf(':')) return ~str.indexOf('[[') ? str.replace(InlineAnchorRx, '<a name="$1"></a>') : str
if (~str.indexOf('m:[')) str = str.replace(InlineStemMacroRx, (_, expr) => '$' + expr.replace(/\\]/g, ']') + '$')
if (~str.indexOf('m:[')) {
// Skip processing stem:[index] placeholders (they are already processed)
str = str.replace(InlineStemMacroRx, (_, expr) => {
// Check if this is a placeholder (just a number) or actual stem content
if (/^\d+$/.test(expr)) {
// This is a placeholder, leave it as is
return `stem:[${expr}]`
} else {
// This is actual stem content, process it normally
return '$' + expr.replace(/\\]/g, ']') + '$'
}
})
}
if (~str.indexOf('image:')) str = str.replace(InlineImageMacroRx, image.bind(this))
if (~str.indexOf(':/') || ~str.indexOf('link:')) {
str = str.replace(LinkMacroRx, (_, esc, scheme = '', url, boxed = '', text, bareScheme = scheme, bareUrl) => {
Expand Down
170 changes: 170 additions & 0 deletions test/stem-passthrough-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/* eslint-env mocha */
'use strict'

const { expect, heredoc } = require('./harness')
const downdoc = require('downdoc')

describe('stem macro passthrough', () => {
it('should protect stem macro content from formatting interference', () => {
const input = '*stem:[V^{**}]*'
const result = downdoc(input)
expect(result).to.equal('**$V^{**}$**')
})

it('should handle stem macro with multiple asterisks', () => {
const input = '*stem:[V^{***}]*'
const result = downdoc(input)
expect(result).to.equal('**$V^{***}$**')
})

it('should handle stem macro with escaped brackets', () => {
const input = '*stem:[V^{**\\]}]*'
const result = downdoc(input)
expect(result).to.equal('**$V^{**]}$**')
})

it('should handle stem macro in normal text', () => {
const input = 'The formula *stem:[V^{**}]* is important'
const result = downdoc(input)
expect(result).to.equal('The formula **$V^{**}$** is important')
})

it('should handle multiple stem macros in same line', () => {
const input = 'stem:[x+y] and stem:[a*b]'
const result = downdoc(input)
expect(result).to.equal('$x+y$ and $a*b$')
})

it('should handle stem macros with complex mathematical expressions', () => {
const input = 'stem:[\\sqrt{x^2 + y^2}] and stem:[\\frac{a}{b}]'
const result = downdoc(input)
expect(result).to.equal('$\\sqrt{x^2 + y^2}$ and $\\frac{a}{b}$')
})

it('should handle stem macros with formatting characters', () => {
const input = '*stem:[V^{**}]*, _stem:[x+y]_, and `stem:[a*b]`'
const result = downdoc(input)
expect(result).to.equal('**$V^{**}$**, _$x+y$_, and `$a*b$`')
})

it('should handle stem macros in different contexts', () => {
const input = heredoc`
= Title

The equation *stem:[E=mc^2]* is famous.

.Example
stem:[\\sum_{i=1}^n i = \\frac{n(n+1)}{2}]

== Section with stem:[x^2]
`
const expected = heredoc`
# Title

The equation **$E=mc^2$** is famous.

**Example**

$\\sum_{i=1}^n i = \\frac{n(n+1)}{2}$

## Section with $x^2$
`
expect(downdoc(input)).to.equal(expected)
})

it('should handle stem macros with special characters', () => {
const input = 'stem:[α + β = γ] and stem:[∀x∃y]'
const result = downdoc(input)
expect(result).to.equal('$α + β = γ$ and $∀x∃y$')
})

it('should handle stem macros with escaped content', () => {
const input = 'stem:[a\\]b] and stem:[c\\[d]'
const result = downdoc(input)
expect(result).to.equal('$a]b$ and $c\\[d$')
})

it('should handle stem macros in lists', () => {
const input = heredoc`
* stem:[x+y]
* *stem:[V^{**}]* is bold
* _stem:[a*b]_ is italic
`
const expected = heredoc`
* $x+y$
* **$V^{**}$** is bold
* _$a*b$_ is italic
`
expect(downdoc(input)).to.equal(expected)
})

it('should handle stem macros in tables', () => {
const input = heredoc`
|===
| Formula | Description

| stem:[E=mc^2]
| Energy-mass equivalence

| *stem:[V^{**}]*
| Bold formula
|===
`
const expected = heredoc`
| Formula | Description |
| --- | --- |
| $E=mc^2$ | Energy-mass equivalence |
| **$V^{**}$** | Bold formula |
`
expect(downdoc(input)).to.equal(expected)
})

it('should handle stem macros with mixed formatting', () => {
const input = '*stem:[V^{**}]*, _stem:[x+y]_, `stem:[a*b]`, and #stem:[c+d]#'
const result = downdoc(input)
expect(result).to.equal('**$V^{**}$**, _$x+y$_, `$a*b$`, and <mark>$c+d$</mark>')
})

it('should handle stem macros with attribute references', () => {
const input = heredoc`
:formula: E=mc^2

The formula stem:[{formula}] is famous.
`
const result = downdoc(input)
expect(result).to.equal('The formula ${formula}$ is famous.')
})

it('should handle stem macros in block titles', () => {
const input = heredoc`
.stem:[x^2] Example
----
code
----
`
const expected = heredoc`
**$x^2$ Example**

\`\`\`
code
\`\`\`
`
expect(downdoc(input)).to.equal(expected)
})

it('should handle stem macros in admonitions', () => {
const input = heredoc`
NOTE: Remember the formula *stem:[V^{**}]*.

TIP: Use stem:[\\sqrt{x}] for square root.
`
const expected = heredoc`
**📌 NOTE**\\
Remember the formula **$V^{**}$**.

**💡 TIP**\\
Use $\\sqrt{x}$ for square root.
`
expect(downdoc(input)).to.equal(expected)
})
})