@tradik/xslt-processor 1.0.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.
@@ -0,0 +1,3271 @@
1
+ // src/xpath/tokenizer.js
2
+ var TokenType = {
3
+ // Literals
4
+ NUMBER: "NUMBER",
5
+ LITERAL: "LITERAL",
6
+ // Operators
7
+ SLASH: "SLASH",
8
+ DOUBLE_SLASH: "DOUBLE_SLASH",
9
+ PIPE: "PIPE",
10
+ PLUS: "PLUS",
11
+ MINUS: "MINUS",
12
+ STAR: "STAR",
13
+ DIV: "DIV",
14
+ MOD: "MOD",
15
+ EQUALS: "EQUALS",
16
+ NOT_EQUALS: "NOT_EQUALS",
17
+ LT: "LT",
18
+ LTE: "LTE",
19
+ GT: "GT",
20
+ GTE: "GTE",
21
+ AND: "AND",
22
+ OR: "OR",
23
+ // Brackets
24
+ LPAREN: "LPAREN",
25
+ RPAREN: "RPAREN",
26
+ LBRACKET: "LBRACKET",
27
+ RBRACKET: "RBRACKET",
28
+ // Axes
29
+ AXIS: "AXIS",
30
+ AT: "AT",
31
+ DOT: "DOT",
32
+ DOUBLE_DOT: "DOUBLE_DOT",
33
+ DOUBLE_COLON: "DOUBLE_COLON",
34
+ // Names
35
+ NAME: "NAME",
36
+ NCNAME: "NCNAME",
37
+ PREFIX: "PREFIX",
38
+ FUNCTION: "FUNCTION",
39
+ NODE_TYPE: "NODE_TYPE",
40
+ // Other
41
+ COMMA: "COMMA",
42
+ DOLLAR: "DOLLAR",
43
+ COLON: "COLON",
44
+ EOF: "EOF"
45
+ };
46
+ var AXIS_NAMES = /* @__PURE__ */ new Set([
47
+ "ancestor",
48
+ "ancestor-or-self",
49
+ "attribute",
50
+ "child",
51
+ "descendant",
52
+ "descendant-or-self",
53
+ "following",
54
+ "following-sibling",
55
+ "namespace",
56
+ "parent",
57
+ "preceding",
58
+ "preceding-sibling",
59
+ "self"
60
+ ]);
61
+ var NODE_TYPES = /* @__PURE__ */ new Set([
62
+ "comment",
63
+ "text",
64
+ "processing-instruction",
65
+ "node"
66
+ ]);
67
+ var OPERATORS = /* @__PURE__ */ new Set(["and", "or", "mod", "div"]);
68
+ var Token = class {
69
+ constructor(type, value, position) {
70
+ this.type = type;
71
+ this.value = value;
72
+ this.position = position;
73
+ }
74
+ toString() {
75
+ return `Token(${this.type}, ${JSON.stringify(this.value)}, pos=${this.position})`;
76
+ }
77
+ };
78
+ var OPERATOR_CONTEXT_BLOCKERS = /* @__PURE__ */ new Set([
79
+ TokenType.AT,
80
+ TokenType.DOUBLE_COLON,
81
+ TokenType.LPAREN,
82
+ TokenType.LBRACKET,
83
+ TokenType.COMMA,
84
+ // Operators - after an operator, next name token should be a name, not an operator
85
+ TokenType.SLASH,
86
+ TokenType.DOUBLE_SLASH,
87
+ TokenType.PIPE,
88
+ TokenType.PLUS,
89
+ TokenType.MINUS,
90
+ TokenType.STAR,
91
+ TokenType.DIV,
92
+ TokenType.MOD,
93
+ TokenType.EQUALS,
94
+ TokenType.NOT_EQUALS,
95
+ TokenType.LT,
96
+ TokenType.LTE,
97
+ TokenType.GT,
98
+ TokenType.GTE,
99
+ TokenType.AND,
100
+ TokenType.OR
101
+ ]);
102
+ var XPathTokenizer = class {
103
+ constructor(expression) {
104
+ this.expression = expression;
105
+ this.position = 0;
106
+ this.tokens = [];
107
+ }
108
+ /**
109
+ * Get the last token that was added (if any)
110
+ */
111
+ getLastToken() {
112
+ return this.tokens.length > 0 ? this.tokens[this.tokens.length - 1] : null;
113
+ }
114
+ /**
115
+ * Check if the current context allows operator names (div, mod, and, or)
116
+ * Per XPath 1.0 spec disambiguation rules.
117
+ */
118
+ isOperatorContext() {
119
+ const lastToken = this.getLastToken();
120
+ if (!lastToken) return false;
121
+ if (OPERATOR_CONTEXT_BLOCKERS.has(lastToken.type)) return false;
122
+ return true;
123
+ }
124
+ tokenize() {
125
+ this.tokens = [];
126
+ this.position = 0;
127
+ while (this.position < this.expression.length) {
128
+ this.skipWhitespace();
129
+ if (this.position >= this.expression.length) break;
130
+ const token = this.nextToken();
131
+ if (token) {
132
+ this.tokens.push(token);
133
+ }
134
+ }
135
+ this.tokens.push(new Token(TokenType.EOF, null, this.position));
136
+ return this.tokens;
137
+ }
138
+ skipWhitespace() {
139
+ while (this.position < this.expression.length && /\s/.test(this.expression[this.position])) {
140
+ this.position++;
141
+ }
142
+ }
143
+ peek(offset = 0) {
144
+ return this.expression[this.position + offset];
145
+ }
146
+ consume() {
147
+ return this.expression[this.position++];
148
+ }
149
+ nextToken() {
150
+ const startPos = this.position;
151
+ const char = this.peek();
152
+ if (char === '"' || char === "'") {
153
+ return this.readStringLiteral();
154
+ }
155
+ if (/[0-9]/.test(char) || char === "." && /[0-9]/.test(this.peek(1))) {
156
+ return this.readNumber();
157
+ }
158
+ if (char === "/" && this.peek(1) === "/") {
159
+ this.position += 2;
160
+ return new Token(TokenType.DOUBLE_SLASH, "//", startPos);
161
+ }
162
+ if (char === "." && this.peek(1) === ".") {
163
+ this.position += 2;
164
+ return new Token(TokenType.DOUBLE_DOT, "..", startPos);
165
+ }
166
+ if (char === ":" && this.peek(1) === ":") {
167
+ this.position += 2;
168
+ return new Token(TokenType.DOUBLE_COLON, "::", startPos);
169
+ }
170
+ if (char === "!" && this.peek(1) === "=") {
171
+ this.position += 2;
172
+ return new Token(TokenType.NOT_EQUALS, "!=", startPos);
173
+ }
174
+ if (char === "<" && this.peek(1) === "=") {
175
+ this.position += 2;
176
+ return new Token(TokenType.LTE, "<=", startPos);
177
+ }
178
+ if (char === ">" && this.peek(1) === "=") {
179
+ this.position += 2;
180
+ return new Token(TokenType.GTE, ">=", startPos);
181
+ }
182
+ const singleCharTokens = {
183
+ "/": TokenType.SLASH,
184
+ "|": TokenType.PIPE,
185
+ "+": TokenType.PLUS,
186
+ "-": TokenType.MINUS,
187
+ "*": TokenType.STAR,
188
+ "=": TokenType.EQUALS,
189
+ "<": TokenType.LT,
190
+ ">": TokenType.GT,
191
+ "(": TokenType.LPAREN,
192
+ ")": TokenType.RPAREN,
193
+ "[": TokenType.LBRACKET,
194
+ "]": TokenType.RBRACKET,
195
+ "@": TokenType.AT,
196
+ ".": TokenType.DOT,
197
+ ",": TokenType.COMMA,
198
+ $: TokenType.DOLLAR,
199
+ ":": TokenType.COLON
200
+ };
201
+ if (singleCharTokens[char]) {
202
+ this.position++;
203
+ return new Token(singleCharTokens[char], char, startPos);
204
+ }
205
+ if (this.isNameStartChar(char)) {
206
+ return this.readName();
207
+ }
208
+ throw new Error(
209
+ `Unexpected character '${char}' at position ${this.position} in expression: ${this.expression}`
210
+ );
211
+ }
212
+ readStringLiteral() {
213
+ const startPos = this.position;
214
+ const quote = this.consume();
215
+ let value = "";
216
+ while (this.position < this.expression.length && this.peek() !== quote) {
217
+ value += this.consume();
218
+ }
219
+ if (this.position >= this.expression.length) {
220
+ throw new Error(`Unterminated string literal at position ${startPos}`);
221
+ }
222
+ this.consume();
223
+ return new Token(TokenType.LITERAL, value, startPos);
224
+ }
225
+ readNumber() {
226
+ const startPos = this.position;
227
+ let value = "";
228
+ while (this.position < this.expression.length && /[0-9]/.test(this.peek())) {
229
+ value += this.consume();
230
+ }
231
+ if (this.peek() === "." && /[0-9]/.test(this.peek(1))) {
232
+ value += this.consume();
233
+ while (this.position < this.expression.length && /[0-9]/.test(this.peek())) {
234
+ value += this.consume();
235
+ }
236
+ }
237
+ return new Token(TokenType.NUMBER, parseFloat(value), startPos);
238
+ }
239
+ readName() {
240
+ const startPos = this.position;
241
+ let value = "";
242
+ while (this.position < this.expression.length && this.isNameChar(this.peek())) {
243
+ value += this.consume();
244
+ }
245
+ this.skipWhitespace();
246
+ if (AXIS_NAMES.has(value) && this.peek() === ":" && this.peek(1) === ":") {
247
+ return new Token(TokenType.AXIS, value, startPos);
248
+ }
249
+ const savedPos = this.position;
250
+ this.skipWhitespace();
251
+ if (this.peek() === "(") {
252
+ if (NODE_TYPES.has(value)) {
253
+ return new Token(TokenType.NODE_TYPE, value, startPos);
254
+ }
255
+ return new Token(TokenType.FUNCTION, value, startPos);
256
+ }
257
+ this.position = savedPos;
258
+ if (OPERATORS.has(value) && this.isOperatorContext()) {
259
+ const opTokens = {
260
+ and: TokenType.AND,
261
+ or: TokenType.OR,
262
+ mod: TokenType.MOD,
263
+ div: TokenType.DIV
264
+ };
265
+ return new Token(opTokens[value], value, startPos);
266
+ }
267
+ return new Token(TokenType.NAME, value, startPos);
268
+ }
269
+ isNameStartChar(char) {
270
+ if (!char) return false;
271
+ const code = char.charCodeAt(0);
272
+ return char === "_" || code >= 65 && code <= 90 || // A-Z
273
+ code >= 97 && code <= 122 || // a-z
274
+ code >= 192;
275
+ }
276
+ isNameChar(char) {
277
+ if (!char) return false;
278
+ const code = char.charCodeAt(0);
279
+ return this.isNameStartChar(char) || char === "-" || char === "." || code >= 48 && code <= 57;
280
+ }
281
+ };
282
+ function tokenize(expression) {
283
+ const tokenizer = new XPathTokenizer(expression);
284
+ return tokenizer.tokenize();
285
+ }
286
+
287
+ // src/xpath/parser.js
288
+ var NodeType = {
289
+ // Expression types
290
+ OR_EXPR: "OrExpr",
291
+ AND_EXPR: "AndExpr",
292
+ EQUALITY_EXPR: "EqualityExpr",
293
+ RELATIONAL_EXPR: "RelationalExpr",
294
+ ADDITIVE_EXPR: "AdditiveExpr",
295
+ MULTIPLICATIVE_EXPR: "MultiplicativeExpr",
296
+ UNARY_EXPR: "UnaryExpr",
297
+ UNION_EXPR: "UnionExpr",
298
+ // Path expressions
299
+ PATH_EXPR: "PathExpr",
300
+ LOCATION_PATH: "LocationPath",
301
+ STEP: "Step",
302
+ PREDICATE: "Predicate",
303
+ // Primaries
304
+ VARIABLE_REF: "VariableRef",
305
+ LITERAL: "Literal",
306
+ NUMBER: "Number",
307
+ FUNCTION_CALL: "FunctionCall",
308
+ // Node tests
309
+ NAME_TEST: "NameTest",
310
+ NODE_TYPE_TEST: "NodeTypeTest",
311
+ PI_TEST: "ProcessingInstructionTest"
312
+ };
313
+ var ASTNode = class {
314
+ constructor(type, props = {}) {
315
+ this.type = type;
316
+ Object.assign(this, props);
317
+ }
318
+ };
319
+ var XPathParser = class {
320
+ constructor(tokens) {
321
+ this.tokens = tokens;
322
+ this.position = 0;
323
+ }
324
+ parse() {
325
+ const expr = this.parseExpr();
326
+ if (!this.isAtEnd()) {
327
+ throw new Error(
328
+ `Unexpected token ${this.peek().type} at position ${this.peek().position}`
329
+ );
330
+ }
331
+ return expr;
332
+ }
333
+ // Expr ::= OrExpr
334
+ parseExpr() {
335
+ return this.parseOrExpr();
336
+ }
337
+ // OrExpr ::= AndExpr | OrExpr 'or' AndExpr
338
+ parseOrExpr() {
339
+ let left = this.parseAndExpr();
340
+ while (this.match(TokenType.OR)) {
341
+ const right = this.parseAndExpr();
342
+ left = new ASTNode(NodeType.OR_EXPR, { left, right });
343
+ }
344
+ return left;
345
+ }
346
+ // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr
347
+ parseAndExpr() {
348
+ let left = this.parseEqualityExpr();
349
+ while (this.match(TokenType.AND)) {
350
+ const right = this.parseEqualityExpr();
351
+ left = new ASTNode(NodeType.AND_EXPR, { left, right });
352
+ }
353
+ return left;
354
+ }
355
+ // EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr | EqualityExpr '!=' RelationalExpr
356
+ parseEqualityExpr() {
357
+ let left = this.parseRelationalExpr();
358
+ while (true) {
359
+ if (this.match(TokenType.EQUALS)) {
360
+ const right = this.parseRelationalExpr();
361
+ left = new ASTNode(NodeType.EQUALITY_EXPR, {
362
+ operator: "=",
363
+ left,
364
+ right
365
+ });
366
+ } else if (this.match(TokenType.NOT_EQUALS)) {
367
+ const right = this.parseRelationalExpr();
368
+ left = new ASTNode(NodeType.EQUALITY_EXPR, {
369
+ operator: "!=",
370
+ left,
371
+ right
372
+ });
373
+ } else {
374
+ break;
375
+ }
376
+ }
377
+ return left;
378
+ }
379
+ // RelationalExpr ::= AdditiveExpr | RelationalExpr '<' AdditiveExpr | ...
380
+ parseRelationalExpr() {
381
+ let left = this.parseAdditiveExpr();
382
+ while (true) {
383
+ if (this.match(TokenType.LT)) {
384
+ const right = this.parseAdditiveExpr();
385
+ left = new ASTNode(NodeType.RELATIONAL_EXPR, {
386
+ operator: "<",
387
+ left,
388
+ right
389
+ });
390
+ } else if (this.match(TokenType.LTE)) {
391
+ const right = this.parseAdditiveExpr();
392
+ left = new ASTNode(NodeType.RELATIONAL_EXPR, {
393
+ operator: "<=",
394
+ left,
395
+ right
396
+ });
397
+ } else if (this.match(TokenType.GT)) {
398
+ const right = this.parseAdditiveExpr();
399
+ left = new ASTNode(NodeType.RELATIONAL_EXPR, {
400
+ operator: ">",
401
+ left,
402
+ right
403
+ });
404
+ } else if (this.match(TokenType.GTE)) {
405
+ const right = this.parseAdditiveExpr();
406
+ left = new ASTNode(NodeType.RELATIONAL_EXPR, {
407
+ operator: ">=",
408
+ left,
409
+ right
410
+ });
411
+ } else {
412
+ break;
413
+ }
414
+ }
415
+ return left;
416
+ }
417
+ // AdditiveExpr ::= MultiplicativeExpr | AdditiveExpr '+' MultiplicativeExpr | AdditiveExpr '-' MultiplicativeExpr
418
+ parseAdditiveExpr() {
419
+ let left = this.parseMultiplicativeExpr();
420
+ while (true) {
421
+ if (this.match(TokenType.PLUS)) {
422
+ const right = this.parseMultiplicativeExpr();
423
+ left = new ASTNode(NodeType.ADDITIVE_EXPR, {
424
+ operator: "+",
425
+ left,
426
+ right
427
+ });
428
+ } else if (this.match(TokenType.MINUS)) {
429
+ const right = this.parseMultiplicativeExpr();
430
+ left = new ASTNode(NodeType.ADDITIVE_EXPR, {
431
+ operator: "-",
432
+ left,
433
+ right
434
+ });
435
+ } else {
436
+ break;
437
+ }
438
+ }
439
+ return left;
440
+ }
441
+ // MultiplicativeExpr ::= UnaryExpr | MultiplicativeExpr '*' UnaryExpr | ...
442
+ parseMultiplicativeExpr() {
443
+ let left = this.parseUnaryExpr();
444
+ while (true) {
445
+ if (this.match(TokenType.STAR)) {
446
+ const right = this.parseUnaryExpr();
447
+ left = new ASTNode(NodeType.MULTIPLICATIVE_EXPR, {
448
+ operator: "*",
449
+ left,
450
+ right
451
+ });
452
+ } else if (this.match(TokenType.DIV)) {
453
+ const right = this.parseUnaryExpr();
454
+ left = new ASTNode(NodeType.MULTIPLICATIVE_EXPR, {
455
+ operator: "div",
456
+ left,
457
+ right
458
+ });
459
+ } else if (this.match(TokenType.MOD)) {
460
+ const right = this.parseUnaryExpr();
461
+ left = new ASTNode(NodeType.MULTIPLICATIVE_EXPR, {
462
+ operator: "mod",
463
+ left,
464
+ right
465
+ });
466
+ } else {
467
+ break;
468
+ }
469
+ }
470
+ return left;
471
+ }
472
+ // UnaryExpr ::= UnionExpr | '-' UnaryExpr
473
+ parseUnaryExpr() {
474
+ if (this.match(TokenType.MINUS)) {
475
+ const operand = this.parseUnaryExpr();
476
+ return new ASTNode(NodeType.UNARY_EXPR, { operator: "-", operand });
477
+ }
478
+ return this.parseUnionExpr();
479
+ }
480
+ // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr
481
+ parseUnionExpr() {
482
+ let left = this.parsePathExpr();
483
+ while (this.match(TokenType.PIPE)) {
484
+ const right = this.parsePathExpr();
485
+ left = new ASTNode(NodeType.UNION_EXPR, { left, right });
486
+ }
487
+ return left;
488
+ }
489
+ // PathExpr ::= LocationPath | FilterExpr | FilterExpr '/' RelativeLocationPath | FilterExpr '//' RelativeLocationPath
490
+ parsePathExpr() {
491
+ if (this.check(TokenType.SLASH) || this.check(TokenType.DOUBLE_SLASH)) {
492
+ return this.parseLocationPath();
493
+ }
494
+ if (this.isStepStart()) {
495
+ return this.parseLocationPath();
496
+ }
497
+ const filter = this.parseFilterExpr();
498
+ if (this.check(TokenType.SLASH) || this.check(TokenType.DOUBLE_SLASH)) {
499
+ const steps = [];
500
+ while (this.check(TokenType.SLASH) || this.check(TokenType.DOUBLE_SLASH)) {
501
+ const isDescendant = this.match(TokenType.DOUBLE_SLASH);
502
+ if (!isDescendant) this.advance();
503
+ if (isDescendant) {
504
+ steps.push(
505
+ new ASTNode(NodeType.STEP, {
506
+ axis: "descendant-or-self",
507
+ nodeTest: new ASTNode(NodeType.NODE_TYPE_TEST, {
508
+ nodeType: "node"
509
+ }),
510
+ predicates: []
511
+ })
512
+ );
513
+ }
514
+ steps.push(this.parseStep());
515
+ }
516
+ return new ASTNode(NodeType.PATH_EXPR, { filter, steps });
517
+ }
518
+ return filter;
519
+ }
520
+ // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath
521
+ parseLocationPath() {
522
+ let absolute = false;
523
+ const steps = [];
524
+ if (this.match(TokenType.DOUBLE_SLASH)) {
525
+ absolute = true;
526
+ steps.push(
527
+ new ASTNode(NodeType.STEP, {
528
+ axis: "descendant-or-self",
529
+ nodeTest: new ASTNode(NodeType.NODE_TYPE_TEST, { nodeType: "node" }),
530
+ predicates: []
531
+ })
532
+ );
533
+ if (!this.isAtEnd() && !this.check(TokenType.EOF)) {
534
+ steps.push(this.parseStep());
535
+ }
536
+ } else if (this.match(TokenType.SLASH)) {
537
+ absolute = true;
538
+ if (this.isStepStart()) {
539
+ steps.push(this.parseStep());
540
+ }
541
+ } else {
542
+ steps.push(this.parseStep());
543
+ }
544
+ while (this.check(TokenType.SLASH) || this.check(TokenType.DOUBLE_SLASH)) {
545
+ const isDescendant = this.match(TokenType.DOUBLE_SLASH);
546
+ if (!isDescendant) this.advance();
547
+ if (isDescendant) {
548
+ steps.push(
549
+ new ASTNode(NodeType.STEP, {
550
+ axis: "descendant-or-self",
551
+ nodeTest: new ASTNode(NodeType.NODE_TYPE_TEST, {
552
+ nodeType: "node"
553
+ }),
554
+ predicates: []
555
+ })
556
+ );
557
+ }
558
+ steps.push(this.parseStep());
559
+ }
560
+ return new ASTNode(NodeType.LOCATION_PATH, { absolute, steps });
561
+ }
562
+ // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
563
+ parseStep() {
564
+ if (this.match(TokenType.DOT)) {
565
+ return new ASTNode(NodeType.STEP, {
566
+ axis: "self",
567
+ nodeTest: new ASTNode(NodeType.NODE_TYPE_TEST, { nodeType: "node" }),
568
+ predicates: []
569
+ });
570
+ }
571
+ if (this.match(TokenType.DOUBLE_DOT)) {
572
+ return new ASTNode(NodeType.STEP, {
573
+ axis: "parent",
574
+ nodeTest: new ASTNode(NodeType.NODE_TYPE_TEST, { nodeType: "node" }),
575
+ predicates: []
576
+ });
577
+ }
578
+ let axis = "child";
579
+ if (this.match(TokenType.AT)) {
580
+ axis = "attribute";
581
+ } else if (this.check(TokenType.AXIS)) {
582
+ axis = this.advance().value;
583
+ this.expect(TokenType.DOUBLE_COLON);
584
+ }
585
+ const nodeTest = this.parseNodeTest();
586
+ const predicates = [];
587
+ while (this.check(TokenType.LBRACKET)) {
588
+ predicates.push(this.parsePredicate());
589
+ }
590
+ return new ASTNode(NodeType.STEP, { axis, nodeTest, predicates });
591
+ }
592
+ // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')'
593
+ parseNodeTest() {
594
+ if (this.check(TokenType.NODE_TYPE)) {
595
+ const nodeType = this.advance().value;
596
+ this.expect(TokenType.LPAREN);
597
+ if (nodeType === "processing-instruction" && this.check(TokenType.LITERAL)) {
598
+ const name = this.advance().value;
599
+ this.expect(TokenType.RPAREN);
600
+ return new ASTNode(NodeType.PI_TEST, { name });
601
+ }
602
+ this.expect(TokenType.RPAREN);
603
+ return new ASTNode(NodeType.NODE_TYPE_TEST, { nodeType });
604
+ }
605
+ return this.parseNameTest();
606
+ }
607
+ // NameTest ::= '*' | NCName ':' '*' | QName
608
+ parseNameTest() {
609
+ if (this.match(TokenType.STAR)) {
610
+ return new ASTNode(NodeType.NAME_TEST, { name: "*", prefix: null });
611
+ }
612
+ if (!this.check(TokenType.NAME)) {
613
+ throw new Error(`Expected name at position ${this.peek().position}`);
614
+ }
615
+ const name = this.advance().value;
616
+ if (this.match(TokenType.COLON)) {
617
+ if (this.match(TokenType.STAR)) {
618
+ return new ASTNode(NodeType.NAME_TEST, { name: "*", prefix: name });
619
+ }
620
+ if (this.check(TokenType.NAME)) {
621
+ const localName = this.advance().value;
622
+ return new ASTNode(NodeType.NAME_TEST, {
623
+ name: localName,
624
+ prefix: name
625
+ });
626
+ }
627
+ throw new Error(
628
+ `Expected name or * after : at position ${this.peek().position}`
629
+ );
630
+ }
631
+ return new ASTNode(NodeType.NAME_TEST, { name, prefix: null });
632
+ }
633
+ // Predicate ::= '[' Expr ']'
634
+ parsePredicate() {
635
+ this.expect(TokenType.LBRACKET);
636
+ const expr = this.parseExpr();
637
+ this.expect(TokenType.RBRACKET);
638
+ return new ASTNode(NodeType.PREDICATE, { expr });
639
+ }
640
+ // FilterExpr ::= PrimaryExpr | FilterExpr Predicate
641
+ parseFilterExpr() {
642
+ let primary = this.parsePrimaryExpr();
643
+ while (this.check(TokenType.LBRACKET)) {
644
+ const predicate = this.parsePredicate();
645
+ primary = new ASTNode(NodeType.PATH_EXPR, {
646
+ filter: primary,
647
+ predicates: [predicate]
648
+ });
649
+ }
650
+ return primary;
651
+ }
652
+ // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
653
+ parsePrimaryExpr() {
654
+ if (this.match(TokenType.DOLLAR)) {
655
+ if (!this.check(TokenType.NAME)) {
656
+ throw new Error(
657
+ `Expected variable name at position ${this.peek().position}`
658
+ );
659
+ }
660
+ const name = this.advance().value;
661
+ let prefix = null;
662
+ if (this.match(TokenType.COLON)) {
663
+ prefix = name;
664
+ if (!this.check(TokenType.NAME)) {
665
+ throw new Error(
666
+ `Expected local name at position ${this.peek().position}`
667
+ );
668
+ }
669
+ const localName = this.advance().value;
670
+ return new ASTNode(NodeType.VARIABLE_REF, { name: localName, prefix });
671
+ }
672
+ return new ASTNode(NodeType.VARIABLE_REF, { name, prefix });
673
+ }
674
+ if (this.match(TokenType.LPAREN)) {
675
+ const expr = this.parseExpr();
676
+ this.expect(TokenType.RPAREN);
677
+ return expr;
678
+ }
679
+ if (this.check(TokenType.LITERAL)) {
680
+ return new ASTNode(NodeType.LITERAL, { value: this.advance().value });
681
+ }
682
+ if (this.check(TokenType.NUMBER)) {
683
+ return new ASTNode(NodeType.NUMBER, { value: this.advance().value });
684
+ }
685
+ if (this.check(TokenType.FUNCTION)) {
686
+ return this.parseFunctionCall();
687
+ }
688
+ throw new Error(
689
+ `Unexpected token ${this.peek().type} at position ${this.peek().position}`
690
+ );
691
+ }
692
+ // FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument )* )? ')'
693
+ // Note: Prefixed function calls (prefix:fn()) are not supported because
694
+ // the tokenizer identifies functions by NAME followed by '(' - prefixed
695
+ // names like 'fn:name()' are tokenized as NAME:NAME() which is parsed
696
+ // as a location path, not a function call.
697
+ parseFunctionCall() {
698
+ const name = this.advance().value;
699
+ return this.parseFunctionCallArgs(name, null);
700
+ }
701
+ parseFunctionCallArgs(name, prefix) {
702
+ this.expect(TokenType.LPAREN);
703
+ const args = [];
704
+ if (!this.check(TokenType.RPAREN)) {
705
+ args.push(this.parseExpr());
706
+ while (this.match(TokenType.COMMA)) {
707
+ args.push(this.parseExpr());
708
+ }
709
+ }
710
+ this.expect(TokenType.RPAREN);
711
+ return new ASTNode(NodeType.FUNCTION_CALL, { name, prefix, args });
712
+ }
713
+ // Helper methods
714
+ isStepStart() {
715
+ const type = this.peek().type;
716
+ return type === TokenType.NAME || type === TokenType.STAR || type === TokenType.AT || type === TokenType.DOT || type === TokenType.DOUBLE_DOT || type === TokenType.AXIS || type === TokenType.NODE_TYPE;
717
+ }
718
+ peek() {
719
+ return this.tokens[this.position];
720
+ }
721
+ advance() {
722
+ if (!this.isAtEnd()) {
723
+ return this.tokens[this.position++];
724
+ }
725
+ return this.tokens[this.position];
726
+ }
727
+ check(type) {
728
+ if (this.isAtEnd()) return false;
729
+ return this.peek().type === type;
730
+ }
731
+ match(type) {
732
+ if (this.check(type)) {
733
+ this.advance();
734
+ return true;
735
+ }
736
+ return false;
737
+ }
738
+ expect(type) {
739
+ if (!this.check(type)) {
740
+ throw new Error(
741
+ `Expected ${type} but got ${this.peek().type} at position ${this.peek().position}`
742
+ );
743
+ }
744
+ return this.advance();
745
+ }
746
+ isAtEnd() {
747
+ return this.peek().type === TokenType.EOF;
748
+ }
749
+ };
750
+ function parse(expression) {
751
+ const tokens = tokenize(expression);
752
+ const parser = new XPathParser(tokens);
753
+ return parser.parse();
754
+ }
755
+
756
+ // src/xpath/evaluator.js
757
+ var XPathResultType = {
758
+ ANY_TYPE: 0,
759
+ NUMBER_TYPE: 1,
760
+ STRING_TYPE: 2,
761
+ BOOLEAN_TYPE: 3,
762
+ UNORDERED_NODE_ITERATOR_TYPE: 4,
763
+ ORDERED_NODE_ITERATOR_TYPE: 5,
764
+ UNORDERED_NODE_SNAPSHOT_TYPE: 6,
765
+ ORDERED_NODE_SNAPSHOT_TYPE: 7,
766
+ ANY_UNORDERED_NODE_TYPE: 8,
767
+ FIRST_ORDERED_NODE_TYPE: 9
768
+ };
769
+ var XPathLimits = {
770
+ MAX_RECURSION_DEPTH: 100,
771
+ MAX_RESULT_SIZE: 1e4,
772
+ MAX_STRING_LENGTH: 1e6
773
+ };
774
+ var FORBIDDEN_VARIABLE_NAMES = Object.freeze([
775
+ "__proto__",
776
+ "constructor",
777
+ "prototype",
778
+ "__defineGetter__",
779
+ "__defineSetter__",
780
+ "__lookupGetter__",
781
+ "__lookupSetter__"
782
+ ]);
783
+ var XPathContext = class _XPathContext {
784
+ constructor(node, position = 1, size = 1, variables = {}, namespaces = {}) {
785
+ this.node = node;
786
+ this.position = position;
787
+ this.size = size;
788
+ this.variables = variables;
789
+ this.namespaces = namespaces;
790
+ }
791
+ clone(overrides = {}) {
792
+ return new _XPathContext(
793
+ overrides.node ?? this.node,
794
+ overrides.position ?? this.position,
795
+ overrides.size ?? this.size,
796
+ overrides.variables ?? this.variables,
797
+ overrides.namespaces ?? this.namespaces
798
+ );
799
+ }
800
+ };
801
+ var XPathEvaluator = class {
802
+ constructor(options = {}) {
803
+ this.functions = this.initCoreFunctions();
804
+ this.maxRecursionDepth = options.maxRecursionDepth ?? XPathLimits.MAX_RECURSION_DEPTH;
805
+ this.maxResultSize = options.maxResultSize ?? XPathLimits.MAX_RESULT_SIZE;
806
+ this.maxStringLength = options.maxStringLength ?? XPathLimits.MAX_STRING_LENGTH;
807
+ this.recursionDepth = 0;
808
+ }
809
+ /**
810
+ * Evaluate XPath expression against a context
811
+ * @throws {Error} If recursion depth exceeds limit
812
+ * @throws {Error} If AST is invalid
813
+ */
814
+ evaluate(ast, context) {
815
+ if (!ast || typeof ast !== "object") {
816
+ throw new Error("Invalid AST: expected object");
817
+ }
818
+ if (!ast.type) {
819
+ throw new Error("Invalid AST: missing type property");
820
+ }
821
+ this.recursionDepth++;
822
+ if (this.recursionDepth > this.maxRecursionDepth) {
823
+ this.recursionDepth = 0;
824
+ throw new Error(
825
+ `Maximum recursion depth exceeded (${this.maxRecursionDepth})`
826
+ );
827
+ }
828
+ try {
829
+ return this.evaluateInternal(ast, context);
830
+ } finally {
831
+ this.recursionDepth--;
832
+ }
833
+ }
834
+ /**
835
+ * Internal evaluation (after validation)
836
+ */
837
+ evaluateInternal(ast, context) {
838
+ switch (ast.type) {
839
+ case NodeType.OR_EXPR:
840
+ return this.evalOrExpr(ast, context);
841
+ case NodeType.AND_EXPR:
842
+ return this.evalAndExpr(ast, context);
843
+ case NodeType.EQUALITY_EXPR:
844
+ return this.evalEqualityExpr(ast, context);
845
+ case NodeType.RELATIONAL_EXPR:
846
+ return this.evalRelationalExpr(ast, context);
847
+ case NodeType.ADDITIVE_EXPR:
848
+ return this.evalAdditiveExpr(ast, context);
849
+ case NodeType.MULTIPLICATIVE_EXPR:
850
+ return this.evalMultiplicativeExpr(ast, context);
851
+ case NodeType.UNARY_EXPR:
852
+ return this.evalUnaryExpr(ast, context);
853
+ case NodeType.UNION_EXPR:
854
+ return this.evalUnionExpr(ast, context);
855
+ case NodeType.PATH_EXPR:
856
+ return this.evalPathExpr(ast, context);
857
+ case NodeType.LOCATION_PATH:
858
+ return this.evalLocationPath(ast, context);
859
+ case NodeType.VARIABLE_REF:
860
+ return this.evalVariableRef(ast, context);
861
+ case NodeType.LITERAL:
862
+ return this.validateString(ast.value);
863
+ case NodeType.NUMBER:
864
+ return ast.value;
865
+ case NodeType.FUNCTION_CALL:
866
+ return this.evalFunctionCall(ast, context);
867
+ default:
868
+ throw new Error(`Unknown AST node type: ${String(ast.type)}`);
869
+ }
870
+ }
871
+ /**
872
+ * Validate and limit string length
873
+ */
874
+ validateString(str) {
875
+ if (typeof str === "string" && str.length > this.maxStringLength) {
876
+ throw new Error(
877
+ `String exceeds maximum length (${this.maxStringLength})`
878
+ );
879
+ }
880
+ return str;
881
+ }
882
+ /**
883
+ * Validate result size
884
+ */
885
+ validateResultSize(nodes) {
886
+ if (Array.isArray(nodes) && nodes.length > this.maxResultSize) {
887
+ throw new Error(
888
+ `Result set exceeds maximum size (${this.maxResultSize})`
889
+ );
890
+ }
891
+ return nodes;
892
+ }
893
+ evalOrExpr(ast, context) {
894
+ return this.toBoolean(this.evaluate(ast.left, context)) || this.toBoolean(this.evaluate(ast.right, context));
895
+ }
896
+ evalAndExpr(ast, context) {
897
+ return this.toBoolean(this.evaluate(ast.left, context)) && this.toBoolean(this.evaluate(ast.right, context));
898
+ }
899
+ evalEqualityExpr(ast, context) {
900
+ const left = this.evaluate(ast.left, context);
901
+ const right = this.evaluate(ast.right, context);
902
+ const isEqual = this.compareValues(left, right, "=");
903
+ return ast.operator === "=" ? isEqual : !isEqual;
904
+ }
905
+ evalRelationalExpr(ast, context) {
906
+ const left = this.evaluate(ast.left, context);
907
+ const right = this.evaluate(ast.right, context);
908
+ return this.compareValues(left, right, ast.operator);
909
+ }
910
+ evalAdditiveExpr(ast, context) {
911
+ const left = this.toNumber(this.evaluate(ast.left, context));
912
+ const right = this.toNumber(this.evaluate(ast.right, context));
913
+ return ast.operator === "+" ? left + right : left - right;
914
+ }
915
+ evalMultiplicativeExpr(ast, context) {
916
+ const left = this.toNumber(this.evaluate(ast.left, context));
917
+ const right = this.toNumber(this.evaluate(ast.right, context));
918
+ switch (ast.operator) {
919
+ case "*":
920
+ return left * right;
921
+ case "div":
922
+ return left / right;
923
+ case "mod":
924
+ return left % right;
925
+ default:
926
+ throw new Error(`Unknown multiplicative operator: ${ast.operator}`);
927
+ }
928
+ }
929
+ evalUnaryExpr(ast, context) {
930
+ const value = this.toNumber(this.evaluate(ast.operand, context));
931
+ return -value;
932
+ }
933
+ evalUnionExpr(ast, context) {
934
+ const left = this.evaluate(ast.left, context);
935
+ const right = this.evaluate(ast.right, context);
936
+ const leftNodes = Array.isArray(left) ? left : [left];
937
+ const rightNodes = Array.isArray(right) ? right : [right];
938
+ const seen = /* @__PURE__ */ new Set();
939
+ const result = [];
940
+ for (const node of [...leftNodes, ...rightNodes]) {
941
+ if (!seen.has(node)) {
942
+ seen.add(node);
943
+ result.push(node);
944
+ }
945
+ }
946
+ this.validateResultSize(result);
947
+ return this.sortByDocumentOrder(result);
948
+ }
949
+ evalPathExpr(ast, context) {
950
+ if (ast.filter) {
951
+ let result = this.evaluate(ast.filter, context);
952
+ if (ast.predicates) {
953
+ for (const pred of ast.predicates) {
954
+ result = this.filterByPredicate(result, pred, context);
955
+ }
956
+ }
957
+ if (ast.steps) {
958
+ for (const step of ast.steps) {
959
+ result = this.evalStepOnNodes(step, result, context);
960
+ }
961
+ }
962
+ return result;
963
+ }
964
+ return [];
965
+ }
966
+ evalLocationPath(ast, context) {
967
+ let nodes;
968
+ if (ast.absolute) {
969
+ const doc = context.node.ownerDocument || context.node;
970
+ nodes = [doc];
971
+ } else {
972
+ nodes = [context.node];
973
+ }
974
+ for (const step of ast.steps) {
975
+ nodes = this.evalStepOnNodes(step, nodes, context);
976
+ this.validateResultSize(nodes);
977
+ }
978
+ return nodes;
979
+ }
980
+ evalStepOnNodes(step, nodes, context) {
981
+ const allNodes = Array.isArray(nodes) ? nodes : [nodes];
982
+ let result = [];
983
+ for (const node of allNodes) {
984
+ const stepNodes = this.evalStep(step, context.clone({ node }));
985
+ result = result.concat(stepNodes);
986
+ if (result.length > this.maxResultSize * 2) {
987
+ this.validateResultSize(result);
988
+ }
989
+ }
990
+ const uniqueResult = [...new Set(result)];
991
+ this.validateResultSize(uniqueResult);
992
+ return this.sortByDocumentOrder(uniqueResult);
993
+ }
994
+ evalStep(step, context) {
995
+ let nodes = this.getAxisNodes(step.axis, context.node);
996
+ nodes = nodes.filter((n) => this.matchNodeTest(step.nodeTest, n, context));
997
+ for (const predicate of step.predicates) {
998
+ nodes = this.filterByPredicate(nodes, predicate, context);
999
+ }
1000
+ return nodes;
1001
+ }
1002
+ getAxisNodes(axis, node) {
1003
+ switch (axis) {
1004
+ case "child":
1005
+ return Array.from(node.childNodes || []);
1006
+ case "parent":
1007
+ return node.parentNode ? [node.parentNode] : [];
1008
+ case "self":
1009
+ return [node];
1010
+ case "descendant":
1011
+ return this.getDescendants(node, false);
1012
+ case "descendant-or-self":
1013
+ return this.getDescendants(node, true);
1014
+ case "ancestor":
1015
+ return this.getAncestors(node, false);
1016
+ case "ancestor-or-self":
1017
+ return this.getAncestors(node, true);
1018
+ case "following-sibling":
1019
+ return this.getFollowingSiblings(node);
1020
+ case "preceding-sibling":
1021
+ return this.getPrecedingSiblings(node);
1022
+ case "following":
1023
+ return this.getFollowing(node);
1024
+ case "preceding":
1025
+ return this.getPreceding(node);
1026
+ case "attribute":
1027
+ if (node.attributes) {
1028
+ return Array.from(node.attributes);
1029
+ }
1030
+ return [];
1031
+ case "namespace":
1032
+ return [];
1033
+ default:
1034
+ throw new Error(`Unknown axis: ${axis}`);
1035
+ }
1036
+ }
1037
+ getDescendants(node, includeSelf) {
1038
+ const result = includeSelf ? [node] : [];
1039
+ const stack = Array.from(node.childNodes || []).reverse();
1040
+ while (stack.length > 0) {
1041
+ const current = stack.pop();
1042
+ result.push(current);
1043
+ if (current.childNodes) {
1044
+ for (let i = current.childNodes.length - 1; i >= 0; i--) {
1045
+ stack.push(current.childNodes[i]);
1046
+ }
1047
+ }
1048
+ }
1049
+ return result;
1050
+ }
1051
+ getAncestors(node, includeSelf) {
1052
+ const result = includeSelf ? [node] : [];
1053
+ let current = node.parentNode;
1054
+ while (current) {
1055
+ result.push(current);
1056
+ current = current.parentNode;
1057
+ }
1058
+ return result;
1059
+ }
1060
+ getFollowingSiblings(node) {
1061
+ const result = [];
1062
+ if (!node) return result;
1063
+ let current = node.nextSibling;
1064
+ while (current) {
1065
+ result.push(current);
1066
+ current = current.nextSibling;
1067
+ }
1068
+ return result;
1069
+ }
1070
+ getPrecedingSiblings(node) {
1071
+ const result = [];
1072
+ if (!node) return result;
1073
+ let current = node.previousSibling;
1074
+ while (current) {
1075
+ result.push(current);
1076
+ current = current.previousSibling;
1077
+ }
1078
+ return result.reverse();
1079
+ }
1080
+ getFollowing(node) {
1081
+ const result = [];
1082
+ let current = node;
1083
+ while (current) {
1084
+ if (current.nextSibling) {
1085
+ current = current.nextSibling;
1086
+ result.push(current);
1087
+ result.push(...this.getDescendants(current, false));
1088
+ } else {
1089
+ current = current.parentNode;
1090
+ }
1091
+ }
1092
+ return result;
1093
+ }
1094
+ getPreceding(node) {
1095
+ const result = [];
1096
+ let current = node;
1097
+ while (current) {
1098
+ if (current.previousSibling) {
1099
+ current = current.previousSibling;
1100
+ const descendants = this.getDescendants(current, false);
1101
+ result.unshift(...descendants.reverse());
1102
+ result.unshift(current);
1103
+ } else {
1104
+ current = current.parentNode;
1105
+ if (current && current.nodeType !== 9) {
1106
+ }
1107
+ }
1108
+ }
1109
+ return result;
1110
+ }
1111
+ matchNodeTest(nodeTest, node, context) {
1112
+ switch (nodeTest.type) {
1113
+ case NodeType.NAME_TEST:
1114
+ return this.matchNameTest(nodeTest, node, context);
1115
+ case NodeType.NODE_TYPE_TEST:
1116
+ return this.matchNodeTypeTest(nodeTest.nodeType, node);
1117
+ case NodeType.PI_TEST:
1118
+ return node.nodeType === 7 && node.nodeName === nodeTest.name;
1119
+ default:
1120
+ return false;
1121
+ }
1122
+ }
1123
+ matchNameTest(nodeTest, node, context) {
1124
+ if (node.nodeType !== 1 && node.nodeType !== 2) {
1125
+ return false;
1126
+ }
1127
+ const name = nodeTest.name;
1128
+ const prefix = nodeTest.prefix;
1129
+ if (name === "*" && !prefix) {
1130
+ return true;
1131
+ }
1132
+ const nodeName = node.localName || node.nodeName;
1133
+ const nodeNs = node.namespaceURI || null;
1134
+ if (name === "*" && prefix) {
1135
+ const ns2 = context.namespaces[prefix];
1136
+ return nodeNs === ns2;
1137
+ }
1138
+ if (!prefix) {
1139
+ const doc = node.ownerDocument;
1140
+ if (doc && doc.contentType === "text/html" && node.nodeType === 1) {
1141
+ return nodeName.toLowerCase() === name.toLowerCase();
1142
+ }
1143
+ return nodeName === name;
1144
+ }
1145
+ const ns = context.namespaces[prefix];
1146
+ return nodeName === name && nodeNs === ns;
1147
+ }
1148
+ matchNodeTypeTest(nodeType, node) {
1149
+ switch (nodeType) {
1150
+ case "node":
1151
+ return true;
1152
+ case "text":
1153
+ return node.nodeType === 3;
1154
+ case "comment":
1155
+ return node.nodeType === 8;
1156
+ case "processing-instruction":
1157
+ return node.nodeType === 7;
1158
+ default:
1159
+ return false;
1160
+ }
1161
+ }
1162
+ filterByPredicate(nodes, predicate, context) {
1163
+ const result = [];
1164
+ const nodeArray = Array.isArray(nodes) ? nodes : [nodes];
1165
+ const size = nodeArray.length;
1166
+ for (let i = 0; i < nodeArray.length; i++) {
1167
+ const node = nodeArray[i];
1168
+ const predicateContext = context.clone({
1169
+ node,
1170
+ position: i + 1,
1171
+ size
1172
+ });
1173
+ const value = this.evaluate(predicate.expr, predicateContext);
1174
+ if (typeof value === "number") {
1175
+ if (value === i + 1) {
1176
+ result.push(node);
1177
+ }
1178
+ } else if (this.toBoolean(value)) {
1179
+ result.push(node);
1180
+ }
1181
+ }
1182
+ return result;
1183
+ }
1184
+ evalVariableRef(ast, context) {
1185
+ const name = ast.prefix ? `${ast.prefix}:${ast.name}` : ast.name;
1186
+ if (FORBIDDEN_VARIABLE_NAMES.includes(name) || FORBIDDEN_VARIABLE_NAMES.includes(ast.name)) {
1187
+ throw new Error(`Forbidden variable name: $${name}`);
1188
+ }
1189
+ if (!Object.prototype.hasOwnProperty.call(context.variables, name)) {
1190
+ throw new Error(`Undefined variable: $${name}`);
1191
+ }
1192
+ return context.variables[name];
1193
+ }
1194
+ evalFunctionCall(ast, context) {
1195
+ const name = ast.prefix ? `${ast.prefix}:${ast.name}` : ast.name;
1196
+ const fn = this.functions[name];
1197
+ if (!fn) {
1198
+ throw new Error(`Unknown function: ${name}`);
1199
+ }
1200
+ return fn.call(this, ast.args, context);
1201
+ }
1202
+ // Type conversion functions
1203
+ toBoolean(value) {
1204
+ if (typeof value === "boolean") return value;
1205
+ if (typeof value === "number") return value !== 0 && !isNaN(value);
1206
+ if (typeof value === "string") return value.length > 0;
1207
+ if (Array.isArray(value)) return value.length > 0;
1208
+ if (value && value.nodeType) return true;
1209
+ return Boolean(value);
1210
+ }
1211
+ toNumber(value) {
1212
+ if (typeof value === "number") return value;
1213
+ if (typeof value === "boolean") return value ? 1 : 0;
1214
+ if (typeof value === "string") {
1215
+ const trimmed = value.trim();
1216
+ if (trimmed === "") return NaN;
1217
+ const num = Number(trimmed);
1218
+ return num;
1219
+ }
1220
+ if (Array.isArray(value)) {
1221
+ return this.toNumber(this.toString(value));
1222
+ }
1223
+ if (value && value.nodeType) {
1224
+ return this.toNumber(this.getStringValue(value));
1225
+ }
1226
+ return NaN;
1227
+ }
1228
+ toString(value) {
1229
+ if (typeof value === "string") return value;
1230
+ if (typeof value === "number") {
1231
+ if (isNaN(value)) return "NaN";
1232
+ if (value === Infinity) return "Infinity";
1233
+ if (value === -Infinity) return "-Infinity";
1234
+ if (value === 0) return "0";
1235
+ return String(value);
1236
+ }
1237
+ if (typeof value === "boolean") return value ? "true" : "false";
1238
+ if (Array.isArray(value)) {
1239
+ if (value.length === 0) return "";
1240
+ return this.getStringValue(value[0]);
1241
+ }
1242
+ if (value && value.nodeType) {
1243
+ return this.getStringValue(value);
1244
+ }
1245
+ return String(value);
1246
+ }
1247
+ getStringValue(node) {
1248
+ if (!node) return "";
1249
+ switch (node.nodeType) {
1250
+ case 1:
1251
+ // Element
1252
+ case 9:
1253
+ // Document
1254
+ case 11: {
1255
+ let text = "";
1256
+ const walker = (n) => {
1257
+ if (n.nodeType === 3) {
1258
+ text += n.nodeValue || "";
1259
+ } else if (n.childNodes) {
1260
+ for (const child of n.childNodes) {
1261
+ walker(child);
1262
+ }
1263
+ }
1264
+ };
1265
+ walker(node);
1266
+ return text;
1267
+ }
1268
+ case 2:
1269
+ // Attribute
1270
+ case 3:
1271
+ // Text
1272
+ case 4:
1273
+ // CDATA
1274
+ case 7:
1275
+ // Processing Instruction
1276
+ case 8:
1277
+ return node.nodeValue || "";
1278
+ default:
1279
+ return "";
1280
+ }
1281
+ }
1282
+ // Comparison helper
1283
+ compareValues(left, right, operator) {
1284
+ const leftIsNodeSet = Array.isArray(left);
1285
+ const rightIsNodeSet = Array.isArray(right);
1286
+ if (leftIsNodeSet && rightIsNodeSet) {
1287
+ for (const l of left) {
1288
+ for (const r of right) {
1289
+ if (this.comparePrimitive(
1290
+ this.getStringValue(l),
1291
+ this.getStringValue(r),
1292
+ operator
1293
+ )) {
1294
+ return true;
1295
+ }
1296
+ }
1297
+ }
1298
+ return false;
1299
+ }
1300
+ if (leftIsNodeSet) {
1301
+ for (const l of left) {
1302
+ if (this.comparePrimitive(this.getStringValue(l), right, operator)) {
1303
+ return true;
1304
+ }
1305
+ }
1306
+ return false;
1307
+ }
1308
+ if (rightIsNodeSet) {
1309
+ for (const r of right) {
1310
+ if (this.comparePrimitive(left, this.getStringValue(r), operator)) {
1311
+ return true;
1312
+ }
1313
+ }
1314
+ return false;
1315
+ }
1316
+ return this.comparePrimitive(left, right, operator);
1317
+ }
1318
+ comparePrimitive(left, right, operator) {
1319
+ if (operator === "=" || operator === "!=") {
1320
+ if (typeof left === "boolean" || typeof right === "boolean") {
1321
+ const result2 = this.toBoolean(left) === this.toBoolean(right);
1322
+ return operator === "=" ? result2 : !result2;
1323
+ }
1324
+ if (typeof left === "number" || typeof right === "number") {
1325
+ const result2 = this.toNumber(left) === this.toNumber(right);
1326
+ return operator === "=" ? result2 : !result2;
1327
+ }
1328
+ const result = this.toString(left) === this.toString(right);
1329
+ return operator === "=" ? result : !result;
1330
+ }
1331
+ const leftNum = this.toNumber(left);
1332
+ const rightNum = this.toNumber(right);
1333
+ switch (operator) {
1334
+ case "<":
1335
+ return leftNum < rightNum;
1336
+ case "<=":
1337
+ return leftNum <= rightNum;
1338
+ case ">":
1339
+ return leftNum > rightNum;
1340
+ case ">=":
1341
+ return leftNum >= rightNum;
1342
+ default:
1343
+ throw new Error(`Unknown comparison operator: ${operator}`);
1344
+ }
1345
+ }
1346
+ sortByDocumentOrder(nodes) {
1347
+ if (nodes.length <= 1) return nodes;
1348
+ return nodes.sort((a, b) => {
1349
+ if (a === b) return 0;
1350
+ const position = a.compareDocumentPosition ? a.compareDocumentPosition(b) : this.compareDocumentPositionFallback(a, b);
1351
+ if (position & 4) return -1;
1352
+ if (position & 2) return 1;
1353
+ return 0;
1354
+ });
1355
+ }
1356
+ compareDocumentPositionFallback(a, b) {
1357
+ const getPath = (node) => {
1358
+ const path = [];
1359
+ let current = node;
1360
+ while (current) {
1361
+ if (current.parentNode) {
1362
+ const siblings = Array.from(current.parentNode.childNodes);
1363
+ path.unshift(siblings.indexOf(current));
1364
+ }
1365
+ current = current.parentNode;
1366
+ }
1367
+ return path;
1368
+ };
1369
+ const pathA = getPath(a);
1370
+ const pathB = getPath(b);
1371
+ for (let i = 0; i < Math.min(pathA.length, pathB.length); i++) {
1372
+ if (pathA[i] < pathB[i]) return 4;
1373
+ if (pathA[i] > pathB[i]) return 2;
1374
+ }
1375
+ return pathA.length < pathB.length ? 4 : 2;
1376
+ }
1377
+ /**
1378
+ * Initialize XPath 1.0 core functions
1379
+ */
1380
+ initCoreFunctions() {
1381
+ return {
1382
+ // Node set functions
1383
+ last: (args, ctx) => ctx.size,
1384
+ position: (args, ctx) => ctx.position,
1385
+ count: (args, ctx) => {
1386
+ const nodeSet = this.evaluate(args[0], ctx);
1387
+ return Array.isArray(nodeSet) ? nodeSet.length : 1;
1388
+ },
1389
+ id: (args, ctx) => {
1390
+ const value = this.toString(this.evaluate(args[0], ctx));
1391
+ const doc = ctx.node.ownerDocument || ctx.node;
1392
+ const ids = value.split(/\s+/).filter((id) => id);
1393
+ const result = [];
1394
+ for (const id of ids) {
1395
+ const el = doc.getElementById(id);
1396
+ if (el) result.push(el);
1397
+ }
1398
+ return result;
1399
+ },
1400
+ "local-name": (args, ctx) => {
1401
+ let node;
1402
+ if (args.length === 0) {
1403
+ node = ctx.node;
1404
+ } else {
1405
+ const nodeSet = this.evaluate(args[0], ctx);
1406
+ node = Array.isArray(nodeSet) ? nodeSet[0] : nodeSet;
1407
+ }
1408
+ if (!node) return "";
1409
+ return node.localName || node.nodeName || "";
1410
+ },
1411
+ "namespace-uri": (args, ctx) => {
1412
+ let node;
1413
+ if (args.length === 0) {
1414
+ node = ctx.node;
1415
+ } else {
1416
+ const nodeSet = this.evaluate(args[0], ctx);
1417
+ node = Array.isArray(nodeSet) ? nodeSet[0] : nodeSet;
1418
+ }
1419
+ if (!node) return "";
1420
+ return node.namespaceURI || "";
1421
+ },
1422
+ name: (args, ctx) => {
1423
+ let node;
1424
+ if (args.length === 0) {
1425
+ node = ctx.node;
1426
+ } else {
1427
+ const nodeSet = this.evaluate(args[0], ctx);
1428
+ node = Array.isArray(nodeSet) ? nodeSet[0] : nodeSet;
1429
+ }
1430
+ if (!node) return "";
1431
+ return node.nodeName || "";
1432
+ },
1433
+ // String functions
1434
+ string: (args, ctx) => {
1435
+ if (args.length === 0) {
1436
+ return this.toString([ctx.node]);
1437
+ }
1438
+ return this.toString(this.evaluate(args[0], ctx));
1439
+ },
1440
+ concat: (args, ctx) => {
1441
+ return args.map((arg) => this.toString(this.evaluate(arg, ctx))).join("");
1442
+ },
1443
+ "starts-with": (args, ctx) => {
1444
+ const str = this.toString(this.evaluate(args[0], ctx));
1445
+ const prefix = this.toString(this.evaluate(args[1], ctx));
1446
+ return str.startsWith(prefix);
1447
+ },
1448
+ contains: (args, ctx) => {
1449
+ const str = this.toString(this.evaluate(args[0], ctx));
1450
+ const substr = this.toString(this.evaluate(args[1], ctx));
1451
+ return str.includes(substr);
1452
+ },
1453
+ "substring-before": (args, ctx) => {
1454
+ const str = this.toString(this.evaluate(args[0], ctx));
1455
+ const substr = this.toString(this.evaluate(args[1], ctx));
1456
+ const idx = str.indexOf(substr);
1457
+ return idx === -1 ? "" : str.substring(0, idx);
1458
+ },
1459
+ "substring-after": (args, ctx) => {
1460
+ const str = this.toString(this.evaluate(args[0], ctx));
1461
+ const substr = this.toString(this.evaluate(args[1], ctx));
1462
+ const idx = str.indexOf(substr);
1463
+ return idx === -1 ? "" : str.substring(idx + substr.length);
1464
+ },
1465
+ substring: (args, ctx) => {
1466
+ const str = this.toString(this.evaluate(args[0], ctx));
1467
+ let start = Math.round(this.toNumber(this.evaluate(args[1], ctx)));
1468
+ let length;
1469
+ if (args.length > 2) {
1470
+ length = Math.round(this.toNumber(this.evaluate(args[2], ctx)));
1471
+ }
1472
+ start = start - 1;
1473
+ if (isNaN(start)) return "";
1474
+ if (start < 0) {
1475
+ if (length !== void 0) {
1476
+ length = length + start;
1477
+ }
1478
+ start = 0;
1479
+ }
1480
+ if (length !== void 0) {
1481
+ if (isNaN(length) || length <= 0) return "";
1482
+ return str.substring(start, start + length);
1483
+ }
1484
+ return str.substring(start);
1485
+ },
1486
+ "string-length": (args, ctx) => {
1487
+ const str = args.length === 0 ? this.toString([ctx.node]) : this.toString(this.evaluate(args[0], ctx));
1488
+ return str.length;
1489
+ },
1490
+ "normalize-space": (args, ctx) => {
1491
+ const str = args.length === 0 ? this.toString([ctx.node]) : this.toString(this.evaluate(args[0], ctx));
1492
+ return str.trim().replace(/\s+/g, " ");
1493
+ },
1494
+ translate: (args, ctx) => {
1495
+ const str = this.toString(this.evaluate(args[0], ctx));
1496
+ const from = this.toString(this.evaluate(args[1], ctx));
1497
+ const to = this.toString(this.evaluate(args[2], ctx));
1498
+ let result = "";
1499
+ for (const char of str) {
1500
+ const idx = from.indexOf(char);
1501
+ if (idx === -1) {
1502
+ result += char;
1503
+ } else if (idx < to.length) {
1504
+ result += to[idx];
1505
+ }
1506
+ }
1507
+ return result;
1508
+ },
1509
+ // Boolean functions
1510
+ boolean: (args, ctx) => {
1511
+ return this.toBoolean(this.evaluate(args[0], ctx));
1512
+ },
1513
+ not: (args, ctx) => {
1514
+ return !this.toBoolean(this.evaluate(args[0], ctx));
1515
+ },
1516
+ true: () => true,
1517
+ false: () => false,
1518
+ lang: (args, ctx) => {
1519
+ const lang = this.toString(this.evaluate(args[0], ctx)).toLowerCase();
1520
+ let node = ctx.node;
1521
+ while (node && node.nodeType === 1) {
1522
+ const xmlLang = node.getAttribute("xml:lang") || node.getAttribute("lang");
1523
+ if (xmlLang) {
1524
+ const nodeLang = xmlLang.toLowerCase();
1525
+ return nodeLang === lang || nodeLang.startsWith(lang + "-");
1526
+ }
1527
+ node = node.parentNode;
1528
+ }
1529
+ return false;
1530
+ },
1531
+ // Number functions
1532
+ number: (args, ctx) => {
1533
+ if (args.length === 0) {
1534
+ return this.toNumber([ctx.node]);
1535
+ }
1536
+ return this.toNumber(this.evaluate(args[0], ctx));
1537
+ },
1538
+ sum: (args, ctx) => {
1539
+ const nodeSet = this.evaluate(args[0], ctx);
1540
+ if (!Array.isArray(nodeSet)) return NaN;
1541
+ return nodeSet.reduce(
1542
+ (sum, node) => sum + this.toNumber(this.getStringValue(node)),
1543
+ 0
1544
+ );
1545
+ },
1546
+ floor: (args, ctx) => {
1547
+ return Math.floor(this.toNumber(this.evaluate(args[0], ctx)));
1548
+ },
1549
+ ceiling: (args, ctx) => {
1550
+ return Math.ceil(this.toNumber(this.evaluate(args[0], ctx)));
1551
+ },
1552
+ round: (args, ctx) => {
1553
+ const num = this.toNumber(this.evaluate(args[0], ctx));
1554
+ if (isNaN(num)) return NaN;
1555
+ if (num === -0.5) return -0;
1556
+ return Math.round(num);
1557
+ }
1558
+ };
1559
+ }
1560
+ };
1561
+
1562
+ // src/xslt/engine.js
1563
+ var XSLT_NS = "http://www.w3.org/1999/XSL/Transform";
1564
+ var XsltContext = class _XsltContext {
1565
+ constructor(options = {}) {
1566
+ this.currentNode = options.currentNode;
1567
+ this.currentNodeList = options.currentNodeList || [];
1568
+ this.position = options.position || 1;
1569
+ this.variables = { ...options.variables };
1570
+ this.parameters = { ...options.parameters };
1571
+ this.outputDocument = options.outputDocument;
1572
+ this.stylesheet = options.stylesheet;
1573
+ this.namespaces = { ...options.namespaces };
1574
+ this.templates = options.templates || [];
1575
+ this.keys = options.keys || {};
1576
+ this.decimalFormats = options.decimalFormats || {};
1577
+ this.outputMethod = options.outputMethod || "xml";
1578
+ this.xpathEvaluator = options.xpathEvaluator || new XPathEvaluator();
1579
+ }
1580
+ clone(overrides = {}) {
1581
+ return new _XsltContext({
1582
+ currentNode: overrides.currentNode ?? this.currentNode,
1583
+ currentNodeList: overrides.currentNodeList ?? this.currentNodeList,
1584
+ position: overrides.position ?? this.position,
1585
+ variables: overrides.variables ? { ...this.variables, ...overrides.variables } : { ...this.variables },
1586
+ parameters: overrides.parameters ? { ...this.parameters, ...overrides.parameters } : { ...this.parameters },
1587
+ outputDocument: this.outputDocument,
1588
+ stylesheet: this.stylesheet,
1589
+ namespaces: overrides.namespaces ? { ...this.namespaces, ...overrides.namespaces } : { ...this.namespaces },
1590
+ templates: this.templates,
1591
+ keys: this.keys,
1592
+ decimalFormats: this.decimalFormats,
1593
+ outputMethod: this.outputMethod,
1594
+ xpathEvaluator: this.xpathEvaluator
1595
+ });
1596
+ }
1597
+ getVariable(name) {
1598
+ if (name in this.variables) {
1599
+ return this.variables[name];
1600
+ }
1601
+ if (name in this.parameters) {
1602
+ return this.parameters[name];
1603
+ }
1604
+ throw new Error(`Undefined variable: $${name}`);
1605
+ }
1606
+ setVariable(name, value) {
1607
+ this.variables[name] = value;
1608
+ }
1609
+ };
1610
+ var XsltEngine = class {
1611
+ constructor(options = {}) {
1612
+ this.xpathEvaluator = new XPathEvaluator();
1613
+ this.templates = [];
1614
+ this.keys = {};
1615
+ this.globalVariables = {};
1616
+ this.globalParameters = {};
1617
+ this.outputSettings = {
1618
+ method: "xml",
1619
+ encoding: "UTF-8",
1620
+ indent: "no",
1621
+ omitXmlDeclaration: "no",
1622
+ doctypePublic: null,
1623
+ doctypeSystem: null,
1624
+ mediaType: null,
1625
+ cdataSectionElements: []
1626
+ };
1627
+ this.namespaces = {};
1628
+ this.decimalFormats = {};
1629
+ this.stylesheetDoc = null;
1630
+ this.attributeSets = {};
1631
+ this.namespaceAliases = {};
1632
+ this.stripSpace = [];
1633
+ this.preserveSpace = [];
1634
+ this.stylesheetLoader = options.stylesheetLoader || null;
1635
+ this.currentImportPrecedence = 0;
1636
+ this.processedStylesheets = /* @__PURE__ */ new Set();
1637
+ this.baseUri = options.baseUri || "";
1638
+ }
1639
+ /**
1640
+ * Set the stylesheet loader function for xsl:import and xsl:include
1641
+ * @param {Function} loader - Function(href, baseUri) => Document or string (XML)
1642
+ */
1643
+ setStylesheetLoader(loader) {
1644
+ this.stylesheetLoader = loader;
1645
+ }
1646
+ /**
1647
+ * Resolve a relative URI against a base URI
1648
+ */
1649
+ resolveUri(href, baseUri) {
1650
+ if (!baseUri || href.startsWith("http://") || href.startsWith("https://") || href.startsWith("/")) {
1651
+ return href;
1652
+ }
1653
+ const lastSlash = baseUri.lastIndexOf("/");
1654
+ const baseDir = lastSlash >= 0 ? baseUri.substring(0, lastSlash + 1) : "";
1655
+ return baseDir + href;
1656
+ }
1657
+ /**
1658
+ * Load an external stylesheet document
1659
+ */
1660
+ loadStylesheet(href, baseUri) {
1661
+ if (!this.stylesheetLoader) {
1662
+ throw new Error(
1663
+ `Cannot load stylesheet "${href}": no stylesheetLoader configured. Use engine.setStylesheetLoader(fn) to provide a loader function.`
1664
+ );
1665
+ }
1666
+ const resolvedUri = this.resolveUri(href, baseUri);
1667
+ const result = this.stylesheetLoader(resolvedUri, baseUri);
1668
+ return { document: result, uri: resolvedUri };
1669
+ }
1670
+ /**
1671
+ * Parse XML string to document (helper for stylesheet loading)
1672
+ */
1673
+ parseXmlString(xmlString) {
1674
+ if (typeof DOMParser !== "undefined") {
1675
+ const parser = new DOMParser();
1676
+ const doc = parser.parseFromString(xmlString, "application/xml");
1677
+ const parseError = doc.querySelector("parsererror");
1678
+ if (parseError) {
1679
+ throw new Error(`XML parse error: ${parseError.textContent}`);
1680
+ }
1681
+ return doc;
1682
+ }
1683
+ throw new Error("XML parsing not available in this environment");
1684
+ }
1685
+ /**
1686
+ * Import and compile an XSLT stylesheet
1687
+ * @param {Document|Element} stylesheetNode - The stylesheet document or root element
1688
+ * @param {string} [stylesheetUri] - Optional URI of the stylesheet for resolving imports
1689
+ */
1690
+ importStylesheet(stylesheetNode, stylesheetUri) {
1691
+ const isMainStylesheet = this.stylesheetDoc === null;
1692
+ if (isMainStylesheet) {
1693
+ this.stylesheetDoc = stylesheetNode.ownerDocument || stylesheetNode;
1694
+ if (stylesheetUri) {
1695
+ this.baseUri = stylesheetUri;
1696
+ this.processedStylesheets.add(stylesheetUri);
1697
+ }
1698
+ }
1699
+ const root = stylesheetNode.documentElement || stylesheetNode;
1700
+ if (!this.isXsltElement(root, "stylesheet") && !this.isXsltElement(root, "transform")) {
1701
+ if (root.getAttribute && root.getAttribute("xsl:version")) {
1702
+ this.processLiteralResultStylesheet(root);
1703
+ return;
1704
+ }
1705
+ throw new Error(
1706
+ "Invalid XSLT stylesheet: root element must be xsl:stylesheet or xsl:transform"
1707
+ );
1708
+ }
1709
+ this.collectNamespaces(root);
1710
+ const imports = [];
1711
+ const otherElements = [];
1712
+ for (const child of root.childNodes) {
1713
+ if (child.nodeType !== 1) continue;
1714
+ if (this.isXsltElement(child, "import")) {
1715
+ imports.push(child);
1716
+ } else {
1717
+ otherElements.push(child);
1718
+ }
1719
+ }
1720
+ for (const importNode of imports) {
1721
+ this.processImport(importNode, stylesheetUri || this.baseUri);
1722
+ }
1723
+ for (const child of otherElements) {
1724
+ if (this.isXsltElement(child, "template")) {
1725
+ this.registerTemplate(child);
1726
+ } else if (this.isXsltElement(child, "output")) {
1727
+ this.processOutput(child);
1728
+ } else if (this.isXsltElement(child, "variable")) {
1729
+ this.processGlobalVariable(child);
1730
+ } else if (this.isXsltElement(child, "param")) {
1731
+ this.processGlobalParam(child);
1732
+ } else if (this.isXsltElement(child, "key")) {
1733
+ this.processKey(child);
1734
+ } else if (this.isXsltElement(child, "decimal-format")) {
1735
+ this.processDecimalFormat(child);
1736
+ } else if (this.isXsltElement(child, "namespace-alias")) {
1737
+ this.processNamespaceAlias(child);
1738
+ } else if (this.isXsltElement(child, "attribute-set")) {
1739
+ this.processAttributeSet(child);
1740
+ } else if (this.isXsltElement(child, "strip-space")) {
1741
+ this.processStripSpace(child);
1742
+ } else if (this.isXsltElement(child, "preserve-space")) {
1743
+ this.processPreserveSpace(child);
1744
+ } else if (this.isXsltElement(child, "include")) {
1745
+ this.processInclude(child, stylesheetUri || this.baseUri);
1746
+ }
1747
+ }
1748
+ if (isMainStylesheet) {
1749
+ this.currentImportPrecedence++;
1750
+ }
1751
+ }
1752
+ /**
1753
+ * Process xsl:include element
1754
+ * Includes are merged at the same import precedence level
1755
+ */
1756
+ processInclude(node, baseUri) {
1757
+ const href = node.getAttribute("href");
1758
+ if (!href) {
1759
+ throw new Error("xsl:include requires an href attribute");
1760
+ }
1761
+ const resolvedUri = this.resolveUri(href, baseUri);
1762
+ if (this.processedStylesheets.has(resolvedUri)) {
1763
+ throw new Error(`Circular stylesheet reference detected: ${resolvedUri}`);
1764
+ }
1765
+ this.processedStylesheets.add(resolvedUri);
1766
+ try {
1767
+ const { document: stylesheetDoc } = this.loadStylesheet(href, baseUri);
1768
+ let doc = stylesheetDoc;
1769
+ if (typeof stylesheetDoc === "string") {
1770
+ doc = this.parseXmlString(stylesheetDoc);
1771
+ }
1772
+ const savedPrecedence = this.currentImportPrecedence;
1773
+ this.processIncludedStylesheet(doc, resolvedUri);
1774
+ this.currentImportPrecedence = savedPrecedence;
1775
+ } catch (error) {
1776
+ throw new Error(
1777
+ `Failed to include stylesheet "${href}": ${error.message}`
1778
+ );
1779
+ }
1780
+ }
1781
+ /**
1782
+ * Process xsl:import element
1783
+ * Imports have lower precedence than the importing stylesheet
1784
+ */
1785
+ processImport(node, baseUri) {
1786
+ const href = node.getAttribute("href");
1787
+ if (!href) {
1788
+ throw new Error("xsl:import requires an href attribute");
1789
+ }
1790
+ const resolvedUri = this.resolveUri(href, baseUri);
1791
+ if (this.processedStylesheets.has(resolvedUri)) {
1792
+ throw new Error(`Circular stylesheet reference detected: ${resolvedUri}`);
1793
+ }
1794
+ this.processedStylesheets.add(resolvedUri);
1795
+ try {
1796
+ const { document: stylesheetDoc } = this.loadStylesheet(href, baseUri);
1797
+ let doc = stylesheetDoc;
1798
+ if (typeof stylesheetDoc === "string") {
1799
+ doc = this.parseXmlString(stylesheetDoc);
1800
+ }
1801
+ this.processIncludedStylesheet(doc, resolvedUri);
1802
+ this.currentImportPrecedence++;
1803
+ } catch (error) {
1804
+ throw new Error(
1805
+ `Failed to import stylesheet "${href}": ${error.message}`
1806
+ );
1807
+ }
1808
+ }
1809
+ /**
1810
+ * Process an included/imported stylesheet document
1811
+ */
1812
+ processIncludedStylesheet(stylesheetDoc, stylesheetUri) {
1813
+ const root = stylesheetDoc.documentElement || stylesheetDoc;
1814
+ if (!this.isXsltElement(root, "stylesheet") && !this.isXsltElement(root, "transform")) {
1815
+ throw new Error(
1816
+ "Included/imported document is not a valid XSLT stylesheet"
1817
+ );
1818
+ }
1819
+ this.collectNamespaces(root);
1820
+ const imports = [];
1821
+ const otherElements = [];
1822
+ for (const child of root.childNodes) {
1823
+ if (child.nodeType !== 1) continue;
1824
+ if (this.isXsltElement(child, "import")) {
1825
+ imports.push(child);
1826
+ } else {
1827
+ otherElements.push(child);
1828
+ }
1829
+ }
1830
+ for (const importNode of imports) {
1831
+ this.processImport(importNode, stylesheetUri);
1832
+ }
1833
+ for (const child of otherElements) {
1834
+ if (this.isXsltElement(child, "template")) {
1835
+ this.registerTemplate(child);
1836
+ } else if (this.isXsltElement(child, "output")) {
1837
+ this.processOutput(child);
1838
+ } else if (this.isXsltElement(child, "variable")) {
1839
+ this.processGlobalVariable(child);
1840
+ } else if (this.isXsltElement(child, "param")) {
1841
+ this.processGlobalParam(child);
1842
+ } else if (this.isXsltElement(child, "key")) {
1843
+ this.processKey(child);
1844
+ } else if (this.isXsltElement(child, "decimal-format")) {
1845
+ this.processDecimalFormat(child);
1846
+ } else if (this.isXsltElement(child, "namespace-alias")) {
1847
+ this.processNamespaceAlias(child);
1848
+ } else if (this.isXsltElement(child, "attribute-set")) {
1849
+ this.processAttributeSet(child);
1850
+ } else if (this.isXsltElement(child, "strip-space")) {
1851
+ this.processStripSpace(child);
1852
+ } else if (this.isXsltElement(child, "preserve-space")) {
1853
+ this.processPreserveSpace(child);
1854
+ } else if (this.isXsltElement(child, "include")) {
1855
+ this.processInclude(child, stylesheetUri);
1856
+ }
1857
+ }
1858
+ }
1859
+ processLiteralResultStylesheet(root) {
1860
+ this.templates.push({
1861
+ match: "/",
1862
+ name: null,
1863
+ mode: null,
1864
+ priority: 0.5,
1865
+ node: root
1866
+ });
1867
+ }
1868
+ collectNamespaces(node) {
1869
+ if (!node.attributes) return;
1870
+ for (const attr of node.attributes) {
1871
+ if (attr.name.startsWith("xmlns:")) {
1872
+ const prefix = attr.name.substring(6);
1873
+ if (attr.value !== XSLT_NS) {
1874
+ this.namespaces[prefix] = attr.value;
1875
+ }
1876
+ } else if (attr.name === "xmlns" && attr.value !== XSLT_NS) {
1877
+ this.namespaces[""] = attr.value;
1878
+ }
1879
+ }
1880
+ }
1881
+ registerTemplate(node) {
1882
+ const match = node.getAttribute("match");
1883
+ const name = node.getAttribute("name");
1884
+ const mode = node.getAttribute("mode") || null;
1885
+ const priorityAttr = node.getAttribute("priority");
1886
+ const priority = priorityAttr ? parseFloat(priorityAttr) : this.calculatePriority(match);
1887
+ this.templates.push({
1888
+ match,
1889
+ name,
1890
+ mode,
1891
+ priority,
1892
+ importPrecedence: this.currentImportPrecedence,
1893
+ node
1894
+ });
1895
+ }
1896
+ calculatePriority(matchPattern) {
1897
+ if (!matchPattern) return 0.5;
1898
+ if (matchPattern === "*" || matchPattern === "node()" || matchPattern === "text()" || matchPattern === "comment()" || matchPattern === "processing-instruction()") {
1899
+ return -0.5;
1900
+ }
1901
+ if (matchPattern.includes(":*")) {
1902
+ return -0.25;
1903
+ }
1904
+ if (/^[a-zA-Z_][\w.-]*$/.test(matchPattern)) {
1905
+ return 0;
1906
+ }
1907
+ return 0.5;
1908
+ }
1909
+ processOutput(node) {
1910
+ const method = node.getAttribute("method");
1911
+ if (method) this.outputSettings.method = method;
1912
+ const encoding = node.getAttribute("encoding");
1913
+ if (encoding) this.outputSettings.encoding = encoding;
1914
+ const indent = node.getAttribute("indent");
1915
+ if (indent) this.outputSettings.indent = indent;
1916
+ const omit = node.getAttribute("omit-xml-declaration");
1917
+ if (omit) this.outputSettings.omitXmlDeclaration = omit;
1918
+ const doctypePublic = node.getAttribute("doctype-public");
1919
+ if (doctypePublic) this.outputSettings.doctypePublic = doctypePublic;
1920
+ const doctypeSystem = node.getAttribute("doctype-system");
1921
+ if (doctypeSystem) this.outputSettings.doctypeSystem = doctypeSystem;
1922
+ const mediaType = node.getAttribute("media-type");
1923
+ if (mediaType) this.outputSettings.mediaType = mediaType;
1924
+ const cdataElements = node.getAttribute("cdata-section-elements");
1925
+ if (cdataElements) {
1926
+ this.outputSettings.cdataSectionElements = cdataElements.split(/\s+/).filter(Boolean);
1927
+ }
1928
+ }
1929
+ processGlobalVariable(node) {
1930
+ const name = node.getAttribute("name");
1931
+ const select2 = node.getAttribute("select");
1932
+ this.globalVariables[name] = { node, select: select2 };
1933
+ }
1934
+ processGlobalParam(node) {
1935
+ const name = node.getAttribute("name");
1936
+ const select2 = node.getAttribute("select");
1937
+ this.globalParameters[name] = { node, select: select2 };
1938
+ }
1939
+ processKey(node) {
1940
+ const name = node.getAttribute("name");
1941
+ const match = node.getAttribute("match");
1942
+ const use = node.getAttribute("use");
1943
+ this.keys[name] = { match, use };
1944
+ }
1945
+ processDecimalFormat(node) {
1946
+ const name = node.getAttribute("name") || "";
1947
+ this.decimalFormats[name] = {
1948
+ decimalSeparator: node.getAttribute("decimal-separator") || ".",
1949
+ groupingSeparator: node.getAttribute("grouping-separator") || ",",
1950
+ percent: node.getAttribute("percent") || "%",
1951
+ perMille: node.getAttribute("per-mille") || "\u2030",
1952
+ zeroDigit: node.getAttribute("zero-digit") || "0",
1953
+ digit: node.getAttribute("digit") || "#",
1954
+ patternSeparator: node.getAttribute("pattern-separator") || ";",
1955
+ infinity: node.getAttribute("infinity") || "Infinity",
1956
+ nan: node.getAttribute("NaN") || "NaN",
1957
+ minusSign: node.getAttribute("minus-sign") || "-"
1958
+ };
1959
+ }
1960
+ processNamespaceAlias(node) {
1961
+ const stylesheet = node.getAttribute("stylesheet-prefix");
1962
+ const result = node.getAttribute("result-prefix");
1963
+ this.namespaceAliases[stylesheet] = result;
1964
+ }
1965
+ processAttributeSet(node) {
1966
+ const name = node.getAttribute("name");
1967
+ const useAttributeSets = node.getAttribute("use-attribute-sets");
1968
+ this.attributeSets[name] = {
1969
+ node,
1970
+ useAttributeSets: useAttributeSets ? useAttributeSets.split(/\s+/).filter(Boolean) : []
1971
+ };
1972
+ }
1973
+ processStripSpace(node) {
1974
+ const elements = node.getAttribute("elements");
1975
+ if (elements) {
1976
+ this.stripSpace.push(...elements.split(/\s+/).filter(Boolean));
1977
+ }
1978
+ }
1979
+ processPreserveSpace(node) {
1980
+ const elements = node.getAttribute("elements");
1981
+ if (elements) {
1982
+ this.preserveSpace.push(...elements.split(/\s+/).filter(Boolean));
1983
+ }
1984
+ }
1985
+ /**
1986
+ * Transform a source document
1987
+ */
1988
+ transform(sourceNode, ownerDocument) {
1989
+ const doc = ownerDocument || (typeof document !== "undefined" ? document : null);
1990
+ if (!doc) {
1991
+ throw new Error("No output document available");
1992
+ }
1993
+ const context = new XsltContext({
1994
+ currentNode: sourceNode.documentElement || sourceNode,
1995
+ currentNodeList: [sourceNode.documentElement || sourceNode],
1996
+ position: 1,
1997
+ outputDocument: doc,
1998
+ stylesheet: this.stylesheetDoc,
1999
+ namespaces: { ...this.namespaces },
2000
+ templates: this.templates,
2001
+ keys: this.keys,
2002
+ decimalFormats: this.decimalFormats,
2003
+ outputMethod: this.outputSettings.method,
2004
+ xpathEvaluator: this.xpathEvaluator
2005
+ });
2006
+ for (const [name, def] of Object.entries(this.globalParameters)) {
2007
+ if (!(name in context.parameters)) {
2008
+ context.parameters[name] = this.evaluateVariable(def, context);
2009
+ }
2010
+ }
2011
+ for (const [name, def] of Object.entries(this.globalVariables)) {
2012
+ context.variables[name] = this.evaluateVariable(def, context);
2013
+ }
2014
+ const fragment = doc.createDocumentFragment();
2015
+ this.applyTemplates(
2016
+ [sourceNode.documentElement || sourceNode],
2017
+ null,
2018
+ context,
2019
+ fragment
2020
+ );
2021
+ return fragment;
2022
+ }
2023
+ /**
2024
+ * Transform to a complete document
2025
+ */
2026
+ transformToDocument(sourceNode) {
2027
+ const doc = this.createDocument();
2028
+ const fragment = this.transform(sourceNode, doc);
2029
+ while (fragment.firstChild) {
2030
+ doc.appendChild(fragment.firstChild);
2031
+ }
2032
+ return doc;
2033
+ }
2034
+ createDocument() {
2035
+ if (typeof document !== "undefined") {
2036
+ return document.implementation.createDocument(null, null, null);
2037
+ }
2038
+ throw new Error("Document creation not available in this environment");
2039
+ }
2040
+ evaluateVariable(def, context) {
2041
+ if (def.select) {
2042
+ return this.evaluateXPath(def.select, context);
2043
+ }
2044
+ const fragment = context.outputDocument.createDocumentFragment();
2045
+ this.processChildren(def.node, context, fragment);
2046
+ return fragment;
2047
+ }
2048
+ /**
2049
+ * Apply templates to a node list
2050
+ */
2051
+ applyTemplates(nodes, mode, context, output) {
2052
+ const nodeList = Array.isArray(nodes) ? nodes : [nodes];
2053
+ for (let i = 0; i < nodeList.length; i++) {
2054
+ const node = nodeList[i];
2055
+ const template = this.findMatchingTemplate(node, mode, context);
2056
+ if (template) {
2057
+ const newContext = context.clone({
2058
+ currentNode: node,
2059
+ currentNodeList: nodeList,
2060
+ position: i + 1
2061
+ });
2062
+ this.processTemplate(template.node, newContext, output);
2063
+ } else {
2064
+ this.applyBuiltinTemplate(node, mode, context, output);
2065
+ }
2066
+ }
2067
+ }
2068
+ /**
2069
+ * Find the best matching template for a node
2070
+ */
2071
+ findMatchingTemplate(node, mode, context) {
2072
+ let bestMatch = null;
2073
+ let bestPriority = -Infinity;
2074
+ let bestImportPrecedence = -Infinity;
2075
+ for (const template of this.templates) {
2076
+ if (template.mode !== mode) continue;
2077
+ if (!template.match) continue;
2078
+ if (this.matchesPattern(node, template.match, context)) {
2079
+ const priority = template.priority;
2080
+ const importPrecedence = template.importPrecedence || 0;
2081
+ if (importPrecedence > bestImportPrecedence || importPrecedence === bestImportPrecedence && priority > bestPriority) {
2082
+ bestMatch = template;
2083
+ bestPriority = priority;
2084
+ bestImportPrecedence = importPrecedence;
2085
+ }
2086
+ }
2087
+ }
2088
+ return bestMatch;
2089
+ }
2090
+ /**
2091
+ * Check if a node matches an XSLT pattern
2092
+ */
2093
+ matchesPattern(node, pattern, context) {
2094
+ const patterns = this.splitUnionPattern(pattern);
2095
+ for (const p of patterns) {
2096
+ if (this.matchesSinglePattern(node, p.trim(), context)) {
2097
+ return true;
2098
+ }
2099
+ }
2100
+ return false;
2101
+ }
2102
+ splitUnionPattern(pattern) {
2103
+ const parts = [];
2104
+ let current = "";
2105
+ let depth = 0;
2106
+ let inString = false;
2107
+ let stringChar = "";
2108
+ for (let i = 0; i < pattern.length; i++) {
2109
+ const char = pattern[i];
2110
+ if (inString) {
2111
+ current += char;
2112
+ if (char === stringChar) {
2113
+ inString = false;
2114
+ }
2115
+ } else if (char === '"' || char === "'") {
2116
+ inString = true;
2117
+ stringChar = char;
2118
+ current += char;
2119
+ } else if (char === "[") {
2120
+ depth++;
2121
+ current += char;
2122
+ } else if (char === "]") {
2123
+ depth--;
2124
+ current += char;
2125
+ } else if (char === "|" && depth === 0) {
2126
+ parts.push(current);
2127
+ current = "";
2128
+ } else {
2129
+ current += char;
2130
+ }
2131
+ }
2132
+ if (current) {
2133
+ parts.push(current);
2134
+ }
2135
+ return parts;
2136
+ }
2137
+ matchesSinglePattern(node, pattern, context) {
2138
+ try {
2139
+ if (pattern === "/") {
2140
+ return node.nodeType === 9 || node === node.ownerDocument?.documentElement;
2141
+ }
2142
+ const ast = parse(pattern);
2143
+ if (pattern.startsWith("/")) {
2144
+ const doc = node.ownerDocument || node;
2145
+ const xpathContext2 = new XPathContext(
2146
+ doc,
2147
+ 1,
2148
+ 1,
2149
+ { ...context.variables, ...context.parameters },
2150
+ context.namespaces
2151
+ );
2152
+ const result2 = this.xpathEvaluator.evaluate(ast, xpathContext2);
2153
+ const nodes2 = Array.isArray(result2) ? result2 : [result2];
2154
+ return nodes2.includes(node);
2155
+ }
2156
+ if (node.parentNode) {
2157
+ const xpathContext2 = new XPathContext(
2158
+ node.parentNode,
2159
+ 1,
2160
+ 1,
2161
+ { ...context.variables, ...context.parameters },
2162
+ context.namespaces
2163
+ );
2164
+ const result2 = this.xpathEvaluator.evaluate(ast, xpathContext2);
2165
+ const nodes2 = Array.isArray(result2) ? result2 : [result2];
2166
+ return nodes2.includes(node);
2167
+ }
2168
+ const xpathContext = new XPathContext(
2169
+ node,
2170
+ 1,
2171
+ 1,
2172
+ { ...context.variables, ...context.parameters },
2173
+ context.namespaces
2174
+ );
2175
+ const result = this.xpathEvaluator.evaluate(ast, xpathContext);
2176
+ const nodes = Array.isArray(result) ? result : [result];
2177
+ return nodes.includes(node);
2178
+ } catch {
2179
+ return false;
2180
+ }
2181
+ }
2182
+ /**
2183
+ * Apply built-in template rules
2184
+ */
2185
+ applyBuiltinTemplate(node, mode, context, output) {
2186
+ switch (node.nodeType) {
2187
+ case 1:
2188
+ // Element
2189
+ case 9:
2190
+ // Document
2191
+ case 11:
2192
+ this.applyTemplates(Array.from(node.childNodes), mode, context, output);
2193
+ break;
2194
+ case 3:
2195
+ // Text
2196
+ case 4: {
2197
+ const text = context.outputDocument.createTextNode(
2198
+ node.nodeValue || ""
2199
+ );
2200
+ output.appendChild(text);
2201
+ break;
2202
+ }
2203
+ case 2: {
2204
+ const attrText = context.outputDocument.createTextNode(
2205
+ node.nodeValue || ""
2206
+ );
2207
+ output.appendChild(attrText);
2208
+ break;
2209
+ }
2210
+ }
2211
+ }
2212
+ /**
2213
+ * Process template content
2214
+ */
2215
+ processTemplate(templateNode, context, output) {
2216
+ const localContext = context.clone();
2217
+ for (const child of templateNode.childNodes) {
2218
+ if (child.nodeType === 1 && this.isXsltElement(child, "param")) {
2219
+ const name = child.getAttribute("name");
2220
+ if (!(name in localContext.parameters)) {
2221
+ localContext.parameters[name] = this.evaluateVariable(
2222
+ { node: child, select: child.getAttribute("select") },
2223
+ localContext
2224
+ );
2225
+ }
2226
+ }
2227
+ }
2228
+ this.processChildren(templateNode, localContext, output);
2229
+ }
2230
+ /**
2231
+ * Process child nodes of an XSLT element
2232
+ */
2233
+ processChildren(node, context, output) {
2234
+ for (const child of node.childNodes) {
2235
+ this.processNode(child, context, output);
2236
+ }
2237
+ }
2238
+ /**
2239
+ * Process a single node in the stylesheet
2240
+ */
2241
+ processNode(node, context, output) {
2242
+ switch (node.nodeType) {
2243
+ case 1:
2244
+ this.processElement(node, context, output);
2245
+ break;
2246
+ case 3:
2247
+ // Text
2248
+ case 4: {
2249
+ const text = node.nodeValue;
2250
+ if (text && (text.trim() || this.shouldPreserveSpace(node))) {
2251
+ const textNode = context.outputDocument.createTextNode(text);
2252
+ output.appendChild(textNode);
2253
+ }
2254
+ break;
2255
+ }
2256
+ }
2257
+ }
2258
+ shouldPreserveSpace(node) {
2259
+ let current = node.parentNode;
2260
+ while (current && current.nodeType === 1) {
2261
+ const space = current.getAttribute("xml:space");
2262
+ if (space === "preserve") return true;
2263
+ if (space === "default") return false;
2264
+ current = current.parentNode;
2265
+ }
2266
+ return false;
2267
+ }
2268
+ /**
2269
+ * Process an element in the stylesheet
2270
+ */
2271
+ processElement(node, context, output) {
2272
+ if (this.isXsltNamespace(node)) {
2273
+ this.processXsltElement(node, context, output);
2274
+ } else {
2275
+ this.processLiteralResultElement(node, context, output);
2276
+ }
2277
+ }
2278
+ /**
2279
+ * Process an XSLT instruction element
2280
+ */
2281
+ processXsltElement(node, context, output) {
2282
+ const localName = node.localName || node.nodeName.replace(/^xsl:/, "");
2283
+ switch (localName) {
2284
+ case "apply-templates":
2285
+ this.xslApplyTemplates(node, context, output);
2286
+ break;
2287
+ case "call-template":
2288
+ this.xslCallTemplate(node, context, output);
2289
+ break;
2290
+ case "value-of":
2291
+ this.xslValueOf(node, context, output);
2292
+ break;
2293
+ case "text":
2294
+ this.xslText(node, context, output);
2295
+ break;
2296
+ case "element":
2297
+ this.xslElement(node, context, output);
2298
+ break;
2299
+ case "attribute":
2300
+ this.xslAttribute(node, context, output);
2301
+ break;
2302
+ case "if":
2303
+ this.xslIf(node, context, output);
2304
+ break;
2305
+ case "choose":
2306
+ this.xslChoose(node, context, output);
2307
+ break;
2308
+ case "for-each":
2309
+ this.xslForEach(node, context, output);
2310
+ break;
2311
+ case "copy":
2312
+ this.xslCopy(node, context, output);
2313
+ break;
2314
+ case "copy-of":
2315
+ this.xslCopyOf(node, context, output);
2316
+ break;
2317
+ case "variable":
2318
+ this.xslVariable(node, context, output);
2319
+ break;
2320
+ case "param":
2321
+ break;
2322
+ case "comment":
2323
+ this.xslComment(node, context, output);
2324
+ break;
2325
+ case "processing-instruction":
2326
+ this.xslProcessingInstruction(node, context, output);
2327
+ break;
2328
+ case "number":
2329
+ this.xslNumber(node, context, output);
2330
+ break;
2331
+ case "sort":
2332
+ break;
2333
+ case "with-param":
2334
+ break;
2335
+ case "message":
2336
+ this.xslMessage(node, context, output);
2337
+ break;
2338
+ case "fallback":
2339
+ break;
2340
+ default:
2341
+ console.warn(`Unknown XSLT element: ${localName}`);
2342
+ }
2343
+ }
2344
+ /**
2345
+ * Process a literal result element (non-XSLT)
2346
+ */
2347
+ processLiteralResultElement(node, context, output) {
2348
+ let outputElement;
2349
+ const namespaceURI = node.namespaceURI;
2350
+ const nodeName = node.nodeName;
2351
+ let resolvedNS = namespaceURI;
2352
+ if (namespaceURI) {
2353
+ for (const [from, to] of Object.entries(this.namespaceAliases)) {
2354
+ if (this.namespaces[from] === namespaceURI) {
2355
+ resolvedNS = this.namespaces[to] || to;
2356
+ break;
2357
+ }
2358
+ }
2359
+ }
2360
+ if (resolvedNS && context.outputDocument.createElementNS) {
2361
+ outputElement = context.outputDocument.createElementNS(
2362
+ resolvedNS,
2363
+ nodeName
2364
+ );
2365
+ } else {
2366
+ outputElement = context.outputDocument.createElement(nodeName);
2367
+ }
2368
+ if (node.attributes) {
2369
+ for (const attr of node.attributes) {
2370
+ if (attr.namespaceURI === XSLT_NS) continue;
2371
+ if (attr.name.startsWith("xmlns")) continue;
2372
+ const value = this.processAttributeValueTemplate(attr.value, context);
2373
+ outputElement.setAttribute(attr.name, value);
2374
+ }
2375
+ }
2376
+ this.processChildren(node, context, outputElement);
2377
+ output.appendChild(outputElement);
2378
+ }
2379
+ /**
2380
+ * Process attribute value templates (expressions in curly braces)
2381
+ */
2382
+ processAttributeValueTemplate(value, context) {
2383
+ if (!value.includes("{")) return value;
2384
+ let result = "";
2385
+ let i = 0;
2386
+ while (i < value.length) {
2387
+ if (value[i] === "{") {
2388
+ if (value[i + 1] === "{") {
2389
+ result += "{";
2390
+ i += 2;
2391
+ } else {
2392
+ let depth = 1;
2393
+ let j = i + 1;
2394
+ while (j < value.length && depth > 0) {
2395
+ if (value[j] === "{") depth++;
2396
+ else if (value[j] === "}") depth--;
2397
+ j++;
2398
+ }
2399
+ const expr = value.substring(i + 1, j - 1);
2400
+ const evalResult = this.evaluateXPath(expr, context);
2401
+ result += this.xpathEvaluator.toString(evalResult);
2402
+ i = j;
2403
+ }
2404
+ } else if (value[i] === "}") {
2405
+ if (value[i + 1] === "}") {
2406
+ result += "}";
2407
+ i += 2;
2408
+ } else {
2409
+ throw new Error("Unmatched } in attribute value template");
2410
+ }
2411
+ } else {
2412
+ result += value[i];
2413
+ i++;
2414
+ }
2415
+ }
2416
+ return result;
2417
+ }
2418
+ // XSLT Instructions
2419
+ xslApplyTemplates(node, context, output) {
2420
+ const select2 = node.getAttribute("select") || "node()";
2421
+ const mode = node.getAttribute("mode") || null;
2422
+ let nodes = this.evaluateXPath(select2, context);
2423
+ if (!Array.isArray(nodes)) {
2424
+ nodes = nodes ? [nodes] : [];
2425
+ }
2426
+ const sortSpecs = [];
2427
+ for (const child of node.childNodes) {
2428
+ if (child.nodeType === 1 && this.isXsltElement(child, "sort")) {
2429
+ sortSpecs.push({
2430
+ select: child.getAttribute("select") || ".",
2431
+ order: child.getAttribute("order") || "ascending",
2432
+ dataType: child.getAttribute("data-type") || "text",
2433
+ caseOrder: child.getAttribute("case-order") || "upper-first",
2434
+ lang: child.getAttribute("lang")
2435
+ });
2436
+ }
2437
+ }
2438
+ if (sortSpecs.length > 0) {
2439
+ nodes = this.sortNodes(nodes, sortSpecs, context);
2440
+ }
2441
+ const params = {};
2442
+ for (const child of node.childNodes) {
2443
+ if (child.nodeType === 1 && this.isXsltElement(child, "with-param")) {
2444
+ const name = child.getAttribute("name");
2445
+ const selectAttr = child.getAttribute("select");
2446
+ if (selectAttr) {
2447
+ params[name] = this.evaluateXPath(selectAttr, context);
2448
+ } else {
2449
+ const fragment = context.outputDocument.createDocumentFragment();
2450
+ this.processChildren(child, context, fragment);
2451
+ params[name] = fragment;
2452
+ }
2453
+ }
2454
+ }
2455
+ const newContext = context.clone({
2456
+ parameters: { ...context.parameters, ...params }
2457
+ });
2458
+ this.applyTemplates(nodes, mode, newContext, output);
2459
+ }
2460
+ xslCallTemplate(node, context, output) {
2461
+ const name = node.getAttribute("name");
2462
+ const template = this.templates.find((t) => t.name === name);
2463
+ if (!template) {
2464
+ throw new Error(`Template not found: ${name}`);
2465
+ }
2466
+ const params = {};
2467
+ for (const child of node.childNodes) {
2468
+ if (child.nodeType === 1 && this.isXsltElement(child, "with-param")) {
2469
+ const paramName = child.getAttribute("name");
2470
+ const selectAttr = child.getAttribute("select");
2471
+ if (selectAttr) {
2472
+ params[paramName] = this.evaluateXPath(selectAttr, context);
2473
+ } else {
2474
+ const fragment = context.outputDocument.createDocumentFragment();
2475
+ this.processChildren(child, context, fragment);
2476
+ params[paramName] = fragment;
2477
+ }
2478
+ }
2479
+ }
2480
+ const newContext = context.clone({
2481
+ parameters: { ...context.parameters, ...params }
2482
+ });
2483
+ this.processTemplate(template.node, newContext, output);
2484
+ }
2485
+ xslValueOf(node, context, output) {
2486
+ const select2 = node.getAttribute("select");
2487
+ const disableOutputEscaping = node.getAttribute("disable-output-escaping") === "yes";
2488
+ const result = this.evaluateXPath(select2, context);
2489
+ const text = this.xpathEvaluator.toString(result);
2490
+ if (text) {
2491
+ const textNode = context.outputDocument.createTextNode(text);
2492
+ if (disableOutputEscaping) {
2493
+ textNode._disableOutputEscaping = true;
2494
+ }
2495
+ output.appendChild(textNode);
2496
+ }
2497
+ }
2498
+ xslText(node, context, output) {
2499
+ const disableOutputEscaping = node.getAttribute("disable-output-escaping") === "yes";
2500
+ let text = "";
2501
+ for (const child of node.childNodes) {
2502
+ if (child.nodeType === 3 || child.nodeType === 4) {
2503
+ text += child.nodeValue || "";
2504
+ }
2505
+ }
2506
+ if (text) {
2507
+ const textNode = context.outputDocument.createTextNode(text);
2508
+ if (disableOutputEscaping) {
2509
+ textNode._disableOutputEscaping = true;
2510
+ }
2511
+ output.appendChild(textNode);
2512
+ }
2513
+ }
2514
+ xslElement(node, context, output) {
2515
+ const name = this.processAttributeValueTemplate(
2516
+ node.getAttribute("name"),
2517
+ context
2518
+ );
2519
+ const namespace = node.getAttribute("namespace");
2520
+ const useAttributeSets = node.getAttribute("use-attribute-sets");
2521
+ let element;
2522
+ if (namespace) {
2523
+ const ns = this.processAttributeValueTemplate(namespace, context);
2524
+ element = context.outputDocument.createElementNS(ns, name);
2525
+ } else {
2526
+ element = context.outputDocument.createElement(name);
2527
+ }
2528
+ if (useAttributeSets) {
2529
+ this.applyAttributeSets(useAttributeSets, context, element);
2530
+ }
2531
+ this.processChildren(node, context, element);
2532
+ output.appendChild(element);
2533
+ }
2534
+ xslAttribute(node, context, output) {
2535
+ const name = this.processAttributeValueTemplate(
2536
+ node.getAttribute("name"),
2537
+ context
2538
+ );
2539
+ const namespace = node.getAttribute("namespace");
2540
+ const fragment = context.outputDocument.createDocumentFragment();
2541
+ this.processChildren(node, context, fragment);
2542
+ let value = "";
2543
+ const getText = (n) => {
2544
+ if (n.nodeType === 3 || n.nodeType === 4) {
2545
+ value += n.nodeValue || "";
2546
+ } else if (n.childNodes) {
2547
+ for (const child of n.childNodes) {
2548
+ getText(child);
2549
+ }
2550
+ }
2551
+ };
2552
+ getText(fragment);
2553
+ if (output.nodeType === 1) {
2554
+ if (namespace) {
2555
+ const ns = this.processAttributeValueTemplate(namespace, context);
2556
+ output.setAttributeNS(ns, name, value);
2557
+ } else {
2558
+ output.setAttribute(name, value);
2559
+ }
2560
+ }
2561
+ }
2562
+ xslIf(node, context, output) {
2563
+ const test = node.getAttribute("test");
2564
+ const result = this.evaluateXPath(test, context);
2565
+ if (this.xpathEvaluator.toBoolean(result)) {
2566
+ this.processChildren(node, context, output);
2567
+ }
2568
+ }
2569
+ xslChoose(node, context, output) {
2570
+ for (const child of node.childNodes) {
2571
+ if (child.nodeType !== 1) continue;
2572
+ if (this.isXsltElement(child, "when")) {
2573
+ const test = child.getAttribute("test");
2574
+ const result = this.evaluateXPath(test, context);
2575
+ if (this.xpathEvaluator.toBoolean(result)) {
2576
+ this.processChildren(child, context, output);
2577
+ return;
2578
+ }
2579
+ } else if (this.isXsltElement(child, "otherwise")) {
2580
+ this.processChildren(child, context, output);
2581
+ return;
2582
+ }
2583
+ }
2584
+ }
2585
+ xslForEach(node, context, output) {
2586
+ const select2 = node.getAttribute("select");
2587
+ let nodes = this.evaluateXPath(select2, context);
2588
+ if (!Array.isArray(nodes)) {
2589
+ nodes = nodes ? [nodes] : [];
2590
+ }
2591
+ const sortSpecs = [];
2592
+ for (const child of node.childNodes) {
2593
+ if (child.nodeType === 1 && this.isXsltElement(child, "sort")) {
2594
+ sortSpecs.push({
2595
+ select: child.getAttribute("select") || ".",
2596
+ order: child.getAttribute("order") || "ascending",
2597
+ dataType: child.getAttribute("data-type") || "text",
2598
+ caseOrder: child.getAttribute("case-order") || "upper-first",
2599
+ lang: child.getAttribute("lang")
2600
+ });
2601
+ }
2602
+ }
2603
+ if (sortSpecs.length > 0) {
2604
+ nodes = this.sortNodes(nodes, sortSpecs, context);
2605
+ }
2606
+ for (let i = 0; i < nodes.length; i++) {
2607
+ const newContext = context.clone({
2608
+ currentNode: nodes[i],
2609
+ currentNodeList: nodes,
2610
+ position: i + 1
2611
+ });
2612
+ this.processChildren(node, newContext, output);
2613
+ }
2614
+ }
2615
+ xslCopy(node, context, output) {
2616
+ const currentNode = context.currentNode;
2617
+ const useAttributeSets = node.getAttribute("use-attribute-sets");
2618
+ switch (currentNode.nodeType) {
2619
+ case 1: {
2620
+ let copy;
2621
+ if (currentNode.namespaceURI) {
2622
+ copy = context.outputDocument.createElementNS(
2623
+ currentNode.namespaceURI,
2624
+ currentNode.nodeName
2625
+ );
2626
+ } else {
2627
+ copy = context.outputDocument.createElement(currentNode.nodeName);
2628
+ }
2629
+ if (useAttributeSets) {
2630
+ this.applyAttributeSets(useAttributeSets, context, copy);
2631
+ }
2632
+ this.processChildren(node, context, copy);
2633
+ output.appendChild(copy);
2634
+ break;
2635
+ }
2636
+ case 2:
2637
+ if (output.nodeType === 1) {
2638
+ output.setAttribute(currentNode.name, currentNode.value);
2639
+ }
2640
+ break;
2641
+ case 3:
2642
+ // Text
2643
+ case 4: {
2644
+ const textCopy = context.outputDocument.createTextNode(
2645
+ currentNode.nodeValue || ""
2646
+ );
2647
+ output.appendChild(textCopy);
2648
+ break;
2649
+ }
2650
+ case 7: {
2651
+ const piCopy = context.outputDocument.createProcessingInstruction(
2652
+ currentNode.target,
2653
+ currentNode.data
2654
+ );
2655
+ output.appendChild(piCopy);
2656
+ break;
2657
+ }
2658
+ case 8: {
2659
+ const commentCopy = context.outputDocument.createComment(
2660
+ currentNode.nodeValue || ""
2661
+ );
2662
+ output.appendChild(commentCopy);
2663
+ break;
2664
+ }
2665
+ case 9:
2666
+ // Document
2667
+ case 11:
2668
+ this.processChildren(node, context, output);
2669
+ break;
2670
+ }
2671
+ }
2672
+ xslCopyOf(node, context, output) {
2673
+ const select2 = node.getAttribute("select");
2674
+ const result = this.evaluateXPath(select2, context);
2675
+ this.copyToOutput(result, context, output);
2676
+ }
2677
+ copyToOutput(value, context, output) {
2678
+ if (Array.isArray(value)) {
2679
+ for (const item of value) {
2680
+ this.copyToOutput(item, context, output);
2681
+ }
2682
+ return;
2683
+ }
2684
+ if (value && value.nodeType) {
2685
+ const clone = this.deepCloneNode(value, context.outputDocument);
2686
+ output.appendChild(clone);
2687
+ } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2688
+ const text = context.outputDocument.createTextNode(String(value));
2689
+ output.appendChild(text);
2690
+ }
2691
+ }
2692
+ deepCloneNode(node, targetDoc) {
2693
+ switch (node.nodeType) {
2694
+ case 1: {
2695
+ let clone;
2696
+ if (node.namespaceURI && targetDoc.createElementNS) {
2697
+ clone = targetDoc.createElementNS(node.namespaceURI, node.nodeName);
2698
+ } else {
2699
+ clone = targetDoc.createElement(node.nodeName);
2700
+ }
2701
+ if (node.attributes) {
2702
+ for (const attr of node.attributes) {
2703
+ clone.setAttribute(attr.name, attr.value);
2704
+ }
2705
+ }
2706
+ for (const child of node.childNodes) {
2707
+ clone.appendChild(this.deepCloneNode(child, targetDoc));
2708
+ }
2709
+ return clone;
2710
+ }
2711
+ case 3:
2712
+ // Text
2713
+ case 4:
2714
+ return targetDoc.createTextNode(node.nodeValue || "");
2715
+ case 7:
2716
+ return targetDoc.createProcessingInstruction(node.target, node.data);
2717
+ case 8:
2718
+ return targetDoc.createComment(node.nodeValue || "");
2719
+ case 11: {
2720
+ const frag = targetDoc.createDocumentFragment();
2721
+ for (const child of node.childNodes) {
2722
+ frag.appendChild(this.deepCloneNode(child, targetDoc));
2723
+ }
2724
+ return frag;
2725
+ }
2726
+ default:
2727
+ return targetDoc.createTextNode("");
2728
+ }
2729
+ }
2730
+ xslVariable(node, context, _output) {
2731
+ const name = node.getAttribute("name");
2732
+ const select2 = node.getAttribute("select");
2733
+ let value;
2734
+ if (select2) {
2735
+ value = this.evaluateXPath(select2, context);
2736
+ } else {
2737
+ const fragment = context.outputDocument.createDocumentFragment();
2738
+ this.processChildren(node, context, fragment);
2739
+ value = fragment;
2740
+ }
2741
+ context.setVariable(name, value);
2742
+ }
2743
+ xslComment(node, context, output) {
2744
+ const fragment = context.outputDocument.createDocumentFragment();
2745
+ this.processChildren(node, context, fragment);
2746
+ let text = "";
2747
+ const getText = (n) => {
2748
+ if (n.nodeType === 3 || n.nodeType === 4) {
2749
+ text += n.nodeValue || "";
2750
+ } else if (n.childNodes) {
2751
+ for (const child of n.childNodes) {
2752
+ getText(child);
2753
+ }
2754
+ }
2755
+ };
2756
+ getText(fragment);
2757
+ const comment = context.outputDocument.createComment(text);
2758
+ output.appendChild(comment);
2759
+ }
2760
+ xslProcessingInstruction(node, context, output) {
2761
+ const name = this.processAttributeValueTemplate(
2762
+ node.getAttribute("name"),
2763
+ context
2764
+ );
2765
+ const fragment = context.outputDocument.createDocumentFragment();
2766
+ this.processChildren(node, context, fragment);
2767
+ let data = "";
2768
+ const getText = (n) => {
2769
+ if (n.nodeType === 3 || n.nodeType === 4) {
2770
+ data += n.nodeValue || "";
2771
+ } else if (n.childNodes) {
2772
+ for (const child of n.childNodes) {
2773
+ getText(child);
2774
+ }
2775
+ }
2776
+ };
2777
+ getText(fragment);
2778
+ const pi = context.outputDocument.createProcessingInstruction(name, data);
2779
+ output.appendChild(pi);
2780
+ }
2781
+ xslNumber(node, context, output) {
2782
+ const value = node.getAttribute("value");
2783
+ const format = node.getAttribute("format") || "1";
2784
+ const level = node.getAttribute("level") || "single";
2785
+ let number;
2786
+ if (value) {
2787
+ number = Math.round(
2788
+ this.xpathEvaluator.toNumber(this.evaluateXPath(value, context))
2789
+ );
2790
+ } else {
2791
+ number = this.countNumber(context.currentNode, level, node, context);
2792
+ }
2793
+ const formatted = this.formatNumber(number, format);
2794
+ const text = context.outputDocument.createTextNode(formatted);
2795
+ output.appendChild(text);
2796
+ }
2797
+ countNumber(node, level, spec, context) {
2798
+ const count = spec.getAttribute("count");
2799
+ const _from = spec.getAttribute("from");
2800
+ if (level === "single") {
2801
+ let n = 1;
2802
+ let sibling = node.previousSibling;
2803
+ while (sibling) {
2804
+ if (sibling.nodeType === 1) {
2805
+ if (!count || this.matchesPattern(sibling, count, context)) {
2806
+ n++;
2807
+ }
2808
+ }
2809
+ sibling = sibling.previousSibling;
2810
+ }
2811
+ return n;
2812
+ }
2813
+ return 1;
2814
+ }
2815
+ formatNumber(number, format) {
2816
+ if (/^[0-9]+$/.test(format)) {
2817
+ return String(number).padStart(format.length, "0");
2818
+ }
2819
+ if (format === "a") {
2820
+ return String.fromCharCode(96 + (number - 1) % 26 + 1);
2821
+ }
2822
+ if (format === "A") {
2823
+ return String.fromCharCode(64 + (number - 1) % 26 + 1);
2824
+ }
2825
+ if (format === "i") {
2826
+ return this.toRoman(number).toLowerCase();
2827
+ }
2828
+ if (format === "I") {
2829
+ return this.toRoman(number);
2830
+ }
2831
+ return String(number);
2832
+ }
2833
+ toRoman(num) {
2834
+ const romanNumerals = [
2835
+ ["M", 1e3],
2836
+ ["CM", 900],
2837
+ ["D", 500],
2838
+ ["CD", 400],
2839
+ ["C", 100],
2840
+ ["XC", 90],
2841
+ ["L", 50],
2842
+ ["XL", 40],
2843
+ ["X", 10],
2844
+ ["IX", 9],
2845
+ ["V", 5],
2846
+ ["IV", 4],
2847
+ ["I", 1]
2848
+ ];
2849
+ let result = "";
2850
+ for (const [numeral, value] of romanNumerals) {
2851
+ while (num >= value) {
2852
+ result += numeral;
2853
+ num -= value;
2854
+ }
2855
+ }
2856
+ return result;
2857
+ }
2858
+ xslMessage(node, context, _output) {
2859
+ const terminate = node.getAttribute("terminate") === "yes";
2860
+ const fragment = context.outputDocument.createDocumentFragment();
2861
+ this.processChildren(node, context, fragment);
2862
+ let text = "";
2863
+ const getText = (n) => {
2864
+ if (n.nodeType === 3 || n.nodeType === 4) {
2865
+ text += n.nodeValue || "";
2866
+ } else if (n.childNodes) {
2867
+ for (const child of n.childNodes) {
2868
+ getText(child);
2869
+ }
2870
+ }
2871
+ };
2872
+ getText(fragment);
2873
+ console.log("XSLT Message:", text);
2874
+ if (terminate) {
2875
+ throw new Error(`XSLT terminated: ${text}`);
2876
+ }
2877
+ }
2878
+ applyAttributeSets(names, context, element) {
2879
+ const setNames = names.split(/\s+/).filter(Boolean);
2880
+ for (const name of setNames) {
2881
+ const attrSet = this.attributeSets[name];
2882
+ if (attrSet) {
2883
+ if (attrSet.useAttributeSets.length > 0) {
2884
+ this.applyAttributeSets(
2885
+ attrSet.useAttributeSets.join(" "),
2886
+ context,
2887
+ element
2888
+ );
2889
+ }
2890
+ for (const child of attrSet.node.childNodes) {
2891
+ if (child.nodeType === 1 && this.isXsltElement(child, "attribute")) {
2892
+ this.xslAttribute(child, context, element);
2893
+ }
2894
+ }
2895
+ }
2896
+ }
2897
+ }
2898
+ sortNodes(nodes, sortSpecs, context) {
2899
+ return [...nodes].sort((a, b) => {
2900
+ for (const spec of sortSpecs) {
2901
+ const contextA = context.clone({ currentNode: a });
2902
+ const contextB = context.clone({ currentNode: b });
2903
+ let valueA = this.evaluateXPath(spec.select, contextA);
2904
+ let valueB = this.evaluateXPath(spec.select, contextB);
2905
+ valueA = this.xpathEvaluator.toString(valueA) || "";
2906
+ valueB = this.xpathEvaluator.toString(valueB) || "";
2907
+ if (spec.dataType === "number") {
2908
+ valueA = parseFloat(valueA) || 0;
2909
+ valueB = parseFloat(valueB) || 0;
2910
+ } else {
2911
+ if (spec.caseOrder === "lower-first") {
2912
+ valueA = valueA.toLowerCase();
2913
+ valueB = valueB.toLowerCase();
2914
+ } else {
2915
+ valueA = valueA.toUpperCase();
2916
+ valueB = valueB.toUpperCase();
2917
+ }
2918
+ }
2919
+ let cmp;
2920
+ if (typeof valueA === "number") {
2921
+ cmp = valueA - valueB;
2922
+ } else {
2923
+ cmp = valueA.localeCompare(valueB, spec.lang || void 0);
2924
+ }
2925
+ if (spec.order === "descending") {
2926
+ cmp = -cmp;
2927
+ }
2928
+ if (cmp !== 0) return cmp;
2929
+ }
2930
+ return 0;
2931
+ });
2932
+ }
2933
+ evaluateXPath(expr, context) {
2934
+ const ast = parse(expr);
2935
+ const xpathContext = new XPathContext(
2936
+ context.currentNode,
2937
+ context.position,
2938
+ context.currentNodeList.length,
2939
+ { ...context.variables, ...context.parameters },
2940
+ context.namespaces
2941
+ );
2942
+ return this.xpathEvaluator.evaluate(ast, xpathContext);
2943
+ }
2944
+ isXsltNamespace(node) {
2945
+ return node.namespaceURI === XSLT_NS || node.nodeName && node.nodeName.startsWith("xsl:");
2946
+ }
2947
+ isXsltElement(node, localName) {
2948
+ if (node.nodeType !== 1) return false;
2949
+ const nodeName = node.localName || node.nodeName;
2950
+ return node.namespaceURI === XSLT_NS && nodeName === localName || node.nodeName === `xsl:${localName}`;
2951
+ }
2952
+ };
2953
+
2954
+ // src/XSLTProcessor.js
2955
+ var XSLTProcessor = class {
2956
+ constructor() {
2957
+ this._engine = null;
2958
+ this._stylesheet = null;
2959
+ this._parameters = /* @__PURE__ */ new Map();
2960
+ }
2961
+ /**
2962
+ * Imports the XSLT stylesheet.
2963
+ *
2964
+ * If the given node is a document node, you can pass in a full XSL Transform
2965
+ * or a literal result element transform; otherwise, it must be an
2966
+ * <xsl:stylesheet> or <xsl:transform> element.
2967
+ *
2968
+ * @param {Node} style - The XSLT stylesheet to import (Document or Element)
2969
+ * @returns {void}
2970
+ *
2971
+ * @example
2972
+ * const parser = new DOMParser();
2973
+ * const xslDoc = parser.parseFromString(xslText, 'application/xml');
2974
+ * processor.importStylesheet(xslDoc);
2975
+ */
2976
+ importStylesheet(style) {
2977
+ if (!style) {
2978
+ throw new TypeError(
2979
+ "Failed to execute 'importStylesheet' on 'XSLTProcessor': 1 argument required, but only 0 present."
2980
+ );
2981
+ }
2982
+ if (style.nodeType !== 1 && style.nodeType !== 9) {
2983
+ throw new TypeError(
2984
+ "Failed to execute 'importStylesheet' on 'XSLTProcessor': The node provided is not a Document or Element."
2985
+ );
2986
+ }
2987
+ const errorNode = style.querySelector ? style.querySelector("parsererror") : null;
2988
+ if (errorNode) {
2989
+ throw new Error("XSLT stylesheet contains parse errors");
2990
+ }
2991
+ this._stylesheet = style;
2992
+ this._engine = new XsltEngine();
2993
+ for (const [key, value] of this._parameters) {
2994
+ this._engine.globalParameters[key] = { value };
2995
+ }
2996
+ this._engine.importStylesheet(style);
2997
+ }
2998
+ /**
2999
+ * Transforms the node source by applying the XSLT stylesheet.
3000
+ * Returns a document fragment.
3001
+ *
3002
+ * @param {Node} source - The XML document to transform
3003
+ * @param {Document} output - The document that will own the generated fragment
3004
+ * @returns {DocumentFragment} The transformed result as a DocumentFragment
3005
+ *
3006
+ * @example
3007
+ * const fragment = processor.transformToFragment(xmlDoc, document);
3008
+ * document.getElementById('output').appendChild(fragment);
3009
+ */
3010
+ transformToFragment(source, output) {
3011
+ if (!source) {
3012
+ throw new TypeError(
3013
+ "Failed to execute 'transformToFragment' on 'XSLTProcessor': 2 arguments required, but only 0 present."
3014
+ );
3015
+ }
3016
+ if (!output) {
3017
+ throw new TypeError(
3018
+ "Failed to execute 'transformToFragment' on 'XSLTProcessor': 2 arguments required, but only 1 present."
3019
+ );
3020
+ }
3021
+ if (!this._engine || !this._stylesheet) {
3022
+ throw new Error(
3023
+ "Failed to execute 'transformToFragment' on 'XSLTProcessor': No stylesheet has been imported."
3024
+ );
3025
+ }
3026
+ if (source.nodeType !== 1 && source.nodeType !== 9 && source.nodeType !== 11) {
3027
+ throw new TypeError(
3028
+ "Failed to execute 'transformToFragment' on 'XSLTProcessor': The source is not a valid node type."
3029
+ );
3030
+ }
3031
+ if (output.nodeType !== 9) {
3032
+ throw new TypeError(
3033
+ "Failed to execute 'transformToFragment' on 'XSLTProcessor': The output is not a Document."
3034
+ );
3035
+ }
3036
+ try {
3037
+ return this._engine.transform(source, output);
3038
+ } catch (error) {
3039
+ console.error("XSLT transformation error:", error);
3040
+ return null;
3041
+ }
3042
+ }
3043
+ /**
3044
+ * Transforms the node source by applying the XSLT stylesheet.
3045
+ * Returns a full XML document.
3046
+ *
3047
+ * @param {Node} source - The XML document to transform
3048
+ * @returns {XMLDocument} The transformed result as an XMLDocument
3049
+ *
3050
+ * @example
3051
+ * const resultDoc = processor.transformToDocument(xmlDoc);
3052
+ * const serialized = new XMLSerializer().serializeToString(resultDoc);
3053
+ */
3054
+ transformToDocument(source) {
3055
+ if (!source) {
3056
+ throw new TypeError(
3057
+ "Failed to execute 'transformToDocument' on 'XSLTProcessor': 1 argument required, but only 0 present."
3058
+ );
3059
+ }
3060
+ if (!this._engine || !this._stylesheet) {
3061
+ throw new Error(
3062
+ "Failed to execute 'transformToDocument' on 'XSLTProcessor': No stylesheet has been imported."
3063
+ );
3064
+ }
3065
+ if (source.nodeType !== 1 && source.nodeType !== 9 && source.nodeType !== 11) {
3066
+ throw new TypeError(
3067
+ "Failed to execute 'transformToDocument' on 'XSLTProcessor': The source is not a valid node type."
3068
+ );
3069
+ }
3070
+ try {
3071
+ return this._engine.transformToDocument(source);
3072
+ } catch (error) {
3073
+ console.error("XSLT transformation error:", error);
3074
+ return null;
3075
+ }
3076
+ }
3077
+ /**
3078
+ * Sets a parameter in the XSLT stylesheet.
3079
+ *
3080
+ * @param {string|null} namespaceURI - The namespace URI of the XSLT parameter (use null for no namespace)
3081
+ * @param {string} localName - The local name of the parameter
3082
+ * @param {*} value - The value to set (string, number, boolean, or node-set)
3083
+ * @returns {void}
3084
+ *
3085
+ * @example
3086
+ * processor.setParameter(null, 'sortOrder', 'ascending');
3087
+ * processor.setParameter('http://example.com/ns', 'limit', 10);
3088
+ */
3089
+ setParameter(namespaceURI, localName, value) {
3090
+ if (arguments.length < 3) {
3091
+ throw new TypeError(
3092
+ `Failed to execute 'setParameter' on 'XSLTProcessor': 3 arguments required, but only ${arguments.length} present.`
3093
+ );
3094
+ }
3095
+ if (typeof localName !== "string" || localName === "") {
3096
+ throw new TypeError(
3097
+ "Failed to execute 'setParameter' on 'XSLTProcessor': The localName argument must be a non-empty string."
3098
+ );
3099
+ }
3100
+ const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
3101
+ this._parameters.set(key, value);
3102
+ if (this._engine) {
3103
+ this._engine.globalParameters[key] = { value };
3104
+ }
3105
+ }
3106
+ /**
3107
+ * Gets the value of a parameter from the XSLT stylesheet.
3108
+ *
3109
+ * @param {string|null} namespaceURI - The namespace URI of the parameter
3110
+ * @param {string} localName - The local name of the parameter
3111
+ * @returns {*} The parameter value, or empty string if not set
3112
+ *
3113
+ * @example
3114
+ * const sortOrder = processor.getParameter(null, 'sortOrder');
3115
+ */
3116
+ getParameter(namespaceURI, localName) {
3117
+ if (arguments.length < 2) {
3118
+ throw new TypeError(
3119
+ `Failed to execute 'getParameter' on 'XSLTProcessor': 2 arguments required, but only ${arguments.length} present.`
3120
+ );
3121
+ }
3122
+ if (typeof localName !== "string") {
3123
+ throw new TypeError(
3124
+ "Failed to execute 'getParameter' on 'XSLTProcessor': The localName argument must be a string."
3125
+ );
3126
+ }
3127
+ const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
3128
+ if (this._parameters.has(key)) {
3129
+ return this._parameters.get(key);
3130
+ }
3131
+ return "";
3132
+ }
3133
+ /**
3134
+ * Removes a parameter from the XSLT processor.
3135
+ *
3136
+ * The XSLTProcessor will use the default value for the parameter
3137
+ * as specified in the XSLT stylesheet.
3138
+ *
3139
+ * @param {string|null} namespaceURI - The namespace URI of the parameter
3140
+ * @param {string} localName - The local name of the parameter
3141
+ * @returns {void}
3142
+ *
3143
+ * @example
3144
+ * processor.removeParameter(null, 'sortOrder');
3145
+ */
3146
+ removeParameter(namespaceURI, localName) {
3147
+ if (arguments.length < 2) {
3148
+ throw new TypeError(
3149
+ `Failed to execute 'removeParameter' on 'XSLTProcessor': 2 arguments required, but only ${arguments.length} present.`
3150
+ );
3151
+ }
3152
+ if (typeof localName !== "string") {
3153
+ throw new TypeError(
3154
+ "Failed to execute 'removeParameter' on 'XSLTProcessor': The localName argument must be a string."
3155
+ );
3156
+ }
3157
+ const key = namespaceURI ? `{${namespaceURI}}${localName}` : localName;
3158
+ this._parameters.delete(key);
3159
+ if (this._engine) {
3160
+ delete this._engine.globalParameters[key];
3161
+ }
3162
+ }
3163
+ /**
3164
+ * Removes all set parameters from the XSLTProcessor.
3165
+ *
3166
+ * The processor will use default values specified in the XSLT stylesheet.
3167
+ *
3168
+ * @returns {void}
3169
+ *
3170
+ * @example
3171
+ * processor.clearParameters();
3172
+ */
3173
+ clearParameters() {
3174
+ this._parameters.clear();
3175
+ if (this._engine) {
3176
+ this._engine.globalParameters = {};
3177
+ }
3178
+ }
3179
+ /**
3180
+ * Removes all parameters and stylesheets from the XSLTProcessor.
3181
+ *
3182
+ * @returns {void}
3183
+ *
3184
+ * @example
3185
+ * processor.reset();
3186
+ * // Now need to call importStylesheet() again before transforming
3187
+ */
3188
+ reset() {
3189
+ this._engine = null;
3190
+ this._stylesheet = null;
3191
+ this._parameters.clear();
3192
+ }
3193
+ };
3194
+ function isNativeXSLTSupported() {
3195
+ if (typeof globalThis.XSLTProcessor === "undefined") {
3196
+ return false;
3197
+ }
3198
+ try {
3199
+ const processor = new globalThis.XSLTProcessor();
3200
+ const parser = new DOMParser();
3201
+ const xslt = parser.parseFromString(
3202
+ `<?xml version="1.0"?>
3203
+ <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
3204
+ <xsl:template match="/"><test/></xsl:template>
3205
+ </xsl:stylesheet>`,
3206
+ "application/xml"
3207
+ );
3208
+ processor.importStylesheet(xslt);
3209
+ const xml = parser.parseFromString("<root/>", "application/xml");
3210
+ const result = processor.transformToFragment(xml, document);
3211
+ return result !== null && result.childNodes.length > 0;
3212
+ } catch {
3213
+ return false;
3214
+ }
3215
+ }
3216
+ function installGlobal(force = false) {
3217
+ if (!force && isNativeXSLTSupported()) {
3218
+ return false;
3219
+ }
3220
+ globalThis.XSLTProcessor = XSLTProcessor;
3221
+ return true;
3222
+ }
3223
+ var XSLTProcessor_default = XSLTProcessor;
3224
+
3225
+ // src/xpath/index.js
3226
+ function evaluate(expression, contextNode, options = {}) {
3227
+ const ast = parse(expression);
3228
+ const evaluator = new XPathEvaluator();
3229
+ const context = new XPathContext(
3230
+ contextNode,
3231
+ 1,
3232
+ 1,
3233
+ options.variables || {},
3234
+ options.namespaces || {}
3235
+ );
3236
+ return evaluator.evaluate(ast, context);
3237
+ }
3238
+ function select(expression, contextNode, options = {}) {
3239
+ const result = evaluate(expression, contextNode, options);
3240
+ if (Array.isArray(result)) return result;
3241
+ if (result && result.nodeType) return [result];
3242
+ return [];
3243
+ }
3244
+ function selectFirst(expression, contextNode, options = {}) {
3245
+ const nodes = select(expression, contextNode, options);
3246
+ return nodes.length > 0 ? nodes[0] : null;
3247
+ }
3248
+
3249
+ // src/index.js
3250
+ var VERSION = "1.0.0";
3251
+ var isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
3252
+ var isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
3253
+ export {
3254
+ VERSION,
3255
+ XPathContext,
3256
+ XPathEvaluator,
3257
+ XPathResultType,
3258
+ XSLTProcessor,
3259
+ XsltContext,
3260
+ XsltEngine,
3261
+ XSLTProcessor_default as default,
3262
+ evaluate as evaluateXPath,
3263
+ installGlobal,
3264
+ isBrowser,
3265
+ isNativeXSLTSupported,
3266
+ isNode,
3267
+ parse as parseXPath,
3268
+ selectFirst as selectFirstXPath,
3269
+ select as selectXPath
3270
+ };
3271
+ //# sourceMappingURL=xslt-processor.js.map