in packages/xml-parser-ts-codegen/src/codegen.ts [126:485]
async function main() {
const __LOCATION = process.argv[2];
const __ROOT_ELEMENT_NAME = process.argv[3];
const __BASE_LOCATION = path.dirname(__LOCATION);
const __RELATIVE_LOCATION = path.basename(__LOCATION);
const __ROOT_ELEMENT = `${__RELATIVE_LOCATION}__${__ROOT_ELEMENT_NAME}`;
const __RELATIVE_LOCATION_WITHOUT_EXTENSION = __RELATIVE_LOCATION.replace(path.extname(__RELATIVE_LOCATION), "");
const __CONVENTIONS = {
outputFileForGeneratedTypes: path.resolve(".", path.join(__BASE_LOCATION, "ts-gen/types.ts")),
outputFileForGeneratedMeta: path.resolve(".", path.join(__BASE_LOCATION, "ts-gen/meta.ts")),
};
// gather all the XSDs
const __XSDS = new Map<string, XsdSchema>(await parseDeep(__XSD_PARSER, __BASE_LOCATION, __RELATIVE_LOCATION));
// // process <xsd:simpleType>'s
const __SIMPLE_TYPES: XptcSimpleType[] = Array.from(__XSDS.entries()).flatMap(([location, schema]) =>
(schema["xsd:schema"]["xsd:simpleType"] || []).flatMap((s) => {
if (s["xsd:union"]) {
if (s["xsd:union"]["@_memberTypes"] === "xsd:anyURI") {
return [
{
comment: "xsd:anyURI",
type: "simple",
kind: "enum",
name: s["@_name"] ?? s["@_name"],
declaredAtRelativeLocation: location,
values: [],
},
];
}
return (s["xsd:union"]["xsd:simpleType"] ?? []).flatMap((ss) =>
xsdSimpleTypeToXptcSimpleType(ss, location, s["@_name"])
);
} else {
return xsdSimpleTypeToXptcSimpleType(s, location, s["@_name"]);
}
})
);
// // process <xsd:complexType>'s
const __COMPLEX_TYPES: XptcComplexType[] = [];
for (const [location, xsd] of __XSDS.entries()) {
for (const xsdCt of xsd["xsd:schema"]["xsd:complexType"] || []) {
const isAbstract = xsdCt["@_abstract"] ?? false;
const extensionElement =
xsdCt["xsd:complexContent"]?.["xsd:extension"] ?? xsdCt["xsd:simpleContent"]?.["xsd:extension"];
__COMPLEX_TYPES.push({
type: "complex",
comment: isAbstract ? "abstract" : "",
isAbstract,
isAnonymous: false,
name: xsdCt["@_name"]!,
isSimpleContent: !!xsdCt["xsd:simpleContent"],
needsExtensionType: !!xsdCt["xsd:anyAttribute"] || !!xsdCt["xsd:sequence"]?.["xsd:any"],
declaredAtRelativeLocation: location,
childOf: extensionElement?.["@_base"],
elements: [
...(xsdCt["xsd:all"]?.["xsd:element"] ?? []).map((s) =>
xsdElementToXptcElement(xsdCt["@_name"]!, s, location)
),
...(xsdCt["xsd:sequence"]?.["xsd:element"] ?? []).map((s) =>
xsdElementToXptcElement(xsdCt["@_name"]!, s, location)
),
...(extensionElement?.["xsd:sequence"]?.["xsd:element"] ?? []).map((s) =>
xsdElementToXptcElement(xsdCt["@_name"]!, s, location)
),
...(extensionElement?.["xsd:sequence"]?.["xsd:choice"]?.["xsd:element"] ?? []).map((s) =>
xsdElementToXptcElement(xsdCt["@_name"]!, s, location, { forceOptional: true })
),
...(extensionElement?.["xsd:choice"]?.["xsd:element"] ?? []).map((s) =>
xsdElementToXptcElement(xsdCt["@_name"]!, s, location, { forceOptional: true })
),
...(extensionElement?.["xsd:choice"]?.["xsd:sequence"]?.["xsd:element"] ?? []).map((s) =>
xsdElementToXptcElement(xsdCt["@_name"]!, s, location, { forceOptional: true })
),
],
attributes: [
...(xsdCt["xsd:attribute"] ?? []).map((a) => xsdAttributeToXptcAttribute(a)),
...(extensionElement?.["xsd:attribute"] ?? []).map((a) => xsdAttributeToXptcAttribute(a)),
],
});
}
}
// // process <xsd:element>'s
const __GLOBAL_ELEMENTS = new Map<string, XptcElement>();
for (const [location, xsd] of __XSDS.entries()) {
for (const e of xsd["xsd:schema"]["xsd:element"] || []) {
const a = xsdElementToXptcElement("GLOBAL", { ...e, "@_minOccurs": 0, "@_maxOccurs": "unbounded" }, location, {
forceOptional: false,
});
__GLOBAL_ELEMENTS.set(`${location}__${e["@_name"]}`, {
name: e["@_name"],
isAbstract: e["@_abstract"] ?? false,
substitutionGroup: e["@_substitutionGroup"],
type: e["@_type"],
declaredAtRelativeLocation: location,
anonymousType: a.kind === "ofAnonymousType" ? a.anonymousType : undefined,
});
}
}
// // substitutionGroups are SCOPED. Meaning that we need to consider only what the current XSD is importing into it.
// // This map goes from a relativeLocation to an elementName to a list of elementNames.
const __SUBSTITUTIONS = new Map<string, Map<string, string[]>>();
for (const [baseLoc, _] of __XSDS.entries()) {
const xsds = new Map<string, XsdSchema>(await parseDeep(__XSD_PARSER, __BASE_LOCATION, baseLoc));
for (const [xLocation, xsd] of xsds.entries()) {
const localizedSubstitutions = __SUBSTITUTIONS.get(xLocation) ?? new Map<string, string[]>();
__SUBSTITUTIONS.set(xLocation, localizedSubstitutions);
for (const e of xsd["xsd:schema"]["xsd:element"] || []) {
if (e["@_substitutionGroup"]) {
const subsGroup = getXptcElementFromLocalElementRef(
__XSDS,
__GLOBAL_ELEMENTS,
xLocation,
e["@_substitutionGroup"]
);
if (!subsGroup) {
throw new Error(`Invalid subsitution group for element '${e["@_name"]}'`);
}
const elem = getXptcElementFromLocalElementRef(__XSDS, __GLOBAL_ELEMENTS, xLocation, e["@_name"]);
if (!elem) {
throw new Error(`Invalid element '${e["@_name"]}'`);
}
const localizedElementName = `${subsGroup.declaredAtRelativeLocation}__${subsGroup.name}`;
// Using this strategy to remove duplicates.
const accumulatedSubstitutionElements = new Set([
...(localizedSubstitutions.get(localizedElementName) ?? []),
`${xLocation}__${elem.name}`,
]);
localizedSubstitutions.set(localizedElementName, [...accumulatedSubstitutionElements]);
}
}
}
}
Array.from(__GLOBAL_ELEMENTS.values()).forEach((e) => {
if (!e.anonymousType) {
return;
} else {
__COMPLEX_TYPES.push(e.anonymousType);
}
});
const __NAMED_TYPES_BY_TS_NAME = new Map<string, XptcComplexType | XptcSimpleType>([
...__SIMPLE_TYPES.map(
(st) => [getTsNameFromNamedType(st.declaredAtRelativeLocation, st.name), st] as [string, XptcSimpleType]
),
...__COMPLEX_TYPES.map((ct) => {
if (ct.isAnonymous) {
const name = getAnonymousMetaTypeName(ct.forElementWithName, "GLOBAL");
return [getTsNameFromNamedType(ct.declaredAtRelativeLocation, name), ct] as [string, XptcComplexType];
} else {
return [getTsNameFromNamedType(ct.declaredAtRelativeLocation, ct.name), ct] as [string, XptcComplexType];
}
}),
]);
const __META_TYPE_MAPPING = new Map<string, XptcMetaType>();
const rootTsTypeName = getTsNameFromNamedType(
__RELATIVE_LOCATION_WITHOUT_EXTENSION,
__GLOBAL_ELEMENTS.get(__ROOT_ELEMENT)!.type ?? getAnonymousMetaTypeName(__ROOT_ELEMENT_NAME, "GLOBAL")
);
let ts = "";
for (const sp of __SIMPLE_TYPES) {
if (sp.kind === "int") {
// ignore int types, they're only interesting for validation.
continue;
}
const enumName = getTsNameFromNamedType(sp.declaredAtRelativeLocation, sp.name);
if (sp.comment === "xsd:anyURI") {
ts += `
export type ${enumName} = string; // ${sp.comment}
`;
} else if (sp.kind === "enum") {
ts += `
export type ${enumName} = |
${sp.values.map((v) => ` '${v}'`).join(" |\n")}
`;
}
}
for (const ct of __COMPLEX_TYPES) {
const typeName = getTsNameFromNamedType(
ct.declaredAtRelativeLocation,
ct.isAnonymous ? getAnonymousMetaTypeName(ct.forElementWithName, "GLOBAL") : ct.name
);
const { metaProperties, needsExtensionType, anonymousTypes } = getMetaProperties(
__RELATIVE_LOCATION,
__META_TYPE_MAPPING,
__GLOBAL_ELEMENTS,
__SUBSTITUTIONS,
__XSDS,
__NAMED_TYPES_BY_TS_NAME,
ct,
typeName
);
const properties = metaProperties
.map((p) => {
const optionalMarker = p.isOptional ? "?" : "";
const arrayMarker = p.isArray ? "[]" : "";
const tsType =
p.metaType.name === "integer" || p.metaType.name === "float" || p.metaType.name === "long"
? "number"
: p.metaType.name;
const ns = getMetaPropertyNs(__RELATIVE_LOCATION, p);
return ` "${ns}${p.name}"${optionalMarker}: ${p.typeBody?.(tsType) ?? tsType}${arrayMarker}; // from type ${
p.fromType
} @ ${p.declaredAt}`;
})
.join("\n");
const doc = ct.comment.trim() ? `/* ${ct.comment} */` : "";
const anonymousTypesString = anonymousTypes
.map((anonType) => {
const anonymousTypesProperties = anonType.properties.map(
(p) =>
` "${p.name}": ${
p.metaType.name === "integer" || p.metaType.name === "float" || p.metaType.name === "long"
? "number"
: p.metaType.name
};`
);
// FIXME: Tiago: Not all anonymous types are extensible!
return `export interface ${anonType.name} {
__?: undefined;
${anonymousTypesProperties.join("\n")}
}`;
})
.join("\n");
if (needsExtensionType) {
const rootElementBaseType = rootTsTypeName === typeName ? "extends XmlParserTsRootElementBaseType" : "";
ts += `
export interface ${typeName} ${rootElementBaseType} ${doc} {
__?: undefined;
${properties}
}
${anonymousTypesString}
`;
} else {
const rootElementBaseType = rootTsTypeName === typeName ? "XmlParserTsRootElementBaseType & " : "";
ts += `
export type ${typeName} = ${rootElementBaseType} ${doc} {
${properties}
}
${anonymousTypesString}
`;
}
}
ts = `import { XmlParserTsRootElementBaseType } from "@kie-tools/xml-parser-ts"
${ts}
`;
ts = `// This file was automatically generated
${ts}
`;
fs.mkdirSync(path.dirname(__CONVENTIONS.outputFileForGeneratedTypes), { recursive: true });
fs.writeFileSync(__CONVENTIONS.outputFileForGeneratedTypes, ts);
// meta
let meta = `
export const root = {
element: "${getRealtiveLocationNs(__RELATIVE_LOCATION, __RELATIVE_LOCATION) + __ROOT_ELEMENT_NAME}",
type: "${rootTsTypeName}"
} as const;
export const ns = new Map<string, string>([
${[...__XSDS.entries()]
.map(([k, v]) => {
const uri = v["xsd:schema"]["@_targetNamespace"];
const ns = getRealtiveLocationNs(__RELATIVE_LOCATION, k);
return ` ["${uri}", "${ns}"],
["${ns}", "${uri}"],`;
})
.join("\n")}
]);
export const subs = {
${Array.from(__SUBSTITUTIONS.entries())
.map(
([namespace, subs]) => ` "${getRealtiveLocationNs(__RELATIVE_LOCATION, namespace)}": {
${Array.from(subs.entries())
.map(
([head, elements]) =>
`${elements
.map((e) => {
const elementName = `${getRealtiveLocationNs(__RELATIVE_LOCATION, e.split("__")[0]) + e.split("__")[1]}`;
const headName = `${getRealtiveLocationNs(__RELATIVE_LOCATION, head.split("__")[0]) + head.split("__")[1]}`;
return ` "${elementName}": "${headName}",`;
})
.join("\n")}`
)
.join("\n")}
},`
)
.join("\n")}
};
export const elements = {
${Array.from(__GLOBAL_ELEMENTS.entries())
.map(([k, v]) => {
const s = v.type?.split(":") || [getAnonymousMetaTypeName(v.name, "GLOBAL")];
const elementName = `${getRealtiveLocationNs(__RELATIVE_LOCATION, k.split("__")[0])}${v.name}`;
const elementType = `${getTsNameFromNamedType(v.declaredAtRelativeLocation, s.length === 1 ? s[0] : s[1])}`;
return ` "${elementName}": "${elementType}",`;
})
.join("\n")}
};
export const meta = {
`;
Array.from(__META_TYPE_MAPPING.entries()).forEach(([name, type]) => {
meta += ` "${name}": {
`;
type.properties.forEach((p) => {
const ns = getMetaPropertyNs(__RELATIVE_LOCATION, p);
meta += ` "${ns}${p.name}": { type: "${p.metaType.name}", isArray: ${p.isArray}, fromType: "${p.fromType}", xsdType: "${p.metaType.xsdType}" },
`;
});
meta += ` },
`;
});
meta += `} as const;
`;
fs.mkdirSync(path.dirname(__CONVENTIONS.outputFileForGeneratedMeta), { recursive: true });
fs.writeFileSync(__CONVENTIONS.outputFileForGeneratedMeta, meta);
console.log(__LOGS.done(__LOCATION));
}