xml-model 2.0.0-beta.4 → 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 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
- if (!this._decoder) this._decoder = new TextDecoder("utf8");
186
- data = this._decoder.decode(data, { stream: true });
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._decoder) {
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
- emitNode(parser, "onprocessinginstruction", {
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;
@@ -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?: CodecOptions<S>["decode"];
36
- encode?: CodecOptions<S>["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
- fieldOrder: OrderEntry[];
74
+ sequence: OrderEntry[];
75
+ /** Present when xmlStateSchema({ source: true }) is used: the original XMLElement. */
76
+ source?: XMLElement;
75
77
  }
76
78
  /**
77
- * Non-enumerable Symbol attached to decoded data objects (and forwarded to model instances).
78
- * Groups all XML codec round-trip state under a single key.
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 XML_STATE: unique symbol;
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 ?? defaultOptions().decode,
32
- encode: options.encode ?? defaultOptions().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
- * Non-enumerable Symbol attached to decoded data objects (and forwarded to model instances).
70
- * Groups all XML codec round-trip state under a single key.
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 XML_STATE = Symbol("xml-model.state");
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 elOptions = resolveCodecOptions(schema.def.element);
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) => elOptions.encode({
124
- options: elOptions,
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 (!ctx.xml) return void 0;
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[XML_STATE]?.fieldOrder ?? Object.keys(options);
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 { XML_STATE, decode, encode, normalizeCodecOptions, registerDefault };
356
+ export { XML_STATE_KEY, decode, encode, normalizeCodecOptions, registerDefault, xmlStateSchema };
338
357
 
339
358
  //# sourceMappingURL=codec.js.map
@@ -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
@@ -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 { XML_STATE, decode, encode } from "./codec.js";
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[XML_STATE];
35
+ const xmlState = inputData[XML_STATE_KEY];
16
36
  const parsed = schema.parse(inputData);
17
- parsed[XML_STATE] = xmlState;
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) => Partial<Record<string, unknown>> | undefined;
19
- encode?: (ctx: PropertyEncodingContext) => XMLElement | undefined;
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;
@@ -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 res = userDecode(ctx);
23
- if (typeof res !== "undefined") Object.assign(ctx.result, res);
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 res = userEncode(ctx);
31
- if (typeof res === "undefined") return;
32
- if (property.options.inlineProperty) ctx.result.elements.push(...res.elements);
33
- else {
34
- res.name = property.tagname;
35
- ctx.result.elements.push(res);
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
- ...getUserOptions(parentSchema),
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.4",
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",