xnl-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,2176 @@
1
+ 'use strict';
2
+
3
+ // src/position.ts
4
+ function positionToLineColumn(input, index) {
5
+ let line = 1;
6
+ let column = 1;
7
+ for (let i = 0; i < index && i < input.length; i++) {
8
+ const ch = input[i];
9
+ if (ch === "\n") {
10
+ line += 1;
11
+ column = 1;
12
+ } else {
13
+ column += 1;
14
+ }
15
+ }
16
+ return { line, column };
17
+ }
18
+
19
+ // src/errors.ts
20
+ var XnlParseError = class extends Error {
21
+ constructor(code, message, input, position) {
22
+ const { line, column } = positionToLineColumn(input, position);
23
+ super(`${message} (at ${line}:${column})`);
24
+ this.code = code;
25
+ this.position = position;
26
+ this.line = line;
27
+ this.column = column;
28
+ }
29
+ };
30
+
31
+ // src/parser.ts
32
+ function parseXnl(input) {
33
+ const warnings = [];
34
+ const nodes = parseNodesFromString(input, warnings);
35
+ return { nodes, warnings };
36
+ }
37
+ function parseXnlSingleNode(input) {
38
+ const warnings = [];
39
+ const state = { input, pos: 0, length: input.length, warnings };
40
+ skipWhitespaceAndComments(state);
41
+ const node = parseNode(state);
42
+ skipWhitespaceAndComments(state);
43
+ if (!eof(state)) {
44
+ throw error(state, "UNEXPECTED_TOKEN", "Expected a single node");
45
+ }
46
+ return { node, warnings };
47
+ }
48
+ function parseUniqueChildren(name, input, metadata = {}, attributes = {}) {
49
+ const warnings = [];
50
+ const children = parseNodesFromString(input, warnings);
51
+ const extend = buildExtendBody(children, name, warnings);
52
+ const node = { kind: "DataElement", tag: name, id: void 0, metadata, attributes, extend };
53
+ return { node, warnings };
54
+ }
55
+ function parseNodesFromString(input, warnings) {
56
+ const state = { input, pos: 0, length: input.length, warnings };
57
+ const nodes = [];
58
+ skipWhitespaceAndComments(state);
59
+ while (!eof(state)) {
60
+ nodes.push(parseNode(state));
61
+ skipWhitespaceAndComments(state);
62
+ }
63
+ return nodes;
64
+ }
65
+ function parseNode(state) {
66
+ consumeChar(state, "<", "UNEXPECTED_TOKEN", "Expected '<' to start a node");
67
+ const tag = readIdentifier(state, "Expected node name");
68
+ skipWhitespaceAndComments(state);
69
+ const id = parseOptionalId(state);
70
+ const metadata = parseMetadata(state);
71
+ let attributes;
72
+ let body;
73
+ let extend;
74
+ let text;
75
+ let textMarker;
76
+ skipWhitespaceAndComments(state);
77
+ if (consumeIf(state, "?")) {
78
+ ({ text, textMarker } = parseTextBody(state, tag));
79
+ return { kind: "TextElement", tag, id, metadata, attributes, text, textMarker };
80
+ }
81
+ while (true) {
82
+ if (lookAhead(state, ">")) {
83
+ state.pos += 1;
84
+ return { kind: "DataElement", tag, id, metadata, attributes, body, extend };
85
+ }
86
+ if (lookAhead(state, "?")) {
87
+ state.pos += 1;
88
+ if (body || extend) {
89
+ throw error(state, "INVALID_CONTENT", `Text block not allowed with array/extend sections in <${tag}>`);
90
+ }
91
+ ({ text, textMarker } = parseTextBody(state, tag));
92
+ return { kind: "TextElement", tag, id, metadata, attributes, text, textMarker };
93
+ }
94
+ if (lookAhead(state, "{")) {
95
+ if (attributes) {
96
+ throw error(state, "INVALID_CONTENT", "Multiple attribute blocks are not allowed");
97
+ }
98
+ attributes = parseAttributeBlock(state, tag);
99
+ skipWhitespaceAndComments(state);
100
+ continue;
101
+ }
102
+ if (lookAhead(state, "[")) {
103
+ if (text) throw error(state, "INVALID_CONTENT", `Text block cannot include array block in <${tag}>`);
104
+ if (body) {
105
+ throw error(state, "INVALID_CONTENT", "Multiple array blocks are not allowed");
106
+ }
107
+ body = parseArrayBody(state, tag);
108
+ skipWhitespaceAndComments(state);
109
+ continue;
110
+ }
111
+ if (lookAhead(state, "(")) {
112
+ if (text) throw error(state, "INVALID_CONTENT", `Text block cannot include extend block in <${tag}>`);
113
+ if (extend) {
114
+ throw error(state, "INVALID_CONTENT", "Multiple extend blocks are not allowed");
115
+ }
116
+ extend = parseExtendBody(state, tag);
117
+ skipWhitespaceAndComments(state);
118
+ continue;
119
+ }
120
+ if (eof(state)) {
121
+ throw error(state, "UNEXPECTED_EOF", "Unexpected end of input");
122
+ }
123
+ throw error(state, "UNEXPECTED_TOKEN", `Unexpected token '${peek(state)}' while parsing node <${tag}>`);
124
+ }
125
+ }
126
+ function parseMetadata(state) {
127
+ const attrs = {};
128
+ while (true) {
129
+ skipWhitespaceAndComments(state);
130
+ if (lookAhead(state, "{") || lookAhead(state, "[") || lookAhead(state, "(") || lookAhead(state, "?") || lookAhead(state, ">")) {
131
+ break;
132
+ }
133
+ if (eof(state)) {
134
+ throw error(state, "UNEXPECTED_EOF", "Unexpected end while reading metadata");
135
+ }
136
+ const key = readKey(state, "Expected metadata key");
137
+ skipWhitespaceAndComments(state);
138
+ consumeChar(state, "=", "UNEXPECTED_TOKEN", "Expected '=' after metadata key");
139
+ skipWhitespaceAndComments(state);
140
+ attrs[key] = parseValueNode(state);
141
+ }
142
+ return attrs;
143
+ }
144
+ function parseAttributeBlock(state, name) {
145
+ consumeChar(state, "{", "UNEXPECTED_TOKEN", "Expected '{' to start attribute block");
146
+ const attrs = {};
147
+ while (true) {
148
+ skipWhitespaceAndComments(state);
149
+ if (consumeIf(state, "}")) {
150
+ return attrs;
151
+ }
152
+ if (eof(state)) {
153
+ throw error(state, "UNEXPECTED_EOF", `Missing closing '}' for attributes in <${name}>`);
154
+ }
155
+ const key = readKey(state, "Expected key in attribute block");
156
+ skipWhitespaceAndComments(state);
157
+ consumeChar(state, "=", "UNEXPECTED_TOKEN", "Expected '=' after key in attribute block");
158
+ skipWhitespaceAndComments(state);
159
+ attrs[key] = parseValueNode(state);
160
+ }
161
+ }
162
+ function parseArrayBody(state, name) {
163
+ consumeChar(state, "[", "UNEXPECTED_TOKEN", "Expected '[' to start array block");
164
+ const items = [];
165
+ while (true) {
166
+ skipWhitespaceAndComments(state);
167
+ if (consumeIf(state, "]")) {
168
+ return items;
169
+ }
170
+ if (eof(state)) {
171
+ throw error(state, "UNEXPECTED_EOF", `Missing closing ']' for array in <${name}>`);
172
+ }
173
+ if (lookAhead(state, "<")) {
174
+ items.push(parseNode(state));
175
+ } else {
176
+ items.push(parseValueNode(state));
177
+ }
178
+ }
179
+ }
180
+ function parseExtendBody(state, name) {
181
+ consumeChar(state, "(", "UNEXPECTED_TOKEN", "Expected '(' to start extend block");
182
+ const children = {};
183
+ const order = [];
184
+ while (true) {
185
+ skipWhitespaceAndComments(state);
186
+ if (consumeIf(state, ")")) {
187
+ return { children, order };
188
+ }
189
+ if (eof(state)) {
190
+ throw error(state, "UNEXPECTED_EOF", `Missing closing ')' for extend in <${name}>`);
191
+ }
192
+ if (!lookAhead(state, "<")) {
193
+ throw error(state, "INVALID_CONTENT", `Extend block inside <${name}> must contain child nodes`);
194
+ }
195
+ const child = parseNode(state);
196
+ mergeChild(children, order, child, name, state.warnings);
197
+ }
198
+ }
199
+ function parseOptionalId(state) {
200
+ if (!consumeIf(state, "#")) return void 0;
201
+ const word = parseWordLiteral(state);
202
+ skipWhitespaceAndComments(state);
203
+ return word;
204
+ }
205
+ function parseTextBody(state, name) {
206
+ const marker = readOptionalMarker(state);
207
+ consumeChar(state, ">", "UNEXPECTED_TOKEN", "Expected '>' after text marker");
208
+ const start = state.pos;
209
+ while (true) {
210
+ const idx = state.input.indexOf("</?", state.pos);
211
+ if (idx === -1) {
212
+ throw error(state, "MISMATCHED_TAG", `Missing closing text tag </?${marker ?? ""}> for <${name}>`);
213
+ }
214
+ const markerStart = idx + 3;
215
+ let i = markerStart;
216
+ while (i < state.length && isIdentifierChar(state.input[i])) i++;
217
+ const foundMarker = state.input.slice(markerStart, i);
218
+ if (state.input[i] !== ">") {
219
+ state.pos = idx;
220
+ throw error(
221
+ state,
222
+ "UNEXPECTED_TOKEN",
223
+ `Invalid closing text tag for <${name}>; expected '>' after marker '${foundMarker}'`
224
+ );
225
+ }
226
+ if ((marker ?? "") !== foundMarker) {
227
+ state.pos = idx;
228
+ throw error(
229
+ state,
230
+ "MISMATCHED_TAG",
231
+ `Mismatched text marker for <${name}>: expected '${marker ?? ""}' but found '${foundMarker}'`
232
+ );
233
+ }
234
+ const closingIndent = indentationBefore(state.input, idx);
235
+ const content = stripComments(dedentContent(state.input.slice(start, idx), closingIndent));
236
+ state.pos = i + 1;
237
+ return { text: content, textMarker: marker ?? void 0 };
238
+ }
239
+ }
240
+ function buildExtendBody(nodes, parentName, warnings) {
241
+ const children = {};
242
+ const order = [];
243
+ for (const node of nodes) {
244
+ const element = node;
245
+ if (element.kind !== "DataElement" && element.kind !== "TextElement") continue;
246
+ mergeChild(children, order, element, parentName, warnings);
247
+ }
248
+ return { children, order };
249
+ }
250
+ function mergeChild(children, order, node, parentName, warnings) {
251
+ if (children[node.tag]) {
252
+ warnings.push({
253
+ code: "DUPLICATE_CHILD",
254
+ message: `Duplicate child '${node.tag}' inside <${parentName} ( ... )> (later node overwrote earlier)`,
255
+ parentName,
256
+ childName: node.tag
257
+ });
258
+ const idx = order.indexOf(node.tag);
259
+ if (idx !== -1) order.splice(idx, 1);
260
+ }
261
+ children[node.tag] = node;
262
+ order.push(node.tag);
263
+ }
264
+ function parseValueNode(state) {
265
+ const ch = state.input[state.pos];
266
+ if (ch === "<") return parseNode(state);
267
+ if (ch === "{") return parseObjectLiteral(state);
268
+ if (ch === "[") return parseArrayLiteral(state);
269
+ if (ch === "'" || ch === '"') return parseStringLiteral(state);
270
+ if (startsWithNumber(state)) return parseNumberLiteral(state);
271
+ if (startsWithBoolean(state)) return parseBooleanLiteral(state);
272
+ if (startsWithNull(state)) return parseNullLiteral(state);
273
+ if (isIdentifierStart(ch)) return parseWordLiteral(state);
274
+ throw error(state, "INVALID_LITERAL", `Unexpected literal starting with '${ch}'`);
275
+ }
276
+ function parseObjectLiteral(state) {
277
+ consumeChar(state, "{", "UNEXPECTED_TOKEN", "Expected '{' to start object literal");
278
+ const entries = {};
279
+ while (true) {
280
+ skipWhitespaceAndComments(state);
281
+ if (consumeIf(state, "}")) break;
282
+ const key = readKey(state, "Expected key in object literal");
283
+ skipWhitespaceAndComments(state);
284
+ consumeChar(state, "=", "UNEXPECTED_TOKEN", "Expected '=' after key in object literal");
285
+ skipWhitespaceAndComments(state);
286
+ entries[key] = parseValueNode(state);
287
+ skipWhitespaceAndComments(state);
288
+ }
289
+ return entries;
290
+ }
291
+ function parseArrayLiteral(state) {
292
+ consumeChar(state, "[", "UNEXPECTED_TOKEN", "Expected '[' to start array literal");
293
+ const items = [];
294
+ while (true) {
295
+ skipWhitespaceAndComments(state);
296
+ if (consumeIf(state, "]")) break;
297
+ items.push(parseValueNode(state));
298
+ skipWhitespaceAndComments(state);
299
+ }
300
+ return items;
301
+ }
302
+ function parseStringLiteral(state) {
303
+ const quote = consume(state);
304
+ let value = "";
305
+ while (!eof(state)) {
306
+ const ch = consume(state);
307
+ if (ch === quote) {
308
+ return value;
309
+ }
310
+ if (ch === "\\") {
311
+ const next = consume(state);
312
+ if (next === "n") value += "\n";
313
+ else if (next === "t") value += " ";
314
+ else if (next === '"') value += '"';
315
+ else if (next === "'") value += "'";
316
+ else value += next;
317
+ } else {
318
+ value += ch;
319
+ }
320
+ }
321
+ throw error(state, "UNEXPECTED_EOF", "Unterminated string literal");
322
+ }
323
+ function parseNumberLiteral(state) {
324
+ const start = state.pos;
325
+ if (state.input[state.pos] === "+" || state.input[state.pos] === "-") {
326
+ state.pos++;
327
+ }
328
+ while (isDigit(peek(state))) {
329
+ state.pos++;
330
+ }
331
+ if (peek(state) === ".") {
332
+ state.pos++;
333
+ if (!isDigit(peek(state))) {
334
+ throw error(state, "INVALID_LITERAL", "Invalid float literal");
335
+ }
336
+ while (isDigit(peek(state))) state.pos++;
337
+ }
338
+ if (peek(state) && (peek(state) === "e" || peek(state) === "E")) {
339
+ state.pos++;
340
+ if (peek(state) === "+" || peek(state) === "-") state.pos++;
341
+ if (!isDigit(peek(state))) throw error(state, "INVALID_LITERAL", "Invalid exponent in number");
342
+ while (isDigit(peek(state))) state.pos++;
343
+ }
344
+ const raw = state.input.slice(start, state.pos);
345
+ const value = Number(raw);
346
+ if (Number.isNaN(value)) {
347
+ throw error(state, "INVALID_LITERAL", "Invalid number literal");
348
+ }
349
+ return value;
350
+ }
351
+ function parseBooleanLiteral(state) {
352
+ if (lookAhead(state, "true")) {
353
+ state.pos += 4;
354
+ return true;
355
+ }
356
+ if (lookAhead(state, "false")) {
357
+ state.pos += 5;
358
+ return false;
359
+ }
360
+ throw error(state, "INVALID_LITERAL", "Invalid boolean literal");
361
+ }
362
+ function parseNullLiteral(state) {
363
+ consumeString(state, "null", "INVALID_LITERAL", "Invalid null literal");
364
+ return null;
365
+ }
366
+ function parseWordLiteral(state) {
367
+ const first = readIdentifier(state, "Expected identifier literal");
368
+ const parts = [first];
369
+ while (lookAhead(state, ".")) {
370
+ state.pos += 1;
371
+ const next = readIdentifier(state, "Expected identifier segment after '.'");
372
+ parts.push(next);
373
+ }
374
+ const name = parts.pop();
375
+ return { kind: "Word", namespace: parts, name };
376
+ }
377
+ function readOptionalMarker(state) {
378
+ if (!isIdentifierStart(peek(state))) return void 0;
379
+ return readIdentifier(state, "Expected marker");
380
+ }
381
+ function readKey(state, message) {
382
+ const ch = peek(state);
383
+ if (ch === '"' || ch === "'") {
384
+ return parseStringLiteral(state);
385
+ }
386
+ return readIdentifier(state, message);
387
+ }
388
+ function readIdentifier(state, message) {
389
+ const start = state.pos;
390
+ const first = state.input[state.pos];
391
+ if (!isIdentifierStart(first)) {
392
+ throw error(state, "UNEXPECTED_TOKEN", message);
393
+ }
394
+ state.pos++;
395
+ while (!eof(state) && isIdentifierChar(state.input[state.pos])) {
396
+ state.pos++;
397
+ }
398
+ return state.input.slice(start, state.pos);
399
+ }
400
+ function startsWithNumber(state) {
401
+ const ch = state.input[state.pos];
402
+ if (ch === "+" || ch === "-") {
403
+ const next = peek(state, 1);
404
+ return next !== void 0 && isDigit(next);
405
+ }
406
+ return isDigit(ch);
407
+ }
408
+ function startsWithBoolean(state) {
409
+ return lookAhead(state, "true") || lookAhead(state, "false");
410
+ }
411
+ function startsWithNull(state) {
412
+ return lookAhead(state, "null");
413
+ }
414
+ function consumeIf(state, token) {
415
+ if (lookAhead(state, token)) {
416
+ state.pos += token.length;
417
+ return true;
418
+ }
419
+ return false;
420
+ }
421
+ function consumeString(state, token, code, message) {
422
+ if (!lookAhead(state, token)) {
423
+ throw error(state, code, message);
424
+ }
425
+ state.pos += token.length;
426
+ }
427
+ function lookAhead(state, token) {
428
+ return state.input.startsWith(token, state.pos);
429
+ }
430
+ function consumeChar(state, expected, code, message) {
431
+ const ch = consume(state);
432
+ if (ch !== expected) {
433
+ throw error(state, code, message);
434
+ }
435
+ return ch;
436
+ }
437
+ function consume(state) {
438
+ if (eof(state)) throw error(state, "UNEXPECTED_EOF", "Unexpected end of input");
439
+ const ch = state.input[state.pos];
440
+ state.pos += 1;
441
+ return ch;
442
+ }
443
+ function skipWhitespaceAndComments(state) {
444
+ while (!eof(state)) {
445
+ const ch = state.input[state.pos];
446
+ if (isWhitespace(ch)) {
447
+ state.pos++;
448
+ continue;
449
+ }
450
+ if (lookAhead(state, "<!--")) {
451
+ skipComment(state);
452
+ continue;
453
+ }
454
+ break;
455
+ }
456
+ }
457
+ function indentationBefore(input, index) {
458
+ const lastNewline = input.lastIndexOf("\n", index - 1);
459
+ if (lastNewline === -1) return "";
460
+ const indent = input.slice(lastNewline + 1, index);
461
+ return /^[ \t]*$/.test(indent) ? indent : "";
462
+ }
463
+ function dedentContent(content, indent) {
464
+ if (!content.includes("\n") || indent === "") return content;
465
+ const lines = content.split("\n");
466
+ const startIndex = lines[0].length === 0 ? 1 : 0;
467
+ const dedented = lines.slice(startIndex).map((line) => {
468
+ let remove = 0;
469
+ while (remove < indent.length && remove < line.length && line[remove] === indent[remove] && (indent[remove] === " " || indent[remove] === " ")) {
470
+ remove++;
471
+ }
472
+ return line.slice(remove);
473
+ });
474
+ return dedented.join("\n");
475
+ }
476
+ function stripComments(content) {
477
+ return content.replace(/<!--[\\s\\S]*?-->/g, "");
478
+ }
479
+ function eof(state) {
480
+ return state.pos >= state.length;
481
+ }
482
+ function peek(state, offset = 0) {
483
+ const idx = state.pos + offset;
484
+ return idx < state.length ? state.input[idx] : void 0;
485
+ }
486
+ function isIdentifierStart(ch) {
487
+ if (!ch) return false;
488
+ return /[A-Za-z_]/.test(ch);
489
+ }
490
+ function isIdentifierChar(ch) {
491
+ if (!ch) return false;
492
+ return /[A-Za-z0-9_-]/.test(ch);
493
+ }
494
+ function isWhitespace(ch) {
495
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r";
496
+ }
497
+ function isDigit(ch) {
498
+ return ch !== void 0 && ch >= "0" && ch <= "9";
499
+ }
500
+ function error(state, code, message) {
501
+ return new XnlParseError(code, message, state.input, state.pos);
502
+ }
503
+ function skipComment(state) {
504
+ if (!lookAhead(state, "<!--")) return;
505
+ const end = state.input.indexOf("-->", state.pos + 4);
506
+ if (end === -1) {
507
+ throw error(state, "UNEXPECTED_EOF", "Unterminated comment");
508
+ }
509
+ state.pos = end + 3;
510
+ }
511
+
512
+ // src/types.ts
513
+ function isWord(value) {
514
+ return value !== null && typeof value === "object" && value.kind === "Word";
515
+ }
516
+ function wordToString(word) {
517
+ if (!word) return void 0;
518
+ const ns = word.namespace ?? [];
519
+ const parts = [...ns.filter(Boolean), word.name].filter((p) => p !== void 0 && p !== null);
520
+ const str = parts.join(".");
521
+ return str.length ? str : void 0;
522
+ }
523
+
524
+ // src/formatter.ts
525
+ function stringify(value, options = {}) {
526
+ const pretty = options.pretty === true;
527
+ const indent = typeof options.indent === "string" ? options.indent : " ".repeat(options.indent ?? 0);
528
+ const state = { pretty, indent, depth: 0 };
529
+ const separator = pretty ? "\n" : "";
530
+ const content = isDocument(value) ? value.nodes.map((n) => serializeNode(n, state)).join(separator) : serializeNode(value, state);
531
+ return content;
532
+ }
533
+ function isDocument(value) {
534
+ return value && Array.isArray(value.nodes);
535
+ }
536
+ function serializeNode(node, state) {
537
+ if (isComment(node)) {
538
+ const pad = state.pretty ? state.indent.repeat(state.depth) : "";
539
+ return `${pad}<!-- ${node.value} -->`;
540
+ }
541
+ if (isElement(node)) {
542
+ const pad = state.pretty ? state.indent.repeat(state.depth) : "";
543
+ if (node.kind === "TextElement") {
544
+ const metaStr2 = serializeInlineAttributes(node.metadata, state);
545
+ const attrStr = node.attributes ? ` ${serializeAttributeBlock(node.attributes, state)}` : "";
546
+ const idPart2 = node.id ? ` #${formatWord(node.id)}` : "";
547
+ const marker = node.textMarker ?? "";
548
+ return `${pad}<${node.tag}${idPart2}${metaStr2}${attrStr} ?${marker}>${node.text ?? ""}</?${marker}>`;
549
+ }
550
+ const metaStr = serializeInlineAttributes(node.metadata, state);
551
+ const attrPart = node.attributes ? ` ${serializeAttributeBlock(node.attributes, state)}` : "";
552
+ const bodyPart = node.body ? ` ${serializeArrayBlock(node.body, state)}` : "";
553
+ const extendPart = node.extend ? ` ${serializeExtendBlock(node.extend, state)}` : "";
554
+ const idPart = node.id ? ` #${formatWord(node.id)}` : "";
555
+ return `${pad}<${node.tag}${idPart}${metaStr}${attrPart}${bodyPart}${extendPart}>`;
556
+ }
557
+ if (Array.isArray(node)) {
558
+ return serializeArrayLiteral(node, state);
559
+ }
560
+ if (isPlainObject(node)) {
561
+ return serializeObjectLiteral(node, state);
562
+ }
563
+ return serializePrimitive(node);
564
+ }
565
+ function serializeInlineAttributes(attrs, state) {
566
+ const parts = [];
567
+ for (const [key, value] of Object.entries(attrs)) {
568
+ parts.push(`${serializeKey(key)}=${serializeValueNode(value, state)}`);
569
+ }
570
+ return parts.length ? " " + parts.join(" ") : "";
571
+ }
572
+ function serializeAttributeBlock(attrs, state) {
573
+ if (!state.pretty) {
574
+ const entries = Object.entries(attrs).map(([k, v]) => `${serializeKey(k)} = ${serializeValueNode(v, state)}`).join(" ");
575
+ return `{ ${entries} }`;
576
+ }
577
+ const nextDepth = state.depth + 1;
578
+ const pad = state.indent.repeat(nextDepth);
579
+ const lines = Object.entries(attrs).map(
580
+ ([k, v]) => `${pad}${serializeKey(k)} = ${serializeValueNode(v, { ...state, depth: nextDepth })}`
581
+ );
582
+ const closingPad = state.indent.repeat(state.depth);
583
+ return `{
584
+ ${lines.join("\n")}
585
+ ${closingPad}}`;
586
+ }
587
+ function serializeArrayBlock(items, state) {
588
+ if (!state.pretty) {
589
+ const serialized = items.map((item) => serializeValueNode(item, state)).join(" ");
590
+ return `[ ${serialized} ]`;
591
+ }
592
+ const nextDepth = state.depth + 1;
593
+ const pad = state.indent.repeat(nextDepth);
594
+ const lines = items.map((item) => `${pad}${serializeValueNode(item, { ...state, depth: nextDepth })}`);
595
+ const closingPad = state.indent.repeat(state.depth);
596
+ return `[
597
+ ${lines.join("\n")}
598
+ ${closingPad}]`;
599
+ }
600
+ function serializeExtendBlock(extend, state) {
601
+ if (!state.pretty) {
602
+ const children = extend.order.map((name) => serializeNode(extend.children[name], state)).join(" ");
603
+ return `( ${children} )`;
604
+ }
605
+ const nextDepth = state.depth + 1;
606
+ const pad = state.indent.repeat(nextDepth);
607
+ const childStrings = extend.order.map((name) => `${pad}${serializeNode(extend.children[name], { ...state, depth: nextDepth })}`);
608
+ const closingPad = state.indent.repeat(state.depth);
609
+ return `(
610
+ ${childStrings.join("\n")}
611
+ ${closingPad})`;
612
+ }
613
+ function serializeValueNode(node, state) {
614
+ if (isComment(node) || isElement(node)) return serializeNode(node, state);
615
+ if (isWord(node)) return formatWord(node);
616
+ if (Array.isArray(node)) return serializeArrayLiteral(node, state);
617
+ if (isPlainObject(node)) return serializeObjectLiteral(node, state);
618
+ return serializePrimitive(node);
619
+ }
620
+ function serializePrimitive(value) {
621
+ if (value === null) return "null";
622
+ if (typeof value === "string") return `"${escapeString(value)}"`;
623
+ if (typeof value === "boolean") return value ? "true" : "false";
624
+ return String(value);
625
+ }
626
+ function serializeObjectLiteral(obj, state) {
627
+ if (!state.pretty) {
628
+ return `{ ${Object.entries(obj).map(([k, v]) => `${serializeKey(k)} = ${serializeValueNode(v, state)}`).join(" ")} }`;
629
+ }
630
+ const nextDepth = state.depth + 1;
631
+ const pad = state.indent.repeat(nextDepth);
632
+ const lines = Object.entries(obj).map(
633
+ ([k, v]) => `${pad}${serializeKey(k)} = ${serializeValueNode(v, { ...state, depth: nextDepth })}`
634
+ );
635
+ const closingPad = state.indent.repeat(state.depth);
636
+ return `{
637
+ ${lines.join("\n")}
638
+ ${closingPad}}`;
639
+ }
640
+ function serializeArrayLiteral(arr, state) {
641
+ if (!state.pretty) {
642
+ return `[${arr.map((v) => serializeValueNode(v, state)).join(" ")}]`;
643
+ }
644
+ const nextDepth = state.depth + 1;
645
+ const pad = state.indent.repeat(nextDepth);
646
+ const lines = arr.map((v) => `${pad}${serializeValueNode(v, { ...state, depth: nextDepth })}`);
647
+ const closingPad = state.indent.repeat(state.depth);
648
+ return `[
649
+ ${lines.join("\n")}
650
+ ${closingPad}]`;
651
+ }
652
+ function isComment(node) {
653
+ return typeof node === "object" && node !== null && node.kind === "Comment";
654
+ }
655
+ function isElement(node) {
656
+ return typeof node === "object" && node !== null && (node.kind === "DataElement" || node.kind === "TextElement");
657
+ }
658
+ function isPlainObject(value) {
659
+ return typeof value === "object" && value !== null && !Array.isArray(value) && value.kind === void 0;
660
+ }
661
+ function serializeKey(key) {
662
+ if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(key)) return key;
663
+ return `"${escapeString(key)}"`;
664
+ }
665
+ function escapeString(value) {
666
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r");
667
+ }
668
+ function formatWord(word) {
669
+ return wordToString(word) ?? "";
670
+ }
671
+
672
+ // src/path/index.ts
673
+ var XnlPathError = class extends Error {
674
+ };
675
+ function parsePath(input) {
676
+ if (!input) return [];
677
+ const items = [];
678
+ let i = 0;
679
+ while (i < input.length) {
680
+ const c = input[i];
681
+ const next = input[i + 1];
682
+ if (c === "#") {
683
+ i += 1;
684
+ const { value, length } = readWordToken(input, i);
685
+ items.push({ type: "UniqueName", value });
686
+ i += length;
687
+ continue;
688
+ }
689
+ if (c === "<") {
690
+ const parsed = parseMetadataSelector(input, i);
691
+ items.push(parsed.item);
692
+ i = parsed.nextIndex;
693
+ continue;
694
+ }
695
+ if (c === ":" && next === ":") {
696
+ i += 2;
697
+ if (input[i] === "'") {
698
+ i += 1;
699
+ const end = input.indexOf("'", i);
700
+ if (end === -1) throw new XnlPathError("Unterminated map key literal");
701
+ const key = input.slice(i, end);
702
+ items.push({ type: "MapKey", value: key });
703
+ i = end + 1;
704
+ } else {
705
+ const digits = readWhile(input, i, (ch) => /[0-9]/.test(ch));
706
+ if (!digits) throw new XnlPathError("ListIndex must be numeric");
707
+ items.push({ type: "ListIndex", value: digits });
708
+ i += digits.length;
709
+ }
710
+ continue;
711
+ }
712
+ if (c === ":") {
713
+ i += 1;
714
+ const value = readUntilDelimiter(input, i);
715
+ if (!value) throw new XnlPathError("InstanceProperty cannot be empty");
716
+ items.push({ type: "InstanceProperty", value });
717
+ i += value.length;
718
+ continue;
719
+ }
720
+ if (c === ".") {
721
+ throw new XnlPathError("Namespace/static segments are not supported for XNL paths");
722
+ }
723
+ throw new XnlPathError(`Unexpected character '${c}' at position ${i}`);
724
+ }
725
+ return items;
726
+ }
727
+ function resolvePath(target, path, options = {}) {
728
+ const { strict = true, metadataIdMode } = options;
729
+ const parsed = Array.isArray(path) ? path : parsePath(path);
730
+ let current = target;
731
+ for (let i = 0; i < parsed.length; i++) {
732
+ const item = parsed[i];
733
+ if (item.type === "UniqueName") {
734
+ const found = findByUniqueName(target, item.value);
735
+ if (!found) {
736
+ if (strict) throw new XnlPathError(`UniqueName '${item.value}' not found`);
737
+ return void 0;
738
+ }
739
+ current = found;
740
+ continue;
741
+ }
742
+ if (item.type === "MetadataSelector") {
743
+ const isLast = i === parsed.length - 1;
744
+ if (metadataIdMode === "identity") {
745
+ const selector = parseMetadataSelectorValue(item.value);
746
+ const found = selector.key === "id" ? findByUniqueName(target, selector.value) ?? findByMetadataSelector(target, item.value) : findByMetadataSelector(target, item.value);
747
+ if (!found) {
748
+ if (strict) throw new XnlPathError(`MetadataSelector '${item.value}' not found`);
749
+ return void 0;
750
+ }
751
+ current = found;
752
+ continue;
753
+ }
754
+ const allFound = findAllByMetadataSelector(target, item.value);
755
+ if (allFound.length === 0) {
756
+ if (strict) throw new XnlPathError(`MetadataSelector '${item.value}' not found`);
757
+ return void 0;
758
+ }
759
+ current = isLast ? allFound : allFound[0];
760
+ continue;
761
+ }
762
+ if (item.type === "InstanceProperty") {
763
+ if (current && isDataElement(current)) {
764
+ current = current[item.value];
765
+ } else if (current && isTextElement(current)) {
766
+ current = current[item.value];
767
+ } else if (isPlainObject2(current) || isDocument2(current)) {
768
+ current = current[item.value];
769
+ } else {
770
+ if (strict) throw new XnlPathError(`InstanceProperty '${item.value}' not allowed on current node`);
771
+ return void 0;
772
+ }
773
+ if (current === void 0 && strict) {
774
+ throw new XnlPathError(`InstanceProperty '${item.value}' not found`);
775
+ }
776
+ continue;
777
+ }
778
+ if (item.type === "MapKey") {
779
+ if (isExtendBody(current)) {
780
+ const child = current.children[item.value];
781
+ if (!child && strict) throw new XnlPathError(`Extend child '${item.value}' not found`);
782
+ current = child;
783
+ continue;
784
+ }
785
+ if (!isPlainObject2(current)) {
786
+ if (strict) throw new XnlPathError("MapKey requires a map/object target");
787
+ return void 0;
788
+ }
789
+ if (!(item.value in current) && strict) {
790
+ throw new XnlPathError(`Key '${item.value}' not found`);
791
+ }
792
+ current = current[item.value];
793
+ continue;
794
+ }
795
+ if (item.type === "ListIndex") {
796
+ const index = Number(item.value);
797
+ if (isExtendBody(current)) {
798
+ const tag = current.order[index];
799
+ if (tag === void 0) {
800
+ if (strict) throw new XnlPathError(`Extend index ${index} out of bounds`);
801
+ return void 0;
802
+ }
803
+ current = current.children[tag];
804
+ continue;
805
+ }
806
+ if (!Array.isArray(current)) {
807
+ if (strict) throw new XnlPathError("ListIndex requires an array target");
808
+ return void 0;
809
+ }
810
+ if (index < 0 || index >= current.length) {
811
+ if (strict) throw new XnlPathError(`Index ${index} out of bounds`);
812
+ return void 0;
813
+ }
814
+ current = current[index];
815
+ }
816
+ }
817
+ return current;
818
+ }
819
+ function setPathValue(target, path, value, options = {}) {
820
+ const { mode = "insert", strict = true } = options;
821
+ const parsed = Array.isArray(path) ? path : parsePath(path);
822
+ if (parsed.length === 0) {
823
+ return value;
824
+ }
825
+ const { parent, last } = getParentAndLast(target, parsed, { strict, createMissing: true });
826
+ switch (last.type) {
827
+ case "InstanceProperty":
828
+ parent[last.value] = value;
829
+ return target;
830
+ case "MapKey":
831
+ if (isExtendBody(parent)) {
832
+ parent.children[last.value] = value;
833
+ if (!parent.order.includes(last.value)) {
834
+ parent.order.push(last.value);
835
+ }
836
+ return target;
837
+ }
838
+ ensureMap(parent, strict);
839
+ parent[last.value] = value;
840
+ return target;
841
+ case "ListIndex": {
842
+ const idx = Number(last.value);
843
+ if (isExtendBody(parent)) {
844
+ if (mode === "insert") {
845
+ parent.order.splice(idx, 0, value.tag ?? String(idx));
846
+ parent.children[value.tag ?? String(idx)] = value;
847
+ } else {
848
+ const tag = parent.order[idx];
849
+ if (tag === void 0 && strict) {
850
+ throw new XnlPathError(`Extend index ${idx} out of bounds`);
851
+ }
852
+ const useTag = value?.tag ?? tag;
853
+ parent.order[idx] = useTag;
854
+ parent.children[useTag] = value;
855
+ }
856
+ return target;
857
+ }
858
+ if (!Array.isArray(parent)) {
859
+ throw new XnlPathError("ListIndex requires an array target");
860
+ }
861
+ if (mode === "insert") {
862
+ const insertAt = idx < 0 ? 0 : idx > parent.length ? parent.length : idx;
863
+ parent.splice(insertAt, 0, value);
864
+ } else {
865
+ if (idx < 0 || idx >= parent.length) {
866
+ throw new XnlPathError(`Index ${idx} out of bounds for replace`);
867
+ }
868
+ parent[idx] = value;
869
+ }
870
+ return target;
871
+ }
872
+ default:
873
+ throw new XnlPathError(`Unsupported path item ${last.type}`);
874
+ }
875
+ }
876
+ function deleteAtPath(target, path, options = {}) {
877
+ const { strict = true } = options;
878
+ const parsed = Array.isArray(path) ? path : parsePath(path);
879
+ if (parsed.length === 0) return void 0;
880
+ const { parent, last } = getParentAndLast(target, parsed, { strict, createMissing: false });
881
+ switch (last.type) {
882
+ case "InstanceProperty":
883
+ if (isPlainObject2(parent) || isDataElement(parent) || isTextElement(parent) || isDocument2(parent)) {
884
+ if (!(last.value in parent) && strict) {
885
+ throw new XnlPathError(`InstanceProperty '${last.value}' not found`);
886
+ }
887
+ delete parent[last.value];
888
+ return target;
889
+ }
890
+ throw new XnlPathError("InstanceProperty delete requires an object-like target");
891
+ case "MapKey":
892
+ if (isExtendBody(parent)) {
893
+ if (!(last.value in parent.children) && strict) {
894
+ throw new XnlPathError(`Extend child '${last.value}' not found`);
895
+ }
896
+ delete parent.children[last.value];
897
+ parent.order = parent.order.filter((t) => t !== last.value);
898
+ return target;
899
+ }
900
+ ensureMap(parent, strict);
901
+ delete parent[last.value];
902
+ return target;
903
+ case "ListIndex": {
904
+ const idx = Number(last.value);
905
+ if (isExtendBody(parent)) {
906
+ if (idx < 0 || idx >= parent.order.length) {
907
+ if (strict) throw new XnlPathError(`Extend index ${idx} out of bounds`);
908
+ return target;
909
+ }
910
+ const tag = parent.order[idx];
911
+ parent.order.splice(idx, 1);
912
+ delete parent.children[tag];
913
+ return target;
914
+ }
915
+ if (!Array.isArray(parent)) throw new XnlPathError("ListIndex delete requires an array target");
916
+ if (idx < 0 || idx >= parent.length) {
917
+ if (strict) throw new XnlPathError(`Index ${idx} out of bounds`);
918
+ return target;
919
+ }
920
+ parent.splice(idx, 1);
921
+ return target;
922
+ }
923
+ default:
924
+ throw new XnlPathError(`Unsupported path item ${last.type}`);
925
+ }
926
+ }
927
+ function getParentAndLast(target, path, opts) {
928
+ if (path.length === 0) throw new XnlPathError("Path is empty");
929
+ const { createMissing, strict = true } = opts;
930
+ const parentPath = path.slice(0, -1);
931
+ const last = path[path.length - 1];
932
+ let current = target;
933
+ for (const item of parentPath) {
934
+ if (item.type === "UniqueName") {
935
+ const found = findByUniqueName(target, item.value);
936
+ if (!found) {
937
+ if (strict) throw new XnlPathError(`UniqueName '${item.value}' not found`);
938
+ return { parent: void 0, last };
939
+ }
940
+ current = found;
941
+ continue;
942
+ }
943
+ if (item.type === "MetadataSelector") {
944
+ const selector = parseMetadataSelectorValue(item.value);
945
+ const found = selector.key === "id" ? findByUniqueName(target, selector.value) ?? findByMetadataSelector(target, item.value) : findByMetadataSelector(target, item.value);
946
+ if (!found) {
947
+ if (strict) throw new XnlPathError(`MetadataSelector '${item.value}' not found`);
948
+ return { parent: void 0, last };
949
+ }
950
+ current = found;
951
+ continue;
952
+ }
953
+ if (item.type === "InstanceProperty") {
954
+ if (current && isDataElement(current)) {
955
+ if (current[item.value] === void 0 && createMissing) {
956
+ if (item.value === "metadata" || item.value === "attributes") {
957
+ current[item.value] = {};
958
+ } else if (item.value === "body") {
959
+ current[item.value] = [];
960
+ } else if (item.value === "extend") {
961
+ current[item.value] = { order: [], children: {} };
962
+ }
963
+ }
964
+ current = current[item.value];
965
+ } else if (current && isTextElement(current)) {
966
+ current = current[item.value];
967
+ } else if (isPlainObject2(current) || isDocument2(current)) {
968
+ if (current[item.value] === void 0 && createMissing && isPlainObject2(current)) {
969
+ current[item.value] = {};
970
+ }
971
+ current = current[item.value];
972
+ } else {
973
+ throw new XnlPathError(`InstanceProperty '${item.value}' not allowed on current node`);
974
+ }
975
+ if (current === void 0 && strict) {
976
+ throw new XnlPathError(`InstanceProperty '${item.value}' not found`);
977
+ }
978
+ continue;
979
+ }
980
+ if (item.type === "MapKey") {
981
+ if (isExtendBody(current)) {
982
+ const child = current.children[item.value];
983
+ if (!child && createMissing) {
984
+ current.children[item.value] = void 0;
985
+ if (!current.order.includes(item.value)) {
986
+ current.order.push(item.value);
987
+ }
988
+ }
989
+ current = current.children[item.value];
990
+ if (current === void 0 && strict && !createMissing) {
991
+ throw new XnlPathError(`Extend child '${item.value}' not found`);
992
+ }
993
+ continue;
994
+ }
995
+ ensureMap(current, strict);
996
+ if (!(item.value in current) && createMissing) {
997
+ current[item.value] = {};
998
+ }
999
+ current = current[item.value];
1000
+ if (current === void 0 && strict && !createMissing) {
1001
+ throw new XnlPathError(`Key '${item.value}' not found`);
1002
+ }
1003
+ continue;
1004
+ }
1005
+ if (item.type === "ListIndex") {
1006
+ const idx = Number(item.value);
1007
+ if (isExtendBody(current)) {
1008
+ if (idx < 0 || idx > current.order.length) {
1009
+ throw new XnlPathError(`Extend index ${idx} out of bounds`);
1010
+ }
1011
+ const tag = current.order[idx];
1012
+ if (!tag && createMissing) {
1013
+ const placeholder = String(idx);
1014
+ current.order[idx] = placeholder;
1015
+ current.children[placeholder] = void 0;
1016
+ current = current.children[placeholder];
1017
+ } else {
1018
+ if (tag === void 0 && strict) {
1019
+ throw new XnlPathError(`Extend index ${idx} out of bounds`);
1020
+ }
1021
+ current = current.children[tag];
1022
+ }
1023
+ continue;
1024
+ }
1025
+ if (!Array.isArray(current)) {
1026
+ if (!strict && !current) return { parent: void 0, last };
1027
+ throw new XnlPathError("ListIndex requires an array target");
1028
+ }
1029
+ if (idx < 0 || idx > current.length) {
1030
+ throw new XnlPathError(`Index ${idx} out of bounds`);
1031
+ }
1032
+ if (createMissing && idx === current.length) {
1033
+ current.push(void 0);
1034
+ }
1035
+ current = current[idx];
1036
+ continue;
1037
+ }
1038
+ }
1039
+ return { parent: current, last };
1040
+ }
1041
+ function readUntilDelimiter(input, start) {
1042
+ let i = start;
1043
+ let value = "";
1044
+ while (i < input.length) {
1045
+ const c = input[i];
1046
+ if (c === ":" || c === "#" || c === "." || c === "<") break;
1047
+ value += c;
1048
+ i++;
1049
+ }
1050
+ return value;
1051
+ }
1052
+ function readWordToken(input, start) {
1053
+ let i = start;
1054
+ let value = "";
1055
+ while (i < input.length) {
1056
+ const c = input[i];
1057
+ if (c === ":" || c === "#") break;
1058
+ value += c;
1059
+ i++;
1060
+ }
1061
+ if (!value) throw new XnlPathError("UniqueName cannot be empty");
1062
+ const segments = value.split(".");
1063
+ for (const segment of segments) {
1064
+ if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(segment)) {
1065
+ throw new XnlPathError("UniqueName must use word identifiers (letters/digits/_/- with dots for namespace)");
1066
+ }
1067
+ }
1068
+ return { value, length: value.length };
1069
+ }
1070
+ function readWhile(input, start, pred) {
1071
+ let i = start;
1072
+ let out = "";
1073
+ while (i < input.length && pred(input[i])) {
1074
+ out += input[i];
1075
+ i++;
1076
+ }
1077
+ return out;
1078
+ }
1079
+ function findByUniqueName(target, id) {
1080
+ const roots = isDocument2(target) ? target.nodes : [target];
1081
+ for (const root of roots) {
1082
+ const found = findInNode(root, id);
1083
+ if (found) return found;
1084
+ }
1085
+ return void 0;
1086
+ }
1087
+ function findByMetadataSelector(target, selector) {
1088
+ const parsed = parseMetadataSelectorValue(selector);
1089
+ const roots = isDocument2(target) ? target.nodes : [target];
1090
+ for (const root of roots) {
1091
+ const found = findInNodeByMeta(root, parsed.key, parsed.value);
1092
+ if (found) return found;
1093
+ }
1094
+ return void 0;
1095
+ }
1096
+ function findAllByMetadataSelector(target, selector) {
1097
+ const parsed = parseMetadataSelectorValue(selector);
1098
+ const roots = isDocument2(target) ? target.nodes : [target];
1099
+ const out = [];
1100
+ for (const root of roots) {
1101
+ collectInNodeByMeta(root, parsed.key, parsed.value, out);
1102
+ }
1103
+ return out;
1104
+ }
1105
+ function findInNodeByMeta(node, key, value) {
1106
+ if (isElementNode(node)) {
1107
+ const meta = node.metadata;
1108
+ const raw = meta ? meta[key] : void 0;
1109
+ const rawStr = typeof raw === "string" ? raw : isWord(raw) ? wordToString(raw) : void 0;
1110
+ if (rawStr === value) return node;
1111
+ if (node.kind === "DataElement") {
1112
+ if (node.body) {
1113
+ for (const child of node.body) {
1114
+ const found = findInNodeByMeta(child, key, value);
1115
+ if (found) return found;
1116
+ }
1117
+ }
1118
+ if (node.extend) {
1119
+ for (const tag of node.extend.order) {
1120
+ const child = node.extend.children[tag];
1121
+ if (!child) continue;
1122
+ const found = findInNodeByMeta(child, key, value);
1123
+ if (found) return found;
1124
+ }
1125
+ }
1126
+ }
1127
+ return void 0;
1128
+ }
1129
+ if (Array.isArray(node)) {
1130
+ for (const child of node) {
1131
+ const found = findInNodeByMeta(child, key, value);
1132
+ if (found) return found;
1133
+ }
1134
+ } else if (isPlainObject2(node)) {
1135
+ for (const k of Object.keys(node)) {
1136
+ const found = findInNodeByMeta(node[k], key, value);
1137
+ if (found) return found;
1138
+ }
1139
+ }
1140
+ return void 0;
1141
+ }
1142
+ function collectInNodeByMeta(node, key, value, out) {
1143
+ if (isElementNode(node)) {
1144
+ const meta = node.metadata;
1145
+ const raw = meta ? meta[key] : void 0;
1146
+ const rawStr = typeof raw === "string" ? raw : isWord(raw) ? wordToString(raw) : void 0;
1147
+ if (rawStr === value) out.push(node);
1148
+ if (node.kind === "DataElement") {
1149
+ if (node.body) {
1150
+ for (const child of node.body) {
1151
+ collectInNodeByMeta(child, key, value, out);
1152
+ }
1153
+ }
1154
+ if (node.extend) {
1155
+ for (const tag of node.extend.order) {
1156
+ const child = node.extend.children[tag];
1157
+ if (!child) continue;
1158
+ collectInNodeByMeta(child, key, value, out);
1159
+ }
1160
+ }
1161
+ }
1162
+ return;
1163
+ }
1164
+ if (Array.isArray(node)) {
1165
+ for (const child of node) {
1166
+ collectInNodeByMeta(child, key, value, out);
1167
+ }
1168
+ } else if (isPlainObject2(node)) {
1169
+ for (const k of Object.keys(node)) {
1170
+ collectInNodeByMeta(node[k], key, value, out);
1171
+ }
1172
+ }
1173
+ }
1174
+ function parseMetadataSelectorValue(selector) {
1175
+ const m = selector.match(/^<([A-Za-z_][A-Za-z0-9_-]*)=("([^"]*)"|'([^']*)')>$/);
1176
+ if (!m) throw new XnlPathError("Invalid metadata selector");
1177
+ const key = m[1];
1178
+ const value = m[3] ?? m[4] ?? "";
1179
+ return { key, value };
1180
+ }
1181
+ function parseMetadataSelector(input, start) {
1182
+ if (input[start] !== "<") throw new XnlPathError("Metadata selector must start with '<'");
1183
+ const end = input.indexOf(">", start);
1184
+ if (end === -1) throw new XnlPathError("Unterminated metadata selector");
1185
+ const raw = input.slice(start, end + 1);
1186
+ parseMetadataSelectorValue(raw);
1187
+ return { item: { type: "MetadataSelector", value: raw }, nextIndex: end + 1 };
1188
+ }
1189
+ function findInNode(node, id) {
1190
+ if (isElementNode(node)) {
1191
+ if (readId(node) === id) return node;
1192
+ if (node.kind === "DataElement") {
1193
+ if (node.body) {
1194
+ for (const child of node.body) {
1195
+ const found = findInNode(child, id);
1196
+ if (found) return found;
1197
+ }
1198
+ }
1199
+ if (node.extend) {
1200
+ for (const tag of node.extend.order) {
1201
+ const child = node.extend.children[tag];
1202
+ if (!child) continue;
1203
+ const found = findInNode(child, id);
1204
+ if (found) return found;
1205
+ }
1206
+ }
1207
+ }
1208
+ return void 0;
1209
+ }
1210
+ if (Array.isArray(node)) {
1211
+ for (const child of node) {
1212
+ const found = findInNode(child, id);
1213
+ if (found) return found;
1214
+ }
1215
+ } else if (isPlainObject2(node)) {
1216
+ for (const key of Object.keys(node)) {
1217
+ const found = findInNode(node[key], id);
1218
+ if (found) return found;
1219
+ }
1220
+ }
1221
+ return void 0;
1222
+ }
1223
+ function isElementNode(node) {
1224
+ return node && (node.kind === "DataElement" || node.kind === "TextElement");
1225
+ }
1226
+ function isDataElement(node) {
1227
+ return node && node.kind === "DataElement";
1228
+ }
1229
+ function isTextElement(node) {
1230
+ return node && node.kind === "TextElement";
1231
+ }
1232
+ function isExtendBody(value) {
1233
+ return value && typeof value === "object" && Array.isArray(value.order) && value.children;
1234
+ }
1235
+ function isDocument2(value) {
1236
+ return value && typeof value === "object" && Array.isArray(value.nodes);
1237
+ }
1238
+ function isPlainObject2(value) {
1239
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !isElementNode(value) && !isExtendBody(value) && !isWord(value);
1240
+ }
1241
+ function ensureMap(value, strict) {
1242
+ if (!isPlainObject2(value)) {
1243
+ if (strict) throw new XnlPathError("Target is not a map/object");
1244
+ }
1245
+ }
1246
+ function readId(node) {
1247
+ if (!node) return void 0;
1248
+ const idValue = node.id;
1249
+ const wordStr = wordToString(idValue);
1250
+ if (wordStr) return wordStr;
1251
+ const metaId = node.metadata?.id;
1252
+ if (isWord(metaId)) return wordToString(metaId);
1253
+ if (typeof metaId === "string") return metaId;
1254
+ return void 0;
1255
+ }
1256
+
1257
+ // src/mutation/index.ts
1258
+ function resolveMetaIdMode(opts) {
1259
+ return opts.metadataIdMode ?? "identity";
1260
+ }
1261
+ function isMetadataIdPath(path) {
1262
+ const n = path.length;
1263
+ return n >= 2 && path[n - 2]?.type === "InstanceProperty" && path[n - 2]?.value === "metadata" && path[n - 1]?.type === "MapKey" && path[n - 1]?.value === "id";
1264
+ }
1265
+ function isMetadataMapPath(path) {
1266
+ const last = path[path.length - 1];
1267
+ return last?.type === "InstanceProperty" && last.value === "metadata";
1268
+ }
1269
+ function applyMutations(root, mutations, opts = {}) {
1270
+ let current = root;
1271
+ for (const mutation of mutations) {
1272
+ current = applySingle(current, mutation, opts);
1273
+ }
1274
+ return current;
1275
+ }
1276
+ function diffNodes(oldNode, newNode, basePath = [], opts = {}) {
1277
+ const pathItems = Array.isArray(basePath) ? basePath : parsePath(basePath);
1278
+ if (!sameKind(oldNode, newNode)) {
1279
+ throw new XnlPathError("Root kinds must match to diff");
1280
+ }
1281
+ if (isValueLiteral(oldNode) || isComment2(oldNode)) {
1282
+ return oldNode === newNode ? [] : [{ type: "OBJECT_UPDATE", path: pathItems, valueAfter: newNode }];
1283
+ }
1284
+ if (Array.isArray(oldNode) && Array.isArray(newNode)) {
1285
+ return diffArray(oldNode, newNode, pathItems, void 0, void 0, opts);
1286
+ }
1287
+ if (isPlainObject3(oldNode) && isPlainObject3(newNode)) {
1288
+ return diffMap(oldNode, newNode, pathItems, void 0, void 0, opts);
1289
+ }
1290
+ if (isTextElement2(oldNode) && isTextElement2(newNode)) {
1291
+ return diffTextElement(oldNode, newNode, pathItems, opts);
1292
+ }
1293
+ if (isDataElement2(oldNode) && isDataElement2(newNode)) {
1294
+ const mutations = diffDataElement(oldNode, newNode, pathItems, opts);
1295
+ return reconcileMoves(mutations);
1296
+ }
1297
+ return [];
1298
+ }
1299
+ function applySingle(root, mutation, opts) {
1300
+ const { type, path, valueAfter } = mutation;
1301
+ const pathItems = Array.isArray(path) ? path : parsePath(path);
1302
+ if (resolveMetaIdMode(opts) === "identity" && isMetadataIdPath(pathItems)) {
1303
+ return root;
1304
+ }
1305
+ if (type === "TREE_MOVE" || type === "TREE_MOVE_SAME_LEVEL" || type === "TREE_MOVE_CROSS_LEVEL") {
1306
+ if (!mutation.targetUniqueName && !mutation.pathBefore) {
1307
+ throw new XnlPathError("TREE_MOVE requires targetUniqueName or pathBefore");
1308
+ }
1309
+ const fromPath = mutation.pathBefore ? Array.isArray(mutation.pathBefore) ? mutation.pathBefore : parsePath(mutation.pathBefore) : [];
1310
+ let moved = mutation.targetUniqueName ? extractByUniqueId(root, mutation.targetUniqueName) : void 0;
1311
+ if (moved === void 0 && fromPath.length > 0) {
1312
+ try {
1313
+ const found = resolvePath(root, fromPath, { strict: false });
1314
+ if (moved === void 0) moved = found;
1315
+ deleteAtPath(root, fromPath, { strict: false });
1316
+ } catch {
1317
+ }
1318
+ }
1319
+ if (moved === void 0 && mutation.targetUniqueName) {
1320
+ throw new XnlPathError(`TREE_MOVE could not find node '${mutation.targetUniqueName}' to move`);
1321
+ }
1322
+ return applySingle(root, { ...mutation, type: "TREE_ADD", path, valueAfter: moved }, opts);
1323
+ }
1324
+ switch (type) {
1325
+ case "TREE_ADD":
1326
+ setPathValue(root, pathItems, valueAfter, { mode: "insert" });
1327
+ return root;
1328
+ case "TREE_DELETE":
1329
+ deleteAtPath(root, pathItems);
1330
+ return root;
1331
+ case "TREE_UPDATE":
1332
+ setPathValue(root, pathItems, valueAfter, { mode: "replace" });
1333
+ return root;
1334
+ case "OBJECT_ADD":
1335
+ case "OBJECT_UPDATE":
1336
+ setPathValue(root, pathItems, valueAfter, { mode: "replace" });
1337
+ return root;
1338
+ case "OBJECT_DELETE":
1339
+ deleteAtPath(root, pathItems);
1340
+ return root;
1341
+ default:
1342
+ throw new XnlPathError(`Unknown mutation type ${type}`);
1343
+ }
1344
+ }
1345
+ function diffTextElement(oldNode, newNode, basePath, opts) {
1346
+ const mutations = [];
1347
+ mutations.push(...diffMap(oldNode.metadata, newNode.metadata, [...basePath, ip("metadata")], oldNode, newNode, opts));
1348
+ if (oldNode.attributes || newNode.attributes) {
1349
+ mutations.push(...diffMap(oldNode.attributes ?? {}, newNode.attributes ?? {}, [...basePath, ip("attributes")], oldNode, newNode, opts));
1350
+ }
1351
+ if (oldNode.text !== newNode.text) {
1352
+ mutations.push({
1353
+ type: "TREE_UPDATE",
1354
+ path: [...basePath, ip("text")],
1355
+ valueAfter: newNode.text
1356
+ });
1357
+ }
1358
+ if (oldNode.textMarker !== newNode.textMarker) {
1359
+ mutations.push({
1360
+ type: "TREE_UPDATE",
1361
+ path: [...basePath, ip("textMarker")],
1362
+ valueAfter: newNode.textMarker
1363
+ });
1364
+ }
1365
+ return mutations;
1366
+ }
1367
+ function diffDataElement(oldNode, newNode, basePath, opts) {
1368
+ const mutations = [];
1369
+ const metaPath = [...basePath, ip("metadata")];
1370
+ mutations.push(...diffMap(oldNode.metadata, newNode.metadata, metaPath, oldNode, newNode, opts));
1371
+ const attrPath = [...basePath, ip("attributes")];
1372
+ mutations.push(...diffMap(oldNode.attributes ?? {}, newNode.attributes ?? {}, attrPath, oldNode, newNode, opts));
1373
+ if (oldNode.body || newNode.body) {
1374
+ mutations.push(
1375
+ ...diffArray(oldNode.body ?? [], newNode.body ?? [], [...basePath, ip("body")], oldNode, newNode, opts)
1376
+ );
1377
+ }
1378
+ if (oldNode.extend || newNode.extend) {
1379
+ mutations.push(...diffExtend(oldNode.extend, newNode.extend, [...basePath, ip("extend")], oldNode, newNode, opts));
1380
+ }
1381
+ return mutations;
1382
+ }
1383
+ function diffArray(oldArr, newArr, basePath, parentBefore, parentAfter, opts) {
1384
+ const mutations = [];
1385
+ const parentId = resolveMetaIdMode(opts) === "identity" ? readIdOrMetadaataId(parentBefore) ?? readIdOrMetadaataId(parentAfter) : void 0;
1386
+ const pathBase = parentId ? [ms("id", parentId), ip("body")] : basePath;
1387
+ const oldById = {};
1388
+ const newById = {};
1389
+ oldArr.forEach((item, idx) => {
1390
+ const id = readIdOrMetadaataId(item);
1391
+ if (id) oldById[id] = { index: idx, value: item };
1392
+ });
1393
+ newArr.forEach((item, idx) => {
1394
+ const id = readIdOrMetadaataId(item);
1395
+ if (id) newById[id] = { index: idx, value: item };
1396
+ });
1397
+ const max = Math.max(oldArr.length, newArr.length);
1398
+ for (let i = 0; i < max; i++) {
1399
+ const oldItem = oldArr[i];
1400
+ const newItem = newArr[i];
1401
+ const path = [...pathBase, li(i)];
1402
+ if (oldItem === void 0 && newItem !== void 0) {
1403
+ const id = readIdOrMetadaataId(newItem);
1404
+ mutations.push({
1405
+ type: "TREE_ADD",
1406
+ path,
1407
+ valueAfter: newItem,
1408
+ targetUniqueName: id,
1409
+ parentUniqueNameAfter: readIdOrMetadaataId(parentAfter)
1410
+ });
1411
+ continue;
1412
+ }
1413
+ if (oldItem !== void 0 && newItem === void 0) {
1414
+ const id = readIdOrMetadaataId(oldItem);
1415
+ mutations.push({
1416
+ type: "TREE_DELETE",
1417
+ path,
1418
+ valueBefore: oldItem,
1419
+ targetUniqueName: id,
1420
+ parentUniqueNameBefore: readIdOrMetadaataId(parentBefore)
1421
+ });
1422
+ continue;
1423
+ }
1424
+ if (oldItem !== void 0 && newItem !== void 0) {
1425
+ const useIdentity = resolveMetaIdMode(opts) === "identity";
1426
+ const oldId = readIdOrMetadaataId(oldItem);
1427
+ const newId = readIdOrMetadaataId(newItem);
1428
+ if (useIdentity && oldId && newId && oldId !== newId) {
1429
+ mutations.push({
1430
+ type: "TREE_DELETE",
1431
+ path,
1432
+ valueBefore: oldItem,
1433
+ targetUniqueName: oldId,
1434
+ parentUniqueNameBefore: readIdOrMetadaataId(parentBefore)
1435
+ });
1436
+ mutations.push({
1437
+ type: "TREE_ADD",
1438
+ path,
1439
+ valueAfter: newItem,
1440
+ targetUniqueName: newId,
1441
+ parentUniqueNameAfter: readIdOrMetadaataId(parentAfter)
1442
+ });
1443
+ continue;
1444
+ }
1445
+ if (isEqual(oldItem, newItem)) {
1446
+ continue;
1447
+ }
1448
+ const nested = diffNodes(oldItem, newItem, path, opts);
1449
+ if (nested.length === 0) {
1450
+ if (!useIdentity) {
1451
+ mutations.push({ type: "TREE_UPDATE", path, valueAfter: newItem, targetUniqueName: readIdOrMetadaataId(newItem) });
1452
+ }
1453
+ } else {
1454
+ mutations.push(...nested);
1455
+ }
1456
+ }
1457
+ }
1458
+ if (resolveMetaIdMode(opts) === "identity") {
1459
+ const oldIds = oldArr.map(readIdOrMetadaataId).filter(Boolean);
1460
+ const newIds = newArr.map(readIdOrMetadaataId).filter(Boolean);
1461
+ if (oldIds.length && newIds.length) {
1462
+ for (const id of oldIds) {
1463
+ if (!(id in newById)) continue;
1464
+ const oldIdx = oldById[id]?.index ?? -1;
1465
+ const newIdx = newById[id]?.index ?? -1;
1466
+ if (oldIdx !== -1 && newIdx !== -1 && oldIdx !== newIdx) {
1467
+ mutations.push({
1468
+ type: "TREE_MOVE_SAME_LEVEL",
1469
+ pathBefore: [...pathBase, li(oldIdx)],
1470
+ path: [...pathBase, li(newIdx)],
1471
+ targetUniqueName: id,
1472
+ parentUniqueNameBefore: readIdOrMetadaataId(parentBefore),
1473
+ parentUniqueNameAfter: readIdOrMetadaataId(parentAfter)
1474
+ });
1475
+ }
1476
+ }
1477
+ }
1478
+ }
1479
+ return mutations;
1480
+ }
1481
+ function diffMap(oldMap, newMap, basePath, parentBefore, parentAfter, opts) {
1482
+ const mutations = [];
1483
+ const keys = /* @__PURE__ */ new Set([...Object.keys(oldMap || {}), ...Object.keys(newMap || {})]);
1484
+ for (const key of keys) {
1485
+ if (resolveMetaIdMode(opts) === "identity" && isMetadataMapPath(basePath) && key === "id") {
1486
+ continue;
1487
+ }
1488
+ const oldVal = (oldMap || {})[key];
1489
+ const newVal = (newMap || {})[key];
1490
+ const path = [...basePath, mk(key)];
1491
+ if (oldVal === void 0 && newVal !== void 0) {
1492
+ mutations.push({
1493
+ type: "OBJECT_ADD",
1494
+ path,
1495
+ valueAfter: newVal,
1496
+ targetUniqueName: readIdOrMetadaataId(newVal),
1497
+ parentUniqueNameAfter: readIdOrMetadaataId(parentAfter)
1498
+ });
1499
+ continue;
1500
+ }
1501
+ if (oldVal !== void 0 && newVal === void 0) {
1502
+ mutations.push({
1503
+ type: "OBJECT_DELETE",
1504
+ path,
1505
+ valueBefore: oldVal,
1506
+ targetUniqueName: readIdOrMetadaataId(oldVal),
1507
+ parentUniqueNameBefore: readIdOrMetadaataId(parentBefore)
1508
+ });
1509
+ continue;
1510
+ }
1511
+ if (!isEqual(oldVal, newVal)) {
1512
+ const nested = diffNodes(oldVal, newVal, path, opts);
1513
+ if (nested.length === 0) {
1514
+ mutations.push({ type: "OBJECT_UPDATE", path, valueAfter: newVal });
1515
+ } else {
1516
+ mutations.push(...nested);
1517
+ }
1518
+ }
1519
+ }
1520
+ return mutations;
1521
+ }
1522
+ function diffExtend(oldExtend, newExtend, basePath, parentBefore, parentAfter, opts) {
1523
+ const mutations = [];
1524
+ const parentId = resolveMetaIdMode(opts) === "identity" ? readIdOrMetadaataId(parentBefore) ?? readIdOrMetadaataId(parentAfter) : void 0;
1525
+ const pathBase = parentId ? [ms("id", parentId), ip("extend")] : basePath;
1526
+ const oldChildren = oldExtend?.children ?? {};
1527
+ const newChildren = newExtend?.children ?? {};
1528
+ const allTags = /* @__PURE__ */ new Set([...Object.keys(oldChildren), ...Object.keys(newChildren)]);
1529
+ for (const tag of allTags) {
1530
+ const oldChild = oldChildren[tag];
1531
+ const newChild = newChildren[tag];
1532
+ const childPath = [...pathBase, mk(tag)];
1533
+ if (!oldChild && newChild) {
1534
+ mutations.push({
1535
+ type: "TREE_ADD",
1536
+ path: childPath,
1537
+ valueAfter: newChild,
1538
+ targetUniqueName: readIdOrMetadaataId(newChild),
1539
+ parentUniqueNameAfter: readIdOrMetadaataId(parentAfter)
1540
+ });
1541
+ continue;
1542
+ }
1543
+ if (oldChild && !newChild) {
1544
+ mutations.push({
1545
+ type: "TREE_DELETE",
1546
+ path: childPath,
1547
+ valueBefore: oldChild,
1548
+ targetUniqueName: readIdOrMetadaataId(oldChild),
1549
+ parentUniqueNameBefore: readIdOrMetadaataId(parentBefore)
1550
+ });
1551
+ continue;
1552
+ }
1553
+ if (oldChild && newChild) {
1554
+ const nested = diffNodes(oldChild, newChild, childPath, opts);
1555
+ if (nested.length === 0) {
1556
+ if (!isEqual(oldChild, newChild)) {
1557
+ mutations.push({ type: "TREE_UPDATE", path: childPath, valueAfter: newChild });
1558
+ }
1559
+ } else {
1560
+ mutations.push(...nested);
1561
+ }
1562
+ }
1563
+ }
1564
+ const oldOrder = oldExtend?.order ?? [];
1565
+ const newOrder = newExtend?.order ?? [];
1566
+ if (!isEqual(oldOrder, newOrder)) {
1567
+ mutations.push({
1568
+ type: "TREE_UPDATE",
1569
+ path: [...basePath, ip("order")],
1570
+ valueAfter: newOrder
1571
+ });
1572
+ }
1573
+ return mutations;
1574
+ }
1575
+ function sameKind(a, b) {
1576
+ if (isDataElement2(a) && isDataElement2(b)) return true;
1577
+ if (isTextElement2(a) && isTextElement2(b)) return true;
1578
+ if (Array.isArray(a) && Array.isArray(b)) return true;
1579
+ if (isPlainObject3(a) && isPlainObject3(b)) return true;
1580
+ if (isValueLiteral(a) && isValueLiteral(b)) return true;
1581
+ return typeof a === typeof b;
1582
+ }
1583
+ function isEqual(a, b) {
1584
+ if (isWord(a) && isWord(b)) {
1585
+ return wordToString(a) === wordToString(b);
1586
+ }
1587
+ if (a === b) return true;
1588
+ if (typeof a !== typeof b) return false;
1589
+ if (Array.isArray(a) && Array.isArray(b)) {
1590
+ if (a.length !== b.length) return false;
1591
+ return a.every((item, idx) => isEqual(item, b[idx]));
1592
+ }
1593
+ if (isPlainObject3(a) && isPlainObject3(b)) {
1594
+ const keysA = Object.keys(a);
1595
+ const keysB = Object.keys(b);
1596
+ if (keysA.length !== keysB.length) return false;
1597
+ return keysA.every((key) => isEqual(a[key], b[key]));
1598
+ }
1599
+ return false;
1600
+ }
1601
+ function isPlainObject3(value) {
1602
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !isDataElement2(value) && !isTextElement2(value) && !isWord(value);
1603
+ }
1604
+ function isDataElement2(node) {
1605
+ return node && node.kind === "DataElement";
1606
+ }
1607
+ function isTextElement2(node) {
1608
+ return node && node.kind === "TextElement";
1609
+ }
1610
+ function isValueLiteral(node) {
1611
+ return typeof node === "string" || typeof node === "number" || typeof node === "boolean" || node === null || isWord(node);
1612
+ }
1613
+ function isComment2(node) {
1614
+ return node && node.kind === "Comment";
1615
+ }
1616
+ var ip = (value) => ({ type: "InstanceProperty", value });
1617
+ var mk = (value) => ({ type: "MapKey", value });
1618
+ var li = (value) => ({ type: "ListIndex", value: String(value) });
1619
+ function ms(key, value) {
1620
+ return { type: "MetadataSelector", value: `<${key}=${JSON.stringify(value)}>` };
1621
+ }
1622
+ function readIdOrMetadaataId(node) {
1623
+ if (isDataElement2(node) || isTextElement2(node)) {
1624
+ const nodeId = wordToString(node.id);
1625
+ if (nodeId) return nodeId;
1626
+ const metaId = node.metadata?.id;
1627
+ if (typeof metaId === "string") return metaId;
1628
+ if (isWord(metaId)) return wordToString(metaId);
1629
+ }
1630
+ return void 0;
1631
+ }
1632
+ function extractByUniqueId(root, id) {
1633
+ if (Array.isArray(root)) {
1634
+ const idx = root.findIndex((item) => readIdOrMetadaataId(item) === id);
1635
+ if (idx !== -1) {
1636
+ const [removed] = root.splice(idx, 1);
1637
+ return removed;
1638
+ }
1639
+ for (const item of root) {
1640
+ const found = extractByUniqueId(item, id);
1641
+ if (found !== void 0) return found;
1642
+ }
1643
+ } else if (isDataElement2(root)) {
1644
+ if (root.body) {
1645
+ const idx = root.body.findIndex((item) => readIdOrMetadaataId(item) === id);
1646
+ if (idx !== -1) {
1647
+ const [removed] = root.body.splice(idx, 1);
1648
+ return removed;
1649
+ }
1650
+ for (const child of root.body) {
1651
+ const found = extractByUniqueId(child, id);
1652
+ if (found !== void 0) return found;
1653
+ }
1654
+ }
1655
+ if (root.extend) {
1656
+ const tags = [...root.extend.order];
1657
+ for (const tag of tags) {
1658
+ const child = root.extend.children[tag];
1659
+ if (readIdOrMetadaataId(child) === id) {
1660
+ delete root.extend.children[tag];
1661
+ root.extend.order = root.extend.order.filter((t) => t !== tag);
1662
+ return child;
1663
+ }
1664
+ const found = extractByUniqueId(child, id);
1665
+ if (found !== void 0) return found;
1666
+ }
1667
+ }
1668
+ }
1669
+ return void 0;
1670
+ }
1671
+ function reconcileMoves(mutations) {
1672
+ const addsById = {};
1673
+ const deletesById = {};
1674
+ for (const m of mutations) {
1675
+ if (m.type === "TREE_ADD" && m.targetUniqueName) {
1676
+ addsById[m.targetUniqueName] = addsById[m.targetUniqueName] ?? [];
1677
+ addsById[m.targetUniqueName].push(m);
1678
+ }
1679
+ if (m.type === "TREE_DELETE" && m.targetUniqueName) {
1680
+ deletesById[m.targetUniqueName] = deletesById[m.targetUniqueName] ?? [];
1681
+ deletesById[m.targetUniqueName].push(m);
1682
+ }
1683
+ }
1684
+ const result = [];
1685
+ const usedAdds = /* @__PURE__ */ new Set();
1686
+ const usedDeletes = /* @__PURE__ */ new Set();
1687
+ for (const [id, dels] of Object.entries(deletesById)) {
1688
+ const adds = addsById[id];
1689
+ if (!adds || adds.length === 0) continue;
1690
+ const del = dels[0];
1691
+ const add = adds[0];
1692
+ usedAdds.add(add);
1693
+ usedDeletes.add(del);
1694
+ const sameParent = add.parentUniqueNameAfter && del.parentUniqueNameBefore && add.parentUniqueNameAfter === del.parentUniqueNameBefore;
1695
+ const type = sameParent ? "TREE_MOVE_SAME_LEVEL" : "TREE_MOVE_CROSS_LEVEL";
1696
+ const move = {
1697
+ type,
1698
+ path: add.path,
1699
+ pathBefore: del.path,
1700
+ targetUniqueName: id,
1701
+ parentUniqueNameBefore: del.parentUniqueNameBefore,
1702
+ parentUniqueNameAfter: add.parentUniqueNameAfter
1703
+ };
1704
+ result.push(move);
1705
+ }
1706
+ for (const m of mutations) {
1707
+ if (usedAdds.has(m) || usedDeletes.has(m)) continue;
1708
+ result.push(m);
1709
+ }
1710
+ const filtered = [];
1711
+ const seenMoveKeys = /* @__PURE__ */ new Set();
1712
+ for (const m of result) {
1713
+ if ((m.type === "TREE_MOVE_SAME_LEVEL" || m.type === "TREE_MOVE_CROSS_LEVEL") && m.targetUniqueName) {
1714
+ const key = `${m.type}:${m.targetUniqueName}:${JSON.stringify(m.path)}:${JSON.stringify(m.pathBefore)}`;
1715
+ if (seenMoveKeys.has(key)) continue;
1716
+ seenMoveKeys.add(key);
1717
+ }
1718
+ filtered.push(m);
1719
+ }
1720
+ return filtered;
1721
+ }
1722
+
1723
+ // src/loader/index.ts
1724
+ var SYSTEM_FIELDS = {
1725
+ proto: "proto",
1726
+ extendType: "extendType",
1727
+ exportFlag: "export",
1728
+ remove: "remove",
1729
+ name: "name",
1730
+ id: "id"
1731
+ };
1732
+ function loadFromString(input) {
1733
+ const parsed = parseXnl(input);
1734
+ const ctx = buildPrototypeContext(parsed.nodes);
1735
+ const resolvedNodes = parsed.nodes.map((n) => isDataElement3(n) ? resolveNode(ctx, n, []) : n);
1736
+ return { nodes: resolvedNodes, warnings: parsed.warnings };
1737
+ }
1738
+ function resolveNode(ctx, node, scope) {
1739
+ const protoName = readStringMeta(node.metadata, SYSTEM_FIELDS.proto);
1740
+ const extendType = readStringMeta(node.metadata, SYSTEM_FIELDS.extendType) ?? "Override";
1741
+ const localPrefabs = collectTypedPrefabs(node);
1742
+ const nextScope = [localPrefabs, ...scope];
1743
+ let resolved = cloneDataElement(node);
1744
+ if (protoName) {
1745
+ const proto = lookupPrototype(ctx, node.tag, protoName, nextScope);
1746
+ const merged = mergeNodes(ctx, proto, resolved, extendType, nextScope);
1747
+ resolved = merged;
1748
+ } else {
1749
+ resolved = resolveChildren(ctx, resolved, nextScope);
1750
+ }
1751
+ resolved.metadata = mergeMaps(ctx, {}, resolved.metadata, nextScope);
1752
+ resolved.attributes = mergeMaps(ctx, {}, resolved.attributes ?? {}, nextScope);
1753
+ stripControlMetadata(resolved.metadata);
1754
+ if (resolved.attributes) stripControlMetadata(resolved.attributes);
1755
+ return resolved;
1756
+ }
1757
+ function batchLoad(batches) {
1758
+ const allNodes = batches.flat();
1759
+ const ctx = buildPrototypeContext(allNodes);
1760
+ const exportsMap = {};
1761
+ const resolved = batches.map(
1762
+ (batch) => batch.map((node) => {
1763
+ if (!isDataElement3(node)) return node;
1764
+ const rawExportName = readExportName(node);
1765
+ let resolvedNode = resolveNode(ctx, node, []);
1766
+ resolvedNode = resolvePending(resolvedNode, ctx, []);
1767
+ const exportName = rawExportName ?? readExportName(resolvedNode);
1768
+ if (exportName) {
1769
+ exportsMap[resolvedNode.tag] = exportsMap[resolvedNode.tag] ?? {};
1770
+ exportsMap[resolvedNode.tag][exportName] = resolvedNode;
1771
+ }
1772
+ collectExportsFromPrefabs(resolvedNode, exportsMap);
1773
+ return resolvedNode;
1774
+ })
1775
+ );
1776
+ return { resolved, exports: exportsMap };
1777
+ }
1778
+ function buildPrototypeContext(nodes) {
1779
+ const prototypes = {};
1780
+ for (const node of nodes) {
1781
+ if (!isDataElement3(node)) continue;
1782
+ collectPrefabs(node, prototypes);
1783
+ }
1784
+ return { prototypes };
1785
+ }
1786
+ function collectPrefabs(node, store) {
1787
+ const typed = collectTypedPrefabs(node);
1788
+ for (const [type, prefabs] of Object.entries(typed)) {
1789
+ store[type] = store[type] ?? {};
1790
+ for (const [name, prefab] of Object.entries(prefabs)) {
1791
+ if (!store[type][name]) {
1792
+ store[type][name] = cloneDataElement(prefab);
1793
+ }
1794
+ }
1795
+ }
1796
+ if (node.body) {
1797
+ for (const child of node.body) {
1798
+ if (isDataElement3(child)) collectPrefabs(child, store);
1799
+ }
1800
+ }
1801
+ if (node.extend) {
1802
+ for (const tag of node.extend.order) {
1803
+ const child = node.extend.children[tag];
1804
+ if (isDataElement3(child)) collectPrefabs(child, store);
1805
+ }
1806
+ }
1807
+ }
1808
+ function collectExportsFromPrefabs(node, exportsMap) {
1809
+ if (!node.extend) return;
1810
+ const typed = collectTypedPrefabs(node);
1811
+ for (const [type, prefabs] of Object.entries(typed)) {
1812
+ for (const [name, prefab] of Object.entries(prefabs)) {
1813
+ exportsMap[type] = exportsMap[type] ?? {};
1814
+ exportsMap[type][name] = prefab;
1815
+ }
1816
+ }
1817
+ }
1818
+ function lookupPrototype(ctx, type, name, scope) {
1819
+ for (const prefabs of scope) {
1820
+ const foundScoped = prefabs[type]?.[name];
1821
+ if (foundScoped) return foundScoped;
1822
+ }
1823
+ const found = ctx.prototypes[type]?.[name];
1824
+ if (!found) {
1825
+ throw new Error(`Prototype not found for type '${type}' name '${name}'`);
1826
+ }
1827
+ return found;
1828
+ }
1829
+ function mergeNodes(ctx, base, override, extendType, scope) {
1830
+ if (extendType !== "Override") {
1831
+ throw new Error(`Unsupported extendType '${extendType}'`);
1832
+ }
1833
+ const merged = cloneDataElement(base);
1834
+ merged.id = override.id ?? base.id;
1835
+ merged.metadata = mergeMaps(ctx, base.metadata, override.metadata, scope);
1836
+ merged.attributes = mergeMaps(ctx, base.attributes ?? {}, override.attributes ?? {}, scope);
1837
+ merged.body = mergeBody(ctx, base.body ?? [], override.body ?? [], scope);
1838
+ merged.extend = mergeExtend(ctx, base.extend, override.extend, scope);
1839
+ stripControlMetadata(merged.metadata);
1840
+ if (merged.attributes) stripControlMetadata(merged.attributes);
1841
+ return merged;
1842
+ }
1843
+ function resolveChildren(ctx, node, scope) {
1844
+ const copy = cloneDataElement(node);
1845
+ if (copy.body) {
1846
+ copy.body = copy.body.map((item) => resolveValue(ctx, item, scope));
1847
+ }
1848
+ if (copy.extend) {
1849
+ const nextChildren = {};
1850
+ for (const tag of copy.extend.order) {
1851
+ const child = copy.extend.children[tag];
1852
+ nextChildren[tag] = resolveValue(ctx, child, scope);
1853
+ }
1854
+ copy.extend = { order: [...copy.extend.order], children: nextChildren };
1855
+ }
1856
+ return copy;
1857
+ }
1858
+ function mergeBody(ctx, baseBody, overrideBody, scope) {
1859
+ const result = [];
1860
+ const baseById = {};
1861
+ for (const item of baseBody) {
1862
+ const id = readElementId(item);
1863
+ if (id) baseById[id] = item;
1864
+ result.push(resolveValue(ctx, item, scope));
1865
+ }
1866
+ for (const item of overrideBody) {
1867
+ if (isRemoveFlag(item) || isRemoveMarker(item)) {
1868
+ const id2 = readElementId(item);
1869
+ if (id2 && baseById[id2]) {
1870
+ const index = result.findIndex((n) => readElementId(n) === id2);
1871
+ if (index >= 0) {
1872
+ result.splice(index, 1);
1873
+ }
1874
+ }
1875
+ continue;
1876
+ }
1877
+ const id = readElementId(item);
1878
+ if (id && baseById[id] && isDataElement3(baseById[id]) && isDataElement3(item)) {
1879
+ const merged = mergeNodes(ctx, baseById[id], item, "Override", scope);
1880
+ const idx = result.findIndex((n) => readElementId(n) === id);
1881
+ if (idx >= 0) {
1882
+ result[idx] = merged;
1883
+ continue;
1884
+ }
1885
+ }
1886
+ result.push(resolveValue(ctx, item, scope));
1887
+ }
1888
+ return result;
1889
+ }
1890
+ function mergeExtend(ctx, baseExtend, overrideExtend, scope) {
1891
+ if (!baseExtend && !overrideExtend) return void 0;
1892
+ if (!baseExtend) return resolveExtend(ctx, overrideExtend, scope);
1893
+ if (!overrideExtend) return resolveExtend(ctx, baseExtend, scope);
1894
+ const order = [...baseExtend.order];
1895
+ const children = { ...baseExtend.children };
1896
+ for (const tag of overrideExtend.order) {
1897
+ const child = overrideExtend.children[tag];
1898
+ const existing = children[tag];
1899
+ if (isDataElement3(child) && (isRemoveFlag(child) || isRemoveMarker(child))) {
1900
+ delete children[tag];
1901
+ const idx = order.indexOf(tag);
1902
+ if (idx >= 0) order.splice(idx, 1);
1903
+ continue;
1904
+ }
1905
+ if (existing && isDataElement3(existing) && isDataElement3(child)) {
1906
+ children[tag] = mergeNodes(ctx, existing, child, "Override", scope);
1907
+ } else {
1908
+ children[tag] = resolveValue(ctx, child, scope);
1909
+ }
1910
+ if (!order.includes(tag)) order.push(tag);
1911
+ }
1912
+ return { order, children };
1913
+ }
1914
+ function resolveExtend(ctx, extend, scope) {
1915
+ if (!extend) return void 0;
1916
+ const children = {};
1917
+ for (const tag of extend.order) {
1918
+ const child = extend.children[tag];
1919
+ children[tag] = resolveValue(ctx, child, scope);
1920
+ }
1921
+ return { order: [...extend.order], children };
1922
+ }
1923
+ function cloneDataElement(node) {
1924
+ return {
1925
+ kind: "DataElement",
1926
+ tag: node.tag,
1927
+ id: node.id ? cloneWord(node.id) : void 0,
1928
+ metadata: cloneMap(node.metadata),
1929
+ attributes: node.attributes ? cloneMap(node.attributes) : void 0,
1930
+ body: node.body ? node.body.map((n) => cloneNode(n)) : void 0,
1931
+ extend: node.extend ? cloneExtend(node.extend) : void 0
1932
+ };
1933
+ }
1934
+ function cloneExtend(extend) {
1935
+ const children = {};
1936
+ for (const tag of extend.order) {
1937
+ children[tag] = cloneNode(extend.children[tag]);
1938
+ }
1939
+ return { order: [...extend.order], children };
1940
+ }
1941
+ function cloneNode(node) {
1942
+ if (Array.isArray(node)) {
1943
+ return node.map((n) => cloneNode(n));
1944
+ }
1945
+ if (isWord(node)) {
1946
+ return cloneWord(node);
1947
+ }
1948
+ if (isPlainObject4(node)) {
1949
+ const out = {};
1950
+ for (const key of Object.keys(node)) {
1951
+ out[key] = cloneNode(node[key]);
1952
+ }
1953
+ return out;
1954
+ }
1955
+ if (isDataElement3(node)) return cloneDataElement(node);
1956
+ return node;
1957
+ }
1958
+ function cloneMap(map) {
1959
+ const out = {};
1960
+ for (const key of Object.keys(map || {})) {
1961
+ out[key] = cloneNode(map[key]);
1962
+ }
1963
+ return out;
1964
+ }
1965
+ function mergeMaps(ctx, base, override, scope) {
1966
+ const result = cloneMap(base || {});
1967
+ for (const key of Object.keys(override || {})) {
1968
+ const value = override[key];
1969
+ if (isRemoveMarker(value)) {
1970
+ delete result[key];
1971
+ continue;
1972
+ }
1973
+ const resolvedValue = resolveValue(ctx, value, scope);
1974
+ if (isPlainObject4(resolvedValue) && isPlainObject4(result[key])) {
1975
+ result[key] = mergeMaps(ctx, result[key], resolvedValue, scope);
1976
+ } else if (Array.isArray(resolvedValue) && Array.isArray(result[key])) {
1977
+ result[key] = mergeArray(ctx, result[key], resolvedValue, scope);
1978
+ } else {
1979
+ result[key] = resolvedValue;
1980
+ }
1981
+ }
1982
+ return result;
1983
+ }
1984
+ function mergeArray(ctx, baseArr, overrideArr, scope) {
1985
+ const result = baseArr.map((v) => resolveValue(ctx, v, scope));
1986
+ for (let i = 0; i < overrideArr.length; i++) {
1987
+ const value = overrideArr[i];
1988
+ if (isRemoveMarker(value)) {
1989
+ if (i < result.length) result.splice(i, 1);
1990
+ continue;
1991
+ }
1992
+ const resolved = resolveValue(ctx, value, scope);
1993
+ if (i < result.length) {
1994
+ const baseVal = result[i];
1995
+ if (isPlainObject4(baseVal) && isPlainObject4(resolved)) {
1996
+ result[i] = mergeMaps(ctx, baseVal, resolved, scope);
1997
+ } else if (Array.isArray(baseVal) && Array.isArray(resolved)) {
1998
+ result[i] = mergeArray(ctx, baseVal, resolved, scope);
1999
+ } else {
2000
+ result[i] = resolved;
2001
+ }
2002
+ } else {
2003
+ result.push(resolved);
2004
+ }
2005
+ }
2006
+ return result;
2007
+ }
2008
+ function stripControlMetadata(meta) {
2009
+ delete meta[SYSTEM_FIELDS.proto];
2010
+ delete meta[SYSTEM_FIELDS.extendType];
2011
+ delete meta[SYSTEM_FIELDS.exportFlag];
2012
+ delete meta[SYSTEM_FIELDS.remove];
2013
+ }
2014
+ function readExportName(node) {
2015
+ const exported = node.metadata?.[SYSTEM_FIELDS.exportFlag];
2016
+ if (exported !== true && exported !== "true") return void 0;
2017
+ const id = wordToString(node.id);
2018
+ if (id) return id;
2019
+ const nameVal = node.metadata?.[SYSTEM_FIELDS.name];
2020
+ const name = asString(nameVal);
2021
+ return name;
2022
+ }
2023
+ function readElementId(node) {
2024
+ if (isDataElement3(node) || isTextElement3(node)) {
2025
+ const idVal = node.id;
2026
+ const id = wordToString(idVal);
2027
+ if (id) return id;
2028
+ const metaId = node.metadata?.[SYSTEM_FIELDS.id];
2029
+ const metaIdStr = asString(metaId);
2030
+ if (metaIdStr) return metaIdStr;
2031
+ }
2032
+ return void 0;
2033
+ }
2034
+ function readStringMeta(meta, key) {
2035
+ const val = meta?.[key];
2036
+ return asString(val);
2037
+ }
2038
+ function isRemoveFlag(node) {
2039
+ return !!(node && (node.metadata?.[SYSTEM_FIELDS.remove] === true || node.metadata?.[SYSTEM_FIELDS.remove] === "true"));
2040
+ }
2041
+ function isRemoveMarker(node) {
2042
+ return isDataElement3(node) && node.tag === "delta" && isRemoveFlag(node);
2043
+ }
2044
+ function isDataElement3(node) {
2045
+ return node && node.kind === "DataElement";
2046
+ }
2047
+ function isTextElement3(node) {
2048
+ return node && node.kind === "TextElement";
2049
+ }
2050
+ function isPlainObject4(value) {
2051
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !isDataElement3(value) && !isWord(value);
2052
+ }
2053
+ function resolveValue(ctx, value, scope) {
2054
+ if (isDataElement3(value)) {
2055
+ const proto = readStringMeta(value.metadata, SYSTEM_FIELDS.proto);
2056
+ if (proto) {
2057
+ return resolveNode(ctx, value, scope);
2058
+ }
2059
+ const resolved = resolveChildren(ctx, value, scope);
2060
+ resolved.attributes = mergeMaps(ctx, {}, resolved.attributes ?? {}, scope);
2061
+ resolved.metadata = mergeMaps(ctx, {}, resolved.metadata ?? {}, scope);
2062
+ return resolved;
2063
+ }
2064
+ if (Array.isArray(value)) {
2065
+ return value.map((v) => resolveValue(ctx, v, scope));
2066
+ }
2067
+ if (isPlainObject4(value)) {
2068
+ const out = {};
2069
+ for (const key of Object.keys(value)) {
2070
+ out[key] = resolveValue(ctx, value[key], scope);
2071
+ }
2072
+ return out;
2073
+ }
2074
+ return cloneNode(value);
2075
+ }
2076
+ function collectTypedPrefabs(node) {
2077
+ const result = {};
2078
+ if (!node.extend) return result;
2079
+ const suffix = "Prefabs";
2080
+ for (const tag of node.extend.order) {
2081
+ const child = node.extend.children[tag];
2082
+ if (!isDataElement3(child) || !child.body) continue;
2083
+ if (tag === "Prefabs") {
2084
+ for (const prefab of child.body) {
2085
+ if (!isDataElement3(prefab)) continue;
2086
+ const name = readExportName(prefab) ?? readElementId(prefab);
2087
+ if (!name) continue;
2088
+ const type = prefab.tag;
2089
+ result[type] = result[type] ?? {};
2090
+ result[type][name] = cloneDataElement(prefab);
2091
+ }
2092
+ continue;
2093
+ }
2094
+ if (tag.endsWith(suffix)) {
2095
+ const type = tag.slice(0, tag.length - suffix.length);
2096
+ for (const prefab of child.body) {
2097
+ if (!isDataElement3(prefab)) continue;
2098
+ const name = readExportName(prefab) ?? readElementId(prefab);
2099
+ if (!name) continue;
2100
+ result[type] = result[type] ?? {};
2101
+ result[type][name] = cloneDataElement(prefab);
2102
+ }
2103
+ }
2104
+ }
2105
+ return result;
2106
+ }
2107
+ function resolvePending(node, ctx, scope) {
2108
+ const resolved = resolveNode(ctx, node, scope);
2109
+ return resolved;
2110
+ }
2111
+ function cloneWord(word) {
2112
+ return { kind: "Word", namespace: [...word.namespace ?? []], name: word.name };
2113
+ }
2114
+ function asString(value) {
2115
+ if (isWord(value)) return wordToString(value) ?? void 0;
2116
+ if (typeof value === "string") return value;
2117
+ return void 0;
2118
+ }
2119
+
2120
+ // src/NodeHelper.ts
2121
+ function GetWordFullName(word) {
2122
+ const parts = [...word.namespace ?? [], word.name].filter((part) => part.length > 0);
2123
+ return parts.join(".");
2124
+ }
2125
+ function MakeWord(wordStr, namespace = []) {
2126
+ return {
2127
+ kind: "Word",
2128
+ namespace,
2129
+ name: wordStr
2130
+ };
2131
+ }
2132
+
2133
+ // src/index.ts
2134
+ var XNL = {
2135
+ parseMany: parseXnl,
2136
+ parseSingle: parseXnlSingleNode,
2137
+ parseUnique: parseUniqueChildren,
2138
+ stringify,
2139
+ path: {
2140
+ parse: parsePath,
2141
+ resolve: resolvePath,
2142
+ set: setPathValue,
2143
+ delete: deleteAtPath
2144
+ },
2145
+ mutation: {
2146
+ apply: applyMutations,
2147
+ diff: diffNodes
2148
+ },
2149
+ loader: {
2150
+ loadFromString,
2151
+ loadNode: resolveNode,
2152
+ batchLoad
2153
+ }
2154
+ };
2155
+
2156
+ exports.GetWordFullName = GetWordFullName;
2157
+ exports.MakeWord = MakeWord;
2158
+ exports.XNL = XNL;
2159
+ exports.XnlParseError = XnlParseError;
2160
+ exports.XnlPathError = XnlPathError;
2161
+ exports.applyMutations = applyMutations;
2162
+ exports.batchLoad = batchLoad;
2163
+ exports.deleteAtPath = deleteAtPath;
2164
+ exports.diffNodes = diffNodes;
2165
+ exports.isWord = isWord;
2166
+ exports.loadFromString = loadFromString;
2167
+ exports.loadNode = resolveNode;
2168
+ exports.parsePath = parsePath;
2169
+ exports.parseUniqueChildren = parseUniqueChildren;
2170
+ exports.parseXnl = parseXnl;
2171
+ exports.parseXnlSingleNode = parseXnlSingleNode;
2172
+ exports.resolvePath = resolvePath;
2173
+ exports.setPathValue = setPathValue;
2174
+ exports.wordToString = wordToString;
2175
+ //# sourceMappingURL=index.cjs.map
2176
+ //# sourceMappingURL=index.cjs.map