Skip to content

Instantly share code, notes, and snippets.

@dvliman
Created March 7, 2019 00:21
Show Gist options
  • Save dvliman/5f9955195f2a8e51445bae7bad759efd to your computer and use it in GitHub Desktop.
Save dvliman/5f9955195f2a8e51445bae7bad759efd to your computer and use it in GitHub Desktop.
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