diff --git a/package.json b/package.json index 9f61b5b2..f8dc092a 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "highlight.js": "^9.18.1", "katex": "^0.11.1", "markdown-it-emoji": "^1.4.0", + "mathjax-full": "^3.0.5", "postcss": "^7.0.27", "postcss-minify-params": "^4.0.2", "postcss-minify-selectors": "^4.0.2", diff --git a/src/fitting/fitting.scss b/src/fitting/fitting.scss index d78abf11..c15e897a 100644 --- a/src/fitting/fitting.scss +++ b/src/fitting/fitting.scss @@ -19,6 +19,7 @@ svg[data-marp-fitting='svg'].__reflow__ { } [data-marp-fitting-svg-content] { + width: 100%; display: table; white-space: nowrap; } diff --git a/src/marp.ts b/src/marp.ts index 1477110d..2c364000 100644 --- a/src/marp.ts +++ b/src/marp.ts @@ -9,6 +9,7 @@ import * as emojiPlugin from './emoji/emoji' import * as fittingPlugin from './fitting/fitting' import * as htmlPlugin from './html/html' import * as mathPlugin from './math/math' +import * as mathjaxPlugin from './mathjax/math' import * as scriptPlugin from './script/script' import * as sizePlugin from './size/size' import defaultTheme from '../themes/default.scss' @@ -25,6 +26,7 @@ export interface MarpOptions extends Options { | { [attr: string]: boolean | ((value: string) => string) } } markdown?: object + mathjax?: boolean math?: mathPlugin.MathOptions minifyCSS?: boolean script?: boolean | scriptPlugin.ScriptOptions @@ -47,6 +49,7 @@ export class Marp extends Marpit { super({ inlineSVG: true, looseYAML: true, + mathjax: false, math: true, minifyCSS: true, script: true, @@ -86,7 +89,10 @@ export class Marp extends Marpit { md.use(htmlPlugin.markdown) .use(emojiPlugin.markdown) - .use(mathPlugin.markdown, (flag) => (this.renderedMath = flag)) + .use( + this.options.mathjax ? mathjaxPlugin.markdown : mathPlugin.markdown, + (flag) => (this.renderedMath = flag) + ) .use(fittingPlugin.markdown) .use(sizePlugin.markdown) .use(scriptPlugin.markdown) @@ -126,7 +132,7 @@ export class Marp extends Marpit { if (typeof math === 'object') path = math.katexFontPath || undefined // Add KaTeX css - prepend(mathPlugin.css(path)) + prepend(this.options.mathjax ? mathjaxPlugin.css() : mathPlugin.css(path)) } return base diff --git a/src/mathjax/math.ts b/src/mathjax/math.ts new file mode 100644 index 00000000..2984336e --- /dev/null +++ b/src/mathjax/math.ts @@ -0,0 +1,206 @@ +import marpitPlugin from '@marp-team/marpit/plugin' + +import { mathjax } from 'mathjax-full/js/mathjax' +import { TeX } from 'mathjax-full/js/input/tex' +import { SVG } from 'mathjax-full/js/output/svg' +import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor' +import { RegisterHTMLHandler } from 'mathjax-full/js/handlers/html' +import { AllPackages } from 'mathjax-full/js/input/tex/AllPackages' + +const adaptor = liteAdaptor() +RegisterHTMLHandler(adaptor) +const tex = new TeX({ packages: AllPackages }) +const svg = new SVG({ fontCache: 'none' }) +const mathDocument = mathjax.document('', { InputJax: tex, OutputJax: svg }) + +interface MathOptionsInterface { + katexOption?: object +} + +export type MathOptions = boolean | MathOptionsInterface + +/** + * marp-core math plugin + * + * It is implemented based on markdown-it-katex plugin. However, that is no + * longer maintained by author. So we have ported math typesetting parser. + * + * @see https://github.com/waylonflinn/markdown-it-katex + */ +export const markdown = marpitPlugin( + (md, updateState: (rendered: boolean) => void = () => {}) => { + const genOpts = (display: boolean) => { + const math: MathOptions = md.marpit.options.math + + return { + ...(typeof math === 'object' && typeof math.katexOption === 'object' + ? math.katexOption + : {}), + display, + } + } + + md.core.ruler.before('block', 'marp_math_initialize', (state) => { + if (state.inlineMode) return + + updateState(false) + + if (md.marpit.options.math) { + md.block.ruler.enable('marp_math_block') + md.inline.ruler.enable('marp_math_inline') + } else { + md.block.ruler.disable('marp_math_block') + md.inline.ruler.disable('marp_math_inline') + } + }) + + // Inline + md.inline.ruler.after('escape', 'marp_math_inline', (state, silent) => { + if (parseInlineMath(state, silent)) { + updateState(true) + return true + } + return false + }) + + md.renderer.rules.marp_math_inline = (tokens, idx) => { + const { content } = tokens[idx] + + try { + return adaptor.outerHTML(mathDocument.convert(content, genOpts(false))) + } catch (e) { + console.warn(e) + return content + } + } + + // Block + md.block.ruler.after( + 'blockquote', + 'marp_math_block', + (state, start, end, silent) => { + if (parseMathBlock(state, start, end, silent)) { + updateState(true) + return true + } + return false + }, + { alt: ['paragraph', 'reference', 'blockquote', 'list'] } + ) + + md.renderer.rules.marp_math_block = (tokens, idx) => { + const { content } = tokens[idx] + + try { + return `
${adaptor.outerHTML( + mathDocument.convert(content, genOpts(true)) + )}
` + } catch (e) { + console.warn(e) + return `${content}
` + } + } + } +) + +export function css(): string { + return adaptor.textContent(svg.styleSheet(mathDocument) as any) +} + +function isValidDelim(state, pos = state.pos) { + const ret = { openable: true, closable: true } + const { posMax, src } = state + const prev = pos > 0 ? src.charCodeAt(pos - 1) : -1 + const next = pos + 1 <= posMax ? src.charCodeAt(pos + 1) : -1 + + if (next === 0x20 || next === 0x09) ret.openable = false + if (prev === 0x20 || prev === 0x09 || (next >= 0x30 && next <= 0x39)) { + ret.closable = false + } + + return ret +} + +function parseInlineMath(state, silent) { + const { src, pos } = state + if (src[pos] !== '$') return false + + const addPending = (stt: string) => (state.pending += stt) + const found = (manipulation: () => void, newPos: number) => { + if (!silent) manipulation() + state.pos = newPos + return true + } + + const start = pos + 1 + if (!isValidDelim(state).openable) return found(() => addPending('$'), start) + + let match = start + while ((match = src.indexOf('$', match)) !== -1) { + let dollarPos = match - 1 + while (src[dollarPos] === '\\') dollarPos -= 1 + + if ((match - dollarPos) % 2 === 1) break + match += 1 + } + + if (match === -1) return found(() => addPending('$'), start) + if (match - start === 0) return found(() => addPending('$$'), start + 1) + if (!isValidDelim(state, match).closable) { + return found(() => addPending('$'), start) + } + + return found(() => { + const token = state.push('marp_math_inline', 'math', 0) + token.markup = '$' + token.content = src.slice(start, match) + }, match + 1) +} + +function parseMathBlock(state, start, end, silent) { + const { blkIndent, bMarks, eMarks, src, tShift } = state + let pos = bMarks[start] + tShift[start] + let max = eMarks[start] + + if (pos + 2 > max || src.slice(pos, pos + 2) !== '$$') return false + if (silent) return true + + pos += 2 + + let firstLine = src.slice(pos, max) + let lastLine + let found = firstLine.trim().slice(-2) === '$$' + + if (found) firstLine = firstLine.trim().slice(0, -2) + + let next = start + for (; !found; ) { + next += 1 + if (next >= end) break + + pos = bMarks[next] + tShift[next] + max = eMarks[next] + if (pos < max && tShift[next] < blkIndent) break + + const target = src.slice(pos, max).trim() + + if (target.slice(-2) === '$$') { + found = true + lastLine = src.slice(pos, src.slice(0, max).lastIndexOf('$$')) + } + } + + state.line = next + 1 + + const token = state.push('marp_math_block', 'math', 0) + token.block = true + token.content = '' + token.map = [start, state.line] + token.markup = '$$' + + if (firstLine?.trim()) token.content += `${firstLine}\n` + token.content += state.getLines(start + 1, next, tShift[start], true) + if (lastLine?.trim()) token.content += lastLine + + return true +} diff --git a/test/fitting/observer.ts b/test/fitting/observer.ts index 06f362ef..24cb8e49 100644 --- a/test/fitting/observer.ts +++ b/test/fitting/observer.ts @@ -155,4 +155,78 @@ describe('Fitting observer', () => { expect(mathSvg.getAttribute('viewBox')).toBe('0 0 60 100') }) }) + + context('when the auto-scalable elements is rendered with MathJax', () => { + let codeSvg: SVGSVGElement + let codePre: HTMLPreElement + let codeContent: HTMLSpanElement + let mathSvg: SVGSVGElement + let mathP: HTMLParagraphElement + let mathContent: HTMLSpanElement + + beforeEach(() => { + document.body.innerHTML = new Marp({ mathjax: true }).render( + '```\nauto-scalble\n```\n\n$$ auto-scalable $$' + ).html + + codeSvg = document.querySelectorelement's width without padding", () => { + const computed = jest.spyOn(window, 'getComputedStyle') + + computed.mockImplementation((): any => ({ + paddingLeft: 0, + paddingRight: 0, + getPropertyValue: () => undefined, + })) + + setClientWidth(codePre, 300) // pre width > code svg width + setClientWidth(mathP, 400) // p width > math svg width + fittingObserver() + expect(codeSvg.getAttribute('viewBox')).toBe('0 0 300 100') + expect(mathSvg.getAttribute('viewBox')).toBe('0 0 400 100') + + setClientWidth(codePre, 100) // pre width < code svg width + setClientWidth(mathP, 25) // o width < math svg width + fittingObserver() + expect(codeSvg.getAttribute('viewBox')).toBe('0 0 200 100') + expect(mathSvg.getAttribute('viewBox')).toBe('0 0 50 100') + + // Consider padding + computed.mockImplementation((): any => ({ + paddingLeft: '50px', + paddingRight: '70px', + getPropertyValue: () => undefined, + })) + + setClientWidth(codePre, 300) // 300 - 50 - 70 = 180px + setClientWidth(mathP, 180) // 180 - 50 - 70 = 60px + fittingObserver() + expect(codeSvg.getAttribute('viewBox')).toBe('0 0 200 100') + expect(mathSvg.getAttribute('viewBox')).toBe('0 0 60 100') + }) + }) }) diff --git a/test/marp.ts b/test/marp.ts index 639a60c1..533aed09 100644 --- a/test/marp.ts +++ b/test/marp.ts @@ -319,6 +319,13 @@ describe('Marp', () => { expect($('.katex')).toHaveLength(2) }) + it('renders math typesetting by MathJax', () => { + const { html } = marp({ mathjax: true }).render(`${inline}\n\n${block}`) + const $ = cheerio.load(html) + + expect($('.MathJax')).toHaveLength(2) + }) + it('injects KaTeX css with replacing web font URL to CDN', () => { const { css } = marp().render(block) expect(css).toContain('.katex') diff --git a/test/mathjax/__snapshots__/math.ts.snap b/test/mathjax/__snapshots__/math.ts.snap new file mode 100644 index 00000000..a75c3d38 --- /dev/null +++ b/test/mathjax/__snapshots__/math.ts.snap @@ -0,0 +1,113 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`markdown-it math plugin allows to place text immediately after inline math 1`] = ` +"+" +`; + +exports[`markdown-it math plugin can appear both maths in lists 1`] = ` +"
-th order
foo $1+1
+= 2$ bar
+" +`; + +exports[`markdown-it math plugin does not process apparent markup in inline math 1`] = ` +"foo
It's well know that $$1 + 1 = 3$$ for sufficiently large 1.
+" +`; + +exports[`markdown-it math plugin does not render block math with indented up to 4 spaces (code block) 1`] = ` +"$$
+1+1 = 2
+$$
+"
+`;
+
+exports[`markdown-it math plugin does not render math when delimiters are escaped 1`] = `
+"Foo $1$ bar +$$ +1 +$$
+" +`; + +exports[`markdown-it math plugin does not render math when numbers are followed closing inline math 1`] = ` +"Thus, $20,000 and USD$30,000 won't parse as math.
+" +`; + +exports[`markdown-it math plugin does not render with empty content 1`] = ` +"aaa $$ bbb
+" +`; + +exports[`markdown-it math plugin recognizes escaped delimiters in math mode 1`] = ` +"Money adds:
Weird-o:
foo
foo
foo
aaa $5.99 bbb
+" +`; + +exports[`markdown-it math plugin requires non whitespace to left of closing inline math 1`] = ` +"I will give you $20 today, if you give me more $ tomorrow.
+" +`; + +exports[`markdown-it math plugin requires non whitespace to right of opening inline math 1`] = ` +"For some Europeans, it is 2$ for a can of soda, not 1$.
+" +`; diff --git a/test/mathjax/math.ts b/test/mathjax/math.ts new file mode 100644 index 00000000..f7aa4416 --- /dev/null +++ b/test/mathjax/math.ts @@ -0,0 +1,175 @@ +/** + * All test cases are ported from markdown-it-katex. + * + * @see https://github.com/waylonflinn/markdown-it-katex/blob/master/test/fixtures/default.txt + */ + +import MarkdownIt from 'markdown-it' +import { markdown as mathjaxPlugin } from '../../src/mathjax/math' + +const countMath = (stt) => stt.split('class="MathJax"').length - 1 +const countBlockMath = (stt) => stt.split('display="true"').length - 1 + +describe('markdown-it math plugin', () => { + const md = new MarkdownIt() + + md.marpit = { options: { mathjax: true, math: true } } + md.use(mathjaxPlugin) + + it('renders simple inline math', () => { + const rendered = md.render('$1+1 = 2$') + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders simple block math', () => { + const rendered = md.render('$$1+1 = 2$$') + expect(countBlockMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders math without whitespace before and after delimiter', () => { + const rendered = md.render('foo$1+1 = 2$bar') + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders math even when it starts with a negative sign', () => { + const rendered = md.render('foo$-1+1 = 0$bar') + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('does not render with empty content', () => { + const rendered = md.render('aaa $$ bbb') + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('requires a closing delimiter to render math', () => { + const rendered = md.render('aaa $5.99 bbb') + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('does not allow paragraph break in inline math', () => { + const rendered = md.render('foo $1+1\n\n= 2$ bar') + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('does not process apparent markup in inline math', () => { + const rendered = md.render('foo $1 *i* 1$ bar') + expect(countMath(rendered)).toBe(1) + expect(rendered).not.toContain('') + expect(rendered).toMatchSnapshot() + }) + + it('renders block math with indented up to 3 spaces', () => { + const rendered = md.render(' $$\n 1+1 = 2\n $$') + expect(countBlockMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('does not render block math with indented up to 4 spaces (code block)', () => { + const rendered = md.render(' $$\n 1+1 = 2\n $$') + expect(countBlockMath(rendered)).toBe(0) + expect(rendered).toContain('')
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('renders multiline inline math', () => {
+ const rendered = md.render('foo $1 + 1\n= 2$ bar')
+ expect(countMath(rendered)).toBe(1)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('renders multiline display math', () => {
+ const rendered = md.render('$$\n\n 1\n+ 1\n\n= 2\n\n$$')
+ expect(countBlockMath(rendered)).toBe(1)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('allows to place text immediately after inline math', () => {
+ const rendered = md.render('$n$-th order')
+ expect(countMath(rendered)).toBe(1)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('renders block math with self-closing at the end of document', () => {
+ const rendered = md.render('$$\n1+1 = 2')
+ expect(countBlockMath(rendered)).toBe(1)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('can appear both maths in lists', () => {
+ const rendered = md.render('* $1+1 = 2$\n* $$\n 1+1 = 2\n $$')
+ expect(countMath(rendered)).toBe(2)
+ expect(countBlockMath(rendered)).toBe(1)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('renders block math written in one line', () => {
+ const rendered = md.render('$$1+1 = 2$$')
+ expect(countBlockMath(rendered)).toBe(1)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('renders block math composed multiple lines with starting/ending expression on delimited lines', () => {
+ const rendered = md.render('$$[\n[1, 2]\n[3, 4]\n]$$')
+ expect(countBlockMath(rendered)).toBe(1)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('does not render math when delimiters are escaped', () => {
+ const rendered = md.render('Foo \\$1$ bar\n\\$\\$\n1\n\\$\\$')
+ expect(countMath(rendered)).toBe(0)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('does not render math when numbers are followed closing inline math', () => {
+ const rendered = md.render(
+ "Thus, $20,000 and USD$30,000 won't parse as math."
+ )
+ expect(countMath(rendered)).toBe(0)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('requires non whitespace to right of opening inline math', () => {
+ const rendered = md.render(
+ 'For some Europeans, it is 2$ for a can of soda, not 1$.'
+ )
+ expect(countMath(rendered)).toBe(0)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('requires non whitespace to left of closing inline math', () => {
+ const rendered = md.render(
+ 'I will give you $20 today, if you give me more $ tomorrow.'
+ )
+ expect(countMath(rendered)).toBe(0)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('does not recognize inline block math', () => {
+ const rendered = md.render(
+ "It's well know that $$1 + 1 = 3$$ for sufficiently large 1."
+ )
+ expect(countMath(rendered)).toBe(0)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('recognizes escaped delimiters in math mode', () => {
+ const rendered = md.render('Money adds: $\\$X + \\$Y = \\$Z$.')
+ expect(countMath(rendered)).toBe(1)
+ expect(rendered).toMatchSnapshot()
+ })
+
+ it('recognizes multiline escaped delimiters in math module', () => {
+ const rendered = md.render(
+ 'Weird-o: $\\displaystyle{\\begin{pmatrix} \\$ & 1\\\\\\$ \\end{pmatrix}}$.'
+ )
+ expect(countMath(rendered)).toBe(1)
+ expect(rendered).toMatchSnapshot()
+ })
+})
diff --git a/tsconfig.json b/tsconfig.json
index e8430cf5..e645814d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -9,7 +9,8 @@
"rootDir": ".",
"sourceMap": true,
"strict": true,
- "target": "es2015"
+ "target": "es2015",
+ "skipLibCheck": true
},
"include": ["src"]
}
diff --git a/yarn.lock b/yarn.lock
index 8405f6b5..f1ec3255 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1694,6 +1694,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
+commander@*:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
+ integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
+
commander@^2.12.1, commander@^2.19.0, commander@^2.20.0, commander@^2.9.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -2406,6 +2411,11 @@ escodegen@^1.11.1:
optionalDependencies:
source-map "~0.6.1"
+esm@^3.2.25:
+ version "3.2.25"
+ resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
+ integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
+
esprima@^4.0.0, esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@@ -4673,6 +4683,15 @@ marky@^1.2.0:
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.1.tgz#a3fcf82ffd357756b8b8affec9fdbf3a30dc1b02"
integrity sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==
+mathjax-full@^3.0.5:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/mathjax-full/-/mathjax-full-3.0.5.tgz#f8d08965b318b9e1f830fde6ae7caa731797b393"
+ integrity sha512-RzZ03DDMOgqeHPl3dkqDVS3bmXU+lfZZimj7+k3VA+s93LbZcOAKa2hlILF3rgBXgzGLR3YOR8Z8R/B3uAeAeA==
+ dependencies:
+ esm "^3.2.25"
+ mj-context-menu "^0.2.2"
+ speech-rule-engine "^3.0.0-beta.10"
+
mathml-tag-names@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
@@ -4848,6 +4867,11 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
+mj-context-menu@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/mj-context-menu/-/mj-context-menu-0.2.2.tgz#446647475e22c6d83f7c6ff4afa37e66fd280784"
+ integrity sha512-OHlnKQqfFPEYZGdz2JWL0obrr82vVilha0WCUZslYfN+v+oz4VpmERnoHdTUWvOUVHNYjFkpOYnLEeHnt1BdsQ==
+
mkdirp-classic@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b"
@@ -6895,6 +6919,15 @@ specificity@^0.4.1:
resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
+speech-rule-engine@^3.0.0-beta.10:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/speech-rule-engine/-/speech-rule-engine-3.0.1.tgz#84d3a8e936488329b1f3485289efc90430ba054c"
+ integrity sha512-07B/V6eKZQfoKHdw8QaRu3ENKwbE8XcgCFNJAGeKnz751TixF6xUmxqLnsAN4zIj0qUCUKxCy4/LHybBMBmNfQ==
+ dependencies:
+ commander "*"
+ wicked-good-xpath "*"
+ xmldom-sre "^0.1.31"
+
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -8069,6 +8102,11 @@ which@^2.0.1, which@^2.0.2:
dependencies:
isexe "^2.0.0"
+wicked-good-xpath@*:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz#81b0e95e8650e49c94b22298fff8686b5553cf6c"
+ integrity sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w=
+
widest-line@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
@@ -8144,6 +8182,11 @@ xmlchars@^2.1.1:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
+xmldom-sre@^0.1.31:
+ version "0.1.31"
+ resolved "https://registry.yarnpkg.com/xmldom-sre/-/xmldom-sre-0.1.31.tgz#10860d5bab2c603144597d04bf2c4980e98067f4"
+ integrity sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==
+
xss@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.6.tgz#eaf11e9fc476e3ae289944a1009efddd8a124b51"