temml 0.10.33 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@
11
11
  * https://mit-license.org/
12
12
  */
13
13
 
14
- const version = "0.10.33";
14
+ const version = "0.11.00";
15
15
 
16
16
  function postProcess(block) {
17
17
  const labelMap = {};
@@ -27,15 +27,15 @@
27
27
  // No need to write a number into the text content of the element.
28
28
  // A CSS counter has done that even if this postProcess() function is not used.
29
29
 
30
- // Find any \label that refers to an AMS eqn number.
30
+ // Find any \label that refers to an AMS automatic eqn number.
31
31
  while (true) {
32
+ if (parent.tagName === "mtable") { break }
32
33
  const labels = parent.getElementsByClassName("tml-label");
33
34
  if (labels.length > 0) {
34
- parent.setAttribute("id", labels[0].id);
35
- labelMap[labels[0].id] = String(i);
35
+ const id = parent.attributes.id.value;
36
+ labelMap[id] = String(i);
36
37
  break
37
38
  } else {
38
- if (parent.tagName === "mtable") { break }
39
39
  parent = parent.parentElement;
40
40
  }
41
41
  }
@@ -48,7 +48,8 @@
48
48
  if (labels.length > 0) {
49
49
  const tags = parent.getElementsByClassName("tml-tag");
50
50
  if (tags.length > 0) {
51
- labelMap[labels[0].id] = tags[0].textContent;
51
+ const id = parent.attributes.id.value;
52
+ labelMap[id] = tags[0].textContent;
52
53
  }
53
54
  }
54
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "temml",
3
- "version": "0.10.33",
3
+ "version": "0.11.00",
4
4
  "description": "TeX to MathML conversion in JavaScript.",
5
5
  "main": "dist/temml.js",
6
6
  "engines": {
@@ -44,6 +44,6 @@
44
44
  "minify": "terser test/temml.js -o site/assets/temml.min.js -c -m && terser contrib/mhchem/mhchem.js -o site/assets/mhchem.min.js -c -m",
45
45
  "build": "rollup --config ./utils/rollupConfig.mjs && yarn minify && node utils/insertPlugins.js",
46
46
  "docs": "node utils/buildDocs.js",
47
- "dist": "yarn build && node ./utils/copyfiles.js && terser contrib/auto-render/test/auto-render.js -o contrib/auto-render/dist/auto-render.min.js -c -m"
47
+ "dist": "yarn build && node ./utils/copyfiles.js"
48
48
  }
49
49
  }
@@ -0,0 +1,263 @@
1
+ import ParseError from "./ParseError.js"
2
+ import { postProcess } from "./postProcess.js";
3
+
4
+ const findEndOfMath = function(delimiter, text, startIndex) {
5
+ // Adapted from
6
+ // https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
7
+ let index = startIndex;
8
+ let braceLevel = 0;
9
+
10
+ const delimLength = delimiter.length;
11
+
12
+ while (index < text.length) {
13
+ const character = text[index];
14
+
15
+ if (braceLevel <= 0 && text.slice(index, index + delimLength) === delimiter) {
16
+ return index;
17
+ } else if (character === "\\") {
18
+ index++;
19
+ } else if (character === "{") {
20
+ braceLevel++;
21
+ } else if (character === "}") {
22
+ braceLevel--;
23
+ }
24
+
25
+ index++;
26
+ }
27
+
28
+ return -1;
29
+ };
30
+
31
+ const escapeRegex = function(string) {
32
+ return string.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
33
+ };
34
+
35
+ const amsRegex = /^\\(?:begin|(?:eq)?ref){/
36
+
37
+ const splitAtDelimiters = function(text, delimiters) {
38
+ let index;
39
+ const data = [];
40
+
41
+ const regexLeft = new RegExp(
42
+ "(" + delimiters.map((x) => escapeRegex(x.left)).join("|") + ")"
43
+ )
44
+
45
+ while (true) {
46
+ index = text.search(regexLeft);
47
+ if (index === -1) {
48
+ break;
49
+ }
50
+ if (index > 0) {
51
+ data.push({
52
+ type: "text",
53
+ data: text.slice(0, index)
54
+ });
55
+ text = text.slice(index); // now text starts with delimiter
56
+ }
57
+ // ... so this always succeeds:
58
+ const i = delimiters.findIndex((delim) => text.startsWith(delim.left));
59
+ index = findEndOfMath(delimiters[i].right, text, delimiters[i].left.length);
60
+ if (index === -1) {
61
+ break;
62
+ }
63
+ const rawData = text.slice(0, index + delimiters[i].right.length);
64
+ const math = amsRegex.test(rawData)
65
+ ? rawData
66
+ : text.slice(delimiters[i].left.length, index);
67
+ data.push({
68
+ type: "math",
69
+ data: math,
70
+ rawData,
71
+ display: delimiters[i].display
72
+ });
73
+ text = text.slice(index + delimiters[i].right.length);
74
+ }
75
+
76
+ if (text !== "") {
77
+ data.push({
78
+ type: "text",
79
+ data: text
80
+ });
81
+ }
82
+
83
+ return data;
84
+ };
85
+
86
+ const defaultDelimiters = [
87
+ { left: "$$", right: "$$", display: true },
88
+ { left: "\\(", right: "\\)", display: false },
89
+ // LaTeX uses $…$, but it ruins the display of normal `$` in text:
90
+ // {left: "$", right: "$", display: false},
91
+ // $ must come after $$
92
+
93
+ // Render AMS environments even if outside $$…$$ delimiters.
94
+ { left: "\\begin{equation}", right: "\\end{equation}", display: true },
95
+ { left: "\\begin{equation*}", right: "\\end{equation*}", display: true },
96
+ { left: "\\begin{align}", right: "\\end{align}", display: true },
97
+ { left: "\\begin{align*}", right: "\\end{align*}", display: true },
98
+ { left: "\\begin{alignat}", right: "\\end{alignat}", display: true },
99
+ { left: "\\begin{alignat*}", right: "\\end{alignat*}", display: true },
100
+ { left: "\\begin{gather}", right: "\\end{gather}", display: true },
101
+ { left: "\\begin{gather*}", right: "\\end{gather*}", display: true },
102
+ { left: "\\begin{CD}", right: "\\end{CD}", display: true },
103
+ // Ditto \ref & \eqref
104
+ { left: "\\ref{", right: "}", display: false },
105
+ { left: "\\eqref{", right: "}", display: false },
106
+
107
+ { left: "\\[", right: "\\]", display: true }
108
+ ];
109
+
110
+ const firstDraftDelimiters = {
111
+ "$": [
112
+ { left: "$$", right: "$$", display: true },
113
+ { left: "$`", right: "`$", display: false },
114
+ { left: "$", right: "$", display: false }
115
+ ],
116
+ "(": [
117
+ { left: "\\[", right: "\\]", display: true },
118
+ { left: "\\(", right: "\\)", display: false }
119
+ ]
120
+ }
121
+
122
+ const amsDelimiters = [
123
+ { left: "\\begin{equation}", right: "\\end{equation}", display: true },
124
+ { left: "\\begin{equation*}", right: "\\end{equation*}", display: true },
125
+ { left: "\\begin{align}", right: "\\end{align}", display: true },
126
+ { left: "\\begin{align*}", right: "\\end{align*}", display: true },
127
+ { left: "\\begin{alignat}", right: "\\end{alignat}", display: true },
128
+ { left: "\\begin{alignat*}", right: "\\end{alignat*}", display: true },
129
+ { left: "\\begin{gather}", right: "\\end{gather}", display: true },
130
+ { left: "\\begin{gather*}", right: "\\end{gather*}", display: true },
131
+ { left: "\\begin{CD}", right: "\\end{CD}", display: true },
132
+ { left: "\\ref{", right: "}", display: false },
133
+ { left: "\\eqref{", right: "}", display: false }
134
+ ];
135
+
136
+ const delimitersFromKey = key => {
137
+ if (key === "$" || key === "(") {
138
+ return firstDraftDelimiters[key];
139
+ } else if (key === "$+" || key === "(+") {
140
+ const firstDraft = firstDraftDelimiters[key.slice(0, 1)];
141
+ return firstDraft.concat(amsDelimiters)
142
+ } else if (key === "ams") {
143
+ return amsDelimiters
144
+ } else if (key === "all") {
145
+ return (firstDraftDelimiters["("]).concat(firstDraftDelimiters["$"]).concat(amsDelimiters)
146
+ } else {
147
+ return defaultDelimiters
148
+ }
149
+ }
150
+
151
+ /* Note: optionsCopy is mutated by this method. If it is ever exposed in the
152
+ * API, we should copy it before mutating.
153
+ */
154
+ const renderMathInText = function(text, optionsCopy) {
155
+ const data = splitAtDelimiters(text, optionsCopy.delimiters);
156
+ if (data.length === 1 && data[0].type === "text") {
157
+ // There is no formula in the text.
158
+ // Let's return null which means there is no need to replace
159
+ // the current text node with a new one.
160
+ return null;
161
+ }
162
+
163
+ const fragment = document.createDocumentFragment();
164
+
165
+ for (let i = 0; i < data.length; i++) {
166
+ if (data[i].type === "text") {
167
+ fragment.appendChild(document.createTextNode(data[i].data));
168
+ } else {
169
+ const span = document.createElement("span");
170
+ let math = data[i].data;
171
+ // Override any display mode defined in the settings with that
172
+ // defined by the text itself
173
+ optionsCopy.displayMode = data[i].display;
174
+ try {
175
+ if (optionsCopy.preProcess) {
176
+ math = optionsCopy.preProcess(math);
177
+ }
178
+ // Importing render() from temml.js would be a circular dependency.
179
+ // So call the global version.
180
+ // eslint-disable-next-line no-undef
181
+ temml.render(math, span, optionsCopy);
182
+ } catch (e) {
183
+ if (!(e instanceof ParseError)) {
184
+ throw e;
185
+ }
186
+ optionsCopy.errorCallback(
187
+ "Temml auto-render: Failed to parse `" + data[i].data + "` with ",
188
+ e
189
+ );
190
+ fragment.appendChild(document.createTextNode(data[i].rawData));
191
+ continue;
192
+ }
193
+ fragment.appendChild(span);
194
+ }
195
+ }
196
+
197
+ return fragment;
198
+ };
199
+
200
+ const renderElem = function(elem, optionsCopy) {
201
+ for (let i = 0; i < elem.childNodes.length; i++) {
202
+ const childNode = elem.childNodes[i];
203
+ if (childNode.nodeType === 3) {
204
+ // Text node
205
+ const frag = renderMathInText(childNode.textContent, optionsCopy);
206
+ if (frag) {
207
+ i += frag.childNodes.length - 1;
208
+ elem.replaceChild(frag, childNode);
209
+ }
210
+ } else if (childNode.nodeType === 1) {
211
+ // Element node
212
+ const className = " " + childNode.className + " ";
213
+ const shouldRender =
214
+ optionsCopy.ignoredTags.indexOf(childNode.nodeName.toLowerCase()) === -1 &&
215
+ optionsCopy.ignoredClasses.every((x) => className.indexOf(" " + x + " ") === -1);
216
+
217
+ if (shouldRender) {
218
+ renderElem(childNode, optionsCopy);
219
+ }
220
+ }
221
+ // Otherwise, it's something else, and ignore it.
222
+ }
223
+ };
224
+
225
+ export const renderMathInElement = function(elem, options) {
226
+ if (!elem) {
227
+ throw new Error("No element provided to render");
228
+ }
229
+
230
+ const optionsCopy = {};
231
+
232
+ // Object.assign(optionsCopy, option)
233
+ for (const option in options) {
234
+ if (Object.prototype.hasOwnProperty.call(options, option)) {
235
+ optionsCopy[option] = options[option];
236
+ }
237
+ }
238
+
239
+ if (optionsCopy.fences) {
240
+ optionsCopy.delimiters = delimitersFromKey(optionsCopy.fences);
241
+ } else {
242
+ optionsCopy.delimiters = optionsCopy.delimiters || defaultDelimiters
243
+ }
244
+ optionsCopy.ignoredTags = optionsCopy.ignoredTags || [
245
+ "script",
246
+ "noscript",
247
+ "style",
248
+ "textarea",
249
+ "pre",
250
+ "code",
251
+ "option"
252
+ ];
253
+ optionsCopy.ignoredClasses = optionsCopy.ignoredClasses || [];
254
+ // eslint-disable-next-line no-console
255
+ optionsCopy.errorCallback = optionsCopy.errorCallback || console.error;
256
+
257
+ // Enable sharing of global macros defined via `\gdef` between different
258
+ // math elements within a single call to `renderMathInElement`.
259
+ optionsCopy.macros = optionsCopy.macros || {};
260
+
261
+ renderElem(elem, optionsCopy);
262
+ postProcess(elem);
263
+ };
@@ -263,16 +263,36 @@ const glue = _ => {
263
263
  return new mathMLTree.MathNode("mtd", [], [], { padding: "0", width: "50%" })
264
264
  }
265
265
 
266
+ const labelContainers = ["mrow", "mtd", "mtable", "mtr"];
267
+ const getLabel = parent => {
268
+ for (const node of parent.children) {
269
+ if (node.type && labelContainers.includes(node.type)) {
270
+ if (node.classes && node.classes[0] === "tml-label") {
271
+ const label = node.label
272
+ return label
273
+ } else {
274
+ const label = getLabel(node)
275
+ if (label) { return label }
276
+ }
277
+ } else if (!node.type) {
278
+ const label = getLabel(node)
279
+ if (label) { return label }
280
+ }
281
+ }
282
+ }
283
+
266
284
  const taggedExpression = (expression, tag, style, leqno) => {
267
285
  tag = buildExpressionRow(tag[0].body, style)
268
286
  tag = consolidateText(tag)
269
287
  tag.classes.push("tml-tag")
270
288
 
289
+ const label = getLabel(expression) // from a \label{} function.
271
290
  expression = new mathMLTree.MathNode("mtd", [expression])
272
291
  const rowArray = [glue(), expression, glue()]
273
292
  rowArray[leqno ? 0 : 2].classes.push(leqno ? "tml-left" : "tml-right")
274
293
  rowArray[leqno ? 0 : 2].children.push(tag)
275
294
  const mtr = new mathMLTree.MathNode("mtr", rowArray, ["tml-tageqn"])
295
+ if (label) { mtr.setAttribute("id", label) }
276
296
  const table = new mathMLTree.MathNode("mtable", [mtr])
277
297
  table.style.width = "100%"
278
298
  table.setAttribute("displaystyle", "true")