@azure-tools/typespec-ts 0.48.0 → 0.48.1
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/CHANGELOG.md +14 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +20 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/modular/buildClientContext.d.ts.map +1 -1
- package/dist/src/modular/buildClientContext.js +7 -3
- package/dist/src/modular/buildClientContext.js.map +1 -1
- package/dist/src/modular/buildOperations.d.ts.map +1 -1
- package/dist/src/modular/buildOperations.js +24 -3
- package/dist/src/modular/buildOperations.js.map +1 -1
- package/dist/src/modular/emitModels.d.ts.map +1 -1
- package/dist/src/modular/emitModels.js +19 -0
- package/dist/src/modular/emitModels.js.map +1 -1
- package/dist/src/modular/emitSamples.js +2 -2
- package/dist/src/modular/emitSamples.js.map +1 -1
- package/dist/src/modular/helpers/clientHelpers.d.ts +2 -1
- package/dist/src/modular/helpers/clientHelpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/clientHelpers.js +5 -2
- package/dist/src/modular/helpers/clientHelpers.js.map +1 -1
- package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
- package/dist/src/modular/helpers/operationHelpers.js +266 -43
- package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
- package/dist/src/modular/serialization/buildXmlSerializerFunction.d.ts +32 -0
- package/dist/src/modular/serialization/buildXmlSerializerFunction.d.ts.map +1 -0
- package/dist/src/modular/serialization/buildXmlSerializerFunction.js +373 -0
- package/dist/src/modular/serialization/buildXmlSerializerFunction.js.map +1 -0
- package/dist/src/modular/static-helpers-metadata.d.ts +57 -0
- package/dist/src/modular/static-helpers-metadata.d.ts.map +1 -1
- package/dist/src/modular/static-helpers-metadata.js +57 -0
- package/dist/src/modular/static-helpers-metadata.js.map +1 -1
- package/dist/src/utils/clientUtils.d.ts.map +1 -1
- package/dist/src/utils/clientUtils.js +1 -0
- package/dist/src/utils/clientUtils.js.map +1 -1
- package/dist/src/utils/mediaTypes.d.ts +4 -0
- package/dist/src/utils/mediaTypes.d.ts.map +1 -1
- package/dist/src/utils/mediaTypes.js +10 -0
- package/dist/src/utils/mediaTypes.js.map +1 -1
- package/dist/src/utils/modelUtils.d.ts.map +1 -1
- package/dist/src/utils/modelUtils.js +3 -0
- package/dist/src/utils/modelUtils.js.map +1 -1
- package/dist/src/utils/operationUtil.d.ts +12 -0
- package/dist/src/utils/operationUtil.d.ts.map +1 -1
- package/dist/src/utils/operationUtil.js +22 -1
- package/dist/src/utils/operationUtil.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/src/index.ts +20 -5
- package/src/modular/buildClientContext.ts +12 -5
- package/src/modular/buildOperations.ts +34 -3
- package/src/modular/emitModels.ts +43 -0
- package/src/modular/emitSamples.ts +2 -2
- package/src/modular/helpers/clientHelpers.ts +6 -2
- package/src/modular/helpers/operationHelpers.ts +377 -57
- package/src/modular/serialization/buildXmlSerializerFunction.ts +511 -0
- package/src/modular/static-helpers-metadata.ts +58 -0
- package/src/utils/clientUtils.ts +1 -0
- package/src/utils/mediaTypes.ts +12 -0
- package/src/utils/modelUtils.ts +3 -0
- package/src/utils/operationUtil.ts +34 -1
- package/static/static-helpers/serialization/xml-helpers.ts +596 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import { XMLBuilder, XMLParser, XmlBuilderOptions } from "fast-xml-parser";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* XML serialization options for a property or model
|
|
8
|
+
*/
|
|
9
|
+
export interface XmlSerializationOptions {
|
|
10
|
+
/** Element or attribute name */
|
|
11
|
+
name: string;
|
|
12
|
+
/** If true, serialize as XML attribute instead of element */
|
|
13
|
+
attribute?: boolean;
|
|
14
|
+
/** XML namespace for this element/attribute */
|
|
15
|
+
ns?: {
|
|
16
|
+
namespace: string;
|
|
17
|
+
prefix: string;
|
|
18
|
+
};
|
|
19
|
+
/** For arrays - if true, items are inline without wrapper element */
|
|
20
|
+
unwrapped?: boolean;
|
|
21
|
+
/** For arrays - name of each item element */
|
|
22
|
+
itemsName?: string;
|
|
23
|
+
/** For arrays - namespace for item elements */
|
|
24
|
+
itemsNs?: {
|
|
25
|
+
namespace: string;
|
|
26
|
+
prefix: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Metadata for serializing a model property to XML
|
|
32
|
+
*/
|
|
33
|
+
export interface XmlPropertyMetadata {
|
|
34
|
+
/** Client-side property name */
|
|
35
|
+
propertyName: string;
|
|
36
|
+
/** XML serialization options */
|
|
37
|
+
xmlOptions: XmlSerializationOptions;
|
|
38
|
+
/** Serializer function for complex types */
|
|
39
|
+
serializer?: (value: any) => XmlSerializedValue;
|
|
40
|
+
/** Type of the property for special handling */
|
|
41
|
+
type?: "array" | "object" | "primitive" | "date" | "bytes" | "dict";
|
|
42
|
+
/** Date encoding format */
|
|
43
|
+
dateEncoding?: "rfc3339" | "rfc7231" | "unixTimestamp";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Metadata for deserializing XML to a model property
|
|
48
|
+
*/
|
|
49
|
+
export interface XmlPropertyDeserializeMetadata {
|
|
50
|
+
/** Client-side property name */
|
|
51
|
+
propertyName: string;
|
|
52
|
+
/** XML serialization options */
|
|
53
|
+
xmlOptions: XmlSerializationOptions;
|
|
54
|
+
/** Deserializer function for complex types */
|
|
55
|
+
deserializer?: (value: any) => any;
|
|
56
|
+
/** Type of the property for special handling */
|
|
57
|
+
type?: "array" | "object" | "primitive" | "date" | "bytes" | "dict";
|
|
58
|
+
/** Date encoding format */
|
|
59
|
+
dateEncoding?: "rfc3339" | "rfc7231" | "unixTimestamp";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Result of XML serialization - either a primitive value or structured XML object
|
|
64
|
+
*/
|
|
65
|
+
export type XmlSerializedValue =
|
|
66
|
+
| string
|
|
67
|
+
| number
|
|
68
|
+
| boolean
|
|
69
|
+
| null
|
|
70
|
+
| undefined
|
|
71
|
+
| XmlSerializedObject;
|
|
72
|
+
|
|
73
|
+
export interface XmlSerializedObject {
|
|
74
|
+
[key: string]: XmlSerializedValue | XmlSerializedValue[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Default XML parser/builder options
|
|
78
|
+
const defaultParserOptions = {
|
|
79
|
+
ignoreAttributes: false,
|
|
80
|
+
attributeNamePrefix: "@_",
|
|
81
|
+
textNodeName: "#text",
|
|
82
|
+
parseAttributeValue: true,
|
|
83
|
+
trimValues: false, // Preserve whitespace in text content
|
|
84
|
+
isArray: (
|
|
85
|
+
_name: string,
|
|
86
|
+
_jpath: string,
|
|
87
|
+
isLeafNode: boolean,
|
|
88
|
+
isAttribute: boolean
|
|
89
|
+
) => {
|
|
90
|
+
// Don't auto-detect arrays for attributes or leaf nodes by default
|
|
91
|
+
// Let the metadata drive array detection
|
|
92
|
+
return !isAttribute && !isLeafNode;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const defaultBuilderOptions: Partial<XmlBuilderOptions> = {
|
|
97
|
+
ignoreAttributes: false,
|
|
98
|
+
attributeNamePrefix: "@_",
|
|
99
|
+
textNodeName: "#text",
|
|
100
|
+
format: true,
|
|
101
|
+
suppressEmptyNode: true
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates an XML element name with optional namespace prefix
|
|
106
|
+
*/
|
|
107
|
+
function getElementName(
|
|
108
|
+
name: string,
|
|
109
|
+
ns?: { namespace: string; prefix: string }
|
|
110
|
+
): string {
|
|
111
|
+
if (ns?.prefix) {
|
|
112
|
+
return `${ns.prefix}:${name}`;
|
|
113
|
+
}
|
|
114
|
+
return name;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Creates namespace declaration attributes for the root element
|
|
119
|
+
*/
|
|
120
|
+
function createNamespaceDeclarations(
|
|
121
|
+
namespaces: Map<string, string>
|
|
122
|
+
): Record<string, string> {
|
|
123
|
+
const declarations: Record<string, string> = {};
|
|
124
|
+
for (const [prefix, namespace] of namespaces) {
|
|
125
|
+
if (prefix) {
|
|
126
|
+
declarations[`@_xmlns:${prefix}`] = namespace;
|
|
127
|
+
} else {
|
|
128
|
+
declarations["@_xmlns"] = namespace;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return declarations;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Collects all namespaces from property metadata
|
|
136
|
+
*/
|
|
137
|
+
function collectNamespaces(
|
|
138
|
+
properties: XmlPropertyMetadata[],
|
|
139
|
+
rootNs?: { namespace: string; prefix: string }
|
|
140
|
+
): Map<string, string> {
|
|
141
|
+
const namespaces = new Map<string, string>();
|
|
142
|
+
|
|
143
|
+
if (rootNs) {
|
|
144
|
+
namespaces.set(rootNs.prefix, rootNs.namespace);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const prop of properties) {
|
|
148
|
+
if (prop.xmlOptions.ns) {
|
|
149
|
+
namespaces.set(prop.xmlOptions.ns.prefix, prop.xmlOptions.ns.namespace);
|
|
150
|
+
}
|
|
151
|
+
if (prop.xmlOptions.itemsNs) {
|
|
152
|
+
namespaces.set(
|
|
153
|
+
prop.xmlOptions.itemsNs.prefix,
|
|
154
|
+
prop.xmlOptions.itemsNs.namespace
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return namespaces;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Serializes a primitive value for XML
|
|
164
|
+
*/
|
|
165
|
+
function serializePrimitiveValue(
|
|
166
|
+
value: any,
|
|
167
|
+
type?: "array" | "object" | "primitive" | "date" | "bytes" | "dict",
|
|
168
|
+
dateEncoding?: "rfc3339" | "rfc7231" | "unixTimestamp"
|
|
169
|
+
): string | number | boolean {
|
|
170
|
+
if (value === null || value === undefined) {
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (type === "date" && value instanceof Date) {
|
|
175
|
+
switch (dateEncoding) {
|
|
176
|
+
case "rfc7231":
|
|
177
|
+
return value.toUTCString();
|
|
178
|
+
case "unixTimestamp":
|
|
179
|
+
return Math.floor(value.getTime() / 1000);
|
|
180
|
+
case "rfc3339":
|
|
181
|
+
default:
|
|
182
|
+
return value.toISOString();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (type === "bytes" && value instanceof Uint8Array) {
|
|
187
|
+
// Convert bytes to base64
|
|
188
|
+
return btoa(String.fromCharCode(...value));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (typeof value === "boolean" || typeof value === "number") {
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return String(value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Serializes an array property to XML format
|
|
200
|
+
*/
|
|
201
|
+
function serializeArrayProperty(
|
|
202
|
+
value: any[],
|
|
203
|
+
metadata: XmlPropertyMetadata
|
|
204
|
+
): XmlSerializedObject | XmlSerializedValue[] {
|
|
205
|
+
const { xmlOptions, serializer } = metadata;
|
|
206
|
+
const itemName = getElementName(
|
|
207
|
+
xmlOptions.itemsName || xmlOptions.name,
|
|
208
|
+
xmlOptions.itemsNs || xmlOptions.ns
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const serializedItems = value.map((item) => {
|
|
212
|
+
if (serializer) {
|
|
213
|
+
return serializer(item);
|
|
214
|
+
}
|
|
215
|
+
return serializePrimitiveValue(item, metadata.type, metadata.dateEncoding);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (xmlOptions.unwrapped) {
|
|
219
|
+
// Unwrapped: return items directly (they'll be siblings)
|
|
220
|
+
return serializedItems;
|
|
221
|
+
} else {
|
|
222
|
+
// Wrapped: items are nested under a wrapper element, which contains item elements
|
|
223
|
+
const wrapperName = getElementName(xmlOptions.name, xmlOptions.ns);
|
|
224
|
+
return {
|
|
225
|
+
[wrapperName]: {
|
|
226
|
+
[itemName]: serializedItems
|
|
227
|
+
}
|
|
228
|
+
} as XmlSerializedObject;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Serializes a model to XML object structure
|
|
234
|
+
*/
|
|
235
|
+
export function serializeModelToXml(
|
|
236
|
+
item: Record<string, any>,
|
|
237
|
+
properties: XmlPropertyMetadata[],
|
|
238
|
+
rootName: string,
|
|
239
|
+
rootNs?: { namespace: string; prefix: string }
|
|
240
|
+
): XmlSerializedObject {
|
|
241
|
+
if (item === null || item === undefined) {
|
|
242
|
+
return {};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const result: XmlSerializedObject = {};
|
|
246
|
+
const attributes: Record<string, any> = {};
|
|
247
|
+
|
|
248
|
+
// Collect and add namespace declarations
|
|
249
|
+
const namespaces = collectNamespaces(properties, rootNs);
|
|
250
|
+
const nsDeclarations = createNamespaceDeclarations(namespaces);
|
|
251
|
+
Object.assign(attributes, nsDeclarations);
|
|
252
|
+
|
|
253
|
+
for (const prop of properties) {
|
|
254
|
+
const value = item[prop.propertyName];
|
|
255
|
+
|
|
256
|
+
if (value === undefined) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const { xmlOptions, serializer, type } = prop;
|
|
261
|
+
const elementName = getElementName(xmlOptions.name, xmlOptions.ns);
|
|
262
|
+
|
|
263
|
+
if (xmlOptions.attribute) {
|
|
264
|
+
// Serialize as attribute
|
|
265
|
+
const attrName = xmlOptions.ns?.prefix
|
|
266
|
+
? `@_${xmlOptions.ns.prefix}:${xmlOptions.name}`
|
|
267
|
+
: `@_${xmlOptions.name}`;
|
|
268
|
+
attributes[attrName] = serializePrimitiveValue(
|
|
269
|
+
value,
|
|
270
|
+
type,
|
|
271
|
+
prop.dateEncoding
|
|
272
|
+
);
|
|
273
|
+
} else if (type === "dict" && value !== null && typeof value === "object") {
|
|
274
|
+
// Serialize dictionary - each key-value pair becomes an element
|
|
275
|
+
const dictContent: Record<string, XmlSerializedValue> = {};
|
|
276
|
+
for (const [key, val] of Object.entries(value)) {
|
|
277
|
+
dictContent[key] = String(val);
|
|
278
|
+
}
|
|
279
|
+
result[elementName] = dictContent;
|
|
280
|
+
} else if (Array.isArray(value)) {
|
|
281
|
+
// Serialize array
|
|
282
|
+
const arrayResult = serializeArrayProperty(value, prop);
|
|
283
|
+
if (xmlOptions.unwrapped && Array.isArray(arrayResult)) {
|
|
284
|
+
// For unwrapped arrays, add each item as a separate element
|
|
285
|
+
const itemName = getElementName(
|
|
286
|
+
xmlOptions.itemsName || xmlOptions.name,
|
|
287
|
+
xmlOptions.itemsNs || xmlOptions.ns
|
|
288
|
+
);
|
|
289
|
+
result[itemName] = arrayResult as XmlSerializedValue[];
|
|
290
|
+
} else {
|
|
291
|
+
Object.assign(result, arrayResult);
|
|
292
|
+
}
|
|
293
|
+
} else if (value !== null && typeof value === "object" && serializer) {
|
|
294
|
+
// Serialize nested object
|
|
295
|
+
result[elementName] = serializer(value);
|
|
296
|
+
} else if (xmlOptions.unwrapped && !Array.isArray(value)) {
|
|
297
|
+
// Unwrapped primitive - this becomes the text content of the parent element
|
|
298
|
+
result["#text"] = serializePrimitiveValue(value, type, prop.dateEncoding);
|
|
299
|
+
} else {
|
|
300
|
+
// Serialize primitive
|
|
301
|
+
result[elementName] = serializePrimitiveValue(
|
|
302
|
+
value,
|
|
303
|
+
type,
|
|
304
|
+
prop.dateEncoding
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Merge attributes into result
|
|
310
|
+
Object.assign(result, attributes);
|
|
311
|
+
|
|
312
|
+
// Wrap in root element
|
|
313
|
+
const rootElementName = getElementName(rootName, rootNs);
|
|
314
|
+
return {
|
|
315
|
+
[rootElementName]: result
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Converts an XML object structure to XML string
|
|
321
|
+
*/
|
|
322
|
+
export function xmlObjectToString(
|
|
323
|
+
xmlObject: XmlSerializedObject,
|
|
324
|
+
options?: Partial<XmlBuilderOptions>
|
|
325
|
+
): string {
|
|
326
|
+
const builder = new XMLBuilder({
|
|
327
|
+
...defaultBuilderOptions,
|
|
328
|
+
...options
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return builder.build(xmlObject);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Full serialization: model to XML string
|
|
336
|
+
*/
|
|
337
|
+
export function serializeToXml(
|
|
338
|
+
item: Record<string, any>,
|
|
339
|
+
properties: XmlPropertyMetadata[],
|
|
340
|
+
rootName: string,
|
|
341
|
+
rootNs?: { namespace: string; prefix: string },
|
|
342
|
+
options?: Partial<XmlBuilderOptions>
|
|
343
|
+
): string {
|
|
344
|
+
const xmlObject = serializeModelToXml(item, properties, rootName, rootNs);
|
|
345
|
+
return xmlObjectToString(xmlObject, options);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Parses XML string to object structure
|
|
350
|
+
*/
|
|
351
|
+
export function parseXmlString(
|
|
352
|
+
xmlString: string,
|
|
353
|
+
options?: Partial<typeof defaultParserOptions>
|
|
354
|
+
): any {
|
|
355
|
+
const parser = new XMLParser({
|
|
356
|
+
...defaultParserOptions,
|
|
357
|
+
...options
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return parser.parse(xmlString);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Deserializes a primitive value from XML
|
|
365
|
+
*/
|
|
366
|
+
function deserializePrimitiveValue(
|
|
367
|
+
value: any,
|
|
368
|
+
type?: "array" | "object" | "primitive" | "date" | "bytes" | "dict",
|
|
369
|
+
dateEncoding?: "rfc3339" | "rfc7231" | "unixTimestamp"
|
|
370
|
+
): any {
|
|
371
|
+
if (value === null || value === undefined || value === "") {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (type === "date") {
|
|
376
|
+
if (dateEncoding === "unixTimestamp" && typeof value === "number") {
|
|
377
|
+
return new Date(value * 1000);
|
|
378
|
+
}
|
|
379
|
+
return new Date(value);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (type === "bytes" && typeof value === "string") {
|
|
383
|
+
// Convert base64 to bytes
|
|
384
|
+
const binaryString = atob(value);
|
|
385
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
386
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
387
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
388
|
+
}
|
|
389
|
+
return bytes;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return value;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Unwraps a value if it's a single-element array (parser wraps non-leaf elements in arrays)
|
|
397
|
+
*/
|
|
398
|
+
function unwrapSingleElementArray(value: any): any {
|
|
399
|
+
if (Array.isArray(value) && value.length === 1) {
|
|
400
|
+
return value[0];
|
|
401
|
+
}
|
|
402
|
+
return value;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Extracts element value from parsed XML, handling namespaces
|
|
407
|
+
*/
|
|
408
|
+
function getElementValue(obj: any, xmlOptions: XmlSerializationOptions): any {
|
|
409
|
+
if (!obj) {
|
|
410
|
+
return undefined;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const { name, ns, attribute } = xmlOptions;
|
|
414
|
+
|
|
415
|
+
if (attribute) {
|
|
416
|
+
// Look for attribute with or without namespace prefix
|
|
417
|
+
const attrName = ns?.prefix ? `@_${ns.prefix}:${name}` : `@_${name}`;
|
|
418
|
+
return obj[attrName];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Look for element with or without namespace prefix
|
|
422
|
+
const elementName = ns?.prefix ? `${ns.prefix}:${name}` : name;
|
|
423
|
+
const value = obj[elementName] ?? obj[name]; // Fall back to name without prefix
|
|
424
|
+
|
|
425
|
+
// Unwrap single-element arrays (parser wraps non-leaf elements in arrays)
|
|
426
|
+
return unwrapSingleElementArray(value);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Deserializes an array from XML
|
|
431
|
+
*/
|
|
432
|
+
function deserializeArrayProperty(
|
|
433
|
+
obj: any,
|
|
434
|
+
metadata: XmlPropertyDeserializeMetadata
|
|
435
|
+
): any[] {
|
|
436
|
+
const { xmlOptions, deserializer, type, dateEncoding } = metadata;
|
|
437
|
+
|
|
438
|
+
let arrayData: any;
|
|
439
|
+
|
|
440
|
+
if (xmlOptions.unwrapped) {
|
|
441
|
+
// Items are direct children
|
|
442
|
+
const itemName = getElementName(
|
|
443
|
+
xmlOptions.itemsName || xmlOptions.name,
|
|
444
|
+
xmlOptions.itemsNs || xmlOptions.ns
|
|
445
|
+
);
|
|
446
|
+
arrayData = obj[itemName] ?? obj[xmlOptions.itemsName || xmlOptions.name];
|
|
447
|
+
} else {
|
|
448
|
+
// Items are nested under wrapper element
|
|
449
|
+
const wrapperName = getElementName(xmlOptions.name, xmlOptions.ns);
|
|
450
|
+
let wrapper = obj[wrapperName] ?? obj[xmlOptions.name];
|
|
451
|
+
|
|
452
|
+
if (!wrapper) {
|
|
453
|
+
return [];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Unwrap single-element array if the parser wrapped it
|
|
457
|
+
wrapper = unwrapSingleElementArray(wrapper);
|
|
458
|
+
|
|
459
|
+
const itemName = getElementName(
|
|
460
|
+
xmlOptions.itemsName || xmlOptions.name,
|
|
461
|
+
xmlOptions.itemsNs
|
|
462
|
+
);
|
|
463
|
+
arrayData =
|
|
464
|
+
wrapper[itemName] ??
|
|
465
|
+
wrapper[xmlOptions.itemsName || xmlOptions.name] ??
|
|
466
|
+
wrapper;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (!arrayData) {
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Ensure it's an array
|
|
474
|
+
const items = Array.isArray(arrayData) ? arrayData : [arrayData];
|
|
475
|
+
|
|
476
|
+
return items.map((item) => {
|
|
477
|
+
// Unwrap single-element array for each item if needed
|
|
478
|
+
const unwrappedItem = unwrapSingleElementArray(item);
|
|
479
|
+
if (deserializer) {
|
|
480
|
+
return deserializer(unwrappedItem);
|
|
481
|
+
}
|
|
482
|
+
return deserializePrimitiveValue(unwrappedItem, type, dateEncoding);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Deserializes XML object structure to model
|
|
488
|
+
*/
|
|
489
|
+
export function deserializeXmlToModel<T = Record<string, any>>(
|
|
490
|
+
xmlObject: any,
|
|
491
|
+
properties: XmlPropertyDeserializeMetadata[],
|
|
492
|
+
rootName: string,
|
|
493
|
+
rootNs?: { namespace: string; prefix: string }
|
|
494
|
+
): T {
|
|
495
|
+
if (!xmlObject) {
|
|
496
|
+
return {} as T;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Get root element content - unwrap if it's wrapped in an array by the parser
|
|
500
|
+
const rootElementName = getElementName(rootName, rootNs);
|
|
501
|
+
let content = xmlObject[rootElementName] ?? xmlObject[rootName] ?? xmlObject;
|
|
502
|
+
content = unwrapSingleElementArray(content);
|
|
503
|
+
|
|
504
|
+
const result: Record<string, any> = {};
|
|
505
|
+
|
|
506
|
+
for (const prop of properties) {
|
|
507
|
+
const { propertyName, xmlOptions, deserializer, type, dateEncoding } = prop;
|
|
508
|
+
|
|
509
|
+
if (type === "array" || xmlOptions.itemsName) {
|
|
510
|
+
// Deserialize array
|
|
511
|
+
result[propertyName] = deserializeArrayProperty(content, prop);
|
|
512
|
+
} else if (type === "dict") {
|
|
513
|
+
// Deserialize dictionary - each child element is a key-value pair
|
|
514
|
+
const rawValue = getElementValue(content, xmlOptions);
|
|
515
|
+
if (rawValue !== undefined && typeof rawValue === "object") {
|
|
516
|
+
const dict: Record<string, string> = {};
|
|
517
|
+
for (const [key, val] of Object.entries(rawValue)) {
|
|
518
|
+
// Skip attributes (start with @_) and text nodes (#text)
|
|
519
|
+
if (!key.startsWith("@_") && key !== "#text") {
|
|
520
|
+
dict[key] = String(val);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
result[propertyName] = dict;
|
|
524
|
+
}
|
|
525
|
+
} else if (xmlOptions.unwrapped && type !== "object") {
|
|
526
|
+
// Unwrapped primitive - get text content from the element
|
|
527
|
+
const rawValue = content["#text"];
|
|
528
|
+
if (rawValue !== undefined) {
|
|
529
|
+
result[propertyName] = deserializePrimitiveValue(
|
|
530
|
+
rawValue,
|
|
531
|
+
type,
|
|
532
|
+
dateEncoding
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
} else {
|
|
536
|
+
// Get element or attribute value
|
|
537
|
+
const rawValue = getElementValue(content, xmlOptions);
|
|
538
|
+
|
|
539
|
+
if (rawValue === undefined) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (deserializer && typeof rawValue === "object") {
|
|
544
|
+
// Deserialize nested object
|
|
545
|
+
result[propertyName] = deserializer(rawValue);
|
|
546
|
+
} else {
|
|
547
|
+
// Deserialize primitive
|
|
548
|
+
result[propertyName] = deserializePrimitiveValue(
|
|
549
|
+
rawValue,
|
|
550
|
+
type,
|
|
551
|
+
dateEncoding
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return result as T;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Full deserialization: XML string to model
|
|
562
|
+
*/
|
|
563
|
+
export function deserializeFromXml<T = Record<string, any>>(
|
|
564
|
+
xmlString: string,
|
|
565
|
+
properties: XmlPropertyDeserializeMetadata[],
|
|
566
|
+
rootName: string,
|
|
567
|
+
rootNs?: { namespace: string; prefix: string },
|
|
568
|
+
parserOptions?: Partial<typeof defaultParserOptions>
|
|
569
|
+
): T {
|
|
570
|
+
const xmlObject = parseXmlString(xmlString, parserOptions);
|
|
571
|
+
return deserializeXmlToModel<T>(xmlObject, properties, rootName, rootNs);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Utility to check if a content type is XML
|
|
576
|
+
*/
|
|
577
|
+
export function isXmlContentType(contentType: string): boolean {
|
|
578
|
+
const normalized = contentType.toLowerCase();
|
|
579
|
+
return (
|
|
580
|
+
normalized.includes("application/xml") ||
|
|
581
|
+
normalized.includes("text/xml") ||
|
|
582
|
+
normalized.endsWith("+xml")
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Utility to check if a content type is JSON
|
|
588
|
+
*/
|
|
589
|
+
export function isJsonContentType(contentType: string): boolean {
|
|
590
|
+
const normalized = contentType.toLowerCase();
|
|
591
|
+
return (
|
|
592
|
+
normalized.includes("application/json") ||
|
|
593
|
+
normalized.includes("text/json") ||
|
|
594
|
+
normalized.endsWith("+json")
|
|
595
|
+
);
|
|
596
|
+
}
|