@hla4ts/fom-codegen 0.1.0
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/README.md +247 -0
- package/package.json +29 -0
- package/src/cli.ts +179 -0
- package/src/generated/index.ts +25 -0
- package/src/generated/xsd-types.ts +2073 -0
- package/src/index.ts +62 -0
- package/src/registry.ts +127 -0
- package/src/ts-codegen.ts +59 -0
- package/src/types.ts +172 -0
- package/src/xml-parser.ts +359 -0
- package/src/xml-writer.ts +502 -0
- package/src/xsd-parser.ts +404 -0
- package/src/xsd-type-generator.ts +452 -0
- package/src/xsd-types-adapter.ts +177 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XSD to TypeScript Type Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates TypeScript type definitions from parsed XSD schemas.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { XsdSchema, XsdType, XsdElement } from "./xsd-parser.ts";
|
|
8
|
+
|
|
9
|
+
export interface TypeGenerationOptions {
|
|
10
|
+
outputFile?: string;
|
|
11
|
+
format?: "omt" | "fdd" | "dif";
|
|
12
|
+
includeDocumentation?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate TypeScript types from an XSD schema.
|
|
17
|
+
*/
|
|
18
|
+
export function generateTypesFromXsd(
|
|
19
|
+
schema: XsdSchema,
|
|
20
|
+
options: TypeGenerationOptions = {}
|
|
21
|
+
): string {
|
|
22
|
+
const { includeDocumentation = true } = options;
|
|
23
|
+
const lines: string[] = [];
|
|
24
|
+
|
|
25
|
+
// Header
|
|
26
|
+
lines.push("/**");
|
|
27
|
+
lines.push(" * Generated TypeScript types from IEEE 1516-2025 XSD Schema");
|
|
28
|
+
lines.push(" *");
|
|
29
|
+
lines.push(" * This file is auto-generated. Do not edit manually.");
|
|
30
|
+
lines.push(" * Regenerate with: bun run scripts/generate-types.ts");
|
|
31
|
+
lines.push(" */");
|
|
32
|
+
lines.push("");
|
|
33
|
+
lines.push("// ============================================================================");
|
|
34
|
+
lines.push("// Enumerations");
|
|
35
|
+
lines.push("// ============================================================================");
|
|
36
|
+
lines.push("");
|
|
37
|
+
|
|
38
|
+
// Generate enumerations first
|
|
39
|
+
for (const [typeName, type] of schema.types.entries()) {
|
|
40
|
+
if (type.kind === "enum" && type.enumValues) {
|
|
41
|
+
generateEnum(lines, typeName, type, includeDocumentation);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push("// ============================================================================");
|
|
47
|
+
lines.push("// Complex Types");
|
|
48
|
+
lines.push("// ============================================================================");
|
|
49
|
+
lines.push("");
|
|
50
|
+
|
|
51
|
+
// Generate complex types (track generated to avoid duplicates)
|
|
52
|
+
const generatedInterfaces = new Set<string>();
|
|
53
|
+
for (const [typeName, type] of schema.types.entries()) {
|
|
54
|
+
if (type.kind === "complex") {
|
|
55
|
+
const interfaceName = sanitizeTypeName(typeName);
|
|
56
|
+
if (!generatedInterfaces.has(interfaceName)) {
|
|
57
|
+
generatedInterfaces.add(interfaceName);
|
|
58
|
+
generateInterface(lines, typeName, type, schema, includeDocumentation);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Generate root element type if exists
|
|
64
|
+
if (schema.rootElement) {
|
|
65
|
+
const rootElement = schema.elements.get(schema.rootElement);
|
|
66
|
+
if (rootElement) {
|
|
67
|
+
const rootTypeName = resolveTypeName(rootElement.type);
|
|
68
|
+
const sanitizedRootTypeName = sanitizeTypeName(rootTypeName);
|
|
69
|
+
lines.push("");
|
|
70
|
+
lines.push("// ============================================================================");
|
|
71
|
+
lines.push("// Root Element");
|
|
72
|
+
lines.push("// ============================================================================");
|
|
73
|
+
lines.push("");
|
|
74
|
+
lines.push(`export type ${capitalize(schema.rootElement)}Type = ${sanitizedRootTypeName};`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return lines.join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function generateEnum(
|
|
82
|
+
lines: string[],
|
|
83
|
+
typeName: string,
|
|
84
|
+
type: XsdType,
|
|
85
|
+
includeDocumentation: boolean
|
|
86
|
+
): void {
|
|
87
|
+
if (!type.enumValues || type.enumValues.length === 0) return;
|
|
88
|
+
|
|
89
|
+
if (includeDocumentation && type.documentation) {
|
|
90
|
+
lines.push("/**");
|
|
91
|
+
lines.push(` * ${type.documentation}`);
|
|
92
|
+
lines.push(" */");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const enumName = sanitizeTypeName(typeName);
|
|
96
|
+
const values = type.enumValues.map((v) => `"${v}"`).join(" | ");
|
|
97
|
+
lines.push(`export type ${enumName} = ${values};`);
|
|
98
|
+
lines.push("");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function generateInterface(
|
|
102
|
+
lines: string[],
|
|
103
|
+
typeName: string,
|
|
104
|
+
type: XsdType,
|
|
105
|
+
schema: XsdSchema,
|
|
106
|
+
includeDocumentation: boolean
|
|
107
|
+
): void {
|
|
108
|
+
if (includeDocumentation && type.documentation) {
|
|
109
|
+
lines.push("/**");
|
|
110
|
+
lines.push(` * ${type.documentation}`);
|
|
111
|
+
lines.push(" */");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const interfaceName = sanitizeTypeName(typeName);
|
|
115
|
+
// Track property names to avoid duplicates
|
|
116
|
+
const seenProperties = new Set<string>();
|
|
117
|
+
|
|
118
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
119
|
+
|
|
120
|
+
// Handle base type (inheritance)
|
|
121
|
+
if (type.baseType) {
|
|
122
|
+
const baseTypeName = resolveTypeName(type.baseType);
|
|
123
|
+
lines.push(` // Extends ${baseTypeName}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Generate properties from elements (skip elements without names)
|
|
127
|
+
for (const element of type.elements) {
|
|
128
|
+
if (element.name && element.name.trim() !== "") {
|
|
129
|
+
// Skip if we've already seen this property name
|
|
130
|
+
if (!seenProperties.has(element.name)) {
|
|
131
|
+
seenProperties.add(element.name);
|
|
132
|
+
generateProperty(lines, element, schema, includeDocumentation);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Generate properties from attributes (skip attributes without names)
|
|
138
|
+
for (const attr of type.attributes) {
|
|
139
|
+
if (attr.name && attr.name.trim() !== "") {
|
|
140
|
+
// Skip if we've already seen this property name
|
|
141
|
+
if (!seenProperties.has(attr.name)) {
|
|
142
|
+
seenProperties.add(attr.name);
|
|
143
|
+
generateAttributeProperty(lines, attr, schema);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
lines.push("}");
|
|
149
|
+
lines.push("");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function generateProperty(
|
|
153
|
+
lines: string[],
|
|
154
|
+
element: XsdElement,
|
|
155
|
+
schema: XsdSchema,
|
|
156
|
+
includeDocumentation: boolean
|
|
157
|
+
): void {
|
|
158
|
+
if (includeDocumentation && element.documentation) {
|
|
159
|
+
lines.push(` /**`);
|
|
160
|
+
lines.push(` * ${element.documentation}`);
|
|
161
|
+
lines.push(` */`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const propName = element.name;
|
|
165
|
+
const typeName = resolveTypeName(element.type, schema);
|
|
166
|
+
let tsType = mapXsdTypeToTs(typeName, schema, element.name);
|
|
167
|
+
|
|
168
|
+
// Fallback for empty or unresolved types
|
|
169
|
+
if (!tsType || tsType.trim() === "") {
|
|
170
|
+
tsType = "unknown";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let propertyType = tsType;
|
|
174
|
+
if (element.array) {
|
|
175
|
+
propertyType = `${tsType}[]`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const optional = element.optional ? "?" : "";
|
|
179
|
+
lines.push(` ${propName}${optional}: ${propertyType};`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function generateAttributeProperty(
|
|
183
|
+
lines: string[],
|
|
184
|
+
attr: { name: string; type: string; required: boolean },
|
|
185
|
+
schema: XsdSchema
|
|
186
|
+
): void {
|
|
187
|
+
const propName = attr.name;
|
|
188
|
+
const typeName = resolveTypeName(attr.type, schema);
|
|
189
|
+
let tsType = mapXsdTypeToTs(typeName, schema);
|
|
190
|
+
|
|
191
|
+
// Fallback for empty or unresolved types
|
|
192
|
+
if (!tsType || tsType.trim() === "") {
|
|
193
|
+
tsType = "unknown";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const optional = attr.required ? "" : "?";
|
|
197
|
+
lines.push(` ${propName}${optional}: ${tsType};`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function resolveTypeName(typeRef: string, schema?: XsdSchema): string {
|
|
201
|
+
// Remove namespace prefix (e.g., "xs:string" -> "string")
|
|
202
|
+
const parts = typeRef.split(":");
|
|
203
|
+
const localName = parts[parts.length - 1];
|
|
204
|
+
|
|
205
|
+
// Map XSD built-in types
|
|
206
|
+
const builtInTypes: Record<string, string> = {
|
|
207
|
+
string: "string",
|
|
208
|
+
int: "number",
|
|
209
|
+
integer: "number",
|
|
210
|
+
long: "number",
|
|
211
|
+
short: "number",
|
|
212
|
+
byte: "number",
|
|
213
|
+
decimal: "number",
|
|
214
|
+
float: "number",
|
|
215
|
+
double: "number",
|
|
216
|
+
boolean: "boolean",
|
|
217
|
+
date: "string", // ISO date string
|
|
218
|
+
dateTime: "string", // ISO datetime string
|
|
219
|
+
time: "string",
|
|
220
|
+
duration: "string",
|
|
221
|
+
anyURI: "string",
|
|
222
|
+
ID: "string",
|
|
223
|
+
IDREF: "string",
|
|
224
|
+
NCName: "string",
|
|
225
|
+
base64Binary: "string",
|
|
226
|
+
anyType: "unknown",
|
|
227
|
+
positiveInteger: "number", // XSD positiveInteger maps to number
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
if (builtInTypes[localName]) {
|
|
231
|
+
return builtInTypes[localName];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Check if it's a type defined in the schema
|
|
235
|
+
if (schema && schema.types.has(localName)) {
|
|
236
|
+
const type = schema.types.get(localName)!;
|
|
237
|
+
|
|
238
|
+
// If it's an enum, return the enum type name
|
|
239
|
+
if (type.kind === "enum") {
|
|
240
|
+
return sanitizeTypeName(localName);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// If it's a simple type with a base type, resolve the base type
|
|
244
|
+
if (type.kind === "simple" && type.baseType) {
|
|
245
|
+
return resolveTypeName(type.baseType, schema);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// If it's a complex type with simpleContent, resolve the base type recursively
|
|
249
|
+
if (type.kind === "complex" && type.baseType) {
|
|
250
|
+
const baseResolved = resolveTypeName(type.baseType, schema);
|
|
251
|
+
// If base resolves to a primitive, return it
|
|
252
|
+
if (builtInTypes[baseResolved] || ["string", "number", "boolean"].includes(baseResolved)) {
|
|
253
|
+
return baseResolved;
|
|
254
|
+
}
|
|
255
|
+
// If base resolves to an enum, return the enum name (not the complex type wrapper)
|
|
256
|
+
const baseType = schema.types.get(baseResolved);
|
|
257
|
+
if (baseType && baseType.kind === "enum") {
|
|
258
|
+
return sanitizeTypeName(baseResolved);
|
|
259
|
+
}
|
|
260
|
+
// Otherwise, continue resolving
|
|
261
|
+
return baseResolved;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return sanitizeTypeName(localName);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Default: return as-is (will be sanitized later)
|
|
268
|
+
return localName;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function mapXsdTypeToTs(typeName: string, schema: XsdSchema, elementName?: string): string {
|
|
272
|
+
const resolved = resolveTypeName(typeName, schema);
|
|
273
|
+
|
|
274
|
+
// If it's a primitive, return it
|
|
275
|
+
if (
|
|
276
|
+
["string", "number", "boolean", "unknown"].includes(resolved)
|
|
277
|
+
) {
|
|
278
|
+
return resolved;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Get the type from schema - try both with and without "Type" suffix
|
|
282
|
+
let type = schema.types.get(resolved);
|
|
283
|
+
let typeKey = resolved;
|
|
284
|
+
|
|
285
|
+
// If not found, try with "Type" suffix
|
|
286
|
+
if (!type && !resolved.endsWith("Type")) {
|
|
287
|
+
const withType = resolved + "Type";
|
|
288
|
+
type = schema.types.get(withType);
|
|
289
|
+
if (type) {
|
|
290
|
+
typeKey = withType;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check if it's an enum type
|
|
295
|
+
if (type && type.kind === "enum") {
|
|
296
|
+
return sanitizeTypeName(typeKey);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Check if it's a complex type with simpleContent (like String, Model, etc.)
|
|
300
|
+
// These should resolve to their base type
|
|
301
|
+
if (type && type.kind === "complex" && type.baseType) {
|
|
302
|
+
const baseResolved = resolveTypeName(type.baseType, schema);
|
|
303
|
+
// If base resolves to a primitive, use that
|
|
304
|
+
if (["string", "number", "boolean"].includes(baseResolved)) {
|
|
305
|
+
return baseResolved;
|
|
306
|
+
}
|
|
307
|
+
// Check if base type is an enum - this is the key case for Model -> OMTypeEnumeration
|
|
308
|
+
const baseType = schema.types.get(baseResolved);
|
|
309
|
+
if (baseType && baseType.kind === "enum") {
|
|
310
|
+
return sanitizeTypeName(baseResolved);
|
|
311
|
+
}
|
|
312
|
+
// Check if base type is a union - resolve to first member if it's an enum
|
|
313
|
+
if (baseType && baseType.kind === "simple" && baseType.baseType) {
|
|
314
|
+
const firstMember = resolveTypeName(baseType.baseType, schema);
|
|
315
|
+
const firstMemberType = schema.types.get(firstMember);
|
|
316
|
+
if (firstMemberType && firstMemberType.kind === "enum") {
|
|
317
|
+
return sanitizeTypeName(firstMember);
|
|
318
|
+
}
|
|
319
|
+
// If union member resolves to string, use string (union of enum + string)
|
|
320
|
+
if (firstMember === "string") {
|
|
321
|
+
return "string";
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Check if base type name suggests it's an enum (fallback)
|
|
325
|
+
if (baseResolved.endsWith("Enumeration")) {
|
|
326
|
+
const enumType = schema.types.get(baseResolved);
|
|
327
|
+
if (enumType && enumType.kind === "enum") {
|
|
328
|
+
return sanitizeTypeName(baseResolved);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// If we can't resolve further, return the complex type name
|
|
332
|
+
// (This handles cases where simpleContent extends a complex type)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Check if it's a simple type with a base type
|
|
336
|
+
if (type && type.kind === "simple" && type.baseType) {
|
|
337
|
+
const baseResolved = resolveTypeName(type.baseType, schema);
|
|
338
|
+
if (["string", "number", "boolean"].includes(baseResolved)) {
|
|
339
|
+
return baseResolved;
|
|
340
|
+
}
|
|
341
|
+
// Check if base is an enum
|
|
342
|
+
const baseType = schema.types.get(baseResolved);
|
|
343
|
+
if (baseType && baseType.kind === "enum") {
|
|
344
|
+
return sanitizeTypeName(baseResolved);
|
|
345
|
+
}
|
|
346
|
+
// Pattern types (dimensionValuePattern, cardinalityPattern) are string-based
|
|
347
|
+
// and should resolve to string
|
|
348
|
+
if (baseResolved === "string") {
|
|
349
|
+
return "string";
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Fallback: if type name suggests a pattern type or unknown simple type, default to string
|
|
354
|
+
if (!type && (resolved.includes("Pattern") || resolved.includes("Enumeration"))) {
|
|
355
|
+
// Try to find it in schema as a simple type
|
|
356
|
+
const simpleType = schema.types.get(resolved);
|
|
357
|
+
if (simpleType && simpleType.kind === "simple" && simpleType.baseType) {
|
|
358
|
+
const baseResolved = resolveTypeName(simpleType.baseType, schema);
|
|
359
|
+
if (baseResolved === "string") {
|
|
360
|
+
return "string";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// If it's an enumeration that wasn't found, it might be a pattern-based enum
|
|
364
|
+
// Default to string for pattern-based types
|
|
365
|
+
if (resolved.includes("Pattern")) {
|
|
366
|
+
return "string";
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Otherwise, it's a complex type - return the interface name
|
|
371
|
+
// The generated interface name is sanitizeTypeName(typeKey)
|
|
372
|
+
// sanitizeTypeName removes "Type" suffix once
|
|
373
|
+
// So if typeKey is "ChangeDefaultAttributeTransportationTypeType", sanitize produces "ChangeDefaultAttributeTransportationType"
|
|
374
|
+
const sanitized = sanitizeTypeName(typeKey);
|
|
375
|
+
|
|
376
|
+
// Special case: if the element name (from XSD) ends with "Type", and we created an inline type,
|
|
377
|
+
// the parser would have created a type with double "Type" (elementName + "Type"),
|
|
378
|
+
// and sanitizeTypeName removes one, leaving us with the correct name.
|
|
379
|
+
// But when we reference it, if the original typeName (from element.type) was the element name itself,
|
|
380
|
+
// we need to ensure we use the correct generated interface name.
|
|
381
|
+
|
|
382
|
+
// If elementName is provided and ends with "Type", and typeKey also ends with "Type",
|
|
383
|
+
// then the generated interface name should keep the "Type" suffix
|
|
384
|
+
if (elementName && elementName.endsWith("Type") && typeKey.endsWith("Type") && type) {
|
|
385
|
+
// The element name ends with "Type", so the parser created a type with double "Type"
|
|
386
|
+
// sanitizeTypeName removes one, so we get the correct name with one "Type"
|
|
387
|
+
return sanitized;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// If typeKey ends with "Type" and we found a type, the generated interface name
|
|
391
|
+
// is sanitized (which removes "Type"), but we might need to add it back if the
|
|
392
|
+
// original XSD type name had it and the parser created it with double "Type"
|
|
393
|
+
// Actually, let's check: if we found the type with "Type" suffix, and sanitize removes it,
|
|
394
|
+
// but the actual generated interface might have "Type" if the parser created it with double "Type"
|
|
395
|
+
|
|
396
|
+
// The safest approach: check what the actual generated interface name would be
|
|
397
|
+
// by looking at what sanitizeTypeName produces for the typeKey
|
|
398
|
+
// If typeKey is "changeDefaultAttributeTransportationTypeType" (from parser),
|
|
399
|
+
// sanitizeTypeName produces "changeDefaultAttributeTransportationType" (removes one "Type")
|
|
400
|
+
// But wait, sanitizeTypeName also capitalizes, so it would be "ChangeDefaultAttributeTransportationType"
|
|
401
|
+
|
|
402
|
+
// Actually, the issue is simpler: when an element name ends with "Type" (like "changeDefaultAttributeTransportationType"),
|
|
403
|
+
// the parser creates type name "ChangeDefaultAttributeTransportationTypeType" (capitalize + "Type"),
|
|
404
|
+
// and sanitizeTypeName("ChangeDefaultAttributeTransportationTypeType") = "ChangeDefaultAttributeTransportationType"
|
|
405
|
+
// So the generated interface IS "ChangeDefaultAttributeTransportationType"
|
|
406
|
+
|
|
407
|
+
// But when we reference it via element.type = "changeDefaultAttributeTransportationType",
|
|
408
|
+
// resolveTypeName doesn't find it (because the type key is "ChangeDefaultAttributeTransportationTypeType"),
|
|
409
|
+
// so it returns sanitizeTypeName("changeDefaultAttributeTransportationType") = "ChangeDefaultAttributeTransportation"
|
|
410
|
+
// Then mapXsdTypeToTs gets "ChangeDefaultAttributeTransportation", doesn't find it,
|
|
411
|
+
// tries "ChangeDefaultAttributeTransportationType", finds it, and should return "ChangeDefaultAttributeTransportationType"
|
|
412
|
+
|
|
413
|
+
// But currently it's returning sanitized = sanitizeTypeName("ChangeDefaultAttributeTransportationType")
|
|
414
|
+
// = "ChangeDefaultAttributeTransportation" (removes "Type")
|
|
415
|
+
|
|
416
|
+
// The fix: if we found the type with "Type" suffix (typeKey ends with "Type"),
|
|
417
|
+
// and sanitizeTypeName would remove it, but the actual generated interface keeps it
|
|
418
|
+
// (because the parser created it with double "Type"), we should return sanitized + "Type"
|
|
419
|
+
if (typeKey.endsWith("Type") && type && typeKey !== sanitized + "Type") {
|
|
420
|
+
// The typeKey had "Type", sanitize removed it, but the actual interface might have "Type"
|
|
421
|
+
// Check if adding "Type" back would match a generated interface
|
|
422
|
+
// Actually, if typeKey is "ChangeDefaultAttributeTransportationType" and we found it,
|
|
423
|
+
// sanitizeTypeName("ChangeDefaultAttributeTransportationType") = "ChangeDefaultAttributeTransportation"
|
|
424
|
+
// But the actual interface is "ChangeDefaultAttributeTransportationType"
|
|
425
|
+
// So we need to add "Type" back
|
|
426
|
+
return sanitized + "Type";
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return sanitized;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function sanitizeTypeName(name: string): string {
|
|
433
|
+
// Remove common prefixes and capitalize
|
|
434
|
+
let sanitized = name
|
|
435
|
+
.replace(/^xs:/, "")
|
|
436
|
+
.replace(/Type$/, "")
|
|
437
|
+
.replace(/^[a-z]/, (c) => c.toUpperCase());
|
|
438
|
+
|
|
439
|
+
// Handle camelCase
|
|
440
|
+
sanitized = sanitized.replace(/([A-Z])/g, "$1");
|
|
441
|
+
|
|
442
|
+
// Ensure it starts with uppercase
|
|
443
|
+
if (sanitized[0] && sanitized[0] === sanitized[0].toLowerCase()) {
|
|
444
|
+
sanitized = sanitized[0].toUpperCase() + sanitized.slice(1);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return sanitized;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function capitalize(str: string): string {
|
|
451
|
+
return str[0].toUpperCase() + str.slice(1);
|
|
452
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XSD Types Adapter
|
|
3
|
+
*
|
|
4
|
+
* Provides adapters to convert between XSD-generated types and FomRegistry schema types.
|
|
5
|
+
* This allows using XSD-generated types for autocomplete while maintaining compatibility
|
|
6
|
+
* with the existing FomRegistry API.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ModelIdentification as XsdModelIdentification,
|
|
11
|
+
ObjectClass as XsdObjectClass,
|
|
12
|
+
InteractionClass as XsdInteractionClass,
|
|
13
|
+
Attribute as XsdAttribute,
|
|
14
|
+
Parameter as XsdParameter,
|
|
15
|
+
SharingEnumeration,
|
|
16
|
+
OrderEnumeration,
|
|
17
|
+
UpdateEnumeration,
|
|
18
|
+
OwnershipEnumeration,
|
|
19
|
+
} from "./generated/xsd-types.ts";
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
ModelIdentification,
|
|
23
|
+
ObjectClassSchema,
|
|
24
|
+
InteractionClassSchema,
|
|
25
|
+
AttributeSchema,
|
|
26
|
+
ParameterSchema,
|
|
27
|
+
} from "./types.ts";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convert XSD ModelIdentification to FomRegistry ModelIdentification.
|
|
31
|
+
* This provides autocomplete based on XSD schema.
|
|
32
|
+
*/
|
|
33
|
+
export function adaptModelIdentification(
|
|
34
|
+
xsd: XsdModelIdentification
|
|
35
|
+
): ModelIdentification {
|
|
36
|
+
return {
|
|
37
|
+
name: xsd.name ?? "",
|
|
38
|
+
type: (xsd.type ?? "FOM") as "FOM" | "SOM",
|
|
39
|
+
version: xsd.version ?? "",
|
|
40
|
+
modificationDate: xsd.modificationDate ?? "",
|
|
41
|
+
securityClassification: xsd.securityClassification ?? "",
|
|
42
|
+
description: xsd.description ?? "",
|
|
43
|
+
purpose: xsd.purpose,
|
|
44
|
+
applicationDomain: xsd.applicationDomain,
|
|
45
|
+
useLimitation: xsd.useLimitation,
|
|
46
|
+
keyword: xsd.keyword?.map((kw) => ({
|
|
47
|
+
taxonomy: kw.taxonomy,
|
|
48
|
+
keywordValue: kw.keywordValue ?? "",
|
|
49
|
+
})),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Convert XSD ObjectClass to FomRegistry ObjectClassSchema.
|
|
55
|
+
* Provides autocomplete based on XSD schema.
|
|
56
|
+
*/
|
|
57
|
+
export function adaptObjectClass(xsd: XsdObjectClass): ObjectClassSchema {
|
|
58
|
+
// XSD uses 'attribute' (singular) for the array property
|
|
59
|
+
const attributes = (xsd as any).attribute || (xsd as any).attributes;
|
|
60
|
+
return {
|
|
61
|
+
name: xsd.name,
|
|
62
|
+
parent: (xsd as any).parent,
|
|
63
|
+
sharing: adaptSharingType(xsd.sharing),
|
|
64
|
+
semantics: (xsd as any).semantics,
|
|
65
|
+
attributes: attributes?.map(adaptAttribute),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Convert XSD InteractionClass to FomRegistry InteractionClassSchema.
|
|
71
|
+
* Provides autocomplete based on XSD schema.
|
|
72
|
+
*/
|
|
73
|
+
export function adaptInteractionClass(
|
|
74
|
+
xsd: XsdInteractionClass
|
|
75
|
+
): InteractionClassSchema {
|
|
76
|
+
// XSD uses 'parameter' (singular) for the array property
|
|
77
|
+
const parameters = (xsd as any).parameter || (xsd as any).parameters;
|
|
78
|
+
return {
|
|
79
|
+
name: xsd.name,
|
|
80
|
+
parent: (xsd as any).parent,
|
|
81
|
+
sharing: adaptSharingType(xsd.sharing),
|
|
82
|
+
transportation: (xsd as any).transportation,
|
|
83
|
+
order: adaptOrderType(xsd.order),
|
|
84
|
+
semantics: (xsd as any).semantics,
|
|
85
|
+
parameters: parameters?.map(adaptParameter),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Convert XSD Attribute to FomRegistry AttributeSchema.
|
|
91
|
+
*/
|
|
92
|
+
function adaptAttribute(xsd: XsdAttribute): AttributeSchema {
|
|
93
|
+
return {
|
|
94
|
+
name: xsd.name,
|
|
95
|
+
dataType: xsd.dataType ?? "",
|
|
96
|
+
updateType: adaptUpdateType(xsd.updateType) ?? "NA",
|
|
97
|
+
updateCondition: xsd.updateCondition ?? "",
|
|
98
|
+
ownership: adaptOwnershipType(xsd.ownership) ?? "NoTransfer",
|
|
99
|
+
sharing: adaptSharingType(xsd.sharing) ?? "Neither",
|
|
100
|
+
transportation: xsd.transportation ?? "",
|
|
101
|
+
order: adaptOrderType(xsd.order) ?? "Receive",
|
|
102
|
+
semantics: xsd.semantics,
|
|
103
|
+
valueRequired: xsd.valueRequired === "true",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Convert XSD Parameter to FomRegistry ParameterSchema.
|
|
109
|
+
*/
|
|
110
|
+
function adaptParameter(xsd: XsdParameter): ParameterSchema {
|
|
111
|
+
return {
|
|
112
|
+
name: xsd.name,
|
|
113
|
+
dataType: xsd.dataType ?? "",
|
|
114
|
+
semantics: xsd.semantics,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Convert XSD SharingEnumeration to FomRegistry SharingType.
|
|
120
|
+
*/
|
|
121
|
+
function adaptSharingType(
|
|
122
|
+
sharing?: SharingEnumeration
|
|
123
|
+
): "Publish" | "Subscribe" | "PublishSubscribe" | "Neither" | undefined {
|
|
124
|
+
if (!sharing) return undefined;
|
|
125
|
+
return sharing as "Publish" | "Subscribe" | "PublishSubscribe" | "Neither";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Convert XSD OrderEnumeration to FomRegistry OrderType.
|
|
130
|
+
*/
|
|
131
|
+
function adaptOrderType(
|
|
132
|
+
order?: OrderEnumeration
|
|
133
|
+
): "Receive" | "TimeStamp" | undefined {
|
|
134
|
+
if (!order) return undefined;
|
|
135
|
+
return order as "Receive" | "TimeStamp";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Convert XSD UpdateEnumeration to FomRegistry UpdateType.
|
|
140
|
+
*/
|
|
141
|
+
function adaptUpdateType(
|
|
142
|
+
update?: UpdateEnumeration
|
|
143
|
+
): "Static" | "Periodic" | "Conditional" | "NA" {
|
|
144
|
+
if (!update) return "NA";
|
|
145
|
+
return update as "Static" | "Periodic" | "Conditional" | "NA";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Convert XSD OwnershipEnumeration to FomRegistry OwnershipType.
|
|
150
|
+
*/
|
|
151
|
+
function adaptOwnershipType(
|
|
152
|
+
ownership?: OwnershipEnumeration
|
|
153
|
+
): "Divest" | "Acquire" | "DivestAcquire" | "NoTransfer" {
|
|
154
|
+
if (!ownership) return "NoTransfer";
|
|
155
|
+
return ownership as "Divest" | "Acquire" | "DivestAcquire" | "NoTransfer";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Helper type that provides autocomplete for ModelIdentification using XSD types.
|
|
160
|
+
* Use this when you want XSD-based autocomplete:
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* import { FomRegistry } from "@hla4ts/fom-codegen";
|
|
165
|
+
* import type { ModelIdentification } from "@hla4ts/fom-codegen/generated";
|
|
166
|
+
*
|
|
167
|
+
* const registry = new FomRegistry();
|
|
168
|
+
* const modelId: ModelIdentification = {
|
|
169
|
+
* name: "MyFOM",
|
|
170
|
+
* type: "FOM", // Autocomplete shows "FOM" | "SOM"
|
|
171
|
+
* version: "1.0",
|
|
172
|
+
* // ... other fields with autocomplete
|
|
173
|
+
* };
|
|
174
|
+
* registry.setModelIdentification(adaptModelIdentification(modelId));
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export type { XsdModelIdentification, XsdObjectClass, XsdInteractionClass };
|