xml-model 2.0.0-beta.4 → 2.0.0-beta.6
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 +3 -3
- package/dist/node_modules/sax/lib/sax.js +69 -6
- package/dist/util/zod.js +2 -0
- package/dist/xml/codec.d.ts +38 -6
- package/dist/xml/codec.js +85 -40
- package/dist/xml/examples.d.ts +13 -0
- package/dist/xml/index.d.ts +3 -3
- package/dist/xml/model.d.ts +32 -0
- package/dist/xml/model.js +24 -9
- package/dist/xml/schema-meta.d.ts +2 -2
- package/dist/xml/schema-meta.js +43 -12
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
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
|
-
import { normalizeCodecOptions, registerDefault } from "./xml/codec.js";
|
|
5
|
-
import { xmlModel } from "./xml/model.js";
|
|
4
|
+
import { XMLCodecError, normalizeCodecOptions, registerDefault } from "./xml/codec.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, XMLCodecError, 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
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { XMLElement } from './xml-js';
|
|
3
|
+
export declare class XMLCodecError extends Error {
|
|
4
|
+
readonly path: readonly (string | number)[];
|
|
5
|
+
readonly rawMessage: string;
|
|
6
|
+
constructor(rawMessage: string, path?: readonly (string | number)[], options?: ErrorOptions);
|
|
7
|
+
}
|
|
3
8
|
export declare function assertSingleElement(xml: XMLElement[]): asserts xml is [XMLElement];
|
|
4
9
|
export declare function assertSingleRoot(xml: XMLElement[]): asserts xml is [XMLElement & {
|
|
5
10
|
elements: XMLElement[];
|
|
@@ -32,8 +37,8 @@ export interface CodecOptions<S extends z.ZodType> {
|
|
|
32
37
|
*/
|
|
33
38
|
export type UserCodecOptions<S extends z.ZodType = z.ZodType> = {
|
|
34
39
|
tagname?: string | CodecOptions<S>["tagname"];
|
|
35
|
-
decode?:
|
|
36
|
-
encode?:
|
|
40
|
+
decode?: (ctx: RootDecodingContext<S>, next: () => z.input<S>) => z.input<S>;
|
|
41
|
+
encode?: (ctx: RootEncodingContext<S>, next: () => XMLElement) => XMLElement;
|
|
37
42
|
propertyTagname?: string | CodecOptions<S>["propertyTagname"];
|
|
38
43
|
inlineProperty?: boolean;
|
|
39
44
|
propertyMatch?: RegExp | CodecOptions<S>["propertyMatch"];
|
|
@@ -71,13 +76,40 @@ export declare function normalizeCodecOptions<S extends z.ZodType>(schema: S, op
|
|
|
71
76
|
type OrderEntry = string | XMLElement;
|
|
72
77
|
export interface XMLState {
|
|
73
78
|
/** Preserves element ordering and unknown elements across a decode → encode round-trip. */
|
|
74
|
-
|
|
79
|
+
sequence: OrderEntry[];
|
|
80
|
+
/** Present when xmlStateSchema({ source: true }) is used: the original XMLElement. */
|
|
81
|
+
source?: XMLElement;
|
|
75
82
|
}
|
|
76
83
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
84
|
+
* String key used to store XML round-trip state on decoded data objects.
|
|
85
|
+
* Using a string (rather than a Symbol) allows Zod's schema.parse() to
|
|
86
|
+
* preserve it naturally when the key is included in the schema via xmlStateSchema().
|
|
87
|
+
*/
|
|
88
|
+
export declare const XML_STATE_KEY: "__xml_state";
|
|
89
|
+
/**
|
|
90
|
+
* Schema for the XML round-trip state field.
|
|
91
|
+
*
|
|
92
|
+
* Include in your base model schema under `XML_STATE_KEY` to preserve element ordering
|
|
93
|
+
* and unknown elements through Zod's `schema.parse()` for nested model instances.
|
|
94
|
+
*
|
|
95
|
+
* Pass `{ source: true }` to also record the original `XMLElement` on each instance.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* class XMLBase extends xmlModel(z.object({
|
|
99
|
+
* [XML_STATE_KEY]: xmlStateSchema(),
|
|
100
|
+
* }), { tagname: "base" }) {}
|
|
101
|
+
*
|
|
102
|
+
* // With source recording:
|
|
103
|
+
* class XMLBase extends xmlModel(z.object({
|
|
104
|
+
* [XML_STATE_KEY]: xmlStateSchema({ source: true }),
|
|
105
|
+
* }), { tagname: "base" }) {}
|
|
79
106
|
*/
|
|
80
|
-
export declare
|
|
107
|
+
export declare function xmlStateSchema(): z.ZodOptional<z.ZodCustom<XMLState>>;
|
|
108
|
+
export declare function xmlStateSchema(options: {
|
|
109
|
+
source: true;
|
|
110
|
+
}): z.ZodOptional<z.ZodCustom<XMLState & {
|
|
111
|
+
source: XMLElement;
|
|
112
|
+
}>>;
|
|
81
113
|
export declare function decode<S extends z.ZodType>(schema: S, xml: XMLElement): z.input<S>;
|
|
82
114
|
export declare function encode<S extends z.ZodType>(schema: S, data: z.output<S>): XMLElement;
|
|
83
115
|
type DefaultResolver<S extends z.ZodType = z.ZodType> = (schema: S) => CodecOptions<S> | void;
|
package/dist/xml/codec.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
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
|
|
7
|
+
var XMLCodecError = class extends Error {
|
|
8
|
+
path;
|
|
9
|
+
rawMessage;
|
|
10
|
+
constructor(rawMessage, path = [], options) {
|
|
11
|
+
super(path.length ? `[${path.join(".")}] ${rawMessage}` : rawMessage, options);
|
|
12
|
+
this.name = "XMLCodecError";
|
|
13
|
+
this.path = path;
|
|
14
|
+
this.rawMessage = rawMessage;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function rethrow(e, segment) {
|
|
18
|
+
const cause = e instanceof XMLCodecError ? e.cause : e;
|
|
19
|
+
const path = e instanceof XMLCodecError ? [segment, ...e.path] : [segment];
|
|
20
|
+
throw new XMLCodecError(e instanceof XMLCodecError ? e.rawMessage : e instanceof Error ? e.message : String(e), path, { cause });
|
|
21
|
+
}
|
|
7
22
|
function assertSingleElement(xml) {
|
|
8
23
|
if (xml.length !== 1) throw new Error(`Expected single XML element, got ${xml.length}`);
|
|
9
24
|
}
|
|
@@ -28,8 +43,8 @@ function normalizeCodecOptions(schema, options = {}) {
|
|
|
28
43
|
const result = {
|
|
29
44
|
schema,
|
|
30
45
|
tagname,
|
|
31
|
-
decode: options.decode
|
|
32
|
-
encode: options.encode
|
|
46
|
+
decode: options.decode ? (ctx) => options.decode(ctx, () => defaultOptions().decode(ctx)) : (ctx) => defaultOptions().decode(ctx),
|
|
47
|
+
encode: options.encode ? (ctx) => options.encode(ctx, () => defaultOptions().encode(ctx)) : (ctx) => defaultOptions().encode(ctx),
|
|
33
48
|
propertyTagname,
|
|
34
49
|
inlineProperty,
|
|
35
50
|
propertyMatch,
|
|
@@ -51,7 +66,10 @@ function normalizeCodecOptions(schema, options = {}) {
|
|
|
51
66
|
data: property.value
|
|
52
67
|
});
|
|
53
68
|
if (XML.isEmpty(res)) return;
|
|
54
|
-
if (property.options.inlineProperty) ctx.result.elements.push(...res.elements)
|
|
69
|
+
if (property.options.inlineProperty) ctx.result.elements.push(...res.elements.map((el) => el.type === "element" ? {
|
|
70
|
+
...el,
|
|
71
|
+
name: property.tagname
|
|
72
|
+
} : el));
|
|
55
73
|
else ctx.result.elements.push(res);
|
|
56
74
|
}
|
|
57
75
|
};
|
|
@@ -66,10 +84,19 @@ function resolveCodecOptions(schema) {
|
|
|
66
84
|
return options;
|
|
67
85
|
}
|
|
68
86
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
87
|
+
* String key used to store XML round-trip state on decoded data objects.
|
|
88
|
+
* Using a string (rather than a Symbol) allows Zod's schema.parse() to
|
|
89
|
+
* preserve it naturally when the key is included in the schema via xmlStateSchema().
|
|
71
90
|
*/
|
|
72
|
-
var
|
|
91
|
+
var XML_STATE_KEY = "__xml_state";
|
|
92
|
+
function xmlStateSchema(options) {
|
|
93
|
+
return prop(z.custom().optional(), {
|
|
94
|
+
decode: options?.source ? (ctx, _next) => {
|
|
95
|
+
(ctx.result[XML_STATE_KEY] ??= {}).source = ctx.xml;
|
|
96
|
+
} : () => {},
|
|
97
|
+
encode: () => {}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
73
100
|
function resolvePropertiesCodecOptions(schema) {
|
|
74
101
|
const shape = schema.def.shape;
|
|
75
102
|
const options = {};
|
|
@@ -103,7 +130,10 @@ function resolveDefault(schema) {
|
|
|
103
130
|
}
|
|
104
131
|
registerDefault((schema) => {
|
|
105
132
|
if (schema instanceof z.ZodArray) {
|
|
106
|
-
const
|
|
133
|
+
const elSchema = schema.def.element;
|
|
134
|
+
if (!isZodType(elSchema)) throw new Error(`Expected a ZodType, got ${elSchema}`);
|
|
135
|
+
const elOptions = resolveCodecOptions(elSchema);
|
|
136
|
+
const elHasOwnTagname = Boolean(getUserOptions(elSchema).tagname);
|
|
107
137
|
return normalizeCodecOptions(schema, {
|
|
108
138
|
decode(ctx) {
|
|
109
139
|
const { xml } = ctx;
|
|
@@ -116,12 +146,16 @@ registerDefault((schema) => {
|
|
|
116
146
|
encode(ctx) {
|
|
117
147
|
const values = ctx.data;
|
|
118
148
|
if (!Array.isArray(values)) throw new Error("expected array");
|
|
149
|
+
const elOptsForEncode = elHasOwnTagname ? elOptions : {
|
|
150
|
+
...elOptions,
|
|
151
|
+
tagname: ctx.options.tagname
|
|
152
|
+
};
|
|
119
153
|
return {
|
|
120
154
|
type: "element",
|
|
121
155
|
name: ctx.options.tagname(ctx),
|
|
122
156
|
attributes: {},
|
|
123
|
-
elements: values.map((v) =>
|
|
124
|
-
options:
|
|
157
|
+
elements: values.map((v) => elOptsForEncode.encode({
|
|
158
|
+
options: elOptsForEncode,
|
|
125
159
|
data: v
|
|
126
160
|
}))
|
|
127
161
|
};
|
|
@@ -134,12 +168,18 @@ registerDefault((schema) => {
|
|
|
134
168
|
const innerOptions = resolveCodecOptions(inner);
|
|
135
169
|
return normalizeCodecOptions(schema, {
|
|
136
170
|
decode(ctx) {
|
|
137
|
-
if (
|
|
171
|
+
if (ctx.xml === null) return void 0;
|
|
138
172
|
else return innerOptions.decode(ctx);
|
|
139
173
|
},
|
|
140
174
|
encode(ctx) {
|
|
141
175
|
if (typeof ctx.data === "undefined") return {};
|
|
142
176
|
else return innerOptions.encode(ctx);
|
|
177
|
+
},
|
|
178
|
+
decodeAsProperty(ctx) {
|
|
179
|
+
if (ctx.property.xml !== null) innerOptions.decodeAsProperty(ctx);
|
|
180
|
+
},
|
|
181
|
+
encodeAsProperty(ctx) {
|
|
182
|
+
if (typeof ctx.property.value !== "undefined") innerOptions.encodeAsProperty(ctx);
|
|
143
183
|
}
|
|
144
184
|
});
|
|
145
185
|
}
|
|
@@ -181,7 +221,10 @@ registerDefault((schema) => {
|
|
|
181
221
|
});
|
|
182
222
|
},
|
|
183
223
|
encode(ctx) {
|
|
184
|
-
const data =
|
|
224
|
+
const data = schema.def.reverseTransform ? schema.def.reverseTransform(ctx.data, {
|
|
225
|
+
value: ctx.data,
|
|
226
|
+
issues: []
|
|
227
|
+
}) : outSchema.encode(ctx.data);
|
|
185
228
|
const innerOpts = ctx.options.tagname !== inputCodecOptions.tagname ? {
|
|
186
229
|
...inputCodecOptions,
|
|
187
230
|
tagname: ctx.options.tagname
|
|
@@ -238,6 +281,7 @@ registerDefault((schema) => {
|
|
|
238
281
|
return normalizeCodecOptions(schema, {
|
|
239
282
|
decode(ctx) {
|
|
240
283
|
const sequence = [];
|
|
284
|
+
const result = { [XML_STATE_KEY]: { sequence } };
|
|
241
285
|
const propContexts = Object.fromEntries(Object.entries(options).map(([name, propOpts]) => {
|
|
242
286
|
return [name, {
|
|
243
287
|
name,
|
|
@@ -274,7 +318,6 @@ registerDefault((schema) => {
|
|
|
274
318
|
} else throw new Error(`Same element was matched by multiple properties: ${matches.join(", ")}`);
|
|
275
319
|
}
|
|
276
320
|
for (const propName in options) if (!seenProperties.has(propName)) sequence.push(propName);
|
|
277
|
-
const result = {};
|
|
278
321
|
for (const prop in options) {
|
|
279
322
|
const o = options[prop];
|
|
280
323
|
const propCtx = propContexts[prop];
|
|
@@ -286,19 +329,17 @@ registerDefault((schema) => {
|
|
|
286
329
|
propCtx.xml = matches[0];
|
|
287
330
|
}
|
|
288
331
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
332
|
+
try {
|
|
333
|
+
o.decodeAsProperty({
|
|
334
|
+
options: ctx.options,
|
|
335
|
+
xml: ctx.xml,
|
|
336
|
+
property: propCtx,
|
|
337
|
+
result
|
|
338
|
+
});
|
|
339
|
+
} catch (e) {
|
|
340
|
+
rethrow(e, prop);
|
|
341
|
+
}
|
|
295
342
|
}
|
|
296
|
-
Object.defineProperty(result, XML_STATE, {
|
|
297
|
-
value: { fieldOrder: sequence },
|
|
298
|
-
enumerable: false,
|
|
299
|
-
writable: true,
|
|
300
|
-
configurable: true
|
|
301
|
-
});
|
|
302
343
|
return result;
|
|
303
344
|
},
|
|
304
345
|
encode(ctx) {
|
|
@@ -309,24 +350,28 @@ registerDefault((schema) => {
|
|
|
309
350
|
attributes: {},
|
|
310
351
|
elements: []
|
|
311
352
|
};
|
|
312
|
-
const sequence = data[
|
|
353
|
+
const sequence = data["__xml_state"]?.sequence ?? Object.keys(options);
|
|
313
354
|
for (const item of sequence) if (typeof item === "string") {
|
|
314
355
|
const o = options[item];
|
|
315
356
|
if (!o) throw new Error(`Failed to resolve property options for sequence item ${item}`);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
options: o,
|
|
322
|
-
tagname: o.propertyTagname({
|
|
357
|
+
try {
|
|
358
|
+
o.encodeAsProperty({
|
|
359
|
+
options: ctx.options,
|
|
360
|
+
data,
|
|
361
|
+
property: {
|
|
323
362
|
name: item,
|
|
324
|
-
options: o
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
363
|
+
options: o,
|
|
364
|
+
tagname: o.propertyTagname({
|
|
365
|
+
name: item,
|
|
366
|
+
options: o
|
|
367
|
+
}),
|
|
368
|
+
value: data[item]
|
|
369
|
+
},
|
|
370
|
+
result
|
|
371
|
+
});
|
|
372
|
+
} catch (e) {
|
|
373
|
+
rethrow(e, item);
|
|
374
|
+
}
|
|
330
375
|
} else result.elements.push(item);
|
|
331
376
|
return result;
|
|
332
377
|
}
|
|
@@ -334,6 +379,6 @@ registerDefault((schema) => {
|
|
|
334
379
|
}
|
|
335
380
|
});
|
|
336
381
|
//#endregion
|
|
337
|
-
export {
|
|
382
|
+
export { XMLCodecError, XML_STATE_KEY, decode, encode, normalizeCodecOptions, registerDefault, xmlStateSchema };
|
|
338
383
|
|
|
339
384
|
//# sourceMappingURL=codec.js.map
|
package/dist/xml/examples.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
declare const Event_base: import('./model').XmlModelConstructor<z.ZodObject<{
|
|
3
|
+
title: z.ZodString;
|
|
4
|
+
publishedAt: z.ZodCodec<z.ZodString, z.ZodDate>;
|
|
5
|
+
}, z.core.$strip>, {
|
|
6
|
+
title: string;
|
|
7
|
+
publishedAt?: Date;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* An event with a typed `Date` field stored as an ISO 8601 string in XML.
|
|
11
|
+
* Demonstrates using `z.codec` to transform a raw XML string into a native JS type.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Event extends Event_base {
|
|
14
|
+
}
|
|
2
15
|
declare const Engine_base: import('./model').XmlModelConstructor<z.ZodObject<{
|
|
3
16
|
type: z.ZodString;
|
|
4
17
|
horsepower: z.ZodNumber;
|
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';
|
|
4
|
-
export { registerDefault, normalizeCodecOptions } from './codec';
|
|
5
|
-
export { xmlModel, type XmlModelConstructor } from './model';
|
|
3
|
+
export type { UserCodecOptions, XMLState } from './codec';
|
|
4
|
+
export { registerDefault, normalizeCodecOptions, XMLCodecError } from './codec';
|
|
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,21 +1,36 @@
|
|
|
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) {
|
|
10
30
|
static fromXML(input) {
|
|
11
31
|
if (typeof input === "string") input = XML.parse(input);
|
|
12
32
|
if (XML.isRoot(input)) input = XML.elementFromRoot(input);
|
|
13
|
-
|
|
14
|
-
const inputData = decode(this.dataSchema, input);
|
|
15
|
-
const xmlState = inputData[XML_STATE];
|
|
16
|
-
const parsed = schema.parse(inputData);
|
|
17
|
-
parsed[XML_STATE] = xmlState;
|
|
18
|
-
return this.fromData(parsed);
|
|
33
|
+
return this.fromData(decode(this.dataSchema, input));
|
|
19
34
|
}
|
|
20
35
|
static toXML(instance) {
|
|
21
36
|
const data = this.toData(instance);
|
|
@@ -28,6 +43,6 @@ function xmlModel(schema, options) {
|
|
|
28
43
|
};
|
|
29
44
|
}
|
|
30
45
|
//#endregion
|
|
31
|
-
export { xmlModel };
|
|
46
|
+
export { XMLBase, XMLBaseWithSource, xmlModel };
|
|
32
47
|
|
|
33
48
|
//# 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/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.6",
|
|
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",
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
|
|
54
54
|
},
|
|
55
55
|
"packageManager": "npm@11.12.0"
|
|
56
|
-
}
|
|
56
|
+
}
|