From 4f2c229103b9e4b8db7d800c5c60e8591b50bf1b Mon Sep 17 00:00:00 2001 From: Norman Walsh Date: Tue, 1 Sep 2015 10:03:19 -0500 Subject: [PATCH] Version 2.0.9a5 includes a java app to run the stylesheets --- .gitignore | 4 +- build.gradle | 94 ++++++++++- gradle.properties | 2 +- src/main/java/org/docbook/DocBook.java | 224 +++++++++++++++++++++++++ src/main/java/org/docbook/Main.java | 122 ++++++++++++++ src/main/resources/etc/README | 3 + xslt/base/VERSION.xsl | 2 +- 7 files changed, 445 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/docbook/DocBook.java create mode 100644 src/main/java/org/docbook/Main.java create mode 100644 src/main/resources/etc/README diff --git a/.gitignore b/.gitignore index de24fef6..39cd9794 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ /local/ /.gradle/ /gentext/build/ -/build/ \ No newline at end of file +/build/ +/src/main/resources/xslt/ +/src/main/resources/etc/version.properties diff --git a/build.gradle b/build.gradle index b67bda8e..a3ef93f1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,13 @@ +// This Gradle script builds the DocBook stylesheet releases. +// +// Note well: there's a little Java tool in this repo, but it is not, +// first and foremost, a Java project. For historical reasons, and +// to keep the paths shorter and simpler, the stylesheets that are +// in src/main/resources/xslt/ *ARE NOT SOURCES*. They're copied there +// from xslt/ by the build process. I couldn't find a way to get +// Gradle to accept resources directly from xslt/ without losing the +// top-level xslt/ part of the path names. So I punted. + buildscript { repositories { mavenLocal() @@ -16,6 +26,10 @@ buildscript { plugins { id "java" + id "osgi" + id "maven" + id "maven-publish" + id "signing" } repositories { @@ -40,11 +54,38 @@ dependencies { [group: 'net.sf.saxon', name: 'Saxon-HE', version: saxonVersion], [group: 'com.xmlcalabash', name: 'xmlcalabash', version: '1.1.6-96'], [group: 'com.xmlcalabash', name: 'xmlcalabash1-deltaxml', version: '1.1.4'], + [group: 'com.xmlcalabash', name: 'xmlcalabash1-print', version: '1.1.2'], [group: 'com.thaiopensource', name: 'jing', version: '20091111', transitive: false], - [group: 'org.docbook', name: 'docbook-xsl-java-saxon', version: '1.2.1-95'] + [group: 'org.docbook', name: 'docbook-xsl-java-saxon', version: '1.2.1-95'], + [group: 'commons-cli', name: 'commons-cli', version: '1.3.1'] ) } +project.ext.saxonRelease = saxonVersion.substring(0,5) +project.ext.saxonBranch = saxonVersion.substring(0,3).replaceAll("\\.", "") +project.ext.releaseVersion = version +project.ext.distVersion = version + "-" + saxonBranch + snapshot + +def Properties versionProps = new Properties() +def versionPropsFile = file("src/main/resources/etc/version.properties") +versionProps['version'] = releaseVersion +versionProps.store(versionPropsFile.newWriter(),null) + +task copyLib(type: Copy) { + FileCollection runtime = configurations.runtime + FileCollection localLib = fileTree(dir: 'delta').include("*.jar") + FileCollection lib = runtime - localLib + String path = "" + lib.each { + File file -> path += " lib/" + file.name + } + project.ext.runtimeClasspath = path.trim() + + from lib + into { "build/libs/lib" } +} +jar.dependsOn copyLib + task runtests(type: JavaExec) { classpath = configurations.runtime @@ -134,7 +175,35 @@ task copyLocales(dependsOn: gentext, type: Copy) { includeEmptyDirs = false } -task buildDist(dependsOn: [makeVersion,copyLocales,makeParams,relnotes,testreport]) { +task copyJarResources(dependsOn: [copyLocales, makeParams], type: Copy) { + FileTree tree = fileTree(dir: 'xslt', + exclude: ['params/**']) + from tree + into 'src/main/resources/xslt' +} +processResources.dependsOn copyJarResources + +jar { + baseName "docbook-xslt2" + manifest { + instruction 'Built-By', builtBy + instruction 'Implementation-Vendor', 'Norman Walsh' + instruction 'Implementation-Title', 'DocBook XSLT Stylesheets' + instruction 'Implementation-Version', project.ext.distVersion + instruction 'Main-Class', 'org.docbook.Main' + instruction 'Bundle-SymbolicName', 'org.docbook' + instruction 'Bundle-RequiredExecutionEnvironment', 'J2SE-1.7' + instruction 'Export-Package', '*, etc' + instruction 'Import-Package', 'net.sf.saxon.*;version=' + project.ext.saxonRelease + ',\ + javax.xml.*,\ + *;resolution:=optional' + instruction 'DynamicImport-Package', '*' + instruction 'Class-Path', project.ext.runtimeClasspath + " lib" + } +} + +task buildDist(dependsOn: [makeVersion,copyLocales,makeParams, + relnotes,testreport]) { // nothing to see here } @@ -208,7 +277,25 @@ task updateDist(dependsOn: [copyAll, buildDist, zip], type: Copy) { into 'build/distributions/' } -task dist(dependsOn: updateDist, type: JavaExec) { +task distJar(dependsOn: [copyAll, jar], type: Copy) { + from "build/libs" + into "build/dist/jar" +} + +task distJarEtc(dependsOn: [updateDist, distJar], type: Copy) { + FileTree tree = fileTree(dir: 'build/dist/bin', + exclude: ['xslt/**']) + from tree + into "build/dist/jar" +} + +task zipJar(dependsOn: distJarEtc, type: Zip) { + from('build/dist/jar') + into 'docbook-xslt2-' + version + archiveName 'docbook-xslt2-' + version + "-jar.zip" +} + +task dist(dependsOn: [updateDist, zipJar], type: JavaExec) { classpath = configurations.runtime main = 'com.xmlcalabash.drivers.Main' @@ -221,6 +308,7 @@ task dist(dependsOn: updateDist, type: JavaExec) { task clean.doFirst { delete "gentext/build/" + delete "src/main/resources/xslt" delete "xslt/base/common/locales/" delete "xslt/base/fo/param.xsl" delete "xslt/base/html/param.xsl" diff --git a/gradle.properties b/gradle.properties index 774ee8e7..99f0a6da 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=2.0.9a4 +version=2.0.9a5 snapshot= builtBy=Norman Walsh diff --git a/src/main/java/org/docbook/DocBook.java b/src/main/java/org/docbook/DocBook.java new file mode 100644 index 00000000..2a4bd415 --- /dev/null +++ b/src/main/java/org/docbook/DocBook.java @@ -0,0 +1,224 @@ +package org.docbook; + + +import com.xmlcalabash.core.XProcConfiguration; +import com.xmlcalabash.core.XProcRuntime; +import com.xmlcalabash.io.WritableDocument; +import com.xmlcalabash.model.RuntimeValue; +import com.xmlcalabash.model.Serialization; +import com.xmlcalabash.piperack.Run; +import com.xmlcalabash.runtime.XPipeline; +import com.xmlcalabash.util.Input; +import com.xmlcalabash.util.XProcURIResolver; +import net.sf.saxon.s9api.QName; +import net.sf.saxon.s9api.SaxonApiException; +import net.sf.saxon.s9api.XdmNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xmlresolver.Catalog; +import org.xmlresolver.Resolver; + +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.security.CodeSource; +import java.util.Hashtable; +import java.util.Vector; + +class DocBook { + private static final QName _output = new QName("", "output"); + + private String proctype = "he"; + private boolean schemaAware = false; + private String classLoc = null; + private Hashtable nsbindings = new Hashtable (); + private Hashtable params = new Hashtable (); + private Vector paramFiles = new Vector(); + private String sourcefn = null; + private Hashtable options = new Hashtable(); + protected Logger logger = null; + + public DocBook() { + logger = LoggerFactory.getLogger(DocBook.class); + // Where am I? + CodeSource src = DocBook.class.getProtectionDomain().getCodeSource(); + classLoc = src.getLocation().toString(); + logger.debug("classLoc=" + classLoc); + } + + public void setParam(String param, String value) { + if (param.contains(":")) { + int pos = param.indexOf(":"); + String pfx = param.substring(0, pos); + String local = param.substring(pos+1); + if (nsbindings.containsKey(pfx)) { + QName name = new QName(pfx, nsbindings.get(pfx), local); + params.put(name, new RuntimeValue(value)); + logger.debug("Parameter " + name.getClarkName() + "=" + value); + } else { + throw new RuntimeException("No namespace binding for prefix: " + pfx); + } + + } else { + params.put(new QName("", param), new RuntimeValue(value)); + logger.debug("Parameter " + param + "=" + value); + } + } + + public void setOption(String opt, String value) { + if (opt.contains(":")) { + int pos = opt.indexOf(":"); + String pfx = opt.substring(0, pos); + String local = opt.substring(pos+1); + if (nsbindings.containsKey(pfx)) { + QName name = new QName(pfx, nsbindings.get(pfx), local); + options.put(name, new RuntimeValue(value)); + logger.debug("Option " + name.getClarkName() + "=" + value); + } else { + throw new RuntimeException("No namespace binding for prefix: " + pfx); + } + + } else { + options.put(new QName("", opt), new RuntimeValue(value)); + logger.debug("Option " + opt + "=" + value); + } + } + + public void setNamespace(String prefix, String uri) { + nsbindings.put(prefix, uri); + } + + public void addParameterFile(String fn) { + paramFiles.add(fn); + logger.debug("Param file=" + fn); + } + + public void run(String sourcefn) throws IOException, SaxonApiException { + XProcConfiguration config = new XProcConfiguration(proctype, schemaAware); + XProcRuntime runtime = new XProcRuntime(config); + + String baseURI = "file://" + System.getProperty("user.dir") + "/"; + + XdmNode source = runtime.parse(sourcefn, baseURI); + + String xpl = "/xslt/base/pipelines/docbook.xpl"; + String finalBase = "/xslt/base/"; + if (classLoc.endsWith(".jar")) { + xpl = "jar:" + classLoc + "!" + xpl; + finalBase = "jar:" + classLoc + "!" + finalBase; + + // Are the XSLT resources actually in the jar? + System.err.println("URL: " + xpl); + URL resource = new URL(xpl); + URLConnection connection = resource.openConnection(); + Object foo = connection.getContent(); + System.err.println(connection); + System.err.println(foo); + } else { + // This is only supposed to happen on a dev box + int pos = classLoc.indexOf("/build/"); + xpl = classLoc.substring(0, pos) + xpl; + finalBase = classLoc.substring(0, pos) + finalBase; + } + + File tempcat = null; + try { + tempcat = File.createTempFile("dbcat", ".xml"); + tempcat.deleteOnExit(); + PrintStream catstream = new PrintStream(tempcat); + + catstream.println(""); + + for (String fn : new String[] { "html/final-pass.xsl", "html/chunktemp.xsl", "fo/final-pass.xsl" }) { + catstream.println(""); + catstream.println(""); + + } + + catstream.println(""); + + catstream.close(); + logger.debug("Final pass catalog: " + tempcat.getAbsolutePath()); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + + Catalog catalog = new Catalog(tempcat.getAbsolutePath()); + + XProcURIResolver resolver = runtime.getResolver(); + URIResolver uriResolver = resolver.getUnderlyingURIResolver(); + URIResolver myResolver = new DocBookResolver(uriResolver, catalog); + resolver.setUnderlyingURIResolver(myResolver); + + logger.debug("Pipline=" + xpl); + + XPipeline pipeline = runtime.load(new Input(xpl)); + pipeline.writeTo("source", source); + + for (String param : paramFiles) { + XdmNode pfile = runtime.parse(param, baseURI); + pipeline.writeTo("parameters", pfile); + } + + for (QName param : params.keySet()) { + pipeline.setParameter(param, params.get(param)); + } + + for (QName opt : options.keySet()) { + pipeline.passOption(opt, options.get(opt)); + } + + pipeline.run(); + + XdmNode result = pipeline.readFrom("result").read(); + Serialization serial = pipeline.getSerialization("result"); + + if (serial == null) { + serial = new Serialization(runtime, pipeline.getNode()); // The node's a hack + serial.setMethod(new QName("", "xhtml")); + } + + WritableDocument wd = null; + if (options.containsKey(_output)) { + String filename = options.get(_output).getStringValue().asString(); + FileOutputStream outfile = new FileOutputStream(filename); + wd = new WritableDocument(runtime, filename, serial, outfile); + logger.info("Writing output to " + filename); + } else { + wd = new WritableDocument(runtime, null, serial); + } + + wd.write(result); + } + + + private class DocBookResolver extends Resolver { + URIResolver nextResolver = null; + Resolver resolver = null; + + public DocBookResolver(URIResolver resolver, Catalog catalog) { + nextResolver = resolver; + this.resolver = new Resolver(catalog); + } + + @Override + public Source resolve(String href, String base) throws TransformerException { + // We go last + Source src = nextResolver.resolve(href, base); + if (src == null) { + return resolver.resolve(href, base); + } else { + return src; + } + } + } +} diff --git a/src/main/java/org/docbook/Main.java b/src/main/java/org/docbook/Main.java new file mode 100644 index 00000000..c6ffb492 --- /dev/null +++ b/src/main/java/org/docbook/Main.java @@ -0,0 +1,122 @@ +package org.docbook; + +import net.sf.saxon.s9api.SaxonApiException; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; +import java.util.List; +import java.util.Properties; + +/** + * Created by ndw on 8/30/15. + */ +public class Main { + private static final String usage = "java -jar docbook-xslt2-" + version() + ".jar [options] dbdoc.xml [param=value [param=value] ...] "; + + public static void main(String[] args) throws IOException, SaxonApiException { + DocBook docbook = new DocBook(); + + Options options = new Options(); + + Option format = Option.builder("f").argName("format").longOpt("format").hasArg().desc("The format: (x)html (css)print foprint").build(); + Option style = Option.builder("s").argName("stylesheet").longOpt("stylesheet").hasArg().desc("Custom final-pass XSL stylesheet").build(); + Option post = Option.builder().argName("postprocess").longOpt("postprocess").hasArg().desc("Post-processing stylesheet").build(); + Option pdf = Option.builder().argName("pdf").longOpt("pdf").hasArg().desc("Name for the output PDF file (print only)").build(); + Option output = Option.builder("o").argName("output").longOpt("output").hasArg().desc("Name for the output file (defaults to stdout)").build(); + Option css = Option.builder().argName("css").longOpt("css").hasArg().desc("A CSS stylesheet (CSS print only)").build(); + + Option help = Option.builder("h").argName("help").longOpt("help").desc("Usage: org.docbook.Main [options] dbdoc.xml").build(); + Option params = Option.builder().argName("params").longOpt("params").hasArg().desc("A file of parameters").build(); + Option ns = Option.builder().argName("namespace").longOpt("ns").hasArgs().numberOfArgs(2).valueSeparator().desc("Namespace binding").build(); + + options.addOption(format); + options.addOption(style); + options.addOption(post); + options.addOption(pdf); + options.addOption(output); + options.addOption(css); + options.addOption(params); + options.addOption(ns); + options.addOption(help); + + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + + try { + CommandLine cmd = parser.parse(options, args); + + if (cmd.hasOption("ns")) { + String[] nsbindings = cmd.getOptionValues("ns"); + for (int idx = 0; idx < nsbindings.length; idx += 2) { + String pfx = nsbindings[idx]; + String uri = nsbindings[idx + 1]; + docbook.setNamespace(pfx,uri); + } + } + + if (cmd.hasOption("params")) { + for (String paramfn : cmd.getOptionValues("params")) { + docbook.addParameterFile(paramfn); + } + } + + for (String opt : new String[] { "postprocess", "pdf", "css", "output", "format", "stylesheet" }) { + if (cmd.hasOption(opt)) { + docbook.setOption(opt, cmd.getOptionValue(opt)); + } + } + + List rest = cmd.getArgList(); + String doc = null; + + for (String param : rest) { + if (param.contains("=")) { + int pos = param.indexOf("="); + String name = param.substring(0, pos); + String value = param.substring(pos+1); + docbook.setParam(name, value); + } else { + doc = param; + } + } + + if (doc == null) { + formatter.printHelp(usage, options); + System.exit(1); + } + + docbook.run(doc); + } catch (ParseException pe) { + System.err.println("Unexpected: " + pe.getMessage()); + formatter.printHelp(usage, options); + System.exit(1); + } + } + + private static String version() { + Properties config = new Properties(); + InputStream stream = null; + try { + stream = Main.class.getResourceAsStream("/etc/version.properties"); + if (stream == null) { + throw new UnsupportedOperationException("JAR file doesn't contain version.properties file!?"); + } + config.load(stream); + String version = config.getProperty("version"); + if (version == null) { + throw new UnsupportedOperationException("Invalid version.properties in JAR file!?"); + } + return version; + } catch (IOException ioe) { + throw new UnsupportedOperationException("No version.properties in JAR file!?"); + } + } +} diff --git a/src/main/resources/etc/README b/src/main/resources/etc/README new file mode 100644 index 00000000..08b2eab1 --- /dev/null +++ b/src/main/resources/etc/README @@ -0,0 +1,3 @@ +This file just exists so that the etc directory will be created. +The file version.properties that gets generated in here should +not be in version control. diff --git a/xslt/base/VERSION.xsl b/xslt/base/VERSION.xsl index c302f573..a5a860fb 100644 --- a/xslt/base/VERSION.xsl +++ b/xslt/base/VERSION.xsl @@ -1,4 +1,4 @@ - +