All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xmlcalabash.XMLCalabashTask.groovy Maven / Gradle / Ivy

package com.xmlcalabash

import com.xmlcalabash.core.XProcConfiguration
import com.xmlcalabash.core.XProcException
import com.xmlcalabash.core.XProcRuntime
import com.xmlcalabash.io.ReadableData
import com.xmlcalabash.io.ReadablePipe
import com.xmlcalabash.io.WritableDocument
import com.xmlcalabash.model.RuntimeValue
import com.xmlcalabash.model.Serialization
import com.xmlcalabash.runtime.XPipeline
import com.xmlcalabash.util.Closer
import com.xmlcalabash.util.Input
import com.xmlcalabash.util.Output
import com.xmlcalabash.util.UserArgs
import net.sf.saxon.s9api.QName
import net.sf.saxon.s9api.XdmNode
import org.gradle.api.internal.ConventionTask
import org.xml.sax.InputSource

import static com.xmlcalabash.core.XProcConstants.c_data
import static java.lang.String.format

class XMLCalabashTask extends ConventionTask {
    protected boolean schemaAware = false
    protected boolean safeMode = false
    protected boolean debug = false
    protected String pipelineURI = null
    protected Hashtable nsBindings = new Hashtable ()
    protected String configFile = null
    protected String saxonConfigFile = null
    protected String entityResolver = null
    protected String uriResolver = null
    protected String library = null          // Should be a list...
    protected String step = null
    protected String profile = null
    protected String edition = null
    protected boolean extensionValues = false
    protected boolean xpointerOnText = false
    protected boolean transparentJson = false
    protected boolean ignoreInvalidXmlBase = false

    @org.gradle.api.tasks.Optional
    protected String jsonFlavor = null

    protected boolean allowTextResults = false
    protected boolean useXslt10 = false
    protected boolean htmlSerializer = false;
    protected UserArgs userArgs = null
    protected Hashtable seenOptions = new Hashtable ();
    protected URI baseURI = project.getProjectDir().toURI();

    protected XProcConfiguration xprocConfiguration = null
    protected XProcRuntime runtime = null
    protected XPipeline pipeline = null
    protected Map portOutputs = null

    XMLCalabashTask() {
        userArgs = new UserArgs()
    }

    @org.gradle.api.tasks.InputFile
    String getPipeline() {
        return pipelineURI
    }

    def setPipeline(String pipeline) {
        pipelineURI = baseURI.resolve(pipeline).toASCIIString()
        getInputs().file(pipelineURI)
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getDebug() {
        return debug
    }

    def setDebug(boolean debug) {
        this.debug = debug
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getSafeMode() {
        return safeMode
    }

    def setSafeMode(boolean safe) {
        this.safeMode = safe
        return this
    }

    @org.gradle.api.tasks.Input
    @org.gradle.api.tasks.Optional
    String getProfilePipeline() {
        return profile
    }

    def setProfilePipeline(String profile) {
        this.profile = profile
        return this
    }

    @org.gradle.api.tasks.Input
    @org.gradle.api.tasks.Optional
    String getSaxonEdition() {
        if (edition == null) {
            return "he"
        } else {
            return edition
        }
    }

    def setSaxonEdition(String edition) {
        this.edition = edition
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getSchemaAware() {
        return schemaAware
    }

    def setSchemaAware(boolean aware) {
        schemaAware = aware
        return this
    }

    @org.gradle.api.tasks.Input
    @org.gradle.api.tasks.Optional
    String getEntityResolver() {
        return entityResolver
    }

    def setEntityResolver(String resolverClass) {
        entityResolver = resolverClass
        return this
    }

    @org.gradle.api.tasks.Input
    @org.gradle.api.tasks.Optional
    String getUriResolver() {
        return uriResolver
    }

    def setUriResolver(String resolverClass) {
        uriResolver = resolverClass
        return this
    }

    def namespaceBinding(String prefix, String uri) {
        nsBindings.put(prefix,uri)
        return this
    }

    @org.gradle.api.tasks.InputFile
    @org.gradle.api.tasks.Optional
    String getConfigFile() {
        return configFile
    }

    def setConfigFile(String config) {
        configFile = config
        return this
    }

    @org.gradle.api.tasks.InputFile
    @org.gradle.api.tasks.Optional
    String getSaxonConfigFile() {
        return saxonConfigFile
    }

    def setSaxonConfigFile(String config) {
        saxonConfigFile = config
        return this
    }

    @org.gradle.api.tasks.InputFile
    @org.gradle.api.tasks.Optional
    String getLibrary() {
        return library
    }

    def setLibrary(String library) {
        this.library = baseURI.resolve(library).toASCIIString()
        return this
    }

    @org.gradle.api.tasks.Input
    @org.gradle.api.tasks.Optional
    String getStep() {
        return step
    }

    /* Remove step property until issue #4 is fixed
    def setStep(String qname) {
        step = qname
        return this
    }
    */

    def input(String port, File file) {
        return input(port, file.getAbsolutePath())
    }

    def input(String port, String filename) {
        String fn = baseURI.resolve(filename).toASCIIString()

        getInputs().file(fn)

        userArgs.addInput(port, fn, Input.Type.XML)
        return this
    }

    def dataInput(String port, File file) {
        return dataInput(port, file.absolutePath)
    }

    def dataInput(String port, String filename) {
        return dataInput(port, filename, null)
    }

    def dataInput(String port, String filename, String contentType) {
        String fn = baseURI.resolve(filename).toASCIIString()

        getInputs().file(fn)

        if (contentType == null) {
            userArgs.addInput(port, fn, Input.Type.DATA)
        } else {
            userArgs.addInput(port, fn, Input.Type.DATA, contentType)
        }

        return this
    }

    def output(String port, File file) {
        return output(port, file.absolutePath)
    }

    def output(String port, String filename) {
        String fn = baseURI.resolve(filename).toASCIIString()

        getOutputs().file(fn)

        userArgs.addOutput(port, fn)
        return this
    }

    def param(String qname, File file) {
        return param(qname, file, null)
    }

    def param(String qname, File file, String port) {
        String value = file.getAbsolutePath()
        getInputs().file(value)
        return param(qname, file.toURI().toASCIIString(), port)
    }

    def param(String qname, Integer value) {
        return param(qname, value.toString(), null)
    }

    def param(String qname, Integer value, String port) {
        return setParam(qname, value.toString(), "xs:integer", port)
    }

    def param(String qname, Float value) {
        return param(qname, value.toString(), null)
    }

    def param(String qname, Float value, String port) {
        return setParam(qname, value.toString(), "xs:float", port)
    }

    def param(String qname, Boolean value) {
        return param(qname, value.toString(), null)
    }

    def param(String qname, Boolean value, String port) {
        return setParam(qname, value.toString(), "xs:boolean", port)
    }

    def param(String qname, String value) {
        return param(qname, value, null)
    }

    def param(String qname, String value, String port) {
        return setParam(qname, value, "xs:string", port)
    }

    def param(String qname, URL value) {
        return param(qname, value, null)
    }

    def param(String qname, URL value, String port) {
        return setParam(qname, value.toString(), "xs:anyURI", port)
    }

    def param(String qname, URI value) {
        return param(qname, value, null)
    }

    def param(String qname, URI value, String port) {
        return setParam(qname, value.toASCIIString(), "xs:anyURI", port)
    }

    private XMLCalabashTask setParam(String qname, String value, String type, String port) {
        if (port == null) {
            userArgs.addParam(qname, value)
        } else {
            userArgs.addParam(port, qname, value)
        }
        return this
    }

    def option(String qname, File file) {
        String value = file.getAbsolutePath()
        getInputs().file(value)
        return setOption(qname, file.toURI().toASCIIString(), "xs:anyURI")
    }

    def option(String qname, String value) {
        return setOption(qname, value, "xs:string")
    }

    def option(String qname, URI value) {
        return setOption(qname, value.toASCIIString(), "xs:anyURI")
    }

    def option(String qname, URL value) {
        return setOption(qname, value.toString(), "xs:anyURI")
    }

    def option(String qname, Integer value) {
        return setOption(qname, value.toString(), "xs:integer")
    }

    def option(String qname, Float value) {
        return setOption(qname, value.toString(), "xs:float")
    }

    def option(String qname, Boolean value) {
        return setOption(qname, value.toString(), "xs:boolean")
    }

    private XMLCalabashTask setOption(String qname, String value, String type) {
        userArgs.addOption(qname, value)
        seenOptions.put(qname, value)
        return this
    }

    def setExtensionValues(boolean value) {
        extensionValues = value
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getExtensionValues() {
        return extensionValues
    }

    def setIgnoreInvalidXmlBase(boolean value) {
        ignoreInvalidXmlBase = value
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getIgnoreInvalidXmlBase() {
        return ignoreInvalidXmlBase
    }

    def setXpointerOnText(boolean value) {
        xpointerOnText = value
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getXpointerOnText() {
        return xpointerOnText
    }

    def setTransparentJson(boolean value) {
        transparentJson = value
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getTransparentJson() {
        return transparentJson
    }

    def setJsonFlavor(String value) {
        jsonFlavor = value
        return this
    }

    @org.gradle.api.tasks.Input
    String getJsonFlavor() {
        return jsonFlavor
    }

    def setAllowTextResults(boolean value) {
        allowTextResults = value
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getAllowTextResults() {
        return allowTextResults
    }

    def setUseXslt10(boolean value) {
        useXslt10 = value
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getUseXslt10() {
        return useXslt10
    }

    def setHtmlSerializer(boolean value) {
        htmlSerializer = value
        return this
    }

    @org.gradle.api.tasks.Input
    boolean getHtmlSerializer() {
        return htmlSerializer
    }

    protected void setupRuntime() {
        if (getPipeline() == null && getStep() == null) {
            throw notAllowed("You must specify a pipeline or a step.")
        }

        if (getPipeline() != null && getStep() != null) {
            throw notAllowed("You must specify either a pipeline or a step.")
        }

        // On reflection, I'm not sure XML Calabash is well designed for this kind of embedding
        userArgs.setSaxonProcessor(getSaxonEdition())
        userArgs.setSchemaAware(getSchemaAware())
        userArgs.setSafeMode(getSafeMode())
        userArgs.setDebug(getDebug())
        userArgs.setExtensionValues(getExtensionValues())
        userArgs.setIgnoreInvalidXmlBase(getIgnoreInvalidXmlBase())
        userArgs.setAllowXPointerOnText(getXpointerOnText())
        userArgs.setTransparentJSON(getTransparentJson())
        userArgs.setAllowTextResults(getAllowTextResults())
        userArgs.setUseXslt10(getUseXslt10())
        userArgs.setHtmlSerializer(getHtmlSerializer())

        for (pfx in nsBindings.keySet()) {
            userArgs.addBinding(pfx, nsBindings.get(pfx))
        }

        if (getLibrary() != null) {
            userArgs.addLibrary(getLibrary())
        }

        if (getPipeline() != null) {
            userArgs.setPipeline(getPipeline())
        }

        if (getStep() != null) {
            userArgs.setCurStepName(getStep())
        }

        if (getConfigFile() != null) {
            userArgs.setConfig(getConfigFile())
        }

        if (getSaxonConfigFile() != null) {
            userArgs.setSaxonConfig(getSaxonConfigFile())
        }

        if (getEntityResolver() != null) {
            userArgs.setEntityResolverClass(getEntityResolver())
        }

        if (getUriResolver() != null) {
            userArgs.setUriResolverClass(getUriResolver())
        }

        if (getProfilePipeline() != null) {
            userArgs.setProfile(profile)
        }

        if (getJsonFlavor() != null) {
            userArgs.setJsonFlavor(getJsonFlavor())
        }

        xprocConfiguration = userArgs.createConfiguration()
        runtime = new XProcRuntime(xprocConfiguration);
    }

    protected void processInputs() {
        setupRuntime()
        pipeline = runtime.load(userArgs.getPipeline())

        // Process parameters from the configuration...
        for (String port : xprocConfiguration.params.keySet()) {
            Map parameters = xprocConfiguration.params.get(port);
            setParametersOnPipeline(pipeline, port, parameters);
        }

        // Now process parameters from the command line...
        for (String port : userArgs.getParameterPorts()) {
            Map parameters = userArgs.getParameters(port);
            setParametersOnPipeline(pipeline, port, parameters);
        }

        Set ports = pipeline.getInputs();
        Set userArgsInputPorts = userArgs.getInputPorts();
        Set cfgInputPorts = xprocConfiguration.inputs.keySet();
        Set allPorts = new HashSet();
        allPorts.addAll(userArgsInputPorts);
        allPorts.addAll(cfgInputPorts);

        // map a given input without port specification to the primary non-parameter input implicitly
        for (String port : ports) {
            if (!allPorts.contains(port) && allPorts.contains(null)
                    && pipeline.getDeclareStep().getInput(port).getPrimary()
                    && !pipeline.getDeclareStep().getInput(port).getParameterInput()) {

                if (userArgsInputPorts.contains(null)) {
                    userArgs.setDefaultInputPort(port);
                    allPorts.remove(null);
                    allPorts.add(port);
                }
                break;
            }
        }

        for (String port : allPorts) {
            if (!ports.contains(port)) {
                throw new XProcException("There is a binding for the port '" + port + "' but the pipeline declares no such port.");
            }

            pipeline.clearInputs(port);

            if (userArgsInputPorts.contains(port)) {
                XdmNode doc = null;
                for (Input input : userArgs.getInputs(port)) {
                    switch (input.getType()) {
                        case Input.Type.XML:
                            switch (input.getKind()) {
                                case Input.Kind.URI:
                                    String uri = input.getUri();
                                    if ("-".equals(uri)) {
                                        doc = runtime.parse(new InputSource(System.in));
                                    } else {
                                        doc = runtime.parse(new InputSource(uri));
                                    }
                                    break;

                                case Input.Kind.INPUT_STREAM:
                                    InputStream inputStream = input.getInputStream();
                                    try {
                                        doc = runtime.parse(new InputSource(inputStream));
                                    } finally {
                                        Closer.close(inputStream);
                                    }
                                    break;

                                default:
                                    throw new UnsupportedOperationException(format("Unsupported input kind '%s'", input.getKind()));
                            }
                            break;

                        case Input.Type.DATA:
                            ReadableData rd;
                            switch (input.getKind()) {
                                case Input.Kind.URI:
                                    rd = new ReadableData(runtime, c_data, input.getUri(), input.getContentType());
                                    doc = rd.read();
                                    break;

                                case Input.Kind.INPUT_STREAM:
                                    InputStream inputStream = input.getInputStream();
                                    try {
                                        rd = new ReadableData(runtime, c_data, inputStream, input.getContentType());
                                        doc = rd.read();
                                    } finally {
                                        Closer.close(inputStream);
                                    }
                                    break;

                                default:
                                    throw new UnsupportedOperationException(format("Unsupported input kind '%s'", input.getKind()));
                            }
                            break;

                        default:
                            throw new UnsupportedOperationException(format("Unsupported input type '%s'", input.getType()));
                    }

                    pipeline.writeTo(port, doc);
                }
            } else {
                for (ReadablePipe pipe : xprocConfiguration.inputs.get(port)) {
                    XdmNode doc = pipe.read();
                    pipeline.writeTo(port, doc);
                }
            }
        }

        // Implicit binding for stdin?
        String implicitPort = null;
        for (String port : ports) {
            if (!allPorts.contains(port)) {
                if (pipeline.getDeclareStep().getInput(port).getPrimary()
                        && !pipeline.getDeclareStep().getInput(port).getParameterInput()) {
                    implicitPort = port;
                }
            }
        }

        if (implicitPort != null && !pipeline.hasReadablePipes(implicitPort)) {
            XdmNode doc = runtime.parse(new InputSource(System.in));
            pipeline.writeTo(implicitPort, doc);
        }

        portOutputs = new HashMap();

        Map userArgsOutputs = userArgs.getOutputs();
        for (String port : pipeline.getOutputs()) {
            // Bind to "-" implicitly
            Output output = null;

            if (userArgsOutputs.containsKey(port)) {
                output = userArgsOutputs.get(port);
            } else if (xprocConfiguration.outputs.containsKey(port)) {
                output = new Output(xprocConfiguration.outputs.get(port));
            } else if (userArgsOutputs.containsKey(null)
                    && pipeline.getDeclareStep().getOutput(port).getPrimary()) {
                // Bind unnamed port to primary output port
                output = userArgsOutputs.get(null);
            }

            // Look for explicit binding to "-"
            if ((output != null) && (output.getKind() == Output.Kind.URI) && "-".equals(output.getUri())) {
                output = null;
            }

            portOutputs.put(port, output);
        }

        for (QName optname : xprocConfiguration.options.keySet()) {
            RuntimeValue value = new RuntimeValue(xprocConfiguration.options.get(optname), null, null);
            pipeline.passOption(optname, value);
        }

        for (QName optname : userArgs.getOptionNames()) {
            RuntimeValue value = new RuntimeValue(userArgs.getOption(optname), null, null);
            pipeline.passOption(optname, value);
        }
    }

    protected void processOutputs() {
        for (String port : pipeline.getOutputs()) {
            Output output;
            if (portOutputs.containsKey(port)) {
                output = portOutputs.get(port);
            } else {
                // You didn't bind it, and it isn't going to stdout, so it's going into the bit bucket.
                continue;
            }

            if ((output == null) || ((output.getKind() == Output.Kind.OUTPUT_STREAM) && System.out.equals(output.getOutputStream()))) {
                logger.trace("Copy output from " + port + " to stdout");
            } else {
                switch (output.getKind()) {
                    case Output.Kind.URI:
                        logger.trace("Copy output from " + port + " to " + output.getUri());
                        break;

                    case Output.Kind.OUTPUT_STREAM:
                        String outputStreamClassName = output.getOutputStream().getClass().getName();
                        logger.trace("Copy output from " + port + " to " + outputStreamClassName + " stream");
                        break;

                    default:
                        throw new UnsupportedOperationException(format("Unsupported output kind '%s'", output.getKind()));
                }
            }

            Serialization serial = pipeline.getSerialization(port);

            if (serial == null) {
                // Use the configuration options
                serial = new Serialization(runtime, pipeline.getNode()); // The node's a hack
                for (String name : xprocConfiguration.serializationOptions.keySet()) {
                    String value = xprocConfiguration.serializationOptions.get(name);

                    if ("byte-order-mark".equals(name)) serial.setByteOrderMark("true".equals(value));
                    if ("escape-uri-attributes".equals(name)) serial.setEscapeURIAttributes("true".equals(value));
                    if ("include-content-type".equals(name)) serial.setIncludeContentType("true".equals(value));
                    if ("indent".equals(name)) serial.setIndent("true".equals(value));
                    if ("omit-xml-declaration".equals(name)) serial.setOmitXMLDeclaration("true".equals(value));
                    if ("undeclare-prefixes".equals(name)) serial.setUndeclarePrefixes("true".equals(value));
                    if ("method".equals(name)) serial.setMethod(new QName("", value));

                    // FIXME: if ("cdata-section-elements".equals(name)) serial.setCdataSectionElements();
                    if ("doctype-public".equals(name)) serial.setDoctypePublic(value);
                    if ("doctype-system".equals(name)) serial.setDoctypeSystem(value);
                    if ("encoding".equals(name)) serial.setEncoding(value);
                    if ("media-type".equals(name)) serial.setMediaType(value);
                    if ("normalization-form".equals(name)) serial.setNormalizationForm(value);
                    if ("standalone".equals(name)) serial.setStandalone(value);
                    if ("version".equals(name)) serial.setVersion(value);
                }
            }

            // Command line values override pipeline or configuration specified values
            for (String name: ["byte-order-mark", "escape-uri-attributes", "include-content-type",
                               "indent", "omit-xml-declaration", "undeclare-prefixes", "method",
                               "doctype-public", "doctype-system", "encoding", "media-type",
                               "normalization-form", "standalone", "version" ]) {
                String value = userArgs.getSerializationParameter(port, name);
                if (value == null) {
                    value = userArgs.getSerializationParameter(name);
                    if (value == null) {
                        continue;
                    }
                }

                if ("byte-order-mark".equals(name)) serial.setByteOrderMark("true".equals(value));
                if ("escape-uri-attributes".equals(name)) serial.setEscapeURIAttributes("true".equals(value));
                if ("include-content-type".equals(name)) serial.setIncludeContentType("true".equals(value));
                if ("indent".equals(name)) serial.setIndent("true".equals(value));
                if ("omit-xml-declaration".equals(name)) serial.setOmitXMLDeclaration("true".equals(value));
                if ("undeclare-prefixes".equals(name)) serial.setUndeclarePrefixes("true".equals(value));
                if ("method".equals(name)) serial.setMethod(new QName("", value));
                // N.B. cdata-section-elements isn't allowed
                if ("doctype-public".equals(name)) serial.setDoctypePublic(value);
                if ("doctype-system".equals(name)) serial.setDoctypeSystem(value);
                if ("encoding".equals(name)) serial.setEncoding(value);
                if ("media-type".equals(name)) serial.setMediaType(value);
                if ("normalization-form".equals(name)) serial.setNormalizationForm(value);
                if ("standalone".equals(name)) serial.setStandalone(value);
                if ("version".equals(name)) serial.setVersion(value);
            }

            // I wonder if there's a better way...
            WritableDocument wd = null;
            if (output == null) {
                wd = new WritableDocument(runtime, null, serial);
            } else {
                switch (output.getKind()) {
                    case Output.Kind.URI:
                        URI furi = new URI(output.getUri());
                        String filename = furi.getPath();
                        FileOutputStream outfile = new FileOutputStream(filename);
                        wd = new WritableDocument(runtime, filename, serial, outfile);
                        break;

                    case Output.Kind.OUTPUT_STREAM:
                        OutputStream outputStream = output.getOutputStream();
                        wd = new WritableDocument(runtime, null, serial, outputStream);
                        break;

                    default:
                        throw new UnsupportedOperationException(format("Unsupported output kind '%s'", output.getKind()));
                }
            }

            try {
                ReadablePipe rpipe = pipeline.readFrom(port);
                while (rpipe.moreDocuments()) {
                    wd.write(rpipe.read());
                }
            } finally {
                if (output != null) {
                    wd.close();
                }
            }
        }
    }

    @org.gradle.api.tasks.TaskAction
    void exec() {
        processInputs()
        pipeline.run()
        processOutputs()
    }

    private static void setParametersOnPipeline(XPipeline pipeline, String port, Map parameters) {
        if ("*".equals(port)) {
            for (QName name : parameters.keySet()) {
                pipeline.setParameter(name, new RuntimeValue(parameters.get(name)));
            }
        } else {
            for (QName name : parameters.keySet()) {
                pipeline.setParameter(port, name, new RuntimeValue(parameters.get(name)));
            }
        }
    }

    private static UnsupportedOperationException notAllowed(final String msg) {
        return new UnsupportedOperationException (msg)
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy