From a8cf790924a837aa2b50101ccfdaa871930e71d3 Mon Sep 17 00:00:00 2001 From: Anton Vasetenkov Date: Fri, 6 Dec 2024 21:32:17 +1300 Subject: [PATCH] XSD Java code gen WIP --- Src/java/buildSrc/build.gradle | 1 + .../groovy/cql.xjc-temp-conventions.gradle | 55 ++ .../cql.xsd-java-gen-conventions.gradle | 39 ++ .../cql/cql2elm/elm/ElmEditTest.java | 2 +- Src/java/elm/build.gradle | 3 +- Src/java/model/build.gradle | 3 +- Src/js/xsd-java-gen/.gitignore | 1 + Src/js/xsd-java-gen/generate.js | 617 ++++++++++++++++++ Src/js/xsd-java-gen/package-lock.json | 47 ++ Src/js/xsd-java-gen/package.json | 9 + 10 files changed, 774 insertions(+), 3 deletions(-) create mode 100644 Src/java/buildSrc/src/main/groovy/cql.xjc-temp-conventions.gradle create mode 100644 Src/java/buildSrc/src/main/groovy/cql.xsd-java-gen-conventions.gradle create mode 100644 Src/js/xsd-java-gen/.gitignore create mode 100644 Src/js/xsd-java-gen/generate.js create mode 100644 Src/js/xsd-java-gen/package-lock.json create mode 100644 Src/js/xsd-java-gen/package.json diff --git a/Src/java/buildSrc/build.gradle b/Src/java/buildSrc/build.gradle index b7ecf8ca3..c3a74dbf6 100644 --- a/Src/java/buildSrc/build.gradle +++ b/Src/java/buildSrc/build.gradle @@ -12,4 +12,5 @@ dependencies { implementation 'ru.vyarus:gradle-animalsniffer-plugin:1.7.0' implementation 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.14' implementation 'com.diffplug.spotless:spotless-plugin-gradle:6.23.3' + implementation 'com.github.node-gradle:gradle-node-plugin:7.1.0' } \ No newline at end of file diff --git a/Src/java/buildSrc/src/main/groovy/cql.xjc-temp-conventions.gradle b/Src/java/buildSrc/src/main/groovy/cql.xjc-temp-conventions.gradle new file mode 100644 index 000000000..6275d251f --- /dev/null +++ b/Src/java/buildSrc/src/main/groovy/cql.xjc-temp-conventions.gradle @@ -0,0 +1,55 @@ +plugins { + id 'java' +} + +configurations { + xjc +} + +dependencies { + xjc 'codes.rafael.jaxb2_commons:jaxb2-basics-ant:3.0.0' + xjc 'codes.rafael.jaxb2_commons:jaxb2-basics:3.0.0' + xjc 'codes.rafael.jaxb2_commons:jaxb2-fluent-api:3.0.0' + // Eclipse has taken over all Java EE reference components + // https://www.infoworld.com/article/3310042/eclipse-takes-over-all-java-ee-reference-components.html + // https://wiki.eclipse.org/Jakarta_EE_Maven_Coordinates + xjc 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.1' + xjc 'org.glassfish.jaxb:jaxb-xjc:3.0.2' + xjc 'org.glassfish.jaxb:jaxb-runtime:4.0.3' + xjc 'org.eclipse.persistence:org.eclipse.persistence.moxy:4.0.2' + xjc 'org.slf4j:slf4j-simple:1.7.36' +} + +ext.xjc = [ + destDir: "${buildDir}/generated/sources/$name-xjc-temp/main/java", + args: '-disableXmlSecurity -Xfluent-api -Xequals -XhashCode -XtoString -Xsetters -Xsetters-mode=direct' +] + + +task generateSources { + + outputs.dir xjc.destDir + + doLast { + file(xjc.destDir).mkdirs() + + ant.taskdef(name: 'xjc', classname: 'org.jvnet.jaxb2_commons.xjc.XJC2Task', classpath: configurations.xjc.asPath) + + /* The above sets up the task, but the real work of the task should be specified in the subproject using + generateSources.doLast. For example: + generateSources.doLast { + ant.xjc(destdir: xjc.destDir, schema: "${projectDir}/path/to/file.xsd") { + arg(line: xjc.args) + } + } + */ + } +} + +compileJava { + dependsOn generateSources +} + +clean { + delete xjc.destDir +} diff --git a/Src/java/buildSrc/src/main/groovy/cql.xsd-java-gen-conventions.gradle b/Src/java/buildSrc/src/main/groovy/cql.xsd-java-gen-conventions.gradle new file mode 100644 index 000000000..cf9af0722 --- /dev/null +++ b/Src/java/buildSrc/src/main/groovy/cql.xsd-java-gen-conventions.gradle @@ -0,0 +1,39 @@ +plugins { + id "com.github.node-gradle.node" +} + +dependencies { + api 'jakarta.xml.bind:jakarta.xml.bind-api:4.0.1' + api 'codes.rafael.jaxb2_commons:jaxb2-basics-runtime:3.0.0' +} + +node { + nodeProjectDir = file('../../js/xsd-java-gen') +} + +var outDir = "${buildDir}/generated/sources/$name/main/java" + +task runXsdJavaGen(type: NodeTask) { + dependsOn npmInstall + script = file('../../js/xsd-java-gen/generate.js') +} + +compileJava { + dependsOn runXsdJavaGen +} + +sourcesJar { + dependsOn runXsdJavaGen +} + +sourceSets { + main { + java { + srcDir(outDir) + } + } +} + +clean { + delete outDir +} diff --git a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/elm/ElmEditTest.java b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/elm/ElmEditTest.java index b86a8b878..f38d5b49b 100644 --- a/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/elm/ElmEditTest.java +++ b/Src/java/cql-to-elm/src/test/java/org/cqframework/cql/cql2elm/elm/ElmEditTest.java @@ -26,7 +26,7 @@ void removeChoiceTypeSpecifierTypeIfEmpty() { private static class ExtChoiceTypeSpecifier extends ChoiceTypeSpecifier { public List getType() { - return type; + return _type; } } } diff --git a/Src/java/elm/build.gradle b/Src/java/elm/build.gradle index 9791c0612..9fc959fe1 100644 --- a/Src/java/elm/build.gradle +++ b/Src/java/elm/build.gradle @@ -1,6 +1,7 @@ plugins { id 'cql.library-conventions' - id 'cql.xjc-conventions' + id 'cql.xjc-temp-conventions' + id 'cql.xsd-java-gen-conventions' } dependencies { diff --git a/Src/java/model/build.gradle b/Src/java/model/build.gradle index 1bb18cc98..51c91fce8 100644 --- a/Src/java/model/build.gradle +++ b/Src/java/model/build.gradle @@ -1,6 +1,7 @@ plugins { id 'cql.library-conventions' - id 'cql.xjc-conventions' + id 'cql.xjc-temp-conventions' + id 'cql.xsd-java-gen-conventions' } dependencies { diff --git a/Src/js/xsd-java-gen/.gitignore b/Src/js/xsd-java-gen/.gitignore new file mode 100644 index 000000000..b512c09d4 --- /dev/null +++ b/Src/js/xsd-java-gen/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/Src/js/xsd-java-gen/generate.js b/Src/js/xsd-java-gen/generate.js new file mode 100644 index 000000000..bc65653f7 --- /dev/null +++ b/Src/js/xsd-java-gen/generate.js @@ -0,0 +1,617 @@ +const fs = require("fs"); +const { xml2js } = require("xml-js"); + +function firstLetterToUpperCase(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +function getParentFields(parentClass, config) { + const parent = config.classes[parentClass]; + + if (parent) { + if (parent.extendsClass) { + return [ + ...parent.fields, + ...getParentFields(parent.extendsClass, config), + ]; + } + + return parent.fields; + } + + return []; +} + +function getParentAttributes(parentClass, config) { + const parent = config.classes[parentClass]; + + if (parent) { + if (parent.extendsClass) { + return [ + ...parent.attributesFields, + ...getParentAttributes(parent.extendsClass, config), + ]; + } + + return parent.attributesFields; + } + + return []; +} + +function getType(rawType) { + return ( + { + "xs:string": "String", + "xs:int": "Integer", + "xs:anySimpleType": "String", + "xs:boolean": "Boolean", + "xs:integer": "Integer", + "xs:decimal": "java.math.BigDecimal", + "xs:dateTime": "String", + "xs:time": "String", + "xs:date": "String", + "xs:base64Binary": "String", + "xs:anyURI": "String", + "xs:QName": "javax.xml.namespace.QName", // "String", + "xs:token": "String", + "xs:NCName": "String", + "xs:ID": "String", + }[rawType] || rawType + ); +} + +function parse(filePath) { + const xml = fs + .readFileSync(filePath, "utf8") + .split( + '', + ) + .join( + '', + ) + .split("a:CqlToElmBase") + .join("org.hl7.cql_annotations.r1.CqlToElmBase"); + const result = xml2js(xml, { compact: false }); + return result; +} + +const includes = { + "clinicalexpression.xsd": + __dirname + "/../../cql-lm/schema/elm/clinicalexpression.xsd", + "expression.xsd": __dirname + "/../../cql-lm/schema/elm/expression.xsd", +}; + +const configs = [ + { + xsd: __dirname + "/../../cql-lm/schema/model/modelinfo.xsd", + outputDir: + __dirname + + "/../../java/model/build/generated/sources/model/main/java/org/hl7/elm_modelinfo/r1", + packageName: "org.hl7.elm_modelinfo.r1", + classes: {}, + scope: "", + namespaceUri: "urn:hl7-org:elm-modelinfo:r1", + localPart: "modelInfo", + }, + { + xsd: __dirname + "/../../cql-lm/schema/elm/library.xsd", + outputDir: + __dirname + + "/../../java/elm/build/generated/sources/elm/main/java/org/hl7/elm/r1", + packageName: "org.hl7.elm.r1", + autoExtend: "org.cqframework.cql.elm.tracking.Trackable", + classes: {}, + scope: "", + namespaceUri: "urn:hl7-org:elm:r1", + localPart: "library", + }, + { + xsd: __dirname + "/../../cql-lm/schema/elm/cqlannotations.xsd", + outputDir: + __dirname + + "/../../java/elm/build/generated/sources/elm/main/java/org/hl7/cql_annotations/r1", + packageName: "org.hl7.cql_annotations.r1", + classes: {}, + scope: "narrative", + namespaceUri: "urn:hl7-org:cql-annotations:r1", + localPart: "s", + }, +]; + +function processXsd(xsdPath, config, mode) { + const result = parse(xsdPath); + + processElements(result.elements, config, mode); + + fs.writeFileSync( + `${config.outputDir}/ObjectFactory.java`, + `package ${config.packageName}; + +public class ObjectFactory { + + private final static javax.xml.namespace.QName _${firstLetterToUpperCase(config.scope)}${firstLetterToUpperCase(config.localPart)}_QNAME = new javax.xml.namespace.QName(${JSON.stringify(config.namespaceUri)}, ${JSON.stringify(config.localPart)}); + + public ObjectFactory() { + } + + ${Object.entries(config.classes) + .filter(([className, v]) => !v.isAbstract) + .map(([className, v]) => { + return ` + public ${className} create${className}() { + return new ${className}(); + } + + ${v.fields + .map((field) => { + const innerSequence = getInnerSequence(field); + + if (innerSequence) { + return ` + + public ${className}.${firstLetterToUpperCase(field.attributes.name)} create${className}${firstLetterToUpperCase(field.attributes.name)}() { + return new ${className}.${firstLetterToUpperCase(field.attributes.name)}(); + } + `; + } + + return ""; + }) + .join("\n")} + `; + }) + .join("\n")} + + public jakarta.xml.bind.JAXBElement<${firstLetterToUpperCase(config.scope) || firstLetterToUpperCase(config.localPart)}> create${firstLetterToUpperCase(config.scope)}${firstLetterToUpperCase(config.localPart)}(${firstLetterToUpperCase(config.scope) || firstLetterToUpperCase(config.localPart)} value) { + return new jakarta.xml.bind.JAXBElement<${firstLetterToUpperCase(config.scope) || firstLetterToUpperCase(config.localPart)}>(_${firstLetterToUpperCase(config.scope)}${firstLetterToUpperCase(config.localPart)}_QNAME, ${firstLetterToUpperCase(config.scope) || firstLetterToUpperCase(config.localPart)}.class, null, value); + } + +} +`, + ); +} + +function getInnerSequence(field) { + const innerComplexType = (field.elements || []).find((element) => { + return element.type === "element" && element.name === "xs:complexType"; + }); + + const innerSequence = innerComplexType + ? innerComplexType.elements.find((element) => { + return element.type === "element" && element.name === "xs:sequence"; + }) + : null; + + return innerSequence; +} + +function getIsList(field) { + return ( + field.attributes.maxOccurs !== undefined && + field.attributes.maxOccurs !== "1" + ); +} + +function processElements(elements, config, mode) { + if (elements) { + for (const element of elements) { + switch (element.name) { + case "xs:schema": + console.log( + `Processing xs:schema ${element.attributes.targetNamespace}`, + ); + processElements(element.elements, config, mode); + continue; + + case "xs:complexType": + const sequence = (element.elements || []).find((element) => { + return element.type === "element" && element.name === "xs:sequence"; + }); + + const attributes = (element.elements || []).filter((element) => { + return ( + element.type === "element" && element.name === "xs:attribute" + ); + }); + + const complexContent = (element.elements || []).find((element) => { + return ( + element.type === "element" && element.name === "xs:complexContent" + ); + }); + + const extension = complexContent + ? complexContent.elements.find((element) => { + return ( + element.type === "element" && element.name === "xs:extension" + ); + }) + : null; + + const extensionSequence = ( + (extension && extension.elements) || + [] + ).find((element) => { + return element.type === "element" && element.name === "xs:sequence"; + }); + + const extensionAttributes = ( + (extension && extension.elements) || + [] + ).filter((element) => { + return ( + element.type === "element" && element.name === "xs:attribute" + ); + }); + + const restriction = complexContent + ? complexContent.elements.find((element) => { + return ( + element.type === "element" && + element.name === "xs:restriction" + ); + }) + : null; + + const restrictionSequence = ( + (restriction && restriction.elements) || + [] + ).find((element) => { + return element.type === "element" && element.name === "xs:sequence"; + }); + + const restrictionAttributes = ( + (restriction && restriction.elements) || + [] + ).filter((element) => { + return ( + element.type === "element" && element.name === "xs:attribute" + ); + }); + + const fields = [ + ...((sequence && sequence.elements) || []).filter((element) => { + return ( + element.type === "element" && element.name === "xs:element" + ); + }), + ...((extensionSequence && extensionSequence.elements) || []).filter( + (element) => { + return ( + element.type === "element" && element.name === "xs:element" + ); + }, + ), + ...( + (restrictionSequence && restrictionSequence.elements) || + [] + ).filter((element) => { + return ( + element.type === "element" && element.name === "xs:element" + ); + }), + ]; + + const attributesFields = [ + ...attributes, + ...extensionAttributes, + ...restrictionAttributes, + ]; + + const extendsClass = + (extension && extension.attributes.base) || + config.autoExtend || + null; + + if (mode === "COLLECT_CLASSES") { + config.classes[element.attributes.name] = { + extendsClass, + fields, + attributesFields, + isAbstract: element.attributes.abstract === "true", + }; + } + + if (mode === "WRITE_FILES") { + const renderWith = (field, className, type) => { + const isList = getIsList(field); + + if (isList) { + return ` + public ${className} with${firstLetterToUpperCase(field.attributes.name)}(${type}... values) { + if (values!= null) { + for (${type} value: values) { + get${firstLetterToUpperCase(field.attributes.name)}().add(value); + } + } + return this; + } + + public ${className} with${firstLetterToUpperCase(field.attributes.name)}(java.util.Collection<${type}> values) { + if (values!= null) { + get${firstLetterToUpperCase(field.attributes.name)}().addAll(values); + } + return this; + } + `; + } + + return ` + public ${className} with${firstLetterToUpperCase(field.attributes.name)}(${type} value) { + set${firstLetterToUpperCase(field.attributes.name)}(value); + return this; + } + + + `; + }; + + const renderGetSet = (field, className, type) => { + const isList = getIsList(field); + + if (isList) { + return ` + protected java.util.List<${type}> _${field.attributes.name}; + + public java.util.List<${type}> get${firstLetterToUpperCase(field.attributes.name)}() { + if (_${field.attributes.name} == null) { + _${field.attributes.name} = new java.util.ArrayList<${type}>(); + } + return _${field.attributes.name}; + } + + public void set${firstLetterToUpperCase(field.attributes.name)}(java.util.List<${type}> value) { + this._${field.attributes.name} = value; + } + `; + } + + return ` + protected ${type} _${field.attributes.name}; + + public ${type} get${firstLetterToUpperCase(field.attributes.name)}() { + return _${field.attributes.name}; + } + + public void set${firstLetterToUpperCase(field.attributes.name)}(${type} value) { + this._${field.attributes.name} = value; + } + + `; + }; + + const renderField = (field, className) => { + const innerSequence = getInnerSequence(field); + + if (innerSequence) { + return ` + + public static class ${firstLetterToUpperCase(field.attributes.name)} + ${config.autoExtend ? `extends ${config.autoExtend}` : ""} + { + + ${(innerSequence.elements || []) + .map((f) => { + return renderField( + f, + className + + "." + + firstLetterToUpperCase( + field.attributes.name, + ), + ); + }) + .join("\n")} + + } + + ${renderGetSet(field, className, className + "." + firstLetterToUpperCase(field.attributes.name))} + + ${renderWith(field, className, className + "." + firstLetterToUpperCase(field.attributes.name))} + + `; + } + + return ` + ${renderGetSet(field, className, field.attributes.type)} + + ${renderWith(field, className, field.attributes.type)} + + `; + }; + + console.log(`Processing xs:complexType ${element.attributes.name}`); + + fs.writeFileSync( + `${config.outputDir}/${element.attributes.name}.java`, + ` +package ${config.packageName}; + +public ${element.attributes.abstract === "true" ? "abstract" : ""} class ${element.attributes.name} ${extendsClass ? `extends ${extendsClass}` : ""} { + +${fields + .map((field) => { + return renderField(field, element.attributes.name); + }) + .join("\n")} + +${getParentFields(config.classes[element.attributes.name]?.extendsClass, config) + .map((field) => { + return renderWith(field, element.attributes.name, field.attributes.type); + }) + .join("\n")} + +${attributesFields + .map((field) => { + const type = getType(field.attributes.type); + + return ` + protected ${type} _${field.attributes.name}; + + + public ${type === "Boolean" && field.attributes.default ? "boolean" : type} ${type === "Boolean" ? "is" : "get"}${firstLetterToUpperCase(field.attributes.name)}() { + ${ + field.attributes.default + ? `if (_${field.attributes.name} == null) { return ${ + { + "xs:boolean": field.attributes.default, + "xs:anyURI": JSON.stringify(field.attributes.default), + AccessModifier: `AccessModifier.${field.attributes.default.toUpperCase()}`, + }[field.attributes.type] + }; }` + : "" + } + return _${field.attributes.name}; + } + + public void set${firstLetterToUpperCase(field.attributes.name)}(${type} value) { + this._${field.attributes.name} = value; + } + + public ${element.attributes.name} with${firstLetterToUpperCase(field.attributes.name)}(${type} value) { + set${firstLetterToUpperCase(field.attributes.name)}(value); + return this; + } + `; + }) + .join("\n")} + +${getParentAttributes( + config.classes[element.attributes.name]?.extendsClass, + config, +) + .map((field) => { + return renderWith( + field, + element.attributes.name, + getType(field.attributes.type), + ); + }) + .join("\n")} + + + @Override + public boolean equals(Object that) { + if (that instanceof ${element.attributes.name}) { + + ${element.attributes.name} that_ = (${element.attributes.name}) that; + + ${ + extension && extension.attributes.base + ? ` + if (!super.equals(that_)) { + return false; + } +` + : "" + } + + ${fields + .map((field) => { + return ` + if (!java.util.Objects.equals(this.get${firstLetterToUpperCase(field.attributes.name)}(), that_.get${firstLetterToUpperCase(field.attributes.name)}())) { + return false; + } +`; + }) + .join("\n")} + + ${attributesFields + .map((field) => { + const isBoolean = field.attributes.type === "xs:boolean"; + + return ` + if (!java.util.Objects.equals(this.${isBoolean ? "is" : "get"}${firstLetterToUpperCase(field.attributes.name)}(), that_.${isBoolean ? "is" : "get"}${firstLetterToUpperCase(field.attributes.name)}())) { + return false; + } +`; + }) + .join("\n")} + + + return true; + + + } + return false; + + } + + @Override + public int hashCode() { + return 1; + } + + +} +`, + ); + } + + continue; + + case "xs:simpleType": + if (mode === "COLLECT_CLASSES") { + // do nothing + } + + if (mode === "WRITE_FILES") { + console.log(`Processing xs:simpleType ${element.attributes.name}`); + fs.writeFileSync( + `${config.outputDir}/${element.attributes.name}.java`, + ` +package ${config.packageName}; + +public enum ${element.attributes.name} { +${element.elements[element.elements.length - 1].elements + .map((element) => { + return `${element.attributes.value.toUpperCase()}(${JSON.stringify(element.attributes.value)})`; + }) + .join(",\n")}; + + private final String value; + + ${element.attributes.name}(String v) { + value = v; + } + + public String value() { + return value; + } + + public static ${element.attributes.name} fromValue(String v) { + for (${element.attributes.name} c: ${element.attributes.name}.values()) { + if (c.value.equals(v)) { + return c; + } + } + throw new IllegalArgumentException(v); + } + + +} +`, + ); + } + + continue; + + case "xs:include": + console.log( + `Processing xs:include ${element.attributes.schemaLocation}`, + ); + processXsd(includes[element.attributes.schemaLocation], config, mode); + + continue; + } + } + } +} + +for (const config of configs) { + fs.rmSync(config.outputDir, { recursive: true, force: true }); + fs.mkdirSync(config.outputDir, { recursive: true }); + processXsd(config.xsd, config, "COLLECT_CLASSES"); + processXsd(config.xsd, config, "WRITE_FILES"); +} diff --git a/Src/js/xsd-java-gen/package-lock.json b/Src/js/xsd-java-gen/package-lock.json new file mode 100644 index 000000000..1fbd90f43 --- /dev/null +++ b/Src/js/xsd-java-gen/package-lock.json @@ -0,0 +1,47 @@ +{ + "name": "xsd-java-gen", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "xsd-java-gen", + "dependencies": { + "xml-js": "^1.6.11" + }, + "devDependencies": { + "prettier": "^3.4.2" + } + }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + } + } +} diff --git a/Src/js/xsd-java-gen/package.json b/Src/js/xsd-java-gen/package.json new file mode 100644 index 000000000..f8e58aef7 --- /dev/null +++ b/Src/js/xsd-java-gen/package.json @@ -0,0 +1,9 @@ +{ + "name": "xsd-java-gen", + "devDependencies": { + "prettier": "^3.4.2" + }, + "dependencies": { + "xml-js": "^1.6.11" + } +}