execonvert 0.5.1 → 0.5.3
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.
- package/CHANGELOG.md +22 -0
- package/dist/cli/cli/execonvert.js +2 -2
- package/dist/cli/src/docx-import.js +57 -6
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,28 @@ Categorías usadas: **Añadido**, **Cambiado**, **Corregido**, **Eliminado**.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.5.3] - 2026-09-05
|
|
13
|
+
|
|
14
|
+
### Corregido
|
|
15
|
+
- Los `.elpx` generados desde `.docx` y `.md` se escribían sin comprimir, de
|
|
16
|
+
modo que ocupaban unas cuatro veces más de lo debido: un proyecto que
|
|
17
|
+
eXeLearning guarda en 2,4 MB salía de 10,3 MB. El empaquetado usa ahora el
|
|
18
|
+
mismo nivel de compresión habitual del formato, sin coste apreciable de
|
|
19
|
+
tiempo. Los archivos anteriores siguen abriéndose con normalidad.
|
|
20
|
+
|
|
21
|
+
## [0.5.2] - 2026-09-05
|
|
22
|
+
|
|
23
|
+
### Corregido
|
|
24
|
+
- Las fórmulas de los `.docx` se perdían al convertir desde la línea de órdenes:
|
|
25
|
+
el documento se recorría buscando los nodos `m:oMath` por espacio de nombres,
|
|
26
|
+
algo que el navegador resuelve pero linkedom —el DOM que usa la CLI— no, de
|
|
27
|
+
modo que no encontraba ninguna y las fórmulas desaparecían sin aviso. Ahora la
|
|
28
|
+
búsqueda funciona en ambos entornos y el fragmento OMML se reinterpreta con
|
|
29
|
+
`@xmldom/xmldom` antes de convertirlo, que es lo que el conversor entiende.
|
|
30
|
+
La versión web no estaba afectada. Se añade una prueba de regresión
|
|
31
|
+
(`npm run test:docx`) que comprueba que una fórmula llega a `content.xml`
|
|
32
|
+
convertida en LaTeX.
|
|
33
|
+
|
|
12
34
|
## [0.5.1] - 2026-09-05
|
|
13
35
|
|
|
14
36
|
### Cambiado
|
|
@@ -10,9 +10,9 @@ import { convertElpToElpx } from '../src/legacy-elp.js';
|
|
|
10
10
|
import { convertMarkdownToElpx } from '../src/markdown-import.js';
|
|
11
11
|
import { installCliRuntime } from './runtime.js';
|
|
12
12
|
import { automaticCheck, autoCheckAllowed, runUpdate, updateText } from './updates.js';
|
|
13
|
-
const CLI_VERSION = '0.5.
|
|
13
|
+
const CLI_VERSION = '0.5.3'.startsWith('__')
|
|
14
14
|
? String(JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')).version)
|
|
15
|
-
: '0.5.
|
|
15
|
+
: '0.5.3';
|
|
16
16
|
const cliMessages = {
|
|
17
17
|
es: {
|
|
18
18
|
'help.title': 'CLI de eXeConvert',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { unzipSync, zipSync } from 'fflate';
|
|
2
2
|
import mammoth from 'mammoth';
|
|
3
3
|
import { MathMLToLaTeX } from 'mathml-to-latex';
|
|
4
|
-
import { XMLSerializer as XmldomSerializer } from '@xmldom/xmldom';
|
|
4
|
+
import { DOMParser as XmldomParser, XMLSerializer as XmldomSerializer } from '@xmldom/xmldom';
|
|
5
5
|
// @ts-expect-error the vendored omml2mathml ships no TypeScript declarations.
|
|
6
6
|
import omml2mathml from './vendor/omml2mathml/index.js';
|
|
7
7
|
import temml from 'temml';
|
|
@@ -197,16 +197,16 @@ async function preprocessDocxMath(inputBuffer) {
|
|
|
197
197
|
const document = parseXml(documentXml, 'No se ha podido interpretar word/document.xml.');
|
|
198
198
|
const formulas = new Map();
|
|
199
199
|
let formulaIndex = 1;
|
|
200
|
-
const blockMathNodes =
|
|
200
|
+
const blockMathNodes = findOmmlNodes(document, 'oMathPara');
|
|
201
201
|
for (const mathNode of blockMathNodes) {
|
|
202
202
|
const placeholder = createMathPlaceholder(formulaIndex++);
|
|
203
203
|
const latex = await convertOmmlElementToLatex(mathNode);
|
|
204
204
|
formulas.set(placeholder, `\\[${latex}\\]`);
|
|
205
205
|
replaceMathNodeWithPlaceholder(document, mathNode, placeholder);
|
|
206
206
|
}
|
|
207
|
-
const inlineMathNodes =
|
|
207
|
+
const inlineMathNodes = findOmmlNodes(document, 'oMath');
|
|
208
208
|
for (const mathNode of inlineMathNodes) {
|
|
209
|
-
if (mathNode.parentElement
|
|
209
|
+
if (isOmmlElement(mathNode.parentElement, 'oMathPara')) {
|
|
210
210
|
continue;
|
|
211
211
|
}
|
|
212
212
|
const placeholder = createMathPlaceholder(formulaIndex++);
|
|
@@ -221,9 +221,57 @@ async function preprocessDocxMath(inputBuffer) {
|
|
|
221
221
|
const patchedBuffer = patchedBytes.buffer.slice(patchedBytes.byteOffset, patchedBytes.byteOffset + patchedBytes.byteLength);
|
|
222
222
|
return { arrayBuffer: patchedBuffer, formulas };
|
|
223
223
|
}
|
|
224
|
+
// Word writes formulas as <m:oMath>. Looking them up by namespace is the correct
|
|
225
|
+
// way and works in the browser, but linkedom -- the DOM the CLI runs on -- does
|
|
226
|
+
// not resolve namespaces in XML documents and finds nothing, which silently
|
|
227
|
+
// dropped every formula from .docx files converted from the command line.
|
|
228
|
+
// So: namespace first, qualified name as a fallback.
|
|
229
|
+
function findOmmlNodes(document, localName) {
|
|
230
|
+
const byNamespace = Array.from(document.getElementsByTagNameNS(M_NS, localName));
|
|
231
|
+
if (byNamespace.length > 0) {
|
|
232
|
+
return byNamespace;
|
|
233
|
+
}
|
|
234
|
+
// Not even getElementsByTagName('*') is usable there: on an XML document it
|
|
235
|
+
// returns nothing. Walking the tree by hand is what survives both DOMs.
|
|
236
|
+
const found = [];
|
|
237
|
+
const visit = (node) => {
|
|
238
|
+
for (const child of Array.from(node.childNodes)) {
|
|
239
|
+
if (child.nodeType !== 1) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const element = child;
|
|
243
|
+
if (isOmmlElement(element, localName)) {
|
|
244
|
+
found.push(element);
|
|
245
|
+
}
|
|
246
|
+
visit(element);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
visit(document);
|
|
250
|
+
return found;
|
|
251
|
+
}
|
|
252
|
+
function isOmmlElement(element, localName) {
|
|
253
|
+
if (!element) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
// linkedom keeps the prefix inside localName and reports the wrong namespace,
|
|
257
|
+
// so compare the name with any prefix stripped, in both DOMs.
|
|
258
|
+
const name = (element.localName ?? element.tagName ?? '').replace(/^[^:]*:/, '');
|
|
259
|
+
if (name !== localName) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return element.namespaceURI === M_NS || (element.tagName ?? '').startsWith('m:');
|
|
263
|
+
}
|
|
224
264
|
async function convertOmmlElementToLatex(element) {
|
|
225
265
|
try {
|
|
226
|
-
|
|
266
|
+
// omml2mathml returns null when fed linkedom nodes, so the fragment is
|
|
267
|
+
// reparsed with the same DOM it builds its output on. Serializing it detaches
|
|
268
|
+
// it from the namespace declarations on <w:document>, so they are restated on
|
|
269
|
+
// a wrapper; without them the fragment is not well-formed XML.
|
|
270
|
+
const ommlSource = new XMLSerializer().serializeToString(element);
|
|
271
|
+
const wrapped = `<w:wrapper xmlns:w="${W_NS}" xmlns:m="${M_NS}">${ommlSource}</w:wrapper>`;
|
|
272
|
+
const ommlDocument = new XmldomParser().parseFromString(wrapped, 'text/xml');
|
|
273
|
+
const ommlRoot = ommlDocument.documentElement?.firstChild;
|
|
274
|
+
const mathElement = omml2mathml(ommlRoot);
|
|
227
275
|
// El árbol MathML lo construye @xmldom/xmldom, así que lo serializa el suyo:
|
|
228
276
|
// el XMLSerializer global es el del entorno (linkedom en la CLI) y no
|
|
229
277
|
// entiende esos nodos.
|
|
@@ -392,7 +440,10 @@ function buildElpxFromTemplate(template, project) {
|
|
|
392
440
|
const { entries } = template;
|
|
393
441
|
entries['content.xml'] = new TextEncoder().encode(generateContentXml(project));
|
|
394
442
|
addPreviewHtmlEntries(entries, project);
|
|
395
|
-
|
|
443
|
+
// Stored entries made a .elpx four times bigger than the one eXeLearning
|
|
444
|
+
// writes for the same project. Level 6 is what the format is usually packed
|
|
445
|
+
// with, and the bundled runtime compresses well.
|
|
446
|
+
return zipSync(entries, { level: 6 });
|
|
396
447
|
}
|
|
397
448
|
function buildStandalonePreviewPages(project, entries, previewOptions = {}) {
|
|
398
449
|
const pages = getPreviewPages(project);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "execonvert",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"prepack": "npm run build:cli",
|
|
39
39
|
"test:updates": "node --import tsx --test tests/updates.test.ts",
|
|
40
40
|
"test:runtime": "node scripts/test-runtime.mjs",
|
|
41
|
-
"test:cli": "node --test tests/cli-update.test.mjs"
|
|
41
|
+
"test:cli": "node --test tests/cli-update.test.mjs",
|
|
42
|
+
"test:docx": "node --test tests/docx-omml.test.mjs"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"@resvg/resvg-js": "^2.6.2",
|