@onerjs/serializers 8.41.6 → 8.41.8
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/3MF/3mfSerializer.configuration.d.ts +9 -0
- package/3MF/3mfSerializer.configuration.js +11 -0
- package/3MF/3mfSerializer.configuration.js.map +1 -0
- package/3MF/3mfSerializer.d.ts +132 -0
- package/3MF/3mfSerializer.js +328 -0
- package/3MF/3mfSerializer.js.map +1 -0
- package/3MF/core/index.d.ts +2 -0
- package/3MF/core/index.js +4 -0
- package/3MF/core/index.js.map +1 -0
- package/3MF/core/model/3mf.builder.d.ts +231 -0
- package/3MF/core/model/3mf.builder.js +403 -0
- package/3MF/core/model/3mf.builder.js.map +1 -0
- package/3MF/core/model/3mf.d.ts +329 -0
- package/3MF/core/model/3mf.interfaces.d.ts +321 -0
- package/3MF/core/model/3mf.interfaces.js +39 -0
- package/3MF/core/model/3mf.interfaces.js.map +1 -0
- package/3MF/core/model/3mf.js +377 -0
- package/3MF/core/model/3mf.js.map +1 -0
- package/3MF/core/model/3mf.opc.d.ts +66 -0
- package/3MF/core/model/3mf.opc.interfaces.d.ts +126 -0
- package/3MF/core/model/3mf.opc.interfaces.js +75 -0
- package/3MF/core/model/3mf.opc.interfaces.js.map +1 -0
- package/3MF/core/model/3mf.opc.js +91 -0
- package/3MF/core/model/3mf.opc.js.map +1 -0
- package/3MF/core/model/3mf.serializer.d.ts +118 -0
- package/3MF/core/model/3mf.serializer.js +171 -0
- package/3MF/core/model/3mf.serializer.js.map +1 -0
- package/3MF/core/model/3mf.types.d.ts +46 -0
- package/3MF/core/model/3mf.types.js +2 -0
- package/3MF/core/model/3mf.types.js.map +1 -0
- package/3MF/core/model/index.d.ts +7 -0
- package/3MF/core/model/index.js +8 -0
- package/3MF/core/model/index.js.map +1 -0
- package/3MF/core/xml/index.d.ts +6 -0
- package/3MF/core/xml/index.js +7 -0
- package/3MF/core/xml/index.js.map +1 -0
- package/3MF/core/xml/xml.builder.bytes.d.ts +33 -0
- package/3MF/core/xml/xml.builder.bytes.js +60 -0
- package/3MF/core/xml/xml.builder.bytes.js.map +1 -0
- package/3MF/core/xml/xml.builder.d.ts +94 -0
- package/3MF/core/xml/xml.builder.js +286 -0
- package/3MF/core/xml/xml.builder.js.map +1 -0
- package/3MF/core/xml/xml.builder.string.d.ts +19 -0
- package/3MF/core/xml/xml.builder.string.js +35 -0
- package/3MF/core/xml/xml.builder.string.js.map +1 -0
- package/3MF/core/xml/xml.interfaces.d.ts +91 -0
- package/3MF/core/xml/xml.interfaces.js +110 -0
- package/3MF/core/xml/xml.interfaces.js.map +1 -0
- package/3MF/core/xml/xml.serializer.d.ts +39 -0
- package/3MF/core/xml/xml.serializer.format.d.ts +92 -0
- package/3MF/core/xml/xml.serializer.format.js +122 -0
- package/3MF/core/xml/xml.serializer.format.js.map +1 -0
- package/3MF/core/xml/xml.serializer.js +261 -0
- package/3MF/core/xml/xml.serializer.js.map +1 -0
- package/3MF/index.d.ts +3 -0
- package/3MF/index.js +5 -0
- package/3MF/index.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/legacy/legacy-3mfSerializer.d.ts +1 -0
- package/legacy/legacy-3mfSerializer.js +20 -0
- package/legacy/legacy-3mfSerializer.js.map +1 -0
- package/legacy/legacy.d.ts +1 -0
- package/legacy/legacy.js +1 -0
- package/legacy/legacy.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param x
|
|
3
|
+
* @returns
|
|
4
|
+
*/
|
|
5
|
+
export function IsQualifiedName(x) {
|
|
6
|
+
return typeof x?.name === "string";
|
|
7
|
+
}
|
|
8
|
+
const XML_CLASS_META = Symbol("__xml:meta$__");
|
|
9
|
+
const XML_CLASS_NAME = Symbol("__xml:name$__");
|
|
10
|
+
function AddXmlMeta(target, meta) {
|
|
11
|
+
const ctor = target.constructor;
|
|
12
|
+
(ctor[XML_CLASS_META] ?? (ctor[XML_CLASS_META] = [])).push(meta);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @param name
|
|
16
|
+
* @returns
|
|
17
|
+
*/
|
|
18
|
+
export function XmlName(name) {
|
|
19
|
+
return (ctor) => {
|
|
20
|
+
ctor[XML_CLASS_NAME] = name;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* tell the serializer to ignore the property
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
export function XmlIgnore() {
|
|
28
|
+
return (target, prop) => AddXmlMeta(target, { kind: "none", prop, ignore: true });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* tell the serializer to serialize the property as attribute
|
|
32
|
+
* @returns
|
|
33
|
+
*/
|
|
34
|
+
export function XmlAttr(opts) {
|
|
35
|
+
return (target, prop) => AddXmlMeta(target, { kind: "attr", prop, ...opts });
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* tell the serializer to serialize the property as element - this is the default behavior but shoud be
|
|
39
|
+
* specified when wanted to update the default name of the classe or if the class is not decorated (without \@XmlName)
|
|
40
|
+
* @returns
|
|
41
|
+
*/
|
|
42
|
+
export function XmlElem(opts) {
|
|
43
|
+
return (target, prop) => AddXmlMeta(target, { kind: "elem", prop, ...opts });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param obj
|
|
48
|
+
* @returns
|
|
49
|
+
*/
|
|
50
|
+
export function GetXmlFieldMeta(obj) {
|
|
51
|
+
return (obj?.constructor?.[XML_CLASS_META] ?? []);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
*
|
|
55
|
+
* @param obj
|
|
56
|
+
* @returns
|
|
57
|
+
*/
|
|
58
|
+
export function GetXmlName(obj) {
|
|
59
|
+
const n = obj?.constructor?.[XML_CLASS_NAME];
|
|
60
|
+
return n ? n : undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param s
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
67
|
+
function LooksLikeXmlNcName(s) {
|
|
68
|
+
// Approximation ASCII de NCName: pas de ":" et demarre par lettre ou underscore
|
|
69
|
+
// Puis lettres/chiffres/underscore/point/tiret.
|
|
70
|
+
return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(s);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
*
|
|
74
|
+
* @param qn
|
|
75
|
+
* @returns
|
|
76
|
+
*/
|
|
77
|
+
export function XmlNameToParts(qn) {
|
|
78
|
+
if (IsQualifiedName(qn)) {
|
|
79
|
+
return qn;
|
|
80
|
+
}
|
|
81
|
+
const s = (qn ?? "").trim();
|
|
82
|
+
if (!s) {
|
|
83
|
+
return { name: "" };
|
|
84
|
+
}
|
|
85
|
+
const i = s.indexOf(":");
|
|
86
|
+
if (i === -1) {
|
|
87
|
+
return { name: s };
|
|
88
|
+
}
|
|
89
|
+
// Un QName XML ne doit contenir qu un seul ":".
|
|
90
|
+
// Si il y en a plusieurs, on considere que ce n est pas un QName.
|
|
91
|
+
if (s.indexOf(":", i + 1) !== -1) {
|
|
92
|
+
return { name: s };
|
|
93
|
+
}
|
|
94
|
+
const prefix = s.slice(0, i);
|
|
95
|
+
const local = s.slice(i + 1);
|
|
96
|
+
if (LooksLikeXmlNcName(prefix) && LooksLikeXmlNcName(local)) {
|
|
97
|
+
return { ns: prefix, name: local };
|
|
98
|
+
}
|
|
99
|
+
return { name: s };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
*
|
|
103
|
+
* @param name
|
|
104
|
+
* @param prefix
|
|
105
|
+
* @returns
|
|
106
|
+
*/
|
|
107
|
+
export function ToQualifiedString(name, prefix) {
|
|
108
|
+
return prefix ? `${prefix}:${name}` : name;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=xml.interfaces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml.interfaces.js","sourceRoot":"","sources":["../../../../../../dev/serializers/src/3MF/core/xml/xml.interfaces.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IXmlSerializerFormatOptions } from \"./xml.serializer.format\";\r\n\r\n/** */\r\nexport interface IQualifiedName {\r\n /** */\r\n ns?: string;\r\n /** */\r\n name: string;\r\n}\r\n\r\n/** */\r\nexport interface IXmlBuilder {\r\n dec(version: string, encoding?: string, standalone?: boolean): IXmlBuilder;\r\n att(ns: string | null, n: string, v: string): IXmlBuilder;\r\n ele(ns: string | null, n: string): IXmlBuilder;\r\n text(txt: string): IXmlBuilder;\r\n end(): IXmlBuilder;\r\n}\r\n\r\n/**\r\n * @param x\r\n * @returns\r\n */\r\nexport function IsQualifiedName(x: unknown): x is { name: string } {\r\n return typeof (x as any)?.name === \"string\";\r\n}\r\n\r\nexport type XmlName = string | IQualifiedName;\r\n\r\ntype FieldKind = \"attr\" | \"elem\" | \"none\";\r\n\r\n/**\r\n *\r\n */\r\nexport interface IFormatter<T = any> {\r\n toString(value: T): string;\r\n}\r\n\r\nexport type FormatterCtor<T> = new (args: IXmlSerializerFormatOptions) => IFormatter<T>;\r\n\r\ntype FieldMeta = {\r\n kind: FieldKind;\r\n prop: string;\r\n name?: XmlName;\r\n ignore?: boolean;\r\n formatter?: FormatterCtor<any>;\r\n};\r\n\r\nconst XML_CLASS_META = Symbol(\"__xml:meta$__\");\r\nconst XML_CLASS_NAME = Symbol(\"__xml:name$__\");\r\n\r\nfunction AddXmlMeta(target: any, meta: FieldMeta) {\r\n const ctor = target.constructor;\r\n (ctor[XML_CLASS_META] ??= []).push(meta);\r\n}\r\n\r\n/**\r\n * @param name\r\n * @returns\r\n */\r\nexport function XmlName(name: XmlName) {\r\n return (ctor: Function) => {\r\n (ctor as any)[XML_CLASS_NAME] = name;\r\n };\r\n}\r\n\r\n/**\r\n * tell the serializer to ignore the property\r\n * @returns\r\n */\r\nexport function XmlIgnore() {\r\n return (target: any, prop: string) => AddXmlMeta(target, { kind: \"none\", prop, ignore: true });\r\n}\r\n\r\n/**\r\n * tell the serializer to serialize the property as attribute\r\n * @returns\r\n */\r\nexport function XmlAttr(opts?: { name: XmlName; formatter?: FormatterCtor<any> }) {\r\n return (target: any, prop: string) => AddXmlMeta(target, { kind: \"attr\", prop, ...opts });\r\n}\r\n\r\n/**\r\n * tell the serializer to serialize the property as element - this is the default behavior but shoud be\r\n * specified when wanted to update the default name of the classe or if the class is not decorated (without \\@XmlName)\r\n * @returns\r\n */\r\nexport function XmlElem(opts?: { name: XmlName }) {\r\n return (target: any, prop: string) => AddXmlMeta(target, { kind: \"elem\", prop, ...opts });\r\n}\r\n\r\n/**\r\n *\r\n * @param obj\r\n * @returns\r\n */\r\nexport function GetXmlFieldMeta(obj: any): FieldMeta[] {\r\n return (obj?.constructor?.[XML_CLASS_META] ?? []) as FieldMeta[];\r\n}\r\n\r\n/**\r\n *\r\n * @param obj\r\n * @returns\r\n */\r\nexport function GetXmlName(obj: any): XmlName | undefined {\r\n const n = obj?.constructor?.[XML_CLASS_NAME];\r\n return n ? (n as XmlName) : undefined;\r\n}\r\n\r\n/**\r\n *\r\n * @param s\r\n * @returns\r\n */\r\nfunction LooksLikeXmlNcName(s: string): boolean {\r\n // Approximation ASCII de NCName: pas de \":\" et demarre par lettre ou underscore\r\n // Puis lettres/chiffres/underscore/point/tiret.\r\n return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(s);\r\n}\r\n\r\n/**\r\n *\r\n * @param qn\r\n * @returns\r\n */\r\nexport function XmlNameToParts(qn: XmlName): IQualifiedName {\r\n if (IsQualifiedName(qn)) {\r\n return qn;\r\n }\r\n const s = (qn ?? \"\").trim();\r\n if (!s) {\r\n return { name: \"\" };\r\n }\r\n const i = s.indexOf(\":\");\r\n if (i === -1) {\r\n return { name: s };\r\n }\r\n\r\n // Un QName XML ne doit contenir qu un seul \":\".\r\n // Si il y en a plusieurs, on considere que ce n est pas un QName.\r\n if (s.indexOf(\":\", i + 1) !== -1) {\r\n return { name: s };\r\n }\r\n\r\n const prefix = s.slice(0, i);\r\n const local = s.slice(i + 1);\r\n\r\n if (LooksLikeXmlNcName(prefix) && LooksLikeXmlNcName(local)) {\r\n return { ns: prefix, name: local };\r\n }\r\n return { name: s };\r\n}\r\n\r\n/**\r\n *\r\n * @param name\r\n * @param prefix\r\n * @returns\r\n */\r\nexport function ToQualifiedString(name: string, prefix?: string): string {\r\n return prefix ? `${prefix}:${name}` : name;\r\n}\r\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type IXmlBuilder, type XmlName } from "./xml.interfaces.js";
|
|
2
|
+
import { type IXmlSerializerFormatOptions } from "./xml.serializer.format.js";
|
|
3
|
+
/**
|
|
4
|
+
*/
|
|
5
|
+
export declare class XmlSerializer {
|
|
6
|
+
/** */
|
|
7
|
+
private _format;
|
|
8
|
+
/** */
|
|
9
|
+
private _builder;
|
|
10
|
+
/** */
|
|
11
|
+
private _ns;
|
|
12
|
+
/** */
|
|
13
|
+
private _prefixCount;
|
|
14
|
+
private _nFmt?;
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @param builder
|
|
18
|
+
* @param format
|
|
19
|
+
*/
|
|
20
|
+
constructor(builder: IXmlBuilder, format?: IXmlSerializerFormatOptions);
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param ns
|
|
24
|
+
* @returns
|
|
25
|
+
*/
|
|
26
|
+
withNamespace(...ns: XmlName[]): XmlSerializer;
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @param root
|
|
30
|
+
* @param name
|
|
31
|
+
*/
|
|
32
|
+
serialize(root: object, name?: XmlName): void;
|
|
33
|
+
private _writeObject;
|
|
34
|
+
private _getPrefix;
|
|
35
|
+
private _writeObjectContent;
|
|
36
|
+
private _gatherNamespaces;
|
|
37
|
+
private _assignNamespace;
|
|
38
|
+
private _buildNsPrefix;
|
|
39
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { IFormatter } from "./xml.interfaces.js";
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
*/
|
|
5
|
+
export interface IXmlSerializerNumberOptions {
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
*/
|
|
9
|
+
eps: number;
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
maxDecimalsCap?: number;
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
trimTrailingZeros?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
fixedDecimals?: number;
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
allowScientific?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
*/
|
|
29
|
+
snapNearZero?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
zeroThreshold?: number;
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
37
|
+
perAttributeEps?: Record<string, number>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
42
|
+
export interface IXmlSerializerFormatOptions {
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
46
|
+
number?: IXmlSerializerNumberOptions;
|
|
47
|
+
}
|
|
48
|
+
export declare const DefaultXmlSerializerNumberOptions: Readonly<IXmlSerializerNumberOptions>;
|
|
49
|
+
export declare const DefaultXmlSerializerFormatOptions: Readonly<IXmlSerializerFormatOptions>;
|
|
50
|
+
/**
|
|
51
|
+
*@param opts
|
|
52
|
+
*@returns
|
|
53
|
+
*/
|
|
54
|
+
export declare function ResolveNumberOptions(opts?: IXmlSerializerNumberOptions): Required<Omit<IXmlSerializerNumberOptions, "perAttributeEps" | "fixedDecimals" | "maxDecimalsCap" | "trimTrailingZeros" | "allowScientific" | "snapNearZero" | "zeroThreshold">> & Pick<IXmlSerializerNumberOptions, "perAttributeEps" | "fixedDecimals"> & {
|
|
55
|
+
maxDecimalsCap: number;
|
|
56
|
+
trimTrailingZeros: boolean;
|
|
57
|
+
allowScientific: boolean;
|
|
58
|
+
snapNearZero: boolean;
|
|
59
|
+
zeroThreshold: number;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
*@param opts
|
|
63
|
+
*@returns
|
|
64
|
+
*/
|
|
65
|
+
export declare function ResolveFormatOptions(opts?: IXmlSerializerFormatOptions): {
|
|
66
|
+
number: Required<Omit<IXmlSerializerNumberOptions, "maxDecimalsCap" | "trimTrailingZeros" | "fixedDecimals" | "allowScientific" | "snapNearZero" | "zeroThreshold" | "perAttributeEps">> & Pick<IXmlSerializerNumberOptions, "fixedDecimals" | "perAttributeEps"> & {
|
|
67
|
+
maxDecimalsCap: number;
|
|
68
|
+
trimTrailingZeros: boolean;
|
|
69
|
+
allowScientific: boolean;
|
|
70
|
+
snapNearZero: boolean;
|
|
71
|
+
zeroThreshold: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
*
|
|
76
|
+
*/
|
|
77
|
+
export declare class NumberFormatter implements IFormatter<number> {
|
|
78
|
+
o: IXmlSerializerFormatOptions;
|
|
79
|
+
private _o;
|
|
80
|
+
/**
|
|
81
|
+
*
|
|
82
|
+
* @param o
|
|
83
|
+
*/
|
|
84
|
+
constructor(o: IXmlSerializerFormatOptions);
|
|
85
|
+
/**
|
|
86
|
+
*
|
|
87
|
+
* @param x
|
|
88
|
+
* @returns
|
|
89
|
+
*/
|
|
90
|
+
toString(x: number): string;
|
|
91
|
+
private _clampInt;
|
|
92
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export const DefaultXmlSerializerNumberOptions = Object.freeze({
|
|
2
|
+
eps: 1e-6,
|
|
3
|
+
maxDecimalsCap: 15,
|
|
4
|
+
trimTrailingZeros: true,
|
|
5
|
+
// fixedDecimals: undefined,
|
|
6
|
+
allowScientific: false,
|
|
7
|
+
snapNearZero: true,
|
|
8
|
+
// zeroThreshold defaults to eps if not provided
|
|
9
|
+
// perAttributeEps: undefined,
|
|
10
|
+
});
|
|
11
|
+
export const DefaultXmlSerializerFormatOptions = Object.freeze({
|
|
12
|
+
number: DefaultXmlSerializerNumberOptions,
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
*@param opts
|
|
16
|
+
*@returns
|
|
17
|
+
*/
|
|
18
|
+
export function ResolveNumberOptions(opts) {
|
|
19
|
+
const eps = opts?.eps ?? DefaultXmlSerializerNumberOptions.eps;
|
|
20
|
+
return {
|
|
21
|
+
eps,
|
|
22
|
+
maxDecimalsCap: opts?.maxDecimalsCap ?? DefaultXmlSerializerNumberOptions.maxDecimalsCap,
|
|
23
|
+
trimTrailingZeros: opts?.trimTrailingZeros ?? DefaultXmlSerializerNumberOptions.trimTrailingZeros,
|
|
24
|
+
fixedDecimals: opts?.fixedDecimals,
|
|
25
|
+
allowScientific: opts?.allowScientific ?? DefaultXmlSerializerNumberOptions.allowScientific,
|
|
26
|
+
snapNearZero: opts?.snapNearZero ?? DefaultXmlSerializerNumberOptions.snapNearZero,
|
|
27
|
+
zeroThreshold: opts?.zeroThreshold ?? eps,
|
|
28
|
+
perAttributeEps: opts?.perAttributeEps,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
*@param opts
|
|
33
|
+
*@returns
|
|
34
|
+
*/
|
|
35
|
+
export function ResolveFormatOptions(opts) {
|
|
36
|
+
return {
|
|
37
|
+
number: ResolveNumberOptions(opts?.number),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
*/
|
|
43
|
+
export class NumberFormatter {
|
|
44
|
+
/**
|
|
45
|
+
*
|
|
46
|
+
* @param o
|
|
47
|
+
*/
|
|
48
|
+
constructor(o) {
|
|
49
|
+
this.o = o;
|
|
50
|
+
this._o = o.number;
|
|
51
|
+
if (!Number.isFinite(this._o.eps) || this._o.eps <= 0) {
|
|
52
|
+
throw new Error("opts.eps must be a finite, positive number");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
*
|
|
57
|
+
* @param x
|
|
58
|
+
* @returns
|
|
59
|
+
*/
|
|
60
|
+
toString(x) {
|
|
61
|
+
if (!Number.isFinite(x)) {
|
|
62
|
+
throw new Error(`Cannot format non-finite number: ${x}`);
|
|
63
|
+
}
|
|
64
|
+
const opts = this._o;
|
|
65
|
+
const maxDecimalsCap = this._clampInt(opts.maxDecimalsCap ?? 15, 0, 20);
|
|
66
|
+
const trimTrailingZeros = opts.trimTrailingZeros ?? true;
|
|
67
|
+
const snapNearZero = opts.snapNearZero ?? true;
|
|
68
|
+
const zeroThreshold = opts.zeroThreshold ?? opts.eps;
|
|
69
|
+
// Quantize to eps grid
|
|
70
|
+
const inv = 1 / opts.eps;
|
|
71
|
+
let q = Math.round(x * inv) / inv;
|
|
72
|
+
// Normalize -0 to 0
|
|
73
|
+
if (Object.is(q, -0)) {
|
|
74
|
+
q = 0;
|
|
75
|
+
}
|
|
76
|
+
// Snap tiny values to 0 (helps size + stability)
|
|
77
|
+
if (snapNearZero && Math.abs(q) <= zeroThreshold) {
|
|
78
|
+
q = 0;
|
|
79
|
+
}
|
|
80
|
+
// Choose decimals policy
|
|
81
|
+
let decimals;
|
|
82
|
+
if (opts.fixedDecimals !== undefined) {
|
|
83
|
+
decimals = this._clampInt(opts.fixedDecimals, 0, maxDecimalsCap);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// decimals needed for eps steps
|
|
87
|
+
decimals = this._clampInt(Math.ceil(-Math.log10(opts.eps)), 0, maxDecimalsCap);
|
|
88
|
+
}
|
|
89
|
+
// Note: this implementation intentionally avoids scientific notation.
|
|
90
|
+
// If allowScientific=true, you may want a different path (toPrecision).
|
|
91
|
+
if (opts.allowScientific) {
|
|
92
|
+
// Still avoid scientific here; keep deterministic fixed output.
|
|
93
|
+
// If you really want scientific, implement a separate branch.
|
|
94
|
+
}
|
|
95
|
+
// Fast path when decimals = 0
|
|
96
|
+
if (decimals === 0) {
|
|
97
|
+
return String(Math.trunc(q));
|
|
98
|
+
}
|
|
99
|
+
// Start fixed, then optionally trim
|
|
100
|
+
let s = q.toFixed(decimals);
|
|
101
|
+
if (trimTrailingZeros && opts.fixedDecimals === undefined) {
|
|
102
|
+
// Trim trailing zeros and optional trailing dot
|
|
103
|
+
s = s
|
|
104
|
+
.replace(/(\.\d*?[1-9])0+$/, "$1")
|
|
105
|
+
.replace(/\.0+$/, "")
|
|
106
|
+
.replace(/\.$/, "");
|
|
107
|
+
}
|
|
108
|
+
// Safety: ensure no scientific notation (should not happen with toFixed)
|
|
109
|
+
if (/[eE]/.test(s)) {
|
|
110
|
+
throw new Error(`Scientific notation not allowed in XML output: ${s}`);
|
|
111
|
+
}
|
|
112
|
+
return s;
|
|
113
|
+
}
|
|
114
|
+
_clampInt(n, min, max) {
|
|
115
|
+
if (!Number.isFinite(n)) {
|
|
116
|
+
return min;
|
|
117
|
+
}
|
|
118
|
+
n = Math.trunc(n);
|
|
119
|
+
return Math.max(min, Math.min(max, n));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=xml.serializer.format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml.serializer.format.js","sourceRoot":"","sources":["../../../../../../dev/serializers/src/3MF/core/xml/xml.serializer.format.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IFormatter } from \"./xml.interfaces\";\r\n\r\n/**\r\n *\r\n */\r\nexport interface IXmlSerializerNumberOptions {\r\n /**\r\n *\r\n */\r\n eps: number;\r\n /**\r\n *\r\n */\r\n maxDecimalsCap?: number; // default 15\r\n /**\r\n *\r\n */\r\n trimTrailingZeros?: boolean; // default true\r\n /**\r\n *\r\n */\r\n fixedDecimals?: number; // optional, overrides trim\r\n /**\r\n *\r\n */\r\n allowScientific?: boolean; // default false\r\n /**\r\n *\r\n */\r\n snapNearZero?: boolean; // default true\r\n /**\r\n *\r\n */\r\n zeroThreshold?: number; // default eps\r\n /**\r\n *\r\n */\r\n perAttributeEps?: Record<string, number>;\r\n}\r\n\r\n/**\r\n *\r\n */\r\nexport interface IXmlSerializerFormatOptions {\r\n /**\r\n *\r\n */\r\n number?: IXmlSerializerNumberOptions;\r\n}\r\n\r\nexport const DefaultXmlSerializerNumberOptions: Readonly<IXmlSerializerNumberOptions> = Object.freeze({\r\n eps: 1e-6,\r\n maxDecimalsCap: 15,\r\n trimTrailingZeros: true,\r\n // fixedDecimals: undefined,\r\n allowScientific: false,\r\n snapNearZero: true,\r\n // zeroThreshold defaults to eps if not provided\r\n // perAttributeEps: undefined,\r\n});\r\n\r\nexport const DefaultXmlSerializerFormatOptions: Readonly<IXmlSerializerFormatOptions> = Object.freeze({\r\n number: DefaultXmlSerializerNumberOptions,\r\n});\r\n\r\n/**\r\n *@param opts\r\n *@returns\r\n */\r\nexport function ResolveNumberOptions(opts?: IXmlSerializerNumberOptions): Required<\r\n Omit<IXmlSerializerNumberOptions, \"perAttributeEps\" | \"fixedDecimals\" | \"maxDecimalsCap\" | \"trimTrailingZeros\" | \"allowScientific\" | \"snapNearZero\" | \"zeroThreshold\">\r\n> &\r\n Pick<IXmlSerializerNumberOptions, \"perAttributeEps\" | \"fixedDecimals\"> & {\r\n maxDecimalsCap: number;\r\n trimTrailingZeros: boolean;\r\n allowScientific: boolean;\r\n snapNearZero: boolean;\r\n zeroThreshold: number;\r\n } {\r\n const eps = opts?.eps ?? DefaultXmlSerializerNumberOptions.eps;\r\n\r\n return {\r\n eps,\r\n maxDecimalsCap: opts?.maxDecimalsCap ?? DefaultXmlSerializerNumberOptions.maxDecimalsCap!,\r\n trimTrailingZeros: opts?.trimTrailingZeros ?? DefaultXmlSerializerNumberOptions.trimTrailingZeros!,\r\n fixedDecimals: opts?.fixedDecimals,\r\n allowScientific: opts?.allowScientific ?? DefaultXmlSerializerNumberOptions.allowScientific!,\r\n snapNearZero: opts?.snapNearZero ?? DefaultXmlSerializerNumberOptions.snapNearZero!,\r\n zeroThreshold: opts?.zeroThreshold ?? eps,\r\n perAttributeEps: opts?.perAttributeEps,\r\n };\r\n}\r\n\r\n/**\r\n *@param opts\r\n *@returns\r\n */\r\nexport function ResolveFormatOptions(opts?: IXmlSerializerFormatOptions) {\r\n return {\r\n number: ResolveNumberOptions(opts?.number),\r\n };\r\n}\r\n\r\n/**\r\n *\r\n */\r\nexport class NumberFormatter implements IFormatter<number> {\r\n private _o: IXmlSerializerNumberOptions;\r\n\r\n /**\r\n *\r\n * @param o\r\n */\r\n public constructor(public o: IXmlSerializerFormatOptions) {\r\n this._o = o.number!;\r\n\r\n if (!Number.isFinite(this._o.eps) || this._o.eps <= 0) {\r\n throw new Error(\"opts.eps must be a finite, positive number\");\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param x\r\n * @returns\r\n */\r\n\r\n public toString(x: number): string {\r\n if (!Number.isFinite(x)) {\r\n throw new Error(`Cannot format non-finite number: ${x}`);\r\n }\r\n\r\n const opts = this._o;\r\n const maxDecimalsCap = this._clampInt(opts.maxDecimalsCap ?? 15, 0, 20);\r\n const trimTrailingZeros = opts.trimTrailingZeros ?? true;\r\n const snapNearZero = opts.snapNearZero ?? true;\r\n const zeroThreshold = opts.zeroThreshold ?? opts.eps;\r\n\r\n // Quantize to eps grid\r\n const inv = 1 / opts.eps;\r\n let q = Math.round(x * inv) / inv;\r\n\r\n // Normalize -0 to 0\r\n if (Object.is(q, -0)) {\r\n q = 0;\r\n }\r\n\r\n // Snap tiny values to 0 (helps size + stability)\r\n if (snapNearZero && Math.abs(q) <= zeroThreshold) {\r\n q = 0;\r\n }\r\n\r\n // Choose decimals policy\r\n let decimals: number;\r\n if (opts.fixedDecimals !== undefined) {\r\n decimals = this._clampInt(opts.fixedDecimals, 0, maxDecimalsCap);\r\n } else {\r\n // decimals needed for eps steps\r\n decimals = this._clampInt(Math.ceil(-Math.log10(opts.eps)), 0, maxDecimalsCap);\r\n }\r\n\r\n // Note: this implementation intentionally avoids scientific notation.\r\n // If allowScientific=true, you may want a different path (toPrecision).\r\n if (opts.allowScientific) {\r\n // Still avoid scientific here; keep deterministic fixed output.\r\n // If you really want scientific, implement a separate branch.\r\n }\r\n\r\n // Fast path when decimals = 0\r\n if (decimals === 0) {\r\n return String(Math.trunc(q));\r\n }\r\n\r\n // Start fixed, then optionally trim\r\n let s = q.toFixed(decimals);\r\n\r\n if (trimTrailingZeros && opts.fixedDecimals === undefined) {\r\n // Trim trailing zeros and optional trailing dot\r\n s = s\r\n .replace(/(\\.\\d*?[1-9])0+$/, \"$1\")\r\n .replace(/\\.0+$/, \"\")\r\n .replace(/\\.$/, \"\");\r\n }\r\n\r\n // Safety: ensure no scientific notation (should not happen with toFixed)\r\n if (/[eE]/.test(s)) {\r\n throw new Error(`Scientific notation not allowed in XML output: ${s}`);\r\n }\r\n\r\n return s;\r\n }\r\n\r\n private _clampInt(n: number, min: number, max: number): number {\r\n if (!Number.isFinite(n)) {\r\n return min;\r\n }\r\n n = Math.trunc(n);\r\n return Math.max(min, Math.min(max, n));\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { XmlNameToParts, GetXmlName, ToQualifiedString, GetXmlFieldMeta } from "./xml.interfaces.js";
|
|
2
|
+
import { NumberFormatter, ResolveFormatOptions } from "./xml.serializer.format.js";
|
|
3
|
+
function IsDate(x) {
|
|
4
|
+
return x instanceof Date;
|
|
5
|
+
}
|
|
6
|
+
function IsString(x) {
|
|
7
|
+
return typeof x === "string";
|
|
8
|
+
}
|
|
9
|
+
function IsNumber(x) {
|
|
10
|
+
return typeof x === "number";
|
|
11
|
+
}
|
|
12
|
+
function IsPrimitive(x) {
|
|
13
|
+
return typeof x === "string" || typeof x === "number" || typeof x === "boolean" || typeof x === "bigint" || IsDate(x);
|
|
14
|
+
}
|
|
15
|
+
function IsPrimitiveButString(x) {
|
|
16
|
+
return typeof x === "number" || typeof x === "boolean" || typeof x === "bigint" || IsDate(x);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
*/
|
|
20
|
+
export class XmlSerializer {
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param builder
|
|
24
|
+
* @param format
|
|
25
|
+
*/
|
|
26
|
+
constructor(builder, format) {
|
|
27
|
+
/** */
|
|
28
|
+
this._ns = new Map();
|
|
29
|
+
/** */
|
|
30
|
+
this._prefixCount = 0;
|
|
31
|
+
this._builder = builder;
|
|
32
|
+
this._format = ResolveFormatOptions(format);
|
|
33
|
+
this._nFmt = new NumberFormatter(this._format);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @param ns
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
withNamespace(...ns) {
|
|
41
|
+
for (const s of ns) {
|
|
42
|
+
this._assignNamespace(s);
|
|
43
|
+
}
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @param root
|
|
49
|
+
* @param name
|
|
50
|
+
*/
|
|
51
|
+
serialize(root, name) {
|
|
52
|
+
name = name ?? GetXmlName(root);
|
|
53
|
+
if (!name) {
|
|
54
|
+
throw new Error("can not find name for given object");
|
|
55
|
+
}
|
|
56
|
+
const currentName = XmlNameToParts(name);
|
|
57
|
+
if (currentName.ns) {
|
|
58
|
+
// ensure we register the root namespace as default if not already set...
|
|
59
|
+
this._assignNamespace(currentName.ns, "xmlns");
|
|
60
|
+
}
|
|
61
|
+
this._gatherNamespaces(root, new WeakSet());
|
|
62
|
+
const doc = this._builder.ele(null, currentName.name);
|
|
63
|
+
for (const [v, n] of Array.from(this._ns.entries())) {
|
|
64
|
+
doc.att("xmlns", n, v);
|
|
65
|
+
}
|
|
66
|
+
this._writeObjectContent(doc, root, new WeakSet().add(root));
|
|
67
|
+
this._builder.end();
|
|
68
|
+
}
|
|
69
|
+
_writeObject(builder, source, visited) {
|
|
70
|
+
if (visited.has(source)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
visited.add(source);
|
|
74
|
+
if (Array.isArray(source)) {
|
|
75
|
+
for (const item of source) {
|
|
76
|
+
if (IsPrimitiveButString(item)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (IsString(item)) {
|
|
80
|
+
this._builder.text(item);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
this._writeObject(builder, item, visited);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const qname = GetXmlName(source);
|
|
88
|
+
if (!qname) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const currentName = XmlNameToParts(qname);
|
|
92
|
+
const prefix = this._getPrefix(currentName);
|
|
93
|
+
const tmp = ToQualifiedString(currentName.name, prefix);
|
|
94
|
+
builder.ele(null, tmp);
|
|
95
|
+
this._writeObjectContent(builder, source, visited);
|
|
96
|
+
this._builder.end();
|
|
97
|
+
}
|
|
98
|
+
_getPrefix(qn) {
|
|
99
|
+
if (qn.ns) {
|
|
100
|
+
const p = this._ns.get(qn.ns.toLowerCase());
|
|
101
|
+
if (p !== "xmlns") {
|
|
102
|
+
return p;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
_writeObjectContent(builder, source, visited) {
|
|
108
|
+
// gather meta and build index
|
|
109
|
+
const metas = GetXmlFieldMeta(source) ?? [];
|
|
110
|
+
const metaByProp = new Map();
|
|
111
|
+
for (const m of metas) {
|
|
112
|
+
const arr = metaByProp.get(m.prop) ?? [];
|
|
113
|
+
arr.push(m);
|
|
114
|
+
metaByProp.set(m.prop, arr);
|
|
115
|
+
}
|
|
116
|
+
// ensure the att are processed first, otherwize, the tag might be closed...
|
|
117
|
+
const keys = Object.keys(source).sort((a, b) => {
|
|
118
|
+
const aHasAttr = (metaByProp.get(a) ?? []).some((m) => m.kind === "attr");
|
|
119
|
+
const bHasAttr = (metaByProp.get(b) ?? []).some((m) => m.kind === "attr");
|
|
120
|
+
if (aHasAttr === bHasAttr) {
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
return aHasAttr ? -1 : 1; // attr d abord
|
|
124
|
+
});
|
|
125
|
+
// We decide per property, using metadata if present
|
|
126
|
+
for (const prop of keys) {
|
|
127
|
+
const value = source[prop];
|
|
128
|
+
if (value === null || value === undefined) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const propMetas = metaByProp.get(prop);
|
|
132
|
+
if (propMetas) {
|
|
133
|
+
const ignored = propMetas.some((m) => m.ignore === true || m.kind === "none");
|
|
134
|
+
if (ignored) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
for (const m of propMetas) {
|
|
138
|
+
const name = m.name ?? m.prop.toLowerCase(); // if the name is not defined, we assume it's the lower case version of name of the property.
|
|
139
|
+
if (name) {
|
|
140
|
+
switch (m.kind) {
|
|
141
|
+
case "attr": {
|
|
142
|
+
let vStr = null;
|
|
143
|
+
if (IsNumber(value) && this._nFmt) {
|
|
144
|
+
vStr = this._nFmt.toString(value);
|
|
145
|
+
}
|
|
146
|
+
if (m.formatter) {
|
|
147
|
+
// TODO : cache the created formatter to avoid to many allocation.
|
|
148
|
+
const f = new m.formatter(this._format);
|
|
149
|
+
vStr = f.toString(value);
|
|
150
|
+
}
|
|
151
|
+
vStr = vStr ?? value.toString();
|
|
152
|
+
if (vStr) {
|
|
153
|
+
const currentName = XmlNameToParts(name);
|
|
154
|
+
const prefix = this._getPrefix(currentName);
|
|
155
|
+
const tmp = ToQualifiedString(currentName.name, prefix);
|
|
156
|
+
builder.att(null, tmp, vStr);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (IsPrimitiveButString(value)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (IsString(value)) {
|
|
169
|
+
this._builder.text(value);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
this._writeObject(builder, value, visited);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// this is the first browse of the hierarchy to collect the namespaces and assign placeholder.( ns0, ns1,...)
|
|
176
|
+
_gatherNamespaces(tag, visited) {
|
|
177
|
+
if (visited.has(tag)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
visited.add(tag);
|
|
181
|
+
if (Array.isArray(tag)) {
|
|
182
|
+
for (const item of tag) {
|
|
183
|
+
if (IsPrimitive(item)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
this._gatherNamespaces(item, visited);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const qname = GetXmlName(tag);
|
|
191
|
+
if (qname) {
|
|
192
|
+
this._assignNamespace(qname);
|
|
193
|
+
}
|
|
194
|
+
// gather meta and build index
|
|
195
|
+
const metas = GetXmlFieldMeta(tag) ?? [];
|
|
196
|
+
const metaByProp = new Map();
|
|
197
|
+
for (const m of metas) {
|
|
198
|
+
const arr = metaByProp.get(m.prop) ?? [];
|
|
199
|
+
arr.push(m);
|
|
200
|
+
metaByProp.set(m.prop, arr);
|
|
201
|
+
}
|
|
202
|
+
// We decide per property, using metadata if present
|
|
203
|
+
const toVisit = [];
|
|
204
|
+
for (const prop of Object.keys(tag)) {
|
|
205
|
+
const value = tag[prop];
|
|
206
|
+
if (value === null || value === undefined) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const propMetas = metaByProp.get(prop);
|
|
210
|
+
if (propMetas) {
|
|
211
|
+
const ignored = propMetas.some((m) => m.ignore === true || m.kind === "none");
|
|
212
|
+
if (ignored) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
for (const m of propMetas) {
|
|
216
|
+
if (m.name) {
|
|
217
|
+
this._assignNamespace(m.name);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
toVisit.push(value);
|
|
222
|
+
}
|
|
223
|
+
for (const v of toVisit) {
|
|
224
|
+
if (IsPrimitive(v)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
this._gatherNamespaces(v, visited);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
_assignNamespace(qn, prefix) {
|
|
231
|
+
const nqn = XmlNameToParts(qn);
|
|
232
|
+
if (nqn?.ns) {
|
|
233
|
+
const ns = nqn.ns.toLowerCase();
|
|
234
|
+
if (!this._ns.get(ns)) {
|
|
235
|
+
this._ns.set(ns, prefix ?? this._buildNsPrefix(ns));
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (prefix === "xmlns") {
|
|
240
|
+
const ns = nqn.name.toLowerCase();
|
|
241
|
+
if (!this._ns.get(ns)) {
|
|
242
|
+
this._ns.set(ns, prefix ?? this._buildNsPrefix(ns));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
_buildNsPrefix(_ns) {
|
|
247
|
+
let alreadyReferenced = false;
|
|
248
|
+
let value;
|
|
249
|
+
do {
|
|
250
|
+
value = `ns${this._prefixCount++}`;
|
|
251
|
+
for (const v of Array.from(this._ns.values())) {
|
|
252
|
+
if (v === value) {
|
|
253
|
+
alreadyReferenced = true;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} while (alreadyReferenced);
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=xml.serializer.js.map
|