Moqui XSD Schema IntelliJ Plugin

What do you think about an IntelliJ plugin for Moqui that will automatically configure the XSD schema?

I’ve just thought about how annoying it is to setup the XSD fschema or each different moqui file that you use and an IntelliJ plugin that would automatically configure the XSD schema automatically.

Maybe the simplest way to solve this problem is by creating a simple gradle task like setupIdeaCatalog that parses the catalog and apply it automatically to misc.xml. It’s relatively easy to do and keeps the solution inside moqui (avoiding yet another thing to download / learn). Gradle / groovy also comes with all the XML parsing you need to build the catalog settings so you won’t need to import / add anything.

1 Like

I think it would be very helpful if there were such a thing.

1 Like

@taher So are you saying that IntelliJ will automatically inherit the gradle / groovy parsing settings, and one of the settings for gradle / groovy is where the XSD schema is defined?

If so, that shouldn’t be difficult at all.

Another simpler solution would be to find the easiest way to setup XSD schema globally (currently I have to setup the XSD schema for each moqui project that I have and it gets annoying.

Hello @michael

What I mean is simply calling a task that generates a .idea/misc.xml and hence your auto-complete from XSDs would work. I’ve already implemented such a task and thus I don’t need to touch the catalog settings. I just run ./gradlew setupIntellij and I’m good. The whole task is around like 20 lines of code and uses XmlSlurper with StreamingMarkupBuilder to do the job.

If you find such a feature useful I’d gladly do a PR or submit the code in whatever form you like. I actually have 2 tasks, one for intellij and one for visual studio code (both of which are awesome editors).

1 Like

I’d love it if you would submit a PR to the moqui organization repositories that are needed, and I’d also like it if I you could send me the code / fork that you have sooner rather than later so that I can use it.

task setupIntellij {
    doLast {
        def ideaDir = "${rootDir}/.idea"
        def xsdDir = "${rootDir}/framework/xsd"
        def parser = new XmlSlurper()
        parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
        parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
        def xmlCatalog = parser.parse(file("${xsdDir}/framework-catalog.xml"))
        def builder = new groovy.xml.StreamingMarkupBuilder()
        builder.encoding = 'UTF-8'
        def rawXml = builder.bind {
            project(version: '4') {
                component(name: 'ExternalStorageConfigurationManager', enabled: true)
                component(name: 'ProjectResources') {
                    xmlCatalog.system.each { entry ->
                        resource(
                            url: entry.@systemId,
                            location: "\$PROJECT_DIR/framework/xsd/${entry.@uri}"
                        )
                    }
                }
            }
        }
        def misc = groovy.xml.XmlUtil.serialize(rawXml)
        delete "${ideaDir}/misc.xml"
        mkdir ideaDir
        new File("${ideaDir}/misc.xml").write(misc)
    }
}

I hope you find that useful

1 Like

Where does that code go? I assume somewhere in the moqui-framework, but not sure what file.

in the main build.gradle (or actually any build.gradle)

Maybe I should make a PR soon to avoid this confusion. Sorry if I’m not explaining things clearly.

1 Like

No worries. I added your code to the end of my moqui-framework build.gradle file and when I ran gradle setupIntelliJ IntelliJ got the external schemas, but the URLs are invalid.

I think that despite the URLs being invalid, the autofill stuff still works.

Hmm could be a bug somewhere in my submitted code, I’ll review it and fix it. Might as well just do a PR based on all of that and get it out of the way

OK As expected, a very small typo was the reason. Posting the corrected stuff:

task setupIntellij {
    doLast {
        def ideaDir = "${rootDir}/.idea"
        def xsdDir = "${rootDir}/framework/xsd"
        def parser = new XmlSlurper()
        parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
        parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
        def xmlCatalog = parser.parse(file("${xsdDir}/framework-catalog.xml"))
        def builder = new groovy.xml.StreamingMarkupBuilder()
        builder.encoding = 'UTF-8'
        def rawXml = builder.bind {
            project(version: '4') {
                component(name: 'ExternalStorageConfigurationManager', enabled: true)
                component(name: 'ProjectResources') {
                    xmlCatalog.system.each { entry ->
                        resource(
                            url: entry.@systemId,
                            location: "\$PROJECT_DIR\$/framework/xsd/${entry.@uri}"
                        )
                    }
                }
            }
        }
        def misc = groovy.xml.XmlUtil.serialize(rawXml)
        delete "${ideaDir}/misc.xml"
        mkdir ideaDir
        new File("${ideaDir}/misc.xml").write(misc)
    }
}
1 Like

That worked thank you!

Github PR issued

1 Like

One thing I noticed when I tried this is that it kills an existing misc.xml file, so if used on an existing IntelliJ project it will clear settings like the JDK selected, etc.

1 Like

@taher Thank you for doing this. This is going to save me so much time

Hi David,

It would get the logic a little bigger, but I can make it append the catalogs instead of replacing the entire misc file if you believe that this is a better approach?

1 Like

Thank you :pray:

1 Like

This is an updated version (I need to get rid of a little copy-paste) that does not destroy misc.xml if it already exists and just rebuilds the resources. WDYT?

task setupIntellij {
    description "Adds all XML catalog items to intellij to enable autocomplete"
    doLast {
        def ideaDir = "${rootDir}/.idea"
        def xsdDir = "${rootDir}/framework/xsd"
        def parser = new XmlSlurper()
        parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
        parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
        def xmlCatalog = parser.parse(file("${xsdDir}/framework-catalog.xml"))
        mkdir ideaDir
        def rawXml
        def miscFile = new File("${ideaDir}/misc.xml")
        if (miscFile.exists()) {
            def projectNode = parser.parse(miscFile)
            projectNode.children().findAll { it.@name == 'ProjectResources' }.replaceNode {}
            projectNode.appendNode {
                component(name: 'ProjectResources') {
                    xmlCatalog.system.each { entry ->
                        resource(
                            url: entry.@systemId,
                            location: "\$PROJECT_DIR\$/framework/xsd/${entry.@uri}"
                        )
                    }
                }
            }
            rawXml = projectNode
        } else {
            def builder = new groovy.xml.StreamingMarkupBuilder()
            builder.encoding = 'UTF-8'
            rawXml = builder.bind {
                project(version: '4') {
                    component(name: 'ExternalStorageConfigurationManager', enabled: true)
                    component(name: 'ProjectResources') {
                        xmlCatalog.system.each { entry ->
                            resource(
                                url: entry.@systemId,
                                location: "\$PROJECT_DIR\$/framework/xsd/${entry.@uri}"
                            )
                        }
                    }
                }
            }
        }
        def misc = groovy.xml.XmlUtil.serialize(rawXml)
        miscFile.write(misc)
    }
}
1 Like