From cb997bbb8ad9435c83bb5bd6e0b8db92b8d8ae87 Mon Sep 17 00:00:00 2001 From: Yuri Sulyma <> Date: Mon, 28 Mar 2022 00:54:43 -0400 Subject: [PATCH 1/5] Add \data command to html extension --- ts/input/tex/html/HtmlConfiguration.ts | 1 + ts/input/tex/html/HtmlMethods.ts | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/ts/input/tex/html/HtmlConfiguration.ts b/ts/input/tex/html/HtmlConfiguration.ts index 15340802c..5e1bbcd6e 100644 --- a/ts/input/tex/html/HtmlConfiguration.ts +++ b/ts/input/tex/html/HtmlConfiguration.ts @@ -28,6 +28,7 @@ import HtmlMethods from './HtmlMethods.js'; new CommandMap('html_macros', { + data: 'Data', href: 'Href', 'class': 'Class', style: 'Style', diff --git a/ts/input/tex/html/HtmlMethods.ts b/ts/input/tex/html/HtmlMethods.ts index 859294b05..b7dd87c53 100644 --- a/ts/input/tex/html/HtmlMethods.ts +++ b/ts/input/tex/html/HtmlMethods.ts @@ -32,6 +32,28 @@ import {MmlNode} from '../../../core/MmlTree/MmlNode.js'; // Namespace let HtmlMethods: Record = {}; +/** + * Implements \data{dataset}{content} + * @param {TexParser} parser The calling parser. + * @param {string} name The macro name. + */ +HtmlMethods.Data = (parser: TexParser, name: string) => { + const dataset = parser.GetArgument(name); + const arg = GetArgumentMML(parser, name); + for (const [prop, val] of splitTokens(dataset)) { + NodeUtil.setAttribute(arg, `data-${prop}`, val); + } + parser.Push(arg); +}; + +/** + * Split a dataset string into tokens. + * @param str String to split. + */ +function splitTokens(str: string) { + const matches = Array.from(str.matchAll(/\b([A-Za-z0-9_-]+)=(['"])(.+?)\2/g)); + return matches.map(([, name, , val]) => [name, val]); +} /** * Implements \href{url}{math} @@ -120,5 +142,4 @@ let GetArgumentMML = function(parser: TexParser, name: string): MmlNode { return mrow; }; - export default HtmlMethods; From 6447655a02b8043d9de0f4a5634845629e0b00aa Mon Sep 17 00:00:00 2001 From: Yuri Sulyma <> Date: Mon, 28 Mar 2022 00:55:54 -0400 Subject: [PATCH 2/5] Fix jsdoc syntax in splitTokens --- ts/input/tex/html/HtmlMethods.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts/input/tex/html/HtmlMethods.ts b/ts/input/tex/html/HtmlMethods.ts index b7dd87c53..5bbacff83 100644 --- a/ts/input/tex/html/HtmlMethods.ts +++ b/ts/input/tex/html/HtmlMethods.ts @@ -48,7 +48,7 @@ HtmlMethods.Data = (parser: TexParser, name: string) => { /** * Split a dataset string into tokens. - * @param str String to split. + * @param {string} str String to split. */ function splitTokens(str: string) { const matches = Array.from(str.matchAll(/\b([A-Za-z0-9_-]+)=(['"])(.+?)\2/g)); @@ -142,4 +142,5 @@ let GetArgumentMML = function(parser: TexParser, name: string): MmlNode { return mrow; }; + export default HtmlMethods; From 6ebb315f47887717fdb3cdc7675ae1313140de18 Mon Sep 17 00:00:00 2001 From: Yuri Sulyma <> Date: Mon, 28 Mar 2022 01:02:44 -0400 Subject: [PATCH 3/5] Add \data command to autoload and textmacros --- ts/input/tex/autoload/AutoloadConfiguration.ts | 2 +- ts/input/tex/textmacros/TextMacrosMappings.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ts/input/tex/autoload/AutoloadConfiguration.ts b/ts/input/tex/autoload/AutoloadConfiguration.ts index aeb7888c1..6d24ac40b 100644 --- a/ts/input/tex/autoload/AutoloadConfiguration.ts +++ b/ts/input/tex/autoload/AutoloadConfiguration.ts @@ -154,7 +154,7 @@ export const AutoloadConfiguration = Configuration.create( color: ['color', 'definecolor', 'textcolor', 'colorbox', 'fcolorbox'], enclose: ['enclose'], extpfeil: ['xtwoheadrightarrow', 'xtwoheadleftarrow', 'xmapsto', 'xlongequal', 'xtofrom', 'Newextarrow'], - html: ['href', 'class', 'style', 'cssId'], + html: ['data', 'href', 'class', 'style', 'cssId'], mhchem: ['ce', 'pu'], newcommand: ['newcommand', 'renewcommand', 'newenvironment', 'renewenvironment', 'def', 'let'], unicode: ['unicode'], diff --git a/ts/input/tex/textmacros/TextMacrosMappings.ts b/ts/input/tex/textmacros/TextMacrosMappings.ts index 1f0fc8623..9f4ca2cf2 100644 --- a/ts/input/tex/textmacros/TextMacrosMappings.ts +++ b/ts/input/tex/textmacros/TextMacrosMappings.ts @@ -140,6 +140,7 @@ new CommandMap('text-macros', { href: 'CheckAutoload', style: 'CheckAutoload', class: 'CheckAutoload', + data: 'CheckAutoload', cssId: 'CheckAutoload', unicode: 'CheckAutoload', From d7f3e39ee14506dbe6bb21a25a201eb4573c3c64 Mon Sep 17 00:00:00 2001 From: Yuri Sulyma <> Date: Sat, 16 Apr 2022 02:21:00 -0400 Subject: [PATCH 4/5] Use ParseUtil.keyvalOptions and validate attribute name --- ts/input/tex/html/HtmlMethods.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ts/input/tex/html/HtmlMethods.ts b/ts/input/tex/html/HtmlMethods.ts index 5bbacff83..4f9f31643 100644 --- a/ts/input/tex/html/HtmlMethods.ts +++ b/ts/input/tex/html/HtmlMethods.ts @@ -26,6 +26,7 @@ import TexParser from '../TexParser.js'; import {ParseMethod} from '../Types.js'; import NodeUtil from '../NodeUtil.js'; +import ParseUtil from "../ParseUtil.js"; import {MmlNode} from '../../../core/MmlTree/MmlNode.js'; @@ -40,19 +41,23 @@ let HtmlMethods: Record = {}; HtmlMethods.Data = (parser: TexParser, name: string) => { const dataset = parser.GetArgument(name); const arg = GetArgumentMML(parser, name); - for (const [prop, val] of splitTokens(dataset)) { - NodeUtil.setAttribute(arg, `data-${prop}`, val); + const data = ParseUtil.keyvalOptions(dataset); + for (const key in data) { + // remove illegal attribute names + if (!isLegalAttributeName(key)) { + continue; + } + NodeUtil.setAttribute(arg, `data-${key}`, data[key]); } parser.Push(arg); }; /** - * Split a dataset string into tokens. - * @param {string} str String to split. + * Whether the string is a valid HTML attribute name according to {@link https://html.spec.whatwg.org/multipage/syntax.html#attributes-2}. + * @param {string} name String to validate. */ -function splitTokens(str: string) { - const matches = Array.from(str.matchAll(/\b([A-Za-z0-9_-]+)=(['"])(.+?)\2/g)); - return matches.map(([, name, , val]) => [name, val]); +function isLegalAttributeName(name: string): boolean { + return !!name.match(/^([^\x00-\x1f\x7f-\x9f "'>\/=]+)$/); } /** From 80235bdc7b26c3972a1196011281a69b94ad9524 Mon Sep 17 00:00:00 2001 From: Yuri Sulyma <> Date: Sat, 16 Apr 2022 02:23:38 -0400 Subject: [PATCH 5/5] Use more typo-proof Boolean cast --- ts/input/tex/html/HtmlMethods.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/input/tex/html/HtmlMethods.ts b/ts/input/tex/html/HtmlMethods.ts index 4f9f31643..69d14e461 100644 --- a/ts/input/tex/html/HtmlMethods.ts +++ b/ts/input/tex/html/HtmlMethods.ts @@ -57,7 +57,7 @@ HtmlMethods.Data = (parser: TexParser, name: string) => { * @param {string} name String to validate. */ function isLegalAttributeName(name: string): boolean { - return !!name.match(/^([^\x00-\x1f\x7f-\x9f "'>\/=]+)$/); + return Boolean(name.match(/^([^\x00-\x1f\x7f-\x9f "'>\/=]+)$/)); } /**