@tradik/xslt-processor 1.0.2 → 1.1.1
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/README.md +292 -47
- package/bin/lib/options.js +114 -0
- package/bin/lib/paths.js +186 -0
- package/bin/lib/transform.js +115 -0
- package/bin/xslt.js +68 -162
- package/dist/xslt-processor.browser.js +2073 -163
- package/dist/xslt-processor.browser.js.map +4 -4
- package/dist/xslt-processor.browser.min.js +6 -2
- package/dist/xslt-processor.browser.min.js.map +4 -4
- package/dist/xslt-processor.cjs +2077 -162
- package/dist/xslt-processor.cjs.map +4 -4
- package/dist/xslt-processor.d.cts +299 -0
- package/dist/xslt-processor.d.ts +92 -4
- package/dist/xslt-processor.js +2072 -161
- package/dist/xslt-processor.js.map +4 -4
- package/package.json +27 -16
- package/src/XSLTProcessor.js +177 -8
- package/src/index.js +11 -5
- package/src/xpath/evaluator.js +48 -7
- package/src/xslt/elements.js +57 -0
- package/src/xslt/engine.js +474 -185
- package/src/xslt/formatNumber.js +220 -0
- package/src/xslt/functions.js +191 -0
- package/src/xslt/index.js +31 -0
- package/src/xslt/keys.js +141 -0
- package/src/xslt/literalResult.js +167 -0
- package/src/xslt/number.js +178 -0
- package/src/xslt/numberFormat.js +155 -0
- package/src/xslt/resultTree.js +74 -0
- package/src/xslt/serializer/baseWriter.js +283 -0
- package/src/xslt/serializer/constants.js +78 -0
- package/src/xslt/serializer/escape.js +98 -0
- package/src/xslt/serializer/htmlSerializer.js +141 -0
- package/src/xslt/serializer/indent.js +51 -0
- package/src/xslt/serializer/namespaces.js +68 -0
- package/src/xslt/serializer/rawText.js +41 -0
- package/src/xslt/serializer/settings.js +103 -0
- package/src/xslt/serializer/textSerializer.js +29 -0
- package/src/xslt/serializer/xmlSerializer.js +127 -0
- package/src/xslt/serializer.js +57 -0
- package/src/xslt/templatePriority.js +45 -0
- package/src/xslt/uri.js +68 -0
- package/src/xslt/whitespace.js +184 -0
- package/src/XSLTProcessor.test.js +0 -930
- package/src/xpath/evaluator.test.js +0 -1852
- package/src/xpath/tokenizer.test.js +0 -224
- package/src/xslt/engine.test.js +0 -3130
package/bin/lib/paths.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XSLT Processor CLI - Path Validation
|
|
3
|
+
*
|
|
4
|
+
* Every file the CLI reads or writes goes through this module first: the raw
|
|
5
|
+
* command line argument is resolved, canonicalized with `realpathSync`, checked
|
|
6
|
+
* to lie inside the trusted base directory and validated against the file
|
|
7
|
+
* system before any read or write is attempted. The base directory is the
|
|
8
|
+
* current working directory, or `XSLT_BASE_DIR` when that variable is set.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
import { realpathSync, statSync } from "node:fs";
|
|
14
|
+
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Error raised for a command line path that cannot be used.
|
|
18
|
+
*/
|
|
19
|
+
export class CliPathError extends Error {
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} message - Human readable explanation
|
|
22
|
+
*/
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "CliPathError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a raw path argument to an absolute path.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} rawPath - Raw command line argument
|
|
33
|
+
* @param {string} label - Human readable role of the path, used in errors
|
|
34
|
+
* @returns {string} The absolute path
|
|
35
|
+
* @throws {CliPathError} When the argument is empty or contains a NUL byte
|
|
36
|
+
*/
|
|
37
|
+
function toAbsolutePath(rawPath, label) {
|
|
38
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) {
|
|
39
|
+
throw new CliPathError(`${label} path is missing`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (rawPath.includes("\0")) {
|
|
43
|
+
throw new CliPathError(`${label} path contains a NUL byte`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return resolve(rawPath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Canonicalize an existing path, turning a missing entry into a CLI error.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} absolute - Absolute path that should exist
|
|
53
|
+
* @param {string} message - Error message when nothing exists there
|
|
54
|
+
* @returns {string} The canonical path with symbolic links resolved
|
|
55
|
+
* @throws {CliPathError} When the path does not exist
|
|
56
|
+
*/
|
|
57
|
+
function canonicalize(absolute, message) {
|
|
58
|
+
try {
|
|
59
|
+
return realpathSync(absolute);
|
|
60
|
+
} catch {
|
|
61
|
+
throw new CliPathError(message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Ensure a canonical path lies inside the trusted base directory.
|
|
67
|
+
*
|
|
68
|
+
* This is the security boundary of the CLI: whatever the caller typed, the
|
|
69
|
+
* canonical path must be the base directory itself or a descendant of it.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} canonical - Canonical absolute path
|
|
72
|
+
* @param {string} baseDir - Canonical absolute base directory
|
|
73
|
+
* @param {string} label - Human readable role of the path, used in errors
|
|
74
|
+
* @returns {string} The same path, now known to be inside baseDir
|
|
75
|
+
* @throws {CliPathError} When the path escapes the base directory
|
|
76
|
+
*/
|
|
77
|
+
function assertInsideBase(canonical, baseDir, label) {
|
|
78
|
+
const inside =
|
|
79
|
+
canonical === baseDir ||
|
|
80
|
+
(canonical.startsWith(baseDir) && canonical.startsWith(baseDir + sep));
|
|
81
|
+
|
|
82
|
+
if (!inside) {
|
|
83
|
+
throw new CliPathError(
|
|
84
|
+
`${label} path is outside the allowed base directory (${baseDir}): ${canonical}. ` +
|
|
85
|
+
"Run the command from that directory or set XSLT_BASE_DIR.",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return canonical;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the trusted base directory all file arguments are confined to.
|
|
94
|
+
*
|
|
95
|
+
* Uses `XSLT_BASE_DIR` when set, otherwise the current working directory.
|
|
96
|
+
*
|
|
97
|
+
* @returns {string} The canonical absolute path of an existing directory
|
|
98
|
+
* @throws {CliPathError} When the configured directory does not exist or is not a directory
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* const baseDir = resolveBaseDir(); // process.cwd() unless XSLT_BASE_DIR is set
|
|
102
|
+
*/
|
|
103
|
+
export function resolveBaseDir() {
|
|
104
|
+
const configured = process.env.XSLT_BASE_DIR || process.cwd();
|
|
105
|
+
const absolute = toAbsolutePath(configured, "Base directory");
|
|
106
|
+
const canonical = canonicalize(
|
|
107
|
+
absolute,
|
|
108
|
+
`Base directory does not exist: ${absolute}`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (!statSync(canonical).isDirectory()) {
|
|
112
|
+
throw new CliPathError(`Base directory is not a directory: ${canonical}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return canonical;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Validate a path the CLI is going to read.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} rawPath - Raw command line argument
|
|
122
|
+
* @param {string} label - Human readable role of the path, used in errors
|
|
123
|
+
* @param {string} baseDir - Canonical base directory from resolveBaseDir()
|
|
124
|
+
* @returns {string} The canonical path of an existing regular file inside baseDir
|
|
125
|
+
* @throws {CliPathError} When the path is malformed, missing, outside baseDir or not a file
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const xmlFile = resolveInputPath("data.xml", "XML", resolveBaseDir());
|
|
129
|
+
*/
|
|
130
|
+
export function resolveInputPath(rawPath, label, baseDir) {
|
|
131
|
+
const absolute = toAbsolutePath(rawPath, label);
|
|
132
|
+
const canonical = assertInsideBase(
|
|
133
|
+
canonicalize(absolute, `File not found: ${absolute}`),
|
|
134
|
+
baseDir,
|
|
135
|
+
label,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (!statSync(canonical).isFile()) {
|
|
139
|
+
throw new CliPathError(`${label} path is not a file: ${canonical}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return canonical;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Validate a path the CLI is going to write.
|
|
147
|
+
*
|
|
148
|
+
* The file itself may be missing, but its parent directory has to exist, lie
|
|
149
|
+
* inside the base directory, and an existing target has to be a regular file.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} rawPath - Raw command line argument
|
|
152
|
+
* @param {string} baseDir - Canonical base directory from resolveBaseDir()
|
|
153
|
+
* @returns {string} The canonical path to write to
|
|
154
|
+
* @throws {CliPathError} When the path is malformed, outside baseDir or not writable as a file
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* const target = resolveOutputPath("build/result.html", resolveBaseDir());
|
|
158
|
+
*/
|
|
159
|
+
export function resolveOutputPath(rawPath, baseDir) {
|
|
160
|
+
const absolute = toAbsolutePath(rawPath, "Output");
|
|
161
|
+
const parent = assertInsideBase(
|
|
162
|
+
canonicalize(
|
|
163
|
+
dirname(absolute),
|
|
164
|
+
`Output directory does not exist: ${dirname(absolute)}`,
|
|
165
|
+
),
|
|
166
|
+
baseDir,
|
|
167
|
+
"Output directory",
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (!statSync(parent).isDirectory()) {
|
|
171
|
+
throw new CliPathError(`Output directory is not a directory: ${parent}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const canonical = assertInsideBase(
|
|
175
|
+
join(parent, basename(absolute)),
|
|
176
|
+
baseDir,
|
|
177
|
+
"Output",
|
|
178
|
+
);
|
|
179
|
+
const targetStats = statSync(canonical, { throwIfNoEntry: false });
|
|
180
|
+
|
|
181
|
+
if (targetStats && !targetStats.isFile()) {
|
|
182
|
+
throw new CliPathError(`Output path is not a file: ${canonical}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return canonical;
|
|
186
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XSLT Processor CLI - Transformation Helpers
|
|
3
|
+
*
|
|
4
|
+
* DOM environment setup, document parsing and the transformation itself.
|
|
5
|
+
* Output is serialized through XSLTProcessor#transformToString so that the
|
|
6
|
+
* xsl:output settings of the stylesheet are honored.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
import { JSDOM } from "jsdom";
|
|
12
|
+
import { XSLTProcessor } from "../../src/XSLTProcessor.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a JSDOM based DOM environment and expose it globally.
|
|
16
|
+
*
|
|
17
|
+
* The XSLT engine builds its result documents through the global `document`,
|
|
18
|
+
* so the globals have to be installed before transforming.
|
|
19
|
+
*
|
|
20
|
+
* @returns {JSDOM} The created JSDOM instance
|
|
21
|
+
*/
|
|
22
|
+
export function createDomEnvironment() {
|
|
23
|
+
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>", {
|
|
24
|
+
contentType: "text/html",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
globalThis.document = dom.window.document;
|
|
28
|
+
globalThis.DOMParser = dom.window.DOMParser;
|
|
29
|
+
globalThis.XMLSerializer = dom.window.XMLSerializer;
|
|
30
|
+
|
|
31
|
+
return dom;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse an XML string, reporting parser errors as exceptions.
|
|
36
|
+
*
|
|
37
|
+
* @param {JSDOM} dom - DOM environment
|
|
38
|
+
* @param {string} content - XML source text
|
|
39
|
+
* @param {string} label - Human readable document label used in errors
|
|
40
|
+
* @returns {Document} Parsed document
|
|
41
|
+
*/
|
|
42
|
+
export function parseDocument(dom, content, label) {
|
|
43
|
+
const doc = new dom.window.DOMParser().parseFromString(
|
|
44
|
+
content,
|
|
45
|
+
"application/xml",
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const error = doc.querySelector("parsererror");
|
|
49
|
+
if (error) {
|
|
50
|
+
throw new Error(`Error parsing ${label}: ${error.textContent}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return doc;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Override the stylesheet xsl:output settings from the command line flags.
|
|
58
|
+
*
|
|
59
|
+
* @param {XSLTProcessor} processor - Processor with an imported stylesheet
|
|
60
|
+
* @param {object} values - Parsed command line option values
|
|
61
|
+
* @returns {object} The effective output settings
|
|
62
|
+
*/
|
|
63
|
+
export function applyOutputOverrides(processor, values) {
|
|
64
|
+
const settings = processor._engine.outputSettings;
|
|
65
|
+
|
|
66
|
+
if (values.format || values.indent) {
|
|
67
|
+
settings.indent = "yes";
|
|
68
|
+
}
|
|
69
|
+
if (values.method) {
|
|
70
|
+
settings.method = values.method;
|
|
71
|
+
}
|
|
72
|
+
if (values["no-declaration"]) {
|
|
73
|
+
settings.omitXmlDeclaration = "yes";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return settings;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run a transformation and serialize its result.
|
|
81
|
+
*
|
|
82
|
+
* @param {object} options - Transformation inputs
|
|
83
|
+
* @param {JSDOM} options.dom - DOM environment
|
|
84
|
+
* @param {string} options.xmlContent - XML source text
|
|
85
|
+
* @param {string} options.xsltContent - XSLT stylesheet text
|
|
86
|
+
* @param {Record<string, string>} options.params - Stylesheet parameters
|
|
87
|
+
* @param {object} options.values - Parsed command line option values
|
|
88
|
+
* @returns {string} The serialized transformation result
|
|
89
|
+
*/
|
|
90
|
+
export function runTransformation({
|
|
91
|
+
dom,
|
|
92
|
+
xmlContent,
|
|
93
|
+
xsltContent,
|
|
94
|
+
params,
|
|
95
|
+
values,
|
|
96
|
+
}) {
|
|
97
|
+
const xmlDoc = parseDocument(dom, xmlContent, "XML");
|
|
98
|
+
const xsltDoc = parseDocument(dom, xsltContent, "XSLT");
|
|
99
|
+
|
|
100
|
+
const processor = new XSLTProcessor();
|
|
101
|
+
processor.importStylesheet(xsltDoc);
|
|
102
|
+
|
|
103
|
+
for (const [name, value] of Object.entries(params)) {
|
|
104
|
+
processor.setParameter(null, name, value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
applyOutputOverrides(processor, values);
|
|
108
|
+
|
|
109
|
+
const output = processor.transformToString(xmlDoc);
|
|
110
|
+
if (output === null) {
|
|
111
|
+
throw new Error("Transformation failed");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return output;
|
|
115
|
+
}
|
package/bin/xslt.js
CHANGED
|
@@ -4,117 +4,64 @@
|
|
|
4
4
|
* XSLT Processor CLI
|
|
5
5
|
*
|
|
6
6
|
* Command-line interface for transforming XML using XSLT stylesheets.
|
|
7
|
+
* The result is serialized according to the xsl:output element of the
|
|
8
|
+
* stylesheet (XSLT 1.0 section 16).
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
14
|
+
import { parseArgs } from "node:util";
|
|
15
|
+
import {
|
|
16
|
+
CLI_OPTIONS,
|
|
17
|
+
parseParameters,
|
|
18
|
+
printHelp,
|
|
19
|
+
printVersion,
|
|
20
|
+
} from "./lib/options.js";
|
|
21
|
+
import { createDomEnvironment, runTransformation } from "./lib/transform.js";
|
|
22
|
+
import {
|
|
23
|
+
resolveBaseDir,
|
|
24
|
+
resolveInputPath,
|
|
25
|
+
resolveOutputPath,
|
|
26
|
+
} from "./lib/paths.js";
|
|
10
27
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
USAGE:
|
|
23
|
-
xslt <xml-file> <xslt-file> [options]
|
|
24
|
-
|
|
25
|
-
ARGUMENTS:
|
|
26
|
-
<xml-file> Path to XML source document
|
|
27
|
-
<xslt-file> Path to XSLT stylesheet
|
|
28
|
-
|
|
29
|
-
OPTIONS:
|
|
30
|
-
-o, --output <file> Write output to file instead of stdout
|
|
31
|
-
-p, --param <n>=<v> Set XSLT parameter (can be used multiple times)
|
|
32
|
-
-f, --format Format output with indentation
|
|
33
|
-
-h, --help Show this help message
|
|
34
|
-
-v, --version Show version number
|
|
35
|
-
|
|
36
|
-
EXAMPLES:
|
|
37
|
-
# Basic transformation
|
|
38
|
-
xslt data.xml transform.xsl
|
|
39
|
-
|
|
40
|
-
# Save output to file
|
|
41
|
-
xslt data.xml transform.xsl -o result.html
|
|
42
|
-
|
|
43
|
-
# With parameters
|
|
44
|
-
xslt data.xml transform.xsl -p title="My Page" -p count=10
|
|
45
|
-
|
|
46
|
-
# Multiple parameters with formatted output
|
|
47
|
-
xslt data.xml transform.xsl -p lang=en -p debug=true -f -o output.html
|
|
48
|
-
`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function printVersion() {
|
|
52
|
-
console.log(`xslt-processor v${VERSION}`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function parseParameters(params) {
|
|
56
|
-
const result = {};
|
|
57
|
-
|
|
58
|
-
if (!params || !Array.isArray(params)) {
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for (const param of params) {
|
|
63
|
-
const equalIndex = param.indexOf('=');
|
|
64
|
-
if (equalIndex === -1) {
|
|
65
|
-
console.error(`Warning: Invalid parameter format "${param}". Expected name=value`);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const name = param.substring(0, equalIndex);
|
|
70
|
-
const value = param.substring(equalIndex + 1);
|
|
71
|
-
result[name] = value;
|
|
28
|
+
/**
|
|
29
|
+
* Parse the command line, exiting on malformed input.
|
|
30
|
+
*
|
|
31
|
+
* @returns {object} The parseArgs result
|
|
32
|
+
*/
|
|
33
|
+
function readArguments() {
|
|
34
|
+
try {
|
|
35
|
+
return parseArgs({ options: CLI_OPTIONS, allowPositionals: true });
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error(`Error: ${err.message}`);
|
|
38
|
+
process.exit(1);
|
|
72
39
|
}
|
|
73
|
-
|
|
74
|
-
return result;
|
|
75
40
|
}
|
|
76
41
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
formatted += ' '.repeat(indent) + trimmed + '\n';
|
|
91
|
-
|
|
92
|
-
if (trimmed.startsWith('<') && !trimmed.startsWith('</') &&
|
|
93
|
-
!trimmed.startsWith('<?') && !trimmed.startsWith('<!') &&
|
|
94
|
-
!trimmed.endsWith('/>') && !trimmed.includes('</')) {
|
|
95
|
-
indent++;
|
|
96
|
-
}
|
|
42
|
+
/**
|
|
43
|
+
* Write the transformation result to a file or to stdout.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} output - Serialized transformation result
|
|
46
|
+
* @param {string|undefined} target - Validated absolute output path, if any
|
|
47
|
+
* @returns {Promise<void>} Resolves once the result has been written
|
|
48
|
+
*/
|
|
49
|
+
async function writeOutput(output, target) {
|
|
50
|
+
if (target) {
|
|
51
|
+
await writeFile(target, output, "utf-8");
|
|
52
|
+
console.error(`Output written to ${target}`);
|
|
53
|
+
return;
|
|
97
54
|
}
|
|
98
|
-
|
|
99
|
-
return formatted;
|
|
55
|
+
console.log(output);
|
|
100
56
|
}
|
|
101
57
|
|
|
58
|
+
/**
|
|
59
|
+
* CLI entry point.
|
|
60
|
+
*
|
|
61
|
+
* @returns {Promise<void>} Resolves once the CLI has finished
|
|
62
|
+
*/
|
|
102
63
|
async function main() {
|
|
103
|
-
const
|
|
104
|
-
output: { type: 'string', short: 'o' },
|
|
105
|
-
param: { type: 'string', short: 'p', multiple: true },
|
|
106
|
-
format: { type: 'boolean', short: 'f', default: false },
|
|
107
|
-
help: { type: 'boolean', short: 'h', default: false },
|
|
108
|
-
version: { type: 'boolean', short: 'v', default: false }
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
let args;
|
|
112
|
-
try {
|
|
113
|
-
args = parseArgs({ options, allowPositionals: true });
|
|
114
|
-
} catch (err) {
|
|
115
|
-
console.error(`Error: ${err.message}`);
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
64
|
+
const args = readArguments();
|
|
118
65
|
|
|
119
66
|
if (args.values.help) {
|
|
120
67
|
printHelp();
|
|
@@ -129,78 +76,37 @@ async function main() {
|
|
|
129
76
|
const [xmlPath, xsltPath] = args.positionals;
|
|
130
77
|
|
|
131
78
|
if (!xmlPath || !xsltPath) {
|
|
132
|
-
console.error(
|
|
79
|
+
console.error("Error: Both XML and XSLT file paths are required");
|
|
133
80
|
console.error('Run "xslt --help" for usage information');
|
|
134
81
|
process.exit(1);
|
|
135
82
|
}
|
|
136
83
|
|
|
137
|
-
|
|
138
|
-
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
|
|
139
|
-
contentType: 'text/html'
|
|
140
|
-
});
|
|
141
|
-
const { DOMParser, XMLSerializer } = dom.window;
|
|
84
|
+
const dom = createDomEnvironment();
|
|
142
85
|
|
|
143
86
|
try {
|
|
144
|
-
|
|
87
|
+
const baseDir = resolveBaseDir();
|
|
88
|
+
const xmlFile = resolveInputPath(xmlPath, "XML", baseDir);
|
|
89
|
+
const xsltFile = resolveInputPath(xsltPath, "XSLT", baseDir);
|
|
90
|
+
const outputFile = args.values.output
|
|
91
|
+
? resolveOutputPath(args.values.output, baseDir)
|
|
92
|
+
: undefined;
|
|
93
|
+
|
|
145
94
|
const [xmlContent, xsltContent] = await Promise.all([
|
|
146
|
-
readFile(
|
|
147
|
-
readFile(
|
|
95
|
+
readFile(xmlFile, "utf-8"),
|
|
96
|
+
readFile(xsltFile, "utf-8"),
|
|
148
97
|
]);
|
|
149
98
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (xmlError) {
|
|
158
|
-
console.error(`Error parsing XML: ${xmlError.textContent}`);
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const xsltError = xsltDoc.querySelector('parsererror');
|
|
163
|
-
if (xsltError) {
|
|
164
|
-
console.error(`Error parsing XSLT: ${xsltError.textContent}`);
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Create processor
|
|
169
|
-
const processor = new XSLTProcessor();
|
|
170
|
-
processor.importStylesheet(xsltDoc);
|
|
171
|
-
|
|
172
|
-
// Set parameters
|
|
173
|
-
const params = parseParameters(args.values.param);
|
|
174
|
-
for (const [name, value] of Object.entries(params)) {
|
|
175
|
-
processor.setParameter(null, name, value);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Transform
|
|
179
|
-
const fragment = processor.transformToFragment(xmlDoc, dom.window.document);
|
|
180
|
-
|
|
181
|
-
// Serialize result
|
|
182
|
-
const serializer = new XMLSerializer();
|
|
183
|
-
let output = serializer.serializeToString(fragment);
|
|
184
|
-
|
|
185
|
-
// Format if requested
|
|
186
|
-
if (args.values.format) {
|
|
187
|
-
output = formatXml(output);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Output result
|
|
191
|
-
if (args.values.output) {
|
|
192
|
-
await writeFile(args.values.output, output, 'utf-8');
|
|
193
|
-
console.error(`Output written to ${args.values.output}`);
|
|
194
|
-
} else {
|
|
195
|
-
console.log(output);
|
|
196
|
-
}
|
|
99
|
+
const output = runTransformation({
|
|
100
|
+
dom,
|
|
101
|
+
xmlContent,
|
|
102
|
+
xsltContent,
|
|
103
|
+
params: parseParameters(args.values.param),
|
|
104
|
+
values: args.values,
|
|
105
|
+
});
|
|
197
106
|
|
|
107
|
+
await writeOutput(output, outputFile);
|
|
198
108
|
} catch (err) {
|
|
199
|
-
|
|
200
|
-
console.error(`Error: File not found: ${err.path}`);
|
|
201
|
-
} else {
|
|
202
|
-
console.error(`Error: ${err.message}`);
|
|
203
|
-
}
|
|
109
|
+
console.error(`Error: ${err.message}`);
|
|
204
110
|
process.exit(1);
|
|
205
111
|
}
|
|
206
112
|
}
|