diff --git a/README.md b/README.md index 3caf6ec..fa2196a 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,9 @@ const content = seeMarkReactParse(markdown); | Option Name | Type | Default Value | Description | | ------------------------- | ------- | -------------- | ------------------------------------------------------------------ | +| enableLatex | boolean | true | When false, LaTeX expressions are not parsed as math. | +| enableAsciimath | boolean | true | When false, AsciiMath expressions are not parsed as math. | +| enableNemeth | boolean | true | When false, the Nemeth braille math extension is disabled and `@…@` syntax is not parsed. | | latexDelimiter | string | 'bracket' | The delimiter for LaTeX expressions. Options: 'bracket', 'dollar'. | | documentFormat | string | 'inline' | The format of the document. Options: 'inline', 'block'. | | imageFiles | object | { [ID]: File } | A map of image IDs to File objects for image rendering. | @@ -123,6 +126,15 @@ const toc = createTableOfContents(markdown); `createTableOfContents` parses a markdown string and returns a flat array of all h1–h6 headings in document order. The `id` of each entry is generated with the same slugify logic used by the seemark's markdown parser, so IDs are guaranteed to match the `id` prop on rendered heading components. +### Options + +| Option Name | Type | Default Value | Description | +| -------------- | ------- | ------------- | ------------------------------------------------------------------------------------------- | +| enableLatex | boolean | true | When false, LaTeX expressions are not parsed as math. | +| enableAsciimath | boolean | true | When false, AsciiMath expressions are not parsed as math. | +| enableNemeth | boolean | true | When false, the Nemeth braille math extension is disabled and `@…@` syntax is not parsed. | +| latexDelimiter | string | 'bracket' | The delimiter for LaTeX expressions. Options: 'bracket', 'dollar'. Must match the renderer. | + ### Return value Each entry in the returned array has the following shape: diff --git a/package.json b/package.json index 47ee76d..fde1c7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coseeing/see-mark", - "version": "1.8.0", + "version": "1.9.0", "description": "A markdown parser for a11y", "main": "./lib/see-mark.cjs", "files": [ diff --git a/src/markdown-processor/markdown-processor.js b/src/markdown-processor/markdown-processor.js index d763989..6a02285 100644 --- a/src/markdown-processor/markdown-processor.js +++ b/src/markdown-processor/markdown-processor.js @@ -18,15 +18,21 @@ import iframe from './marked-extentions/iframe'; export const createMarkdownProcessor = (options = {}) => { const asciimathDelimiter = 'graveaccent'; + const enableLatex = options.enableLatex !== false; + const enableAsciimath = options.enableAsciimath !== false; + const enableNemeth = options.enableNemeth !== false; + return markedProcessorFactory({ latexDelimiter: options.latexDelimiter, asciimathDelimiter, documentFormat: options.documentFormat, imageFiles: options.imageFiles, shouldBuildImageObjectURL: options.shouldBuildImageObjectURL, + enableLatex, + enableAsciimath, extensions: [ - math, - nemeth, + ...(enableLatex || enableAsciimath ? [math] : []), + ...(enableNemeth ? [nemeth] : []), alert, heading, internalLink, diff --git a/src/markdown-processor/markdown-processor.test.js b/src/markdown-processor/markdown-processor.test.js index a8774d0..85d82a1 100644 --- a/src/markdown-processor/markdown-processor.test.js +++ b/src/markdown-processor/markdown-processor.test.js @@ -91,6 +91,24 @@ describe('markdownProcessor', () => { expect(container).toMatchSnapshot(); }); + it('should not parse nemeth braille when enableNemeth is false', () => { + const markdownContent = '@⠁⠘⠆@'; + const options = { + enableNemeth: false, + latexDelimiter: 'bracket', + documentFormat: 'inline', + imageFiles: {}, + }; + + const result = markdownProcessor(markdownContent, options); + + const container = createDOMFromHTML(result); + + const mathEl = getElementByType(container, SUPPORTED_COMPONENT_TYPES.MATH); + + expect(mathEl).toBeNull(); + }); + it('should process alert', () => { const markdownContent = `> [!WARNING]\n> Critical content demanding immediate user attention due to potential risks.`; const options = { @@ -308,6 +326,85 @@ describe('markdownProcessor', () => { expect(container).toMatchSnapshot(); }); + it('should not parse latex when enableLatex is false', () => { + const markdownContent = '\\(a+b=c\\)'; + const options = { + enableLatex: false, + latexDelimiter: 'bracket', + documentFormat: 'inline', + imageFiles: {}, + }; + + const result = markdownProcessor(markdownContent, options); + + const container = createDOMFromHTML(result); + + const mathEl = getElementByType(container, SUPPORTED_COMPONENT_TYPES.MATH); + + expect(mathEl).toBeNull(); + }); + + it('should not parse asciimath when enableAsciimath is false', () => { + const markdownContent = '`a+b=c`'; + const options = { + enableAsciimath: false, + latexDelimiter: 'bracket', + documentFormat: 'inline', + imageFiles: {}, + }; + + const result = markdownProcessor(markdownContent, options); + + const container = createDOMFromHTML(result); + + const mathEl = getElementByType(container, SUPPORTED_COMPONENT_TYPES.MATH); + + expect(mathEl).toBeNull(); + }); + + it('should still parse latex when enableAsciimath is false', () => { + const markdownContent = '\\(a+b=c\\)'; + const options = { + enableAsciimath: false, + latexDelimiter: 'bracket', + documentFormat: 'inline', + imageFiles: {}, + }; + + const result = markdownProcessor(markdownContent, options); + + const container = createDOMFromHTML(result); + + const mathEl = getElementByType(container, SUPPORTED_COMPONENT_TYPES.MATH); + + expect(mathEl).toBeTruthy(); + + const payload = JSON.parse( + mathEl.getAttribute(SEE_MARK_PAYLOAD_DATA_ATTRIBUTES) + ); + + expect(payload.typed).toBe('latex'); + }); + + it('should not parse any math when both enableLatex and enableAsciimath are false', () => { + const markdownContent = '\\(a+b=c\\) `a+b=c`'; + const options = { + enableLatex: false, + enableAsciimath: false, + latexDelimiter: 'bracket', + documentFormat: 'inline', + imageFiles: {}, + }; + + const result = markdownProcessor(markdownContent, options); + + const container = createDOMFromHTML(result); + + const mathEl = getElementByType(container, SUPPORTED_COMPONENT_TYPES.MATH); + + expect(mathEl).toBeNull(); + }); + it('should process image link', () => { const markdownContent = `![alt text](image-123)((https://example.com/target))`; const options = { diff --git a/src/markdown-processor/marked-extentions/math.js b/src/markdown-processor/marked-extentions/math.js index 9fc3b8b..9b6c7ff 100644 --- a/src/markdown-processor/marked-extentions/math.js +++ b/src/markdown-processor/marked-extentions/math.js @@ -51,7 +51,17 @@ const AsciiMath_delimiter_dict = { * @param {string} options.documentFormat - Document format for MathML display ('inline' or 'block') * @returns {Object} Marked extension object with math tokenizer and renderer */ -const markedMath = ({ latexDelimiter, asciimathDelimiter, documentFormat }) => { +const markedMath = ({ + enableLatex = true, + enableAsciimath = true, + latexDelimiter, + asciimathDelimiter, + documentFormat, +}) => { + if (!enableLatex && !enableAsciimath) { + return { extensions: [] }; + } + const asciimath2mml = asciimath2mmlFactory({ htmlMathDisplay: documentFormat, }); @@ -62,16 +72,21 @@ const markedMath = ({ latexDelimiter, asciimathDelimiter, documentFormat }) => { const latex_restring = `(?<=[^\\\\]?)${LaTeX_delimiter.start}(.*?[^\\\\])?${LaTeX_delimiter.end}`; const asciimath_restring = `(?<=[^\\\\]?)${AsciiMath_delimiter.start}(.*?[^\\\\])?${AsciiMath_delimiter.end}`; - const reTexMath = new RegExp( - `(.*?)(${latex_restring}|${asciimath_restring})`, - 's' - ); const latex_start_restring = `(?<=[^\\\\]?)${LaTeX_delimiter.start}`; const asciimath_start_restring = `(?<=[^\\\\]?)${AsciiMath_delimiter.start}`; - const reTexMath_start = new RegExp( - `${latex_start_restring}|${asciimath_start_restring}` - ); + + const matchPatterns = [ + ...(enableLatex ? [latex_restring] : []), + ...(enableAsciimath ? [asciimath_restring] : []), + ]; + const startPatterns = [ + ...(enableLatex ? [latex_start_restring] : []), + ...(enableAsciimath ? [asciimath_start_restring] : []), + ]; + + const reTexMath = new RegExp(`(.*?)(${matchPatterns.join('|')})`, 's'); + const reTexMath_start = new RegExp(startPatterns.join('|')); return { extensions: [ diff --git a/src/markdown-processor/marked-wrapper/marked-wrapper.js b/src/markdown-processor/marked-wrapper/marked-wrapper.js index f400fcd..8d32f7f 100644 --- a/src/markdown-processor/marked-wrapper/marked-wrapper.js +++ b/src/markdown-processor/marked-wrapper/marked-wrapper.js @@ -3,6 +3,8 @@ import { Marked } from 'marked'; import { createPositionTracker } from './position-tracker'; const markedProcessorFactory = ({ + enableLatex = true, + enableAsciimath = true, latexDelimiter, asciimathDelimiter, documentFormat, @@ -25,6 +27,8 @@ const markedProcessorFactory = ({ extensions.forEach((extension) => { marked.use( extension({ + enableLatex, + enableAsciimath, latexDelimiter, asciimathDelimiter, documentFormat, diff --git a/src/parsers/options.js b/src/parsers/options.js index 39fca46..2899b57 100644 --- a/src/parsers/options.js +++ b/src/parsers/options.js @@ -1,4 +1,7 @@ const DEFAULT_OPTINOS = { + enableLatex: true, + enableAsciimath: true, + enableNemeth: true, latexDelimiter: 'bracket', documentFormat: 'inline', imageFiles: null, diff --git a/src/table-of-contents/create-table-of-contents.js b/src/table-of-contents/create-table-of-contents.js index f86a365..3953966 100644 --- a/src/table-of-contents/create-table-of-contents.js +++ b/src/table-of-contents/create-table-of-contents.js @@ -45,6 +45,9 @@ function extractPlainText(inlineTokens = []) { */ const createTableOfContents = (markdown, options = {}) => { const { lexer } = createMarkdownProcessor({ + enableLatex: options.enableLatex !== false, + enableAsciimath: options.enableAsciimath !== false, + enableNemeth: options.enableNemeth !== false, latexDelimiter: options.latexDelimiter ?? 'bracket', }); const tokens = lexer(markdown);