execonvert 0.4.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.
- package/CHANGELOG.md +79 -0
- package/README.md +194 -0
- package/THIRD_PARTY_NOTICES.md +42 -0
- package/bin/execonvert.js +21 -0
- package/dist/cli/app/public/THIRD_PARTY_NOTICES.md +32 -0
- package/dist/cli/app/public/base.elpx +0 -0
- package/dist/cli/app/public/exe_math_assets.zip +0 -0
- package/dist/cli/app/public/exelearning/app/common/common_i18n.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/exe_powered_logo/exe_powered_logo.png +0 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.ca.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.de.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.en.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.eo.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.es.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.eu.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.gl.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.it.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.pt.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.ro.js +79 -0
- package/dist/cli/app/public/exelearning/app/common/i18n/common_i18n.va.js +79 -0
- package/dist/cli/app/public/exelearning/bundles/common.zip +0 -0
- package/dist/cli/app/public/exelearning/bundles/content-css.zip +0 -0
- package/dist/cli/app/public/exelearning/bundles/idevices.zip +0 -0
- package/dist/cli/app/public/exelearning/bundles/libs.zip +0 -0
- package/dist/cli/app/public/exelearning/bundles/manifest.json +110 -0
- package/dist/cli/app/public/exelearning/bundles/themes/base.zip +0 -0
- package/dist/cli/app/public/exelearning/bundles/themes/flux.zip +0 -0
- package/dist/cli/app/public/exelearning/bundles/themes/neo.zip +0 -0
- package/dist/cli/app/public/exelearning/bundles/themes/nova.zip +0 -0
- package/dist/cli/app/public/exelearning/bundles/themes/universal.zip +0 -0
- package/dist/cli/app/public/exelearning/bundles/themes/zen.zip +0 -0
- package/dist/cli/app/public/exelearning/exporters.bundle.js +12638 -0
- package/dist/cli/app/public/exelearning/importers.bundle.js +14061 -0
- package/dist/cli/app/public/exelearning/runtime-source.json +10 -0
- package/dist/cli/app/public/favicon.svg +11 -0
- package/dist/cli/app/public/idevices/text/text.css +16 -0
- package/dist/cli/app/public/idevices/text/text.html +3 -0
- package/dist/cli/app/public/idevices/text/text.js +291 -0
- package/dist/cli/app/public/info/cli.html +955 -0
- package/dist/cli/app/public/info/docx.svg +7 -0
- package/dist/cli/app/public/info/elpx-icon.png +0 -0
- package/dist/cli/app/public/info/exe-old.png +0 -0
- package/dist/cli/app/public/info/execonvert-cli-icon.svg +16 -0
- package/dist/cli/app/public/info/execonvert-icon.svg +11 -0
- package/dist/cli/app/public/info/index.html +723 -0
- package/dist/cli/app/public/info/markdown.svg +1 -0
- package/dist/cli/app/public/info/pdf.svg +14 -0
- package/dist/cli/app/public/libs/exe_math/LICENSE +202 -0
- package/dist/cli/app/public/libs/exe_math/a11y/assistive-mml.js +1 -0
- package/dist/cli/app/public/libs/exe_math/a11y/complexity.js +1 -0
- package/dist/cli/app/public/libs/exe_math/a11y/explorer.js +1 -0
- package/dist/cli/app/public/libs/exe_math/a11y/semantic-enrich.js +1 -0
- package/dist/cli/app/public/libs/exe_math/a11y/speech.js +1 -0
- package/dist/cli/app/public/libs/exe_math/a11y/sre.js +1 -0
- package/dist/cli/app/public/libs/exe_math/adaptors/jsdom.js +1 -0
- package/dist/cli/app/public/libs/exe_math/adaptors/linkedom.js +1 -0
- package/dist/cli/app/public/libs/exe_math/adaptors/liteDOM.js +1 -0
- package/dist/cli/app/public/libs/exe_math/core.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/asciimath.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/mml/entities.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/mml/extensions/mml3.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/mml/extensions/mml3.sef.json +1 -0
- package/dist/cli/app/public/libs/exe_math/input/mml.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/action.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/ams.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/amscd.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/autoload.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/bbm.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/bboldx.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/bbox.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/begingroup.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/boldsymbol.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/braket.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/bussproofs.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/cancel.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/cases.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/centernot.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/color.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/colortbl.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/colorv2.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/configmacros.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/dsfont.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/empheq.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/enclose.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/extpfeil.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/gensymb.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/html.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/mathtools.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/mhchem.js +34 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/newcommand.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/noerrors.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/noundefined.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/physics.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/require.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/setoptions.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/tagformat.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/texhtml.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/textcomp.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/textmacros.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/unicode.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/units.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/upgreek.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex/extensions/verb.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex-base.js +1 -0
- package/dist/cli/app/public/libs/exe_math/input/tex.js +1 -0
- package/dist/cli/app/public/libs/exe_math/loader.js +1 -0
- package/dist/cli/app/public/libs/exe_math/output/chtml.js +1 -0
- package/dist/cli/app/public/libs/exe_math/output/svg.js +1 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/af.json +146 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/base.json +137 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/ca.json +140 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/da.json +140 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/de.json +146 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/en.json +158 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/es.json +140 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/euro.json +32 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/fr.json +146 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/hi.json +146 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/it.json +146 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/ko.json +146 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/nb.json +146 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/nemeth.json +125 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/nn.json +146 -0
- package/dist/cli/app/public/libs/exe_math/sre/mathmaps/sv.json +146 -0
- package/dist/cli/app/public/libs/exe_math/sre/require.d.mts +1 -0
- package/dist/cli/app/public/libs/exe_math/sre/require.mjs +2 -0
- package/dist/cli/app/public/libs/exe_math/sre/speech-worker.js +1 -0
- package/dist/cli/app/public/libs/exe_math/startup.js +1 -0
- package/dist/cli/app/public/libs/exe_math/tex-mml-svg.js +1 -0
- package/dist/cli/app/public/libs/exe_math/ui/lazy.js +1 -0
- package/dist/cli/app/public/libs/exe_math/ui/menu.js +1 -0
- package/dist/cli/app/public/libs/exe_math/ui/safe.js +1 -0
- package/dist/cli/cli/execonvert.js +807 -0
- package/dist/cli/cli/runtime.js +237 -0
- package/dist/cli/package.json +3 -0
- package/dist/cli/src/converter.js +5078 -0
- package/dist/cli/src/docx-import.js +1121 -0
- package/dist/cli/src/elpx-markdown.js +201 -0
- package/dist/cli/src/i18n.js +485 -0
- package/dist/cli/src/index.js +1 -0
- package/dist/cli/src/legacy-elp.js +846 -0
- package/dist/cli/src/markdown-import.js +26 -0
- package/package.json +68 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
import { basename, dirname, extname, resolve } from 'node:path';
|
|
2
|
+
import { globSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { stderr, stdout } from 'node:process';
|
|
5
|
+
import { createI18n } from '../src/i18n.js';
|
|
6
|
+
import { buildPrintableHtmlDocument, buildPdfBlobFromPrintableHtml, convertElpxToDocx, convertElpxToHtml, convertHtmlToDocxResult, inspectElpxPages, } from '../src/converter.js';
|
|
7
|
+
import { convertDocxToElpx } from '../src/docx-import.js';
|
|
8
|
+
import { convertElpxToMarkdown } from '../src/elpx-markdown.js';
|
|
9
|
+
import { convertElpToElpx } from '../src/legacy-elp.js';
|
|
10
|
+
import { convertMarkdownToElpx } from '../src/markdown-import.js';
|
|
11
|
+
import { installCliRuntime } from './runtime.js';
|
|
12
|
+
const CLI_VERSION = '0.3.1';
|
|
13
|
+
const cliMessages = {
|
|
14
|
+
es: {
|
|
15
|
+
'help.title': 'CLI de eXeConvert',
|
|
16
|
+
'help.versionLine': 'Versión: {version}',
|
|
17
|
+
'help.usage': 'Uso',
|
|
18
|
+
'help.inputs': 'Entradas compatibles',
|
|
19
|
+
'help.outputs': 'Salidas compatibles',
|
|
20
|
+
'help.options': 'Opciones',
|
|
21
|
+
'help.examples': 'Ejemplos',
|
|
22
|
+
'help.input.elp': '.elp',
|
|
23
|
+
'help.input.elpx': '.elpx',
|
|
24
|
+
'help.input.docx': '.docx',
|
|
25
|
+
'help.input.markdown': '.md, .txt',
|
|
26
|
+
'help.output.elp': 'desde .elp: .elpx, .md, .docx, .pdf',
|
|
27
|
+
'help.output.elpx': 'desde .elpx: .md, .docx, .pdf',
|
|
28
|
+
'help.output.docx': 'desde .docx: .elpx',
|
|
29
|
+
'help.output.markdown': 'desde .md/.txt: .elpx',
|
|
30
|
+
'help.option.to': 'Formato de salida para conversión múltiple (elpx, docx, md, pdf). Los archivos no compatibles se ignoran.',
|
|
31
|
+
'help.option.outDir': 'Directorio de salida (por defecto: mismo directorio que cada entrada)',
|
|
32
|
+
'help.option.json': 'Imprime JSON legible por máquina',
|
|
33
|
+
'help.option.pages': 'Exporta solo las referencias de página seleccionadas de un .elpx',
|
|
34
|
+
'help.option.pageId': 'Exporta solo el identificador interno de página indicado (repetible)',
|
|
35
|
+
'help.option.pageIds': 'Exporta solo los identificadores internos de página indicados',
|
|
36
|
+
'help.option.images': 'Mantiene imágenes embebidas al exportar .elpx a Markdown',
|
|
37
|
+
'help.option.h1': 'page | resource-title',
|
|
38
|
+
'help.option.h2': 'idevice | subpage',
|
|
39
|
+
'help.option.h3': 'idevice | subpage',
|
|
40
|
+
'help.option.h4': 'idevice | subpage',
|
|
41
|
+
'help.option.lang': 'es | ca | en',
|
|
42
|
+
'help.option.help': 'Muestra esta ayuda',
|
|
43
|
+
'help.option.version': 'Muestra la versión',
|
|
44
|
+
'error.inspectArity': 'inspect espera exactamente un archivo de entrada.',
|
|
45
|
+
'error.convertArity': 'convert espera exactamente un archivo de entrada y uno de salida.',
|
|
46
|
+
'error.unknownOption': 'Opción desconocida: {value}',
|
|
47
|
+
'error.missingValue': 'Falta un valor para {flag}.',
|
|
48
|
+
'error.invalidValue': 'Valor no válido "{value}". Valores permitidos: {allowed}.',
|
|
49
|
+
'error.h3RequiresH2': '--h3 subpage requiere --h2 subpage.',
|
|
50
|
+
'error.h4RequiresH3': '--h4 subpage requiere --h3 subpage.',
|
|
51
|
+
'error.unsupportedExtension': 'Extensión de archivo no compatible: {extension}.',
|
|
52
|
+
'error.unknownPageRef': 'Referencia de página desconocida: {ref}',
|
|
53
|
+
'error.unsupportedConversion': 'Conversión no compatible: {input} -> {output}',
|
|
54
|
+
'error.prefix': 'Error: {message}',
|
|
55
|
+
'error.batchArity': 'El modo --to requiere al menos un archivo de entrada.',
|
|
56
|
+
'error.batchNoTo': 'Se esperaban exactamente dos archivos (entrada y salida). Usa --to <formato> para convertir múltiples archivos.',
|
|
57
|
+
'result.written': 'Escrito {output}',
|
|
58
|
+
'result.batchDone': '{count} archivo(s) convertido(s).',
|
|
59
|
+
'progress.generatePdf': 'Generando el documento .pdf...',
|
|
60
|
+
'progress.composePdf': 'Componiendo el documento .pdf...',
|
|
61
|
+
},
|
|
62
|
+
ca: {
|
|
63
|
+
'help.title': 'CLI d’eXeConvert',
|
|
64
|
+
'help.versionLine': 'Versió: {version}',
|
|
65
|
+
'help.usage': 'Ús',
|
|
66
|
+
'help.inputs': 'Entrades compatibles',
|
|
67
|
+
'help.outputs': 'Sortides compatibles',
|
|
68
|
+
'help.options': 'Opcions',
|
|
69
|
+
'help.examples': 'Exemples',
|
|
70
|
+
'help.input.elp': '.elp',
|
|
71
|
+
'help.input.elpx': '.elpx',
|
|
72
|
+
'help.input.docx': '.docx',
|
|
73
|
+
'help.input.markdown': '.md, .txt',
|
|
74
|
+
'help.output.elp': 'des de .elp: .elpx, .md, .docx, .pdf',
|
|
75
|
+
'help.output.elpx': 'des de .elpx: .md, .docx, .pdf',
|
|
76
|
+
'help.output.docx': 'des de .docx: .elpx',
|
|
77
|
+
'help.output.markdown': 'des de .md/.txt: .elpx',
|
|
78
|
+
'help.option.to': 'Format de sortida per a conversió múltiple (elpx, docx, md, pdf). Els fitxers no compatibles s’ignoren.',
|
|
79
|
+
'help.option.outDir': 'Directori de sortida (per defecte: mateix directori que cada entrada)',
|
|
80
|
+
'help.option.json': 'Imprimeix JSON llegible per màquines',
|
|
81
|
+
'help.option.pages': 'Exporta només les referències de pàgina seleccionades d’un .elpx',
|
|
82
|
+
'help.option.pageId': 'Exporta només l’identificador intern de pàgina indicat (repetible)',
|
|
83
|
+
'help.option.pageIds': 'Exporta només els identificadors interns de pàgina indicats',
|
|
84
|
+
'help.option.images': 'Manté imatges incrustades en exportar .elpx a Markdown',
|
|
85
|
+
'help.option.h1': 'page | resource-title',
|
|
86
|
+
'help.option.h2': 'idevice | subpage',
|
|
87
|
+
'help.option.h3': 'idevice | subpage',
|
|
88
|
+
'help.option.h4': 'idevice | subpage',
|
|
89
|
+
'help.option.lang': 'es | ca | en',
|
|
90
|
+
'help.option.help': 'Mostra aquesta ajuda',
|
|
91
|
+
'help.option.version': 'Mostra la versió',
|
|
92
|
+
'error.inspectArity': 'inspect espera exactament un fitxer d’entrada.',
|
|
93
|
+
'error.convertArity': 'convert espera exactament un fitxer d’entrada i un de sortida.',
|
|
94
|
+
'error.unknownOption': 'Opció desconeguda: {value}',
|
|
95
|
+
'error.missingValue': 'Falta un valor per a {flag}.',
|
|
96
|
+
'error.invalidValue': 'Valor no vàlid "{value}". Valors permesos: {allowed}.',
|
|
97
|
+
'error.h3RequiresH2': '--h3 subpage requereix --h2 subpage.',
|
|
98
|
+
'error.h4RequiresH3': '--h4 subpage requereix --h3 subpage.',
|
|
99
|
+
'error.unsupportedExtension': 'Extensió de fitxer no compatible: {extension}.',
|
|
100
|
+
'error.unknownPageRef': 'Referència de pàgina desconeguda: {ref}',
|
|
101
|
+
'error.unsupportedConversion': 'Conversió no compatible: {input} -> {output}',
|
|
102
|
+
'error.prefix': 'Error: {message}',
|
|
103
|
+
'error.batchArity': 'El mode --to requereix almenys un fitxer d’entrada.',
|
|
104
|
+
'error.batchNoTo': 'S’esperaven exactament dos fitxers (entrada i sortida). Usa --to <format> per convertir múltiples fitxers.',
|
|
105
|
+
'result.written': 'S’ha escrit {output}',
|
|
106
|
+
'result.batchDone': '{count} fitxer(s) convertit(s).',
|
|
107
|
+
'progress.generatePdf': 'S’està generant el document .pdf...',
|
|
108
|
+
'progress.composePdf': 'S’està component el document .pdf...',
|
|
109
|
+
},
|
|
110
|
+
en: {
|
|
111
|
+
'help.title': 'eXeConvert CLI',
|
|
112
|
+
'help.versionLine': 'Version: {version}',
|
|
113
|
+
'help.usage': 'Usage',
|
|
114
|
+
'help.inputs': 'Supported inputs',
|
|
115
|
+
'help.outputs': 'Supported outputs',
|
|
116
|
+
'help.options': 'Options',
|
|
117
|
+
'help.examples': 'Examples',
|
|
118
|
+
'help.input.elp': '.elp',
|
|
119
|
+
'help.input.elpx': '.elpx',
|
|
120
|
+
'help.input.docx': '.docx',
|
|
121
|
+
'help.input.markdown': '.md, .txt',
|
|
122
|
+
'help.output.elp': 'from .elp: .elpx, .md, .docx, .pdf',
|
|
123
|
+
'help.output.elpx': 'from .elpx: .md, .docx, .pdf',
|
|
124
|
+
'help.output.docx': 'from .docx: .elpx',
|
|
125
|
+
'help.output.markdown': 'from .md/.txt: .elpx',
|
|
126
|
+
'help.option.to': 'Output format for batch conversion (elpx, docx, md, pdf). Incompatible files are skipped.',
|
|
127
|
+
'help.option.outDir': 'Output directory (default: same directory as each input)',
|
|
128
|
+
'help.option.json': 'Print machine-readable JSON',
|
|
129
|
+
'help.option.pages': 'Export only selected page refs from an .elpx input',
|
|
130
|
+
'help.option.pageId': 'Export only the given internal page id (repeatable)',
|
|
131
|
+
'help.option.pageIds': 'Export only the given internal page ids',
|
|
132
|
+
'help.option.images': 'Keep embedded images when exporting .elpx to Markdown',
|
|
133
|
+
'help.option.h1': 'page | resource-title',
|
|
134
|
+
'help.option.h2': 'idevice | subpage',
|
|
135
|
+
'help.option.h3': 'idevice | subpage',
|
|
136
|
+
'help.option.h4': 'idevice | subpage',
|
|
137
|
+
'help.option.lang': 'es | ca | en',
|
|
138
|
+
'help.option.help': 'Show this help',
|
|
139
|
+
'help.option.version': 'Show the version',
|
|
140
|
+
'error.inspectArity': 'inspect expects exactly one input file.',
|
|
141
|
+
'error.convertArity': 'convert expects exactly one input file and one output file.',
|
|
142
|
+
'error.unknownOption': 'Unknown option: {value}',
|
|
143
|
+
'error.missingValue': 'Missing value for {flag}.',
|
|
144
|
+
'error.invalidValue': 'Invalid value "{value}". Allowed values: {allowed}.',
|
|
145
|
+
'error.h3RequiresH2': '--h3 subpage requires --h2 subpage.',
|
|
146
|
+
'error.h4RequiresH3': '--h4 subpage requires --h3 subpage.',
|
|
147
|
+
'error.unsupportedExtension': 'Unsupported file extension: {extension}.',
|
|
148
|
+
'error.unknownPageRef': 'Unknown page ref: {ref}',
|
|
149
|
+
'error.unsupportedConversion': 'Unsupported conversion: {input} -> {output}',
|
|
150
|
+
'error.prefix': 'Error: {message}',
|
|
151
|
+
'error.batchArity': '--to mode requires at least one input file.',
|
|
152
|
+
'error.batchNoTo': 'Expected exactly two files (input and output). Use --to <format> to convert multiple files.',
|
|
153
|
+
'result.written': 'Written {output}',
|
|
154
|
+
'result.batchDone': '{count} file(s) converted.',
|
|
155
|
+
'progress.generatePdf': 'Generating the .pdf document...',
|
|
156
|
+
'progress.composePdf': 'Composing the .pdf document...',
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
function createCliTranslator(locale) {
|
|
160
|
+
const { t: baseT } = createI18n(locale);
|
|
161
|
+
const dictionary = cliMessages[locale] ?? cliMessages.en;
|
|
162
|
+
const fallback = cliMessages.en;
|
|
163
|
+
return {
|
|
164
|
+
locale,
|
|
165
|
+
t(key, vars) {
|
|
166
|
+
const template = dictionary[key] ?? fallback[key];
|
|
167
|
+
if (!template) {
|
|
168
|
+
return baseT(key, vars);
|
|
169
|
+
}
|
|
170
|
+
if (!vars) {
|
|
171
|
+
return template;
|
|
172
|
+
}
|
|
173
|
+
return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, name) => {
|
|
174
|
+
const value = vars[name];
|
|
175
|
+
return value === undefined ? '' : String(value);
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function normalizeLocaleToken(value) {
|
|
181
|
+
const normalized = (value || '').trim().toLowerCase();
|
|
182
|
+
if (!normalized) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
if (normalized.startsWith('ca'))
|
|
186
|
+
return 'ca';
|
|
187
|
+
if (normalized.startsWith('es'))
|
|
188
|
+
return 'es';
|
|
189
|
+
if (normalized.startsWith('en'))
|
|
190
|
+
return 'en';
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
function resolveCliLocale(argv) {
|
|
194
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
195
|
+
const value = argv[index];
|
|
196
|
+
if ((value === '--lang' || value === '--locale') && index + 1 < argv.length) {
|
|
197
|
+
return normalizeLocaleToken(argv[index + 1]) ?? 'en';
|
|
198
|
+
}
|
|
199
|
+
if (value.startsWith('--lang=')) {
|
|
200
|
+
return normalizeLocaleToken(value.slice('--lang='.length)) ?? 'en';
|
|
201
|
+
}
|
|
202
|
+
if (value.startsWith('--locale=')) {
|
|
203
|
+
return normalizeLocaleToken(value.slice('--locale='.length)) ?? 'en';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return (normalizeLocaleToken(process.env.LC_ALL) ??
|
|
207
|
+
normalizeLocaleToken(process.env.LC_MESSAGES) ??
|
|
208
|
+
normalizeLocaleToken(process.env.LANG) ??
|
|
209
|
+
'en');
|
|
210
|
+
}
|
|
211
|
+
function printHelp(t) {
|
|
212
|
+
stdout.write(`${t('help.title')}
|
|
213
|
+
${t('help.versionLine', { version: CLI_VERSION })}
|
|
214
|
+
|
|
215
|
+
${t('help.usage')}:
|
|
216
|
+
execonvert <input> <output> [options]
|
|
217
|
+
execonvert <input1> [input2 ...] --to <format> [--out-dir <dir>] [options]
|
|
218
|
+
execonvert inspect <input> [--json]
|
|
219
|
+
|
|
220
|
+
${t('help.inputs')}:
|
|
221
|
+
${t('help.input.elp')}
|
|
222
|
+
${t('help.input.elpx')}
|
|
223
|
+
${t('help.input.docx')}
|
|
224
|
+
${t('help.input.markdown')}
|
|
225
|
+
|
|
226
|
+
${t('help.outputs')}:
|
|
227
|
+
${t('help.output.elp')}
|
|
228
|
+
${t('help.output.elpx')}
|
|
229
|
+
${t('help.output.docx')}
|
|
230
|
+
${t('help.output.markdown')}
|
|
231
|
+
|
|
232
|
+
${t('help.options')}:
|
|
233
|
+
--to <format> ${t('help.option.to')}
|
|
234
|
+
--out-dir <dir> ${t('help.option.outDir')}
|
|
235
|
+
--json ${t('help.option.json')}
|
|
236
|
+
--pages 1,2.1 ${t('help.option.pages')}
|
|
237
|
+
--page-id <id> ${t('help.option.pageId')}
|
|
238
|
+
--page-ids a,b,c ${t('help.option.pageIds')}
|
|
239
|
+
--images ${t('help.option.images')}
|
|
240
|
+
--h1 <mode> ${t('help.option.h1')}
|
|
241
|
+
--h2 <mode> ${t('help.option.h2')}
|
|
242
|
+
--h3 <mode> ${t('help.option.h3')}
|
|
243
|
+
--h4 <mode> ${t('help.option.h4')}
|
|
244
|
+
--lang <code> ${t('help.option.lang')}
|
|
245
|
+
--help ${t('help.option.help')}
|
|
246
|
+
--version ${t('help.option.version')}
|
|
247
|
+
|
|
248
|
+
${t('help.examples')}:
|
|
249
|
+
execonvert notes.md notes.elpx --h1 resource-title --h2 subpage --h3 idevice
|
|
250
|
+
execonvert project.elpx project.docx --pages 1,2.1
|
|
251
|
+
execonvert project.elpx project.pdf --pages 1,2.1
|
|
252
|
+
execonvert *.elpx --to docx
|
|
253
|
+
execonvert *.elpx --to pdf --out-dir ./output
|
|
254
|
+
execonvert inspect project.elpx --json
|
|
255
|
+
`);
|
|
256
|
+
}
|
|
257
|
+
function parseArgs(argv, t, locale) {
|
|
258
|
+
const defaults = {
|
|
259
|
+
command: 'convert',
|
|
260
|
+
locale,
|
|
261
|
+
json: false,
|
|
262
|
+
includeImages: false,
|
|
263
|
+
h1: 'page',
|
|
264
|
+
h2: 'idevice',
|
|
265
|
+
h3: 'idevice',
|
|
266
|
+
h4: 'idevice',
|
|
267
|
+
pages: [],
|
|
268
|
+
pageIds: [],
|
|
269
|
+
};
|
|
270
|
+
const args = [...argv];
|
|
271
|
+
if (args.includes('--version') || args.includes('-V')) {
|
|
272
|
+
return { ...defaults, command: 'version' };
|
|
273
|
+
}
|
|
274
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
275
|
+
return { ...defaults, command: 'help' };
|
|
276
|
+
}
|
|
277
|
+
if (args[0] === 'inspect') {
|
|
278
|
+
args.shift();
|
|
279
|
+
const parsed = parseOptionFlags(args, defaults, t);
|
|
280
|
+
if (parsed.positionals.length !== 1) {
|
|
281
|
+
throw new Error(t('error.inspectArity'));
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
...defaults,
|
|
285
|
+
...parsed.options,
|
|
286
|
+
command: 'inspect',
|
|
287
|
+
inputPath: parsed.positionals[0],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const parsed = parseOptionFlags(args, defaults, t);
|
|
291
|
+
if (parsed.options.to !== undefined) {
|
|
292
|
+
if (parsed.positionals.length === 0) {
|
|
293
|
+
throw new Error(t('error.batchArity'));
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
...defaults,
|
|
297
|
+
...parsed.options,
|
|
298
|
+
command: 'batch',
|
|
299
|
+
inputPaths: parsed.positionals,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (parsed.positionals.length !== 2) {
|
|
303
|
+
throw new Error(t('error.batchNoTo'));
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
...defaults,
|
|
307
|
+
...parsed.options,
|
|
308
|
+
command: 'convert',
|
|
309
|
+
inputPath: parsed.positionals[0],
|
|
310
|
+
outputPath: parsed.positionals[1],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function parseOptionFlags(args, defaults, t) {
|
|
314
|
+
const positionals = [];
|
|
315
|
+
const options = {
|
|
316
|
+
json: defaults.json,
|
|
317
|
+
includeImages: defaults.includeImages,
|
|
318
|
+
h1: defaults.h1,
|
|
319
|
+
h2: defaults.h2,
|
|
320
|
+
h3: defaults.h3,
|
|
321
|
+
h4: defaults.h4,
|
|
322
|
+
pages: [],
|
|
323
|
+
pageIds: [],
|
|
324
|
+
to: defaults.to,
|
|
325
|
+
outDir: defaults.outDir,
|
|
326
|
+
};
|
|
327
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
328
|
+
const value = args[index];
|
|
329
|
+
if (!value.startsWith('--')) {
|
|
330
|
+
positionals.push(value);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const nextValue = args[index + 1];
|
|
334
|
+
switch (value) {
|
|
335
|
+
case '--lang':
|
|
336
|
+
case '--locale':
|
|
337
|
+
ensureOptionValue(value, nextValue, t);
|
|
338
|
+
options.locale = normalizeLocaleToken(nextValue) ?? 'en';
|
|
339
|
+
index += 1;
|
|
340
|
+
break;
|
|
341
|
+
case '--to':
|
|
342
|
+
ensureOptionValue(value, nextValue, t);
|
|
343
|
+
options.to = nextValue.toLowerCase().replace(/^\./, '');
|
|
344
|
+
index += 1;
|
|
345
|
+
break;
|
|
346
|
+
case '--out-dir':
|
|
347
|
+
ensureOptionValue(value, nextValue, t);
|
|
348
|
+
options.outDir = nextValue;
|
|
349
|
+
index += 1;
|
|
350
|
+
break;
|
|
351
|
+
case '--json':
|
|
352
|
+
options.json = true;
|
|
353
|
+
break;
|
|
354
|
+
case '--images':
|
|
355
|
+
options.includeImages = true;
|
|
356
|
+
break;
|
|
357
|
+
case '--pages':
|
|
358
|
+
ensureOptionValue(value, nextValue, t);
|
|
359
|
+
options.pages = mergeCsvValues(options.pages ?? [], nextValue);
|
|
360
|
+
index += 1;
|
|
361
|
+
break;
|
|
362
|
+
case '--page-id':
|
|
363
|
+
ensureOptionValue(value, nextValue, t);
|
|
364
|
+
options.pageIds = [...(options.pageIds ?? []), nextValue];
|
|
365
|
+
index += 1;
|
|
366
|
+
break;
|
|
367
|
+
case '--page-ids':
|
|
368
|
+
ensureOptionValue(value, nextValue, t);
|
|
369
|
+
options.pageIds = mergeCsvValues(options.pageIds ?? [], nextValue);
|
|
370
|
+
index += 1;
|
|
371
|
+
break;
|
|
372
|
+
case '--h1':
|
|
373
|
+
ensureOptionValue(value, nextValue, t);
|
|
374
|
+
options.h1 = parseStructureValue(nextValue, ['page', 'resource-title'], t);
|
|
375
|
+
index += 1;
|
|
376
|
+
break;
|
|
377
|
+
case '--h2':
|
|
378
|
+
ensureOptionValue(value, nextValue, t);
|
|
379
|
+
options.h2 = parseStructureValue(nextValue, ['idevice', 'subpage'], t);
|
|
380
|
+
index += 1;
|
|
381
|
+
break;
|
|
382
|
+
case '--h3':
|
|
383
|
+
ensureOptionValue(value, nextValue, t);
|
|
384
|
+
options.h3 = parseStructureValue(nextValue, ['idevice', 'subpage'], t);
|
|
385
|
+
index += 1;
|
|
386
|
+
break;
|
|
387
|
+
case '--h4':
|
|
388
|
+
ensureOptionValue(value, nextValue, t);
|
|
389
|
+
options.h4 = parseStructureValue(nextValue, ['idevice', 'subpage'], t);
|
|
390
|
+
index += 1;
|
|
391
|
+
break;
|
|
392
|
+
default:
|
|
393
|
+
throw new Error(t('error.unknownOption', { value }));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return { options, positionals };
|
|
397
|
+
}
|
|
398
|
+
function ensureOptionValue(flag, value, t) {
|
|
399
|
+
if (!value || value.startsWith('--')) {
|
|
400
|
+
throw new Error(t('error.missingValue', { flag }));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function mergeCsvValues(current, raw) {
|
|
404
|
+
return [...current, ...raw.split(',').map(part => part.trim()).filter(Boolean)];
|
|
405
|
+
}
|
|
406
|
+
function parseStructureValue(value, allowed, t) {
|
|
407
|
+
const normalized = value.trim().toLowerCase();
|
|
408
|
+
if (!allowed.includes(normalized)) {
|
|
409
|
+
throw new Error(t('error.invalidValue', { value, allowed: allowed.join(', ') }));
|
|
410
|
+
}
|
|
411
|
+
return normalized;
|
|
412
|
+
}
|
|
413
|
+
function validateStructure(structure, t) {
|
|
414
|
+
if (structure.h3 === 'subpage' && structure.h2 !== 'subpage') {
|
|
415
|
+
throw new Error(t('error.h3RequiresH2'));
|
|
416
|
+
}
|
|
417
|
+
if (structure.h4 === 'subpage' && structure.h3 !== 'subpage') {
|
|
418
|
+
throw new Error(t('error.h4RequiresH3'));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function detectFormat(filePath, t) {
|
|
422
|
+
const extension = extname(filePath).toLowerCase();
|
|
423
|
+
switch (extension) {
|
|
424
|
+
case '.elp':
|
|
425
|
+
return 'elp';
|
|
426
|
+
case '.elpx':
|
|
427
|
+
return 'elpx';
|
|
428
|
+
case '.docx':
|
|
429
|
+
return 'docx';
|
|
430
|
+
case '.pdf':
|
|
431
|
+
return 'pdf';
|
|
432
|
+
case '.md':
|
|
433
|
+
case '.markdown':
|
|
434
|
+
case '.mdown':
|
|
435
|
+
case '.txt':
|
|
436
|
+
return 'markdown';
|
|
437
|
+
default:
|
|
438
|
+
throw new Error(t('error.unsupportedExtension', { extension: extension || '(none)' }));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function readInputFile(path, mime) {
|
|
442
|
+
const resolved = resolve(path);
|
|
443
|
+
const data = await readFile(resolved);
|
|
444
|
+
return new File([data], basename(resolved), { type: mime });
|
|
445
|
+
}
|
|
446
|
+
async function writeBlob(path, blob) {
|
|
447
|
+
const resolved = resolve(path);
|
|
448
|
+
await mkdir(dirname(resolved), { recursive: true });
|
|
449
|
+
await writeFile(resolved, Buffer.from(await blob.arrayBuffer()));
|
|
450
|
+
}
|
|
451
|
+
function buildMime(format) {
|
|
452
|
+
switch (format) {
|
|
453
|
+
case 'elp':
|
|
454
|
+
case 'elpx':
|
|
455
|
+
return 'application/zip';
|
|
456
|
+
case 'docx':
|
|
457
|
+
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
|
458
|
+
case 'markdown':
|
|
459
|
+
return 'text/markdown;charset=utf-8';
|
|
460
|
+
case 'pdf':
|
|
461
|
+
return 'application/pdf';
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function progressLogger(progress, i18n) {
|
|
465
|
+
const phaseMessages = {
|
|
466
|
+
'Generando el documento .pdf...': i18n.t('progress.generatePdf'),
|
|
467
|
+
'Componiendo el documento .pdf...': i18n.t('progress.composePdf'),
|
|
468
|
+
};
|
|
469
|
+
const localizedMessage = progress.messageKey ? i18n.t(progress.messageKey) : (phaseMessages[progress.message] || progress.message);
|
|
470
|
+
const signature = `${progress.phase}:${localizedMessage}`;
|
|
471
|
+
if (signature === lastProgressSignature) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
lastProgressSignature = signature;
|
|
475
|
+
stderr.write(`[${progress.phase}] ${localizedMessage}\n`);
|
|
476
|
+
}
|
|
477
|
+
function buildPageRefs(pages) {
|
|
478
|
+
const baseDepth = pages.length > 0 ? Math.min(...pages.map(page => page.depth)) : 0;
|
|
479
|
+
const counters = [];
|
|
480
|
+
return pages.map(page => {
|
|
481
|
+
const normalizedDepth = Math.max(0, page.depth - baseDepth);
|
|
482
|
+
counters[normalizedDepth] = (counters[normalizedDepth] ?? 0) + 1;
|
|
483
|
+
counters.length = normalizedDepth + 1;
|
|
484
|
+
return {
|
|
485
|
+
...page,
|
|
486
|
+
ref: counters.slice(0, normalizedDepth + 1).join('.'),
|
|
487
|
+
};
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
function resolveSelectedPageIds(pages, refs, ids) {
|
|
491
|
+
const selected = new Set(ids);
|
|
492
|
+
if (refs.length === 0) {
|
|
493
|
+
return [...selected];
|
|
494
|
+
}
|
|
495
|
+
const refMap = new Map(pages.map(page => [page.ref, page.id]));
|
|
496
|
+
for (const ref of refs) {
|
|
497
|
+
const pageId = refMap.get(ref);
|
|
498
|
+
if (!pageId) {
|
|
499
|
+
throw new Error(currentCliTranslator.t('error.unknownPageRef', { ref }));
|
|
500
|
+
}
|
|
501
|
+
selected.add(pageId);
|
|
502
|
+
}
|
|
503
|
+
return [...selected];
|
|
504
|
+
}
|
|
505
|
+
function printInspectText(pages) {
|
|
506
|
+
const baseDepth = pages.length > 0 ? Math.min(...pages.map(page => page.depth)) : 0;
|
|
507
|
+
for (const page of pages) {
|
|
508
|
+
const indent = ' '.repeat(Math.max(0, page.depth - baseDepth));
|
|
509
|
+
stdout.write(`${page.ref} ${indent}${page.title}\n`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
async function runInspect(inputPath, json) {
|
|
513
|
+
installCliRuntime();
|
|
514
|
+
const inputFile = await readInputFile(inputPath, buildMime('elpx'));
|
|
515
|
+
const pages = buildPageRefs(await inspectElpxPages(inputFile));
|
|
516
|
+
if (json) {
|
|
517
|
+
stdout.write(`${JSON.stringify({ input: resolve(inputPath), pages }, null, 2)}\n`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
printInspectText(pages);
|
|
521
|
+
}
|
|
522
|
+
async function runConvert(args) {
|
|
523
|
+
installCliRuntime();
|
|
524
|
+
const i18n = createCliTranslator(args.locale);
|
|
525
|
+
const inputPath = args.inputPath;
|
|
526
|
+
const outputPath = args.outputPath;
|
|
527
|
+
const inputFormat = detectFormat(inputPath, i18n.t);
|
|
528
|
+
const outputFormat = detectFormat(outputPath, i18n.t);
|
|
529
|
+
const inputFile = await readInputFile(inputPath, buildMime(inputFormat));
|
|
530
|
+
const selectedPageIds = inputFormat === 'elpx'
|
|
531
|
+
? await loadSelectedPageIds(inputFile, args.pages, args.pageIds)
|
|
532
|
+
: [];
|
|
533
|
+
validateStructure({
|
|
534
|
+
h1: args.h1,
|
|
535
|
+
h2: args.h2,
|
|
536
|
+
h3: args.h3,
|
|
537
|
+
h4: args.h4,
|
|
538
|
+
}, i18n.t);
|
|
539
|
+
if (inputFormat === 'elpx' && outputFormat === 'docx') {
|
|
540
|
+
const result = await convertElpxToDocx(inputFile, selectedPageIds.length > 0 ? { selectedPageIds } : undefined, progress => progressLogger(progress, i18n));
|
|
541
|
+
await writeBlob(outputPath, result.blob);
|
|
542
|
+
printConvertResult(args.json, {
|
|
543
|
+
input: resolve(inputPath),
|
|
544
|
+
output: resolve(outputPath),
|
|
545
|
+
inputFormat,
|
|
546
|
+
outputFormat,
|
|
547
|
+
pageCount: result.pageCount,
|
|
548
|
+
}, i18n.t);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (inputFormat === 'elpx' && outputFormat === 'markdown') {
|
|
552
|
+
const result = await convertElpxToMarkdown(inputFile, {
|
|
553
|
+
includeImages: args.includeImages,
|
|
554
|
+
selectedPageIds: selectedPageIds.length > 0 ? selectedPageIds : undefined,
|
|
555
|
+
}, progress => progressLogger(progress, i18n));
|
|
556
|
+
await writeBlob(outputPath, result.blob);
|
|
557
|
+
printConvertResult(args.json, {
|
|
558
|
+
input: resolve(inputPath),
|
|
559
|
+
output: resolve(outputPath),
|
|
560
|
+
inputFormat,
|
|
561
|
+
outputFormat,
|
|
562
|
+
pageCount: result.pageCount,
|
|
563
|
+
}, i18n.t);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (inputFormat === 'elpx' && outputFormat === 'pdf') {
|
|
567
|
+
const htmlResult = await convertElpxToHtml(inputFile, selectedPageIds.length > 0 ? { selectedPageIds, useRenderedPages: false } : { useRenderedPages: false }, progress => progressLogger(progress, i18n));
|
|
568
|
+
const blob = await buildPdfBlobFromPrintableHtml(buildPrintableHtmlDocument(htmlResult.html, { title: basename(outputPath) }), { title: basename(outputPath) }, progress => progressLogger(progress, i18n));
|
|
569
|
+
await writeBlob(outputPath, blob);
|
|
570
|
+
printConvertResult(args.json, {
|
|
571
|
+
input: resolve(inputPath),
|
|
572
|
+
output: resolve(outputPath),
|
|
573
|
+
inputFormat,
|
|
574
|
+
outputFormat,
|
|
575
|
+
pageCount: htmlResult.pageCount,
|
|
576
|
+
}, i18n.t);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (inputFormat === 'docx' && outputFormat === 'elpx') {
|
|
580
|
+
const result = await convertDocxToElpx(inputFile, {
|
|
581
|
+
heading1Mode: args.h1 === 'resource-title' ? 'resource' : 'page',
|
|
582
|
+
heading2Mode: args.h2 === 'subpage' ? 'page' : 'block',
|
|
583
|
+
heading3Mode: args.h3 === 'subpage' ? 'page' : 'block',
|
|
584
|
+
heading4Mode: args.h4 === 'subpage' ? 'page' : 'block',
|
|
585
|
+
}, progress => progressLogger(progress, i18n));
|
|
586
|
+
await writeBlob(outputPath, result.blob);
|
|
587
|
+
printConvertResult(args.json, {
|
|
588
|
+
input: resolve(inputPath),
|
|
589
|
+
output: resolve(outputPath),
|
|
590
|
+
inputFormat,
|
|
591
|
+
outputFormat,
|
|
592
|
+
pageCount: result.pageCount,
|
|
593
|
+
blockCount: result.blockCount,
|
|
594
|
+
}, i18n.t);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (inputFormat === 'markdown' && outputFormat === 'elpx') {
|
|
598
|
+
const result = await convertMarkdownToElpx(inputFile, {
|
|
599
|
+
heading1Mode: args.h1 === 'resource-title' ? 'resource' : 'page',
|
|
600
|
+
heading2Mode: args.h2 === 'subpage' ? 'page' : 'block',
|
|
601
|
+
heading3Mode: args.h3 === 'subpage' ? 'page' : 'block',
|
|
602
|
+
heading4Mode: args.h4 === 'subpage' ? 'page' : 'block',
|
|
603
|
+
}, progress => progressLogger(progress, i18n));
|
|
604
|
+
await writeBlob(outputPath, result.blob);
|
|
605
|
+
printConvertResult(args.json, {
|
|
606
|
+
input: resolve(inputPath),
|
|
607
|
+
output: resolve(outputPath),
|
|
608
|
+
inputFormat,
|
|
609
|
+
outputFormat,
|
|
610
|
+
pageCount: result.pageCount,
|
|
611
|
+
blockCount: result.blockCount,
|
|
612
|
+
}, i18n.t);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (inputFormat === 'elp') {
|
|
616
|
+
const intermediate = await convertElpToElpx(inputFile, progress => progressLogger(progress, i18n));
|
|
617
|
+
if (outputFormat === 'elpx') {
|
|
618
|
+
await writeBlob(outputPath, intermediate.blob);
|
|
619
|
+
printConvertResult(args.json, {
|
|
620
|
+
input: resolve(inputPath),
|
|
621
|
+
output: resolve(outputPath),
|
|
622
|
+
inputFormat,
|
|
623
|
+
outputFormat,
|
|
624
|
+
pageCount: intermediate.pageCount,
|
|
625
|
+
blockCount: intermediate.blockCount,
|
|
626
|
+
}, i18n.t);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const intermediateFile = new File([intermediate.blob], intermediate.filename, { type: buildMime('elpx') });
|
|
630
|
+
const elpxSelectedPageIds = await loadSelectedPageIds(intermediateFile, args.pages, args.pageIds);
|
|
631
|
+
if (outputFormat === 'markdown') {
|
|
632
|
+
const result = await convertElpxToMarkdown(intermediateFile, {
|
|
633
|
+
includeImages: args.includeImages,
|
|
634
|
+
selectedPageIds: elpxSelectedPageIds.length > 0 ? elpxSelectedPageIds : undefined,
|
|
635
|
+
}, progress => progressLogger(progress, i18n));
|
|
636
|
+
await writeBlob(outputPath, result.blob);
|
|
637
|
+
printConvertResult(args.json, {
|
|
638
|
+
input: resolve(inputPath),
|
|
639
|
+
output: resolve(outputPath),
|
|
640
|
+
inputFormat,
|
|
641
|
+
outputFormat,
|
|
642
|
+
pageCount: result.pageCount,
|
|
643
|
+
}, i18n.t);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (outputFormat === 'pdf') {
|
|
647
|
+
const htmlResult = await convertElpxToHtml(intermediateFile, elpxSelectedPageIds.length > 0 ? { selectedPageIds: elpxSelectedPageIds, useRenderedPages: false } : { useRenderedPages: false }, progress => progressLogger(progress, i18n));
|
|
648
|
+
const blob = await buildPdfBlobFromPrintableHtml(buildPrintableHtmlDocument(htmlResult.html, { title: basename(outputPath) }), { title: basename(outputPath) }, progress => progressLogger(progress, i18n));
|
|
649
|
+
await writeBlob(outputPath, blob);
|
|
650
|
+
printConvertResult(args.json, {
|
|
651
|
+
input: resolve(inputPath),
|
|
652
|
+
output: resolve(outputPath),
|
|
653
|
+
inputFormat,
|
|
654
|
+
outputFormat,
|
|
655
|
+
pageCount: htmlResult.pageCount,
|
|
656
|
+
}, i18n.t);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (outputFormat === 'docx') {
|
|
660
|
+
const htmlResult = await convertElpxToHtml(intermediateFile, elpxSelectedPageIds.length > 0 ? { selectedPageIds: elpxSelectedPageIds, useRenderedPages: true } : { useRenderedPages: true }, progress => progressLogger(progress, i18n));
|
|
661
|
+
const result = await convertHtmlToDocxResult(htmlResult.html, {
|
|
662
|
+
inputName: inputFile.name,
|
|
663
|
+
title: htmlResult.title,
|
|
664
|
+
language: htmlResult.language,
|
|
665
|
+
pageCount: htmlResult.pageCount,
|
|
666
|
+
}, progress => progressLogger(progress, i18n));
|
|
667
|
+
await writeBlob(outputPath, result.blob);
|
|
668
|
+
printConvertResult(args.json, {
|
|
669
|
+
input: resolve(inputPath),
|
|
670
|
+
output: resolve(outputPath),
|
|
671
|
+
inputFormat,
|
|
672
|
+
outputFormat,
|
|
673
|
+
pageCount: result.pageCount,
|
|
674
|
+
}, i18n.t);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
throw new Error(i18n.t('error.unsupportedConversion', { input: inputFormat, output: outputFormat }));
|
|
679
|
+
}
|
|
680
|
+
async function loadSelectedPageIds(inputFile, pageRefs, pageIds) {
|
|
681
|
+
if (pageRefs.length === 0 && pageIds.length === 0) {
|
|
682
|
+
return [];
|
|
683
|
+
}
|
|
684
|
+
const pages = buildPageRefs(await inspectElpxPages(inputFile));
|
|
685
|
+
return resolveSelectedPageIds(pages, pageRefs, pageIds);
|
|
686
|
+
}
|
|
687
|
+
function printConvertResult(json, payload, t) {
|
|
688
|
+
if (json) {
|
|
689
|
+
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
stdout.write(`${t('result.written', { output: String(payload.output) })}\n`);
|
|
693
|
+
}
|
|
694
|
+
function expandGlobs(paths) {
|
|
695
|
+
const result = [];
|
|
696
|
+
for (const p of paths) {
|
|
697
|
+
if (/[*?[\]{]/.test(p)) {
|
|
698
|
+
const expanded = globSync(p);
|
|
699
|
+
result.push(...expanded);
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
result.push(p);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
707
|
+
function isSupportedConversion(inputFormat, outputFormat) {
|
|
708
|
+
if (inputFormat === 'elpx')
|
|
709
|
+
return outputFormat === 'docx' || outputFormat === 'markdown' || outputFormat === 'pdf';
|
|
710
|
+
if (inputFormat === 'elp')
|
|
711
|
+
return outputFormat === 'elpx' || outputFormat === 'docx' || outputFormat === 'markdown' || outputFormat === 'pdf';
|
|
712
|
+
if (inputFormat === 'docx')
|
|
713
|
+
return outputFormat === 'elpx';
|
|
714
|
+
if (inputFormat === 'markdown')
|
|
715
|
+
return outputFormat === 'elpx';
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
function deriveOutputPath(inputPath, toFormat, outDir) {
|
|
719
|
+
const base = basename(inputPath, extname(inputPath));
|
|
720
|
+
const ext = toFormat === 'markdown' ? 'md' : toFormat;
|
|
721
|
+
const filename = `${base}.${ext}`;
|
|
722
|
+
if (outDir) {
|
|
723
|
+
return resolve(outDir, filename);
|
|
724
|
+
}
|
|
725
|
+
return resolve(dirname(inputPath), filename);
|
|
726
|
+
}
|
|
727
|
+
async function runBatch(args) {
|
|
728
|
+
const i18n = createCliTranslator(args.locale);
|
|
729
|
+
const inputPaths = expandGlobs(args.inputPaths);
|
|
730
|
+
const toRaw = args.to;
|
|
731
|
+
const toFormat = toRaw === 'md' || toRaw === 'markdown' || toRaw === 'txt' ? 'markdown' : toRaw;
|
|
732
|
+
const fakeOutputPath = `output.${toRaw}`;
|
|
733
|
+
detectFormat(fakeOutputPath, i18n.t);
|
|
734
|
+
const eligible = inputPaths.filter(inputPath => {
|
|
735
|
+
try {
|
|
736
|
+
const fmt = detectFormat(inputPath, i18n.t);
|
|
737
|
+
return isSupportedConversion(fmt, toFormat);
|
|
738
|
+
}
|
|
739
|
+
catch {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
let count = 0;
|
|
744
|
+
for (const inputPath of eligible) {
|
|
745
|
+
if (!args.json) {
|
|
746
|
+
stderr.write(`[${count + 1}/${eligible.length}] ${basename(inputPath)}\n`);
|
|
747
|
+
}
|
|
748
|
+
const outputPath = deriveOutputPath(inputPath, toRaw, args.outDir);
|
|
749
|
+
await runConvert({ ...args, command: 'convert', inputPath, outputPath });
|
|
750
|
+
count += 1;
|
|
751
|
+
}
|
|
752
|
+
if (!args.json) {
|
|
753
|
+
stdout.write(`${i18n.t('result.batchDone', { count })}\n`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
let currentCliTranslator = createCliTranslator('en');
|
|
757
|
+
let lastProgressSignature = '';
|
|
758
|
+
function formatConsoleArgs(args) {
|
|
759
|
+
return args.map(value => (typeof value === 'string' ? value : String(value))).join(' ');
|
|
760
|
+
}
|
|
761
|
+
function shouldSuppressCliConsoleMessage(message) {
|
|
762
|
+
const normalized = message.trim();
|
|
763
|
+
return (normalized.startsWith('[SharedImporters]') ||
|
|
764
|
+
normalized.startsWith('[SharedExporters]') ||
|
|
765
|
+
normalized.startsWith('[BrowserAssetProvider]') ||
|
|
766
|
+
normalized.startsWith('[BaseExporter] Internal link target not found:'));
|
|
767
|
+
}
|
|
768
|
+
function installCliConsoleFilter() {
|
|
769
|
+
const wrap = (original) => (...args) => {
|
|
770
|
+
const message = formatConsoleArgs(args);
|
|
771
|
+
if (shouldSuppressCliConsoleMessage(message)) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
original(...args);
|
|
775
|
+
};
|
|
776
|
+
console.log = wrap(console.log.bind(console));
|
|
777
|
+
console.warn = wrap(console.warn.bind(console));
|
|
778
|
+
}
|
|
779
|
+
async function main() {
|
|
780
|
+
const locale = resolveCliLocale(process.argv.slice(2));
|
|
781
|
+
currentCliTranslator = createCliTranslator(locale);
|
|
782
|
+
installCliConsoleFilter();
|
|
783
|
+
const args = parseArgs(process.argv.slice(2), currentCliTranslator.t, locale);
|
|
784
|
+
lastProgressSignature = '';
|
|
785
|
+
if (args.command === 'help') {
|
|
786
|
+
printHelp(currentCliTranslator.t);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (args.command === 'version') {
|
|
790
|
+
stdout.write(`${CLI_VERSION}\n`);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (args.command === 'inspect') {
|
|
794
|
+
await runInspect(args.inputPath, args.json);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (args.command === 'batch') {
|
|
798
|
+
await runBatch(args);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
await runConvert(args);
|
|
802
|
+
}
|
|
803
|
+
await main().catch(error => {
|
|
804
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
805
|
+
stderr.write(`${currentCliTranslator.t('error.prefix', { message })}\n`);
|
|
806
|
+
process.exit(1);
|
|
807
|
+
});
|