execonvert 0.5.1 → 0.5.2
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 +13 -0
- package/dist/cli/cli/execonvert.js +2 -2
- package/dist/cli/src/docx-import.js +53 -5
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,19 @@ Categorías usadas: **Añadido**, **Cambiado**, **Corregido**, **Eliminado**.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.5.2] - 2026-09-05
|
|
13
|
+
|
|
14
|
+
### Corregido
|
|
15
|
+
- Las fórmulas de los `.docx` se perdían al convertir desde la línea de órdenes:
|
|
16
|
+
el documento se recorría buscando los nodos `m:oMath` por espacio de nombres,
|
|
17
|
+
algo que el navegador resuelve pero linkedom —el DOM que usa la CLI— no, de
|
|
18
|
+
modo que no encontraba ninguna y las fórmulas desaparecían sin aviso. Ahora la
|
|
19
|
+
búsqueda funciona en ambos entornos y el fragmento OMML se reinterpreta con
|
|
20
|
+
`@xmldom/xmldom` antes de convertirlo, que es lo que el conversor entiende.
|
|
21
|
+
La versión web no estaba afectada. Se añade una prueba de regresión
|
|
22
|
+
(`npm run test:docx`) que comprueba que una fórmula llega a `content.xml`
|
|
23
|
+
convertida en LaTeX.
|
|
24
|
+
|
|
12
25
|
## [0.5.1] - 2026-09-05
|
|
13
26
|
|
|
14
27
|
### 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.2'.startsWith('__')
|
|
14
14
|
? String(JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')).version)
|
|
15
|
-
: '0.5.
|
|
15
|
+
: '0.5.2';
|
|
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "execonvert",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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",
|