xml-model 2.0.0-beta.3 → 2.0.0-beta.5
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.js +2 -2
- package/dist/node_modules/sax/lib/sax.js +69 -6
- package/dist/util/zod.js +2 -0
- package/dist/xml/codec.d.ts +33 -6
- package/dist/xml/codec.js +39 -20
- package/dist/xml/index.d.ts +2 -2
- package/dist/xml/model.d.ts +32 -0
- package/dist/xml/model.js +25 -5
- package/dist/xml/schema-meta.d.ts +2 -2
- package/dist/xml/schema-meta.js +43 -12
- package/dist/xml/xml-js.d.ts +7 -22
- package/dist/xml/xml-js.js +14 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,6 +2,6 @@ import { DATA, isModel, model } from "./model.js";
|
|
|
2
2
|
import XML, { ZXMLCommentNode, ZXMLElementNode, ZXMLNode, ZXMLRoot, ZXMLTextNode } from "./xml/xml-js.js";
|
|
3
3
|
import { xml } from "./xml/schema-meta.js";
|
|
4
4
|
import { normalizeCodecOptions, registerDefault } from "./xml/codec.js";
|
|
5
|
-
import { xmlModel } from "./xml/model.js";
|
|
5
|
+
import { XMLBase, XMLBaseWithSource, xmlModel } from "./xml/model.js";
|
|
6
6
|
import "./xml/index.js";
|
|
7
|
-
export { DATA, XML, ZXMLCommentNode, ZXMLElementNode, ZXMLNode, ZXMLRoot, ZXMLTextNode, isModel, model, normalizeCodecOptions, registerDefault, xml, xmlModel };
|
|
7
|
+
export { DATA, XML, XMLBase, XMLBaseWithSource, ZXMLCommentNode, ZXMLElementNode, ZXMLNode, ZXMLRoot, ZXMLTextNode, isModel, model, normalizeCodecOptions, registerDefault, xml, xmlModel };
|
|
@@ -49,6 +49,7 @@ var require_sax = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
49
49
|
clearBuffers(parser);
|
|
50
50
|
parser.q = parser.c = "";
|
|
51
51
|
parser.bufferCheckPosition = sax.MAX_BUFFER_LENGTH;
|
|
52
|
+
parser.encoding = null;
|
|
52
53
|
parser.opt = opt || {};
|
|
53
54
|
parser.opt.lowercase = parser.opt.lowercase || parser.opt.lowercasetags;
|
|
54
55
|
parser.looseCase = parser.opt.lowercase ? "toLowerCase" : "toUpperCase";
|
|
@@ -146,6 +147,19 @@ var require_sax = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
146
147
|
function createStream(strict, opt) {
|
|
147
148
|
return new SAXStream(strict, opt);
|
|
148
149
|
}
|
|
150
|
+
function determineBufferEncoding(data, isEnd) {
|
|
151
|
+
if (data.length >= 2) {
|
|
152
|
+
if (data[0] === 255 && data[1] === 254) return "utf-16le";
|
|
153
|
+
if (data[0] === 254 && data[1] === 255) return "utf-16be";
|
|
154
|
+
}
|
|
155
|
+
if (data.length >= 3 && data[0] === 239 && data[1] === 187 && data[2] === 191) return "utf8";
|
|
156
|
+
if (data.length >= 4) {
|
|
157
|
+
if (data[0] === 60 && data[1] === 0 && data[2] === 63 && data[3] === 0) return "utf-16le";
|
|
158
|
+
if (data[0] === 0 && data[1] === 60 && data[2] === 0 && data[3] === 63) return "utf-16be";
|
|
159
|
+
return "utf8";
|
|
160
|
+
}
|
|
161
|
+
return isEnd ? "utf8" : null;
|
|
162
|
+
}
|
|
149
163
|
function SAXStream(strict, opt) {
|
|
150
164
|
if (!(this instanceof SAXStream)) return new SAXStream(strict, opt);
|
|
151
165
|
Stream.apply(this);
|
|
@@ -161,6 +175,7 @@ var require_sax = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
161
175
|
me._parser.error = null;
|
|
162
176
|
};
|
|
163
177
|
this._decoder = null;
|
|
178
|
+
this._decoderBuffer = null;
|
|
164
179
|
streamWraps.forEach(function(ev) {
|
|
165
180
|
Object.defineProperty(me, "on" + ev, {
|
|
166
181
|
get: function() {
|
|
@@ -180,10 +195,30 @@ var require_sax = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
180
195
|
});
|
|
181
196
|
}
|
|
182
197
|
SAXStream.prototype = Object.create(Stream.prototype, { constructor: { value: SAXStream } });
|
|
198
|
+
SAXStream.prototype._decodeBuffer = function(data, isEnd) {
|
|
199
|
+
if (this._decoderBuffer) {
|
|
200
|
+
data = Buffer.concat([this._decoderBuffer, data]);
|
|
201
|
+
this._decoderBuffer = null;
|
|
202
|
+
}
|
|
203
|
+
if (!this._decoder) {
|
|
204
|
+
var encoding = determineBufferEncoding(data, isEnd);
|
|
205
|
+
if (!encoding) {
|
|
206
|
+
this._decoderBuffer = data;
|
|
207
|
+
return "";
|
|
208
|
+
}
|
|
209
|
+
this._parser.encoding = encoding;
|
|
210
|
+
this._decoder = new TextDecoder(encoding);
|
|
211
|
+
}
|
|
212
|
+
return this._decoder.decode(data, { stream: !isEnd });
|
|
213
|
+
};
|
|
183
214
|
SAXStream.prototype.write = function(data) {
|
|
184
|
-
if (typeof Buffer === "function" && typeof Buffer.isBuffer === "function" && Buffer.isBuffer(data))
|
|
185
|
-
|
|
186
|
-
|
|
215
|
+
if (typeof Buffer === "function" && typeof Buffer.isBuffer === "function" && Buffer.isBuffer(data)) data = this._decodeBuffer(data, false);
|
|
216
|
+
else if (this._decoderBuffer) {
|
|
217
|
+
var remaining = this._decodeBuffer(Buffer.alloc(0), true);
|
|
218
|
+
if (remaining) {
|
|
219
|
+
this._parser.write(remaining);
|
|
220
|
+
this.emit("data", remaining);
|
|
221
|
+
}
|
|
187
222
|
}
|
|
188
223
|
this._parser.write(data.toString());
|
|
189
224
|
this.emit("data", data);
|
|
@@ -191,7 +226,13 @@ var require_sax = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
191
226
|
};
|
|
192
227
|
SAXStream.prototype.end = function(chunk) {
|
|
193
228
|
if (chunk && chunk.length) this.write(chunk);
|
|
194
|
-
if (this.
|
|
229
|
+
if (this._decoderBuffer) {
|
|
230
|
+
var finalChunk = this._decodeBuffer(Buffer.alloc(0), true);
|
|
231
|
+
if (finalChunk) {
|
|
232
|
+
this._parser.write(finalChunk);
|
|
233
|
+
this.emit("data", finalChunk);
|
|
234
|
+
}
|
|
235
|
+
} else if (this._decoder) {
|
|
195
236
|
var remaining = this._decoder.decode();
|
|
196
237
|
if (remaining) {
|
|
197
238
|
this._parser.write(remaining);
|
|
@@ -548,6 +589,26 @@ var require_sax = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
548
589
|
function emit(parser, event, data) {
|
|
549
590
|
parser[event] && parser[event](data);
|
|
550
591
|
}
|
|
592
|
+
function getDeclaredEncoding(body) {
|
|
593
|
+
var match = body && body.match(/(?:^|\s)encoding\s*=\s*(['"])([^'"]+)\1/i);
|
|
594
|
+
return match ? match[2] : null;
|
|
595
|
+
}
|
|
596
|
+
function normalizeEncodingName(encoding) {
|
|
597
|
+
if (!encoding) return null;
|
|
598
|
+
return encoding.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
599
|
+
}
|
|
600
|
+
function encodingsMatch(detectedEncoding, declaredEncoding) {
|
|
601
|
+
const detected = normalizeEncodingName(detectedEncoding);
|
|
602
|
+
const declared = normalizeEncodingName(declaredEncoding);
|
|
603
|
+
if (!detected || !declared) return true;
|
|
604
|
+
if (declared === "utf16") return detected === "utf16le" || detected === "utf16be";
|
|
605
|
+
return detected === declared;
|
|
606
|
+
}
|
|
607
|
+
function validateXmlDeclarationEncoding(parser, data) {
|
|
608
|
+
if (!parser.strict || !parser.encoding || !data || data.name !== "xml") return;
|
|
609
|
+
var declaredEncoding = getDeclaredEncoding(data.body);
|
|
610
|
+
if (declaredEncoding && !encodingsMatch(parser.encoding, declaredEncoding)) strictFail(parser, "XML declaration encoding " + declaredEncoding + " does not match detected stream encoding " + parser.encoding.toUpperCase());
|
|
611
|
+
}
|
|
551
612
|
function emitNode(parser, nodeType, data) {
|
|
552
613
|
if (parser.textNode) closeText(parser);
|
|
553
614
|
emit(parser, nodeType, data);
|
|
@@ -1014,10 +1075,12 @@ var require_sax = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
1014
1075
|
continue;
|
|
1015
1076
|
case S.PROC_INST_ENDING:
|
|
1016
1077
|
if (c === ">") {
|
|
1017
|
-
|
|
1078
|
+
const procInstEndData = {
|
|
1018
1079
|
name: parser.procInstName,
|
|
1019
1080
|
body: parser.procInstBody
|
|
1020
|
-
}
|
|
1081
|
+
};
|
|
1082
|
+
validateXmlDeclarationEncoding(parser, procInstEndData);
|
|
1083
|
+
emitNode(parser, "onprocessinginstruction", procInstEndData);
|
|
1021
1084
|
parser.procInstName = parser.procInstBody = "";
|
|
1022
1085
|
parser.state = S.TEXT;
|
|
1023
1086
|
} else {
|
package/dist/util/zod.js
CHANGED
|
@@ -9,6 +9,8 @@ function getParentSchema(schema) {
|
|
|
9
9
|
if (isZodType(schema.def.in)) parent = schema.def.in;
|
|
10
10
|
} else if (schema instanceof z.ZodOptional) {
|
|
11
11
|
if (isZodType(schema.def.innerType)) parent = schema.def.innerType;
|
|
12
|
+
} else if (schema instanceof z.ZodDefault) {
|
|
13
|
+
if (isZodType(schema.def.innerType)) parent = schema.def.innerType;
|
|
12
14
|
} else if (schema instanceof z.ZodLazy) {
|
|
13
15
|
const value = schema.def.getter();
|
|
14
16
|
if (isZodType(value)) parent = value;
|
package/dist/xml/codec.d.ts
CHANGED
|
@@ -32,8 +32,8 @@ export interface CodecOptions<S extends z.ZodType> {
|
|
|
32
32
|
*/
|
|
33
33
|
export type UserCodecOptions<S extends z.ZodType = z.ZodType> = {
|
|
34
34
|
tagname?: string | CodecOptions<S>["tagname"];
|
|
35
|
-
decode?:
|
|
36
|
-
encode?:
|
|
35
|
+
decode?: (ctx: RootDecodingContext<S>, next: () => z.input<S>) => z.input<S>;
|
|
36
|
+
encode?: (ctx: RootEncodingContext<S>, next: () => XMLElement) => XMLElement;
|
|
37
37
|
propertyTagname?: string | CodecOptions<S>["propertyTagname"];
|
|
38
38
|
inlineProperty?: boolean;
|
|
39
39
|
propertyMatch?: RegExp | CodecOptions<S>["propertyMatch"];
|
|
@@ -71,13 +71,40 @@ export declare function normalizeCodecOptions<S extends z.ZodType>(schema: S, op
|
|
|
71
71
|
type OrderEntry = string | XMLElement;
|
|
72
72
|
export interface XMLState {
|
|
73
73
|
/** Preserves element ordering and unknown elements across a decode → encode round-trip. */
|
|
74
|
-
|
|
74
|
+
sequence: OrderEntry[];
|
|
75
|
+
/** Present when xmlStateSchema({ source: true }) is used: the original XMLElement. */
|
|
76
|
+
source?: XMLElement;
|
|
75
77
|
}
|
|
76
78
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
+
* String key used to store XML round-trip state on decoded data objects.
|
|
80
|
+
* Using a string (rather than a Symbol) allows Zod's schema.parse() to
|
|
81
|
+
* preserve it naturally when the key is included in the schema via xmlStateSchema().
|
|
79
82
|
*/
|
|
80
|
-
export declare const
|
|
83
|
+
export declare const XML_STATE_KEY: "__xml_state";
|
|
84
|
+
/**
|
|
85
|
+
* Schema for the XML round-trip state field.
|
|
86
|
+
*
|
|
87
|
+
* Include in your base model schema under `XML_STATE_KEY` to preserve element ordering
|
|
88
|
+
* and unknown elements through Zod's `schema.parse()` for nested model instances.
|
|
89
|
+
*
|
|
90
|
+
* Pass `{ source: true }` to also record the original `XMLElement` on each instance.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* class XMLBase extends xmlModel(z.object({
|
|
94
|
+
* [XML_STATE_KEY]: xmlStateSchema(),
|
|
95
|
+
* }), { tagname: "base" }) {}
|
|
96
|
+
*
|
|
97
|
+
* // With source recording:
|
|
98
|
+
* class XMLBase extends xmlModel(z.object({
|
|
99
|
+
* [XML_STATE_KEY]: xmlStateSchema({ source: true }),
|
|
100
|
+
* }), { tagname: "base" }) {}
|
|
101
|
+
*/
|
|
102
|
+
export declare function xmlStateSchema(): z.ZodOptional<z.ZodCustom<XMLState>>;
|
|
103
|
+
export declare function xmlStateSchema(options: {
|
|
104
|
+
source: true;
|
|
105
|
+
}): z.ZodOptional<z.ZodCustom<XMLState & {
|
|
106
|
+
source: XMLElement;
|
|
107
|
+
}>>;
|
|
81
108
|
export declare function decode<S extends z.ZodType>(schema: S, xml: XMLElement): z.input<S>;
|
|
82
109
|
export declare function encode<S extends z.ZodType>(schema: S, data: z.output<S>): XMLElement;
|
|
83
110
|
type DefaultResolver<S extends z.ZodType = z.ZodType> = (schema: S) => CodecOptions<S> | void;
|
package/dist/xml/codec.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import XML from "./xml-js.js";
|
|
2
2
|
import { isZodType } from "../util/zod.js";
|
|
3
|
-
import { getUserOptions } from "./schema-meta.js";
|
|
3
|
+
import { getUserOptions, prop } from "./schema-meta.js";
|
|
4
4
|
import { kebabCase } from "../util/kebab-case.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/xml/codec.ts
|
|
@@ -28,8 +28,8 @@ function normalizeCodecOptions(schema, options = {}) {
|
|
|
28
28
|
const result = {
|
|
29
29
|
schema,
|
|
30
30
|
tagname,
|
|
31
|
-
decode: options.decode
|
|
32
|
-
encode: options.encode
|
|
31
|
+
decode: options.decode ? (ctx) => options.decode(ctx, () => defaultOptions().decode(ctx)) : (ctx) => defaultOptions().decode(ctx),
|
|
32
|
+
encode: options.encode ? (ctx) => options.encode(ctx, () => defaultOptions().encode(ctx)) : (ctx) => defaultOptions().encode(ctx),
|
|
33
33
|
propertyTagname,
|
|
34
34
|
inlineProperty,
|
|
35
35
|
propertyMatch,
|
|
@@ -51,7 +51,10 @@ function normalizeCodecOptions(schema, options = {}) {
|
|
|
51
51
|
data: property.value
|
|
52
52
|
});
|
|
53
53
|
if (XML.isEmpty(res)) return;
|
|
54
|
-
if (property.options.inlineProperty) ctx.result.elements.push(...res.elements)
|
|
54
|
+
if (property.options.inlineProperty) ctx.result.elements.push(...res.elements.map((el) => el.type === "element" ? {
|
|
55
|
+
...el,
|
|
56
|
+
name: property.tagname
|
|
57
|
+
} : el));
|
|
55
58
|
else ctx.result.elements.push(res);
|
|
56
59
|
}
|
|
57
60
|
};
|
|
@@ -66,10 +69,19 @@ function resolveCodecOptions(schema) {
|
|
|
66
69
|
return options;
|
|
67
70
|
}
|
|
68
71
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
72
|
+
* String key used to store XML round-trip state on decoded data objects.
|
|
73
|
+
* Using a string (rather than a Symbol) allows Zod's schema.parse() to
|
|
74
|
+
* preserve it naturally when the key is included in the schema via xmlStateSchema().
|
|
71
75
|
*/
|
|
72
|
-
var
|
|
76
|
+
var XML_STATE_KEY = "__xml_state";
|
|
77
|
+
function xmlStateSchema(options) {
|
|
78
|
+
return prop(z.custom().optional(), {
|
|
79
|
+
decode: options?.source ? (ctx, _next) => {
|
|
80
|
+
(ctx.result[XML_STATE_KEY] ??= {}).source = ctx.xml;
|
|
81
|
+
} : () => {},
|
|
82
|
+
encode: () => {}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
73
85
|
function resolvePropertiesCodecOptions(schema) {
|
|
74
86
|
const shape = schema.def.shape;
|
|
75
87
|
const options = {};
|
|
@@ -103,7 +115,10 @@ function resolveDefault(schema) {
|
|
|
103
115
|
}
|
|
104
116
|
registerDefault((schema) => {
|
|
105
117
|
if (schema instanceof z.ZodArray) {
|
|
106
|
-
const
|
|
118
|
+
const elSchema = schema.def.element;
|
|
119
|
+
if (!isZodType(elSchema)) throw new Error(`Expected a ZodType, got ${elSchema}`);
|
|
120
|
+
const elOptions = resolveCodecOptions(elSchema);
|
|
121
|
+
const elHasOwnTagname = Boolean(getUserOptions(elSchema).tagname);
|
|
107
122
|
return normalizeCodecOptions(schema, {
|
|
108
123
|
decode(ctx) {
|
|
109
124
|
const { xml } = ctx;
|
|
@@ -116,12 +131,16 @@ registerDefault((schema) => {
|
|
|
116
131
|
encode(ctx) {
|
|
117
132
|
const values = ctx.data;
|
|
118
133
|
if (!Array.isArray(values)) throw new Error("expected array");
|
|
134
|
+
const elOptsForEncode = elHasOwnTagname ? elOptions : {
|
|
135
|
+
...elOptions,
|
|
136
|
+
tagname: ctx.options.tagname
|
|
137
|
+
};
|
|
119
138
|
return {
|
|
120
139
|
type: "element",
|
|
121
140
|
name: ctx.options.tagname(ctx),
|
|
122
141
|
attributes: {},
|
|
123
|
-
elements: values.map((v) =>
|
|
124
|
-
options:
|
|
142
|
+
elements: values.map((v) => elOptsForEncode.encode({
|
|
143
|
+
options: elOptsForEncode,
|
|
125
144
|
data: v
|
|
126
145
|
}))
|
|
127
146
|
};
|
|
@@ -134,12 +153,18 @@ registerDefault((schema) => {
|
|
|
134
153
|
const innerOptions = resolveCodecOptions(inner);
|
|
135
154
|
return normalizeCodecOptions(schema, {
|
|
136
155
|
decode(ctx) {
|
|
137
|
-
if (
|
|
156
|
+
if (ctx.xml === null) return void 0;
|
|
138
157
|
else return innerOptions.decode(ctx);
|
|
139
158
|
},
|
|
140
159
|
encode(ctx) {
|
|
141
160
|
if (typeof ctx.data === "undefined") return {};
|
|
142
161
|
else return innerOptions.encode(ctx);
|
|
162
|
+
},
|
|
163
|
+
decodeAsProperty(ctx) {
|
|
164
|
+
if (ctx.property.xml !== null) innerOptions.decodeAsProperty(ctx);
|
|
165
|
+
},
|
|
166
|
+
encodeAsProperty(ctx) {
|
|
167
|
+
if (typeof ctx.property.value !== "undefined") innerOptions.encodeAsProperty(ctx);
|
|
143
168
|
}
|
|
144
169
|
});
|
|
145
170
|
}
|
|
@@ -238,6 +263,7 @@ registerDefault((schema) => {
|
|
|
238
263
|
return normalizeCodecOptions(schema, {
|
|
239
264
|
decode(ctx) {
|
|
240
265
|
const sequence = [];
|
|
266
|
+
const result = { [XML_STATE_KEY]: { sequence } };
|
|
241
267
|
const propContexts = Object.fromEntries(Object.entries(options).map(([name, propOpts]) => {
|
|
242
268
|
return [name, {
|
|
243
269
|
name,
|
|
@@ -274,7 +300,6 @@ registerDefault((schema) => {
|
|
|
274
300
|
} else throw new Error(`Same element was matched by multiple properties: ${matches.join(", ")}`);
|
|
275
301
|
}
|
|
276
302
|
for (const propName in options) if (!seenProperties.has(propName)) sequence.push(propName);
|
|
277
|
-
const result = {};
|
|
278
303
|
for (const prop in options) {
|
|
279
304
|
const o = options[prop];
|
|
280
305
|
const propCtx = propContexts[prop];
|
|
@@ -293,12 +318,6 @@ registerDefault((schema) => {
|
|
|
293
318
|
result
|
|
294
319
|
});
|
|
295
320
|
}
|
|
296
|
-
Object.defineProperty(result, XML_STATE, {
|
|
297
|
-
value: { fieldOrder: sequence },
|
|
298
|
-
enumerable: false,
|
|
299
|
-
writable: true,
|
|
300
|
-
configurable: true
|
|
301
|
-
});
|
|
302
321
|
return result;
|
|
303
322
|
},
|
|
304
323
|
encode(ctx) {
|
|
@@ -309,7 +328,7 @@ registerDefault((schema) => {
|
|
|
309
328
|
attributes: {},
|
|
310
329
|
elements: []
|
|
311
330
|
};
|
|
312
|
-
const sequence = data[
|
|
331
|
+
const sequence = data["__xml_state"]?.sequence ?? Object.keys(options);
|
|
313
332
|
for (const item of sequence) if (typeof item === "string") {
|
|
314
333
|
const o = options[item];
|
|
315
334
|
if (!o) throw new Error(`Failed to resolve property options for sequence item ${item}`);
|
|
@@ -334,6 +353,6 @@ registerDefault((schema) => {
|
|
|
334
353
|
}
|
|
335
354
|
});
|
|
336
355
|
//#endregion
|
|
337
|
-
export {
|
|
356
|
+
export { XML_STATE_KEY, decode, encode, normalizeCodecOptions, registerDefault, xmlStateSchema };
|
|
338
357
|
|
|
339
358
|
//# sourceMappingURL=codec.js.map
|
package/dist/xml/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './xml-js';
|
|
2
2
|
export { xml } from './schema-meta';
|
|
3
|
-
export type { UserCodecOptions } from './codec';
|
|
3
|
+
export type { UserCodecOptions, XMLState } from './codec';
|
|
4
4
|
export { registerDefault, normalizeCodecOptions } from './codec';
|
|
5
|
-
export { xmlModel, type XmlModelConstructor } from './model';
|
|
5
|
+
export { xmlModel, XMLBase, XMLBaseWithSource, type XmlModelConstructor } from './model';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/xml/model.d.ts
CHANGED
|
@@ -14,5 +14,37 @@ export type XmlModelConstructor<S extends z.ZodObject<any> = z.ZodObject<any>, I
|
|
|
14
14
|
/** Converts an instance to an XML string. */
|
|
15
15
|
toXMLString(instance: z.infer<S>, options?: StringifyOptions): string;
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Base class for xmlModel classes. Preserves element ordering and unknown elements
|
|
19
|
+
* through `schema.parse()` for nested model instances.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* class Device extends XMLBase.extend(
|
|
23
|
+
* { name: z.string() },
|
|
24
|
+
* xml.root({ tagname: "device" }),
|
|
25
|
+
* ) {}
|
|
26
|
+
*/
|
|
27
|
+
export declare const XMLBase: XmlModelConstructor<z.ZodObject<{
|
|
28
|
+
__xml_state: z.ZodOptional<z.ZodCustom<import('./codec').XMLState, unknown>>;
|
|
29
|
+
}, z.core.$strip>, {
|
|
30
|
+
__xml_state?: import('./codec').XMLState;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Like {@link XMLBase}, but also records the original `XMLElement` as `.source`
|
|
34
|
+
* on each instance's XML state.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const device = Device.fromXML(`<device>…</device>`);
|
|
38
|
+
* device[XML_STATE_KEY]?.source; // XMLElement
|
|
39
|
+
*/
|
|
40
|
+
export declare const XMLBaseWithSource: XmlModelConstructor<z.ZodObject<{
|
|
41
|
+
__xml_state: z.ZodOptional<z.ZodCustom<import('./codec').XMLState & {
|
|
42
|
+
source: XMLElement;
|
|
43
|
+
}, unknown>>;
|
|
44
|
+
}, z.core.$strip>, {
|
|
45
|
+
__xml_state?: import('./codec').XMLState & {
|
|
46
|
+
source: XMLElement;
|
|
47
|
+
};
|
|
48
|
+
}>;
|
|
17
49
|
export declare function xmlModel<S extends z.ZodObject<any>>(schema: S, options?: UserCodecOptions<S>): XmlModelConstructor<S>;
|
|
18
50
|
//# sourceMappingURL=model.d.ts.map
|
package/dist/xml/model.js
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import { model } from "../model.js";
|
|
2
2
|
import XML from "./xml-js.js";
|
|
3
3
|
import { root } from "./schema-meta.js";
|
|
4
|
-
import {
|
|
5
|
-
import "zod";
|
|
4
|
+
import { XML_STATE_KEY, decode, encode, xmlStateSchema } from "./codec.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
6
|
//#region src/xml/model.ts
|
|
7
|
+
/**
|
|
8
|
+
* Base class for xmlModel classes. Preserves element ordering and unknown elements
|
|
9
|
+
* through `schema.parse()` for nested model instances.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* class Device extends XMLBase.extend(
|
|
13
|
+
* { name: z.string() },
|
|
14
|
+
* xml.root({ tagname: "device" }),
|
|
15
|
+
* ) {}
|
|
16
|
+
*/
|
|
17
|
+
var XMLBase = xmlModel(z.object({ [XML_STATE_KEY]: xmlStateSchema() }));
|
|
18
|
+
/**
|
|
19
|
+
* Like {@link XMLBase}, but also records the original `XMLElement` as `.source`
|
|
20
|
+
* on each instance's XML state.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const device = Device.fromXML(`<device>…</device>`);
|
|
24
|
+
* device[XML_STATE_KEY]?.source; // XMLElement
|
|
25
|
+
*/
|
|
26
|
+
var XMLBaseWithSource = xmlModel(z.object({ [XML_STATE_KEY]: xmlStateSchema({ source: true }) }));
|
|
7
27
|
function xmlModel(schema, options) {
|
|
8
28
|
const _schema = options ? root(schema, options) : schema;
|
|
9
29
|
return class extends model(_schema) {
|
|
@@ -12,9 +32,9 @@ function xmlModel(schema, options) {
|
|
|
12
32
|
if (XML.isRoot(input)) input = XML.elementFromRoot(input);
|
|
13
33
|
const schema = this.dataSchema;
|
|
14
34
|
const inputData = decode(this.dataSchema, input);
|
|
15
|
-
const xmlState = inputData[
|
|
35
|
+
const xmlState = inputData[XML_STATE_KEY];
|
|
16
36
|
const parsed = schema.parse(inputData);
|
|
17
|
-
parsed[
|
|
37
|
+
parsed[XML_STATE_KEY] = xmlState;
|
|
18
38
|
return this.fromData(parsed);
|
|
19
39
|
}
|
|
20
40
|
static toXML(instance) {
|
|
@@ -28,6 +48,6 @@ function xmlModel(schema, options) {
|
|
|
28
48
|
};
|
|
29
49
|
}
|
|
30
50
|
//#endregion
|
|
31
|
-
export { xmlModel };
|
|
51
|
+
export { XMLBase, XMLBaseWithSource, xmlModel };
|
|
32
52
|
|
|
33
53
|
//# sourceMappingURL=model.js.map
|
|
@@ -15,8 +15,8 @@ type UserPropOptions = {
|
|
|
15
15
|
tagname?: string | UserCodecOptions["propertyTagname"];
|
|
16
16
|
inline?: boolean;
|
|
17
17
|
match?: RegExp | ((el: XMLElement) => boolean);
|
|
18
|
-
decode?: (ctx: PropertyDecodingContext) =>
|
|
19
|
-
encode?: (ctx: PropertyEncodingContext) =>
|
|
18
|
+
decode?: (ctx: PropertyDecodingContext, next: () => void) => void;
|
|
19
|
+
encode?: (ctx: PropertyEncodingContext, next: () => void) => void;
|
|
20
20
|
};
|
|
21
21
|
export declare function root<S extends z.ZodType>(schema: S, options: UserRootOptions<S>): S;
|
|
22
22
|
export declare function root(options: UserRootOptions): z.GlobalMeta;
|
package/dist/xml/schema-meta.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import XML from "./xml-js.js";
|
|
1
2
|
import { getParentSchema, isZodType } from "../util/zod.js";
|
|
2
3
|
import "zod";
|
|
3
4
|
//#region src/xml/schema-meta.ts
|
|
@@ -19,21 +20,40 @@ function normalizePropOptions(options) {
|
|
|
19
20
|
if (options.decode !== void 0) {
|
|
20
21
|
const userDecode = options.decode;
|
|
21
22
|
partial.decodeAsProperty = function(ctx) {
|
|
22
|
-
const
|
|
23
|
-
|
|
23
|
+
const next = () => {
|
|
24
|
+
const val = ctx.property.options.decode({
|
|
25
|
+
options: ctx.property.options,
|
|
26
|
+
xml: ctx.property.xml
|
|
27
|
+
});
|
|
28
|
+
ctx.result[ctx.property.name] = val;
|
|
29
|
+
};
|
|
30
|
+
userDecode(ctx, next);
|
|
24
31
|
};
|
|
25
32
|
}
|
|
26
33
|
if (options.encode !== void 0) {
|
|
27
34
|
const userEncode = options.encode;
|
|
28
35
|
partial.encodeAsProperty = function(ctx) {
|
|
29
36
|
const { property } = ctx;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
const next = () => {
|
|
38
|
+
const optsWithTagname = {
|
|
39
|
+
...property.options,
|
|
40
|
+
tagname: () => property.tagname
|
|
41
|
+
};
|
|
42
|
+
const res = property.options.encode({
|
|
43
|
+
options: optsWithTagname,
|
|
44
|
+
data: property.value
|
|
45
|
+
});
|
|
46
|
+
if (XML.isEmpty(res)) return;
|
|
47
|
+
if (property.options.inlineProperty) ctx.result.elements.push(...res.elements.map((el) => el.type === "element" ? {
|
|
48
|
+
...el,
|
|
49
|
+
name: property.tagname
|
|
50
|
+
} : el));
|
|
51
|
+
else ctx.result.elements.push({
|
|
52
|
+
...res,
|
|
53
|
+
name: property.tagname
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
userEncode(ctx, next);
|
|
37
57
|
};
|
|
38
58
|
}
|
|
39
59
|
return partial;
|
|
@@ -52,7 +72,7 @@ function attr(optionsOrSchema, options) {
|
|
|
52
72
|
decodeAsProperty(ctx) {
|
|
53
73
|
const { name, options: propOptions } = ctx.property;
|
|
54
74
|
const attrName = opts.name ?? name;
|
|
55
|
-
const attrValue = ctx.xml?.attributes[attrName];
|
|
75
|
+
const attrValue = ctx.xml?.attributes?.[attrName];
|
|
56
76
|
ctx.result[name] = propOptions.schema.parse(attrValue);
|
|
57
77
|
},
|
|
58
78
|
encodeAsProperty(ctx) {
|
|
@@ -73,16 +93,27 @@ var xml = {
|
|
|
73
93
|
function getOwnUserOptions(schema) {
|
|
74
94
|
return schema.meta()?.[metaKey] ?? {};
|
|
75
95
|
}
|
|
96
|
+
var INHERITABLE_KEYS = [
|
|
97
|
+
"tagname",
|
|
98
|
+
"propertyTagname",
|
|
99
|
+
"inlineProperty",
|
|
100
|
+
"propertyMatch",
|
|
101
|
+
"decodeAsProperty",
|
|
102
|
+
"encodeAsProperty"
|
|
103
|
+
];
|
|
76
104
|
function getUserOptions(schema) {
|
|
77
105
|
const own = getOwnUserOptions(schema);
|
|
78
106
|
const parentSchema = getParentSchema(schema);
|
|
79
107
|
if (!parentSchema) return own;
|
|
108
|
+
const parentOptions = getUserOptions(parentSchema);
|
|
109
|
+
const inherited = {};
|
|
110
|
+
for (const key of INHERITABLE_KEYS) if (parentOptions[key] !== void 0 && own[key] === void 0) inherited[key] = parentOptions[key];
|
|
80
111
|
return {
|
|
81
|
-
...
|
|
112
|
+
...inherited,
|
|
82
113
|
...own
|
|
83
114
|
};
|
|
84
115
|
}
|
|
85
116
|
//#endregion
|
|
86
|
-
export { getUserOptions, root, xml };
|
|
117
|
+
export { getUserOptions, prop, root, xml };
|
|
87
118
|
|
|
88
119
|
//# sourceMappingURL=schema-meta.js.map
|
package/dist/xml/xml-js.d.ts
CHANGED
|
@@ -120,21 +120,6 @@ export type ParseOptions = IgnoreOptions & ChangingKeyNames & {
|
|
|
120
120
|
* @default false
|
|
121
121
|
*/
|
|
122
122
|
addParent?: boolean;
|
|
123
|
-
/**
|
|
124
|
-
* Whether to always wrap sub-elements in an array, even when there is only one.
|
|
125
|
-
* Pass an array of element names to restrict this behaviour to those names only.
|
|
126
|
-
* Only applicable in compact mode.
|
|
127
|
-
* @default false
|
|
128
|
-
*/
|
|
129
|
-
alwaysArray?: boolean | Array<string>;
|
|
130
|
-
/**
|
|
131
|
-
* Whether to always emit an `elements` array even on empty elements.
|
|
132
|
-
* `<a></a>` becomes `{ elements: [{ type: "element", name: "a", elements: [] }] }`
|
|
133
|
-
* instead of `{ elements: [{ type: "element", name: "a" }] }`.
|
|
134
|
-
* Only applicable in non-compact mode.
|
|
135
|
-
* @default false
|
|
136
|
-
*/
|
|
137
|
-
alwaysChildren?: boolean;
|
|
138
123
|
/**
|
|
139
124
|
* Whether to parse the contents of processing instructions as attributes.
|
|
140
125
|
* `<?go to="there"?>` becomes `{ go: { attributes: { to: "there" } } }`
|
|
@@ -221,14 +206,8 @@ declare function isEmpty(xml: object): xml is XMLVoid;
|
|
|
221
206
|
*/
|
|
222
207
|
declare function getContent(xml: XMLElement): string;
|
|
223
208
|
/**
|
|
224
|
-
* Creates a
|
|
225
|
-
* When no tag name is provided, returns a fragment with a text child.
|
|
226
|
-
* When a tag name is provided, returns a full element with the given name and optional attributes.
|
|
227
|
-
*
|
|
209
|
+
* Creates a text-only fragment wrapping the given content.
|
|
228
210
|
* @param content - The text content to wrap (defaults to empty string).
|
|
229
|
-
* @param tag - Optional element tag name.
|
|
230
|
-
* @param attributes - Optional attributes; only valid when `tag` is provided.
|
|
231
|
-
* @throws {TypeError} When `attributes` are provided without a `tag`.
|
|
232
211
|
*/
|
|
233
212
|
declare function fromContent(content: string): {
|
|
234
213
|
elements: [{
|
|
@@ -236,6 +215,12 @@ declare function fromContent(content: string): {
|
|
|
236
215
|
text: string;
|
|
237
216
|
}] | [];
|
|
238
217
|
};
|
|
218
|
+
/**
|
|
219
|
+
* Creates a full element wrapping the given text content.
|
|
220
|
+
* @param content - The text content to wrap (defaults to empty string).
|
|
221
|
+
* @param tag - Element tag name.
|
|
222
|
+
* @param attributes - Optional attributes.
|
|
223
|
+
*/
|
|
239
224
|
declare function fromContent(content: string, tag: string, attributes?: XMLElement["attributes"]): {
|
|
240
225
|
type: "element";
|
|
241
226
|
name: string;
|
package/dist/xml/xml-js.js
CHANGED
|
@@ -25,14 +25,22 @@ var ZXMLNode = z.discriminatedUnion("type", [
|
|
|
25
25
|
]);
|
|
26
26
|
var ZXMLRoot = z.object({ elements: z.array(ZXMLNode) });
|
|
27
27
|
function parse(xml, options = {}) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const res = (0, import_lib.xml2js)(xml,
|
|
28
|
+
if ("compact" in options) throw new Error("xml-model always uses non-compact mode (compact is forced to false)");
|
|
29
|
+
if ("alwaysChildren" in options) throw new Error("xml-model always parses with alwaysChildren: true");
|
|
30
|
+
const res = (0, import_lib.xml2js)(xml, {
|
|
31
|
+
...options,
|
|
32
|
+
compact: false,
|
|
33
|
+
alwaysChildren: true
|
|
34
|
+
});
|
|
31
35
|
if ("elements" in res) return res;
|
|
32
36
|
throw new Error("Got empty XML");
|
|
33
37
|
}
|
|
34
38
|
function stringify(xml, options = {}) {
|
|
35
|
-
|
|
39
|
+
if ("compact" in options) throw new Error("xml-model always uses non-compact mode (compact is forced to false)");
|
|
40
|
+
return (0, import_lib.js2xml)(xml, {
|
|
41
|
+
...options,
|
|
42
|
+
compact: false
|
|
43
|
+
});
|
|
36
44
|
}
|
|
37
45
|
function isRoot(xml) {
|
|
38
46
|
return Object.keys(xml).length === 1 && "elements" in xml && Array.isArray(xml.elements);
|
|
@@ -51,11 +59,11 @@ function isEmpty(xml) {
|
|
|
51
59
|
* @throws {TypeError} When the element has multiple or non-text children.
|
|
52
60
|
*/
|
|
53
61
|
function getContent(xml) {
|
|
54
|
-
if (xml.elements
|
|
62
|
+
if (!xml.elements.length) return "";
|
|
63
|
+
if (xml.elements.length === 1) {
|
|
55
64
|
const content = xml.elements[0];
|
|
56
65
|
if (content.type === "text") return content.text;
|
|
57
66
|
}
|
|
58
|
-
if (!xml.elements) return "";
|
|
59
67
|
throw new TypeError(`can't get text from XMLElement: ${JSON.stringify(xml)}`);
|
|
60
68
|
}
|
|
61
69
|
function fromContent(content = "", tag, attributes) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xml-model",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.5",
|
|
4
4
|
"description": "allows transparent XML <-> Object conversion in typescript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "MathisTLD",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"fmt:check": "vp fmt --check",
|
|
35
35
|
"prepare": "vp config"
|
|
36
36
|
},
|
|
37
|
-
"dependencies": {},
|
|
38
37
|
"devDependencies": {
|
|
39
38
|
"@types/node": "^24.10.11",
|
|
40
39
|
"marmotte": "^0.4.4",
|
|
40
|
+
"oxc-minify": "^0.121.0",
|
|
41
41
|
"typescript": "^5.9.3",
|
|
42
42
|
"vite-plus": "latest",
|
|
43
43
|
"vitepress": "^2.0.0-alpha.16",
|