/** * French prose is full of « 185 000 $ ». remark-math would treat those dollars as inline-math * delimiters. We apply Pandoc's rule before parsing: a single `$` opens math only if followed by * a non-space, closes only if preceded by a non-space and not followed by a digit. Every `$` * that cannot play either role is escaped (`\$`). `$$` blocks and code fences are left untouched. */ export function guardCurrency(md: string): string { let out = ''; let i = 0; let inFence = false; let openIdx = -1; // index in `out` of the current unmatched opener const n = md.length; while (i < n) { const ch = md[i]; // code fences if ((ch === '`' && md.startsWith('```', i)) || (ch === '~' && md.startsWith('~~~', i))) { inFence = !inFence; out += md.slice(i, i + 3); i += 3; continue; } if (inFence) { out += ch; i++; continue; } if (ch === '`') { // inline code: copy through the closing backtick const j = md.indexOf('`', i + 1); if (j > 0) { out += md.slice(i, j + 1); i = j + 1; continue; } } if (ch === '\\' && md[i + 1] === '$') { out += '\\$'; i += 2; continue; } if (ch === '$' && md[i + 1] === '$') { // display math: copy through the closing $$ const j = md.indexOf('$$', i + 2); if (j > 0) { out += md.slice(i, j + 2); i = j + 2; continue; } } if (ch === '$') { const prev = i > 0 ? md[i - 1] : ' '; const next = i + 1 < n ? md[i + 1] : ' '; const canOpen = !/\s/.test(next) && next !== '$'; const canClose = !/\s/.test(prev) && !/\d/.test(next); if (openIdx < 0) { if (canOpen) { openIdx = out.length; out += '$'; } else { out += '\\$'; } } else if (canClose) { out += '$'; openIdx = -1; } else { out += '\\$'; } i++; continue; } if (ch === '\n' && openIdx >= 0 && md[i + 1] === '\n') { // paragraph break with an unmatched opener: it was a currency sign out = out.slice(0, openIdx) + '\\$' + out.slice(openIdx + 1); openIdx = -1; } out += ch; i++; } if (openIdx >= 0) out = out.slice(0, openIdx) + '\\$' + out.slice(openIdx + 1); return out; }