|
@@ -0,0 +1,153 @@ |
|
|
|
|
|
import type { Element } from 'xml-js'; |
|
|
|
|
|
import { TypeOptions } from 'src/common'; |
|
|
|
|
|
|
|
|
|
|
|
interface CommonApplicationXmlOptions extends TypeOptions { |
|
|
|
|
|
type: 'application/xml'; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
interface SerializeApplicationXmlOptions extends CommonApplicationXmlOptions { |
|
|
|
|
|
indent?: number | string; |
|
|
|
|
|
rootElementName?: string; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const constructElement = (data: unknown, name: string): Element[] => { |
|
|
|
|
|
if (Array.isArray(data)) { |
|
|
|
|
|
return [ |
|
|
|
|
|
{ |
|
|
|
|
|
type: 'element', |
|
|
|
|
|
name, |
|
|
|
|
|
attributes: { array: 'array' }, |
|
|
|
|
|
elements: data |
|
|
|
|
|
.flatMap((item, i) => constructElement(item, `_${i}`)), |
|
|
|
|
|
}, |
|
|
|
|
|
]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
typeof data === 'string' |
|
|
|
|
|
|| typeof data === 'number' |
|
|
|
|
|
|| typeof data === 'boolean' |
|
|
|
|
|
) { |
|
|
|
|
|
return [ |
|
|
|
|
|
{ |
|
|
|
|
|
name, |
|
|
|
|
|
type: 'element', |
|
|
|
|
|
attributes: { type: typeof data }, |
|
|
|
|
|
elements: [ |
|
|
|
|
|
{ |
|
|
|
|
|
type: 'text', |
|
|
|
|
|
text: data.toString(), |
|
|
|
|
|
}, |
|
|
|
|
|
], |
|
|
|
|
|
}, |
|
|
|
|
|
]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (typeof data === 'object' && data !== null) { |
|
|
|
|
|
return [ |
|
|
|
|
|
{ |
|
|
|
|
|
name, |
|
|
|
|
|
type: 'element', |
|
|
|
|
|
attributes: { type: 'object' }, |
|
|
|
|
|
elements: Object.entries(data) |
|
|
|
|
|
.flatMap(([key, value]) => constructElement(value, key)), |
|
|
|
|
|
}, |
|
|
|
|
|
]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return []; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let parseElement: (element: Element, allowValueOverride?: boolean) => unknown; |
|
|
|
|
|
|
|
|
|
|
|
const parseObjectElement = (element: Element): unknown => Object.fromEntries( |
|
|
|
|
|
element.elements?.map((e) => [e.name, parseElement(e)]) ?? [], |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const parseNumberElement = (element: Element, allowValueOverride = false): unknown => { |
|
|
|
|
|
if (allowValueOverride) { |
|
|
|
|
|
return Number(element.elements?.[0].text) || Number(element.attributes?.value); |
|
|
|
|
|
} |
|
|
|
|
|
return Number(element.elements?.[0].text); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const parseBooleanElement = (element: Element, allowValueOverride = false): unknown => { |
|
|
|
|
|
const stringValue = ( |
|
|
|
|
|
allowValueOverride |
|
|
|
|
|
? (element.elements?.[0].text ?? element.attributes?.value) |
|
|
|
|
|
: element.elements?.[0].text |
|
|
|
|
|
) |
|
|
|
|
|
?.toString() |
|
|
|
|
|
.toLowerCase(); |
|
|
|
|
|
|
|
|
|
|
|
if (stringValue === 'true') { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (stringValue === 'false') { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return undefined; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
parseElement = (element: Element, allowValueOverride = false): unknown => { |
|
|
|
|
|
if (element.attributes && 'array' in element.attributes) { |
|
|
|
|
|
return element.elements?.map((e) => parseElement(e, allowValueOverride)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (element.attributes?.type === 'object') { |
|
|
|
|
|
return parseObjectElement(element); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (element.attributes?.type === 'string') { |
|
|
|
|
|
return element.elements?.[0].text; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (element.attributes?.type === 'number') { |
|
|
|
|
|
return parseNumberElement(element, allowValueOverride); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (element.attributes?.type === 'boolean') { |
|
|
|
|
|
return parseBooleanElement(element, allowValueOverride); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
allowValueOverride |
|
|
|
|
|
? (element.elements?.[0].text ?? element.attributes?.value) |
|
|
|
|
|
: element.elements?.[0].text |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export const serialize = async <T, O extends TypeOptions = SerializeApplicationXmlOptions>( |
|
|
|
|
|
data: T, |
|
|
|
|
|
options = {} as O, |
|
|
|
|
|
): Promise<string> => { |
|
|
|
|
|
const { js2xml } = await import('xml-js'); |
|
|
|
|
|
const { indent, rootElementName = 'root' } = options as SerializeApplicationXmlOptions; |
|
|
|
|
|
|
|
|
|
|
|
return js2xml( |
|
|
|
|
|
{ elements: constructElement(data, rootElementName) }, |
|
|
|
|
|
{ |
|
|
|
|
|
compact: false, |
|
|
|
|
|
spaces: indent ?? 0, |
|
|
|
|
|
}, |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
interface DeserializeApplicationXmlOptions extends CommonApplicationXmlOptions { |
|
|
|
|
|
allowValueOverride?: boolean; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export const deserialize = async <T, O extends TypeOptions = DeserializeApplicationXmlOptions>( |
|
|
|
|
|
serialized: string, |
|
|
|
|
|
options = {} as O, |
|
|
|
|
|
): Promise<T> => { |
|
|
|
|
|
const { xml2js } = await import('xml-js'); |
|
|
|
|
|
const { allowValueOverride } = options as DeserializeApplicationXmlOptions; |
|
|
|
|
|
const rawElements = xml2js(serialized, { |
|
|
|
|
|
compact: false, |
|
|
|
|
|
nativeType: false, |
|
|
|
|
|
}) as Element; |
|
|
|
|
|
return parseElement(rawElements.elements?.[0] ?? {}, allowValueOverride) as T; |
|
|
|
|
|
}; |