Created
March 7, 2019 00:21
-
-
Save dvliman/5f9955195f2a8e51445bae7bad759efd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.guardtime.amgenlink | |
import com.sun.org.apache.xerces.internal.dom.DOMInputImpl | |
import com.sun.org.apache.xerces.internal.impl.xs.XSComplexTypeDecl | |
import com.sun.org.apache.xerces.internal.impl.xs.XSImplementationImpl | |
import com.sun.org.apache.xerces.internal.impl.xs.XSModelGroupImpl | |
import com.sun.org.apache.xerces.internal.impl.xs.XSParticleDecl | |
import com.sun.org.apache.xerces.internal.xs.* | |
import org.w3c.dom.bootstrap.DOMImplementationRegistry | |
import org.w3c.dom.ls.LSInput | |
import java.io.File | |
import java.io.FileInputStream | |
fun main(args: Array<String>) { | |
val xsdFilePath = args[0].ifEmpty { System.getProperty("user.dir") + "src/main/resources/xml/TPA.xsd" } | |
// note: this can only parse for specific xsd format | |
val xsd = readXsd(xsdFilePath) | |
val parsed = parseXsd(xsd) | |
val structs1 = generateStructs(parsed) | |
val structs2 = structs1.map(::stripType) | |
val javaSrc = toJavaSyntax(structs2) | |
writeToFile(appendPackage(javaSrc), inferOutputFilepath(xsdFilePath)) | |
println("done") | |
} | |
fun toLSInput(filepath: String): LSInput { | |
val input = DOMInputImpl() | |
input.byteStream = FileInputStream(filepath) | |
return input | |
} | |
fun readXsd(filepath: String): XSModel { | |
val registry = DOMImplementationRegistry.newInstance() | |
val impl = registry.getDOMImplementation("XS-Loader") as XSImplementationImpl | |
val schemaLoader = impl.createXSLoader(null) | |
return schemaLoader.load(toLSInput(filepath)) | |
} | |
fun startWithHttp(str: String) = str.startsWith("http") | |
// XSModel.getNamespaces() returns values from [xmlns:xs, targetNamespace] | |
// assume not http://www.w3.org/2001/XMLSchema | |
fun getNamespace(xsd: XSModel) = xsd | |
.namespaces | |
.map { e -> e as String } | |
.filterNot(::startWithHttp) | |
.first() | |
// returns: [ | |
// XSComplexTypeDecl: complex type name = 'urn:gtf:names:tc:xdal:tpa, LocationType | |
// XSComplexTypeDecl: complex type name = 'urn:gtf:names:tc:xdal:tpa, LoginEventType | |
// .. ] | |
fun parseXsd(xsd: XSModel): XSNamedMap { | |
val namespace = getNamespace(xsd) | |
return xsd.getComponentsByNamespace( | |
XSConstants.TYPE_DEFINITION, namespace) // simple and complex types | |
} | |
fun filterSimpleTypeDefs(types: XSNamedMap): List<XSTypeDefinition> { | |
val typeDefs = mutableListOf<XSTypeDefinition>() | |
for (i in 0 until types.length) // XSNamedMap doesn't implement Collection | |
if ((types.item(i) as XSTypeDefinition).typeCategory == XSTypeDefinition.COMPLEX_TYPE) | |
typeDefs.add((types.item(i) as XSTypeDefinition)) | |
return typeDefs.toList() | |
} | |
fun extractName(typeDef: XSTypeDefinition) = typeDef.name | |
// (typeDef.particle as XSParticleDecl).fValue.type = 7; | |
// where 7 is XSConstants.MODEL_GROUP, can be: element, wildcard, model_group | |
// | |
// <xs:complexType name="LocationType"> | |
// <xs:sequence> | |
// [[extract this]] | |
// </xs:sequence> | |
// </xs:complexType> | |
fun extractXsSequence(typeDef: XSTypeDefinition): XSObjectList = | |
((((typeDef as XSComplexTypeDecl) | |
.particle as XSParticleDecl) | |
.fValue as XSModelGroupImpl).particles) | |
// <xs:sequence> | |
// <xs:element name="Lat" type="xs:float"/> => [[extract Lat]] | |
// <xs:element name="Long" type="xs:float"/> => [[extract Long]] | |
// </xs:sequence> | |
fun extractXsElementNames(sequence: XSObjectList): List<String> = | |
sequence.map{ x -> (x as XSParticleDecl).fValue.name } | |
data class Struct( | |
val name: String, | |
val fields: List<String> // TODO: if the value here is complex type, we should recursively generate struct.. | |
) | |
fun generateStructs(typeDefs: XSNamedMap): List<Struct> { | |
val filtered = filterSimpleTypeDefs(typeDefs) // => [ReferenceObjectType, LocationType, LoginEventType, ...] | |
// TODO: we need to eliminate top-level structs, say if filtered looks like: | |
// <xs:complexType name="DocketType"> | |
// <xs:element name="A" type="TypeA"> (top-level) | |
// <xs:element name="B" type="TypeB"> (top-level) | |
// and schema-wise, xdal is defined like so: | |
// <xs:complexType name="TypeA"> | |
// <xs:sequence> | |
// <xs:element name="name" type="B"> (inner) | |
// </xs:sequence> | |
// </xs:complexType> | |
// | |
// output should be: where B is inner struct of A | |
// NOT top-level struct along with A | |
// | |
// currently we generate top-level | |
return filtered.map { typeDef -> Struct( | |
name = extractName(typeDef), | |
fields = extractXsElementNames(extractXsSequence(typeDef)) | |
)} | |
} | |
fun stripType(struct: Struct) = | |
struct.copy(name = struct.name.removeSuffix("Type")) | |
fun toJavaSyntax(struct: Struct): String { | |
val head = struct.name | |
val body = struct.fields | |
return """ | |
class ${head}Json { | |
@JsonProperty("$head") $head ${head.decapitalize()}; | |
static class $head { | |
${body.map { field -> | |
// TODO: the first one is indented properly but rest aren't | |
"@JsonProperty(\"$field\") String ${field.decapitalize()};" | |
}.joinToString(separator = "\n")} | |
} | |
} | |
""".trimIndent() | |
} | |
fun toJavaSyntax(structs: List<Struct>): String = | |
structs.map(::toJavaSyntax).joinToString(separator = "\n\n") | |
fun inferOutputFilepath(xsdFilepath: String) = | |
xsdFilepath.split("src/main/").first() + | |
"src/main/java/com/guardtime/amgenlink/Schema.java" | |
fun appendPackage(javaSrc: String) = | |
""" | |
package com.guardtime.amgenlink; | |
import com.fasterxml.jackson.annotation.JsonProperty; | |
$javaSrc | |
""".trimIndent() | |
fun writeToFile(javaSrc: String, filepath: String) { | |
File(filepath).writeText(javaSrc) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment