Rewrite to use local config instead of jumping through hoops trying to figure it...
authorNorman Walsh <ndw@nwalsh.com>
Wed, 14 Dec 2005 11:52:39 +0000 (11:52 +0000)
committerNorman Walsh <ndw@nwalsh.com>
Wed, 14 Dec 2005 11:52:39 +0000 (11:52 +0000)
cvstools/saxon
cvstools/xalan
cvstools/xslt
cvstools/xsltproc

index a6707c16f4d5e486f4bb8f9c2babb99d21a0cf4d..80183a1fe71d8a36c922817b796e18d676524c44 100755 (executable)
-#!/bin/bash
-
-# This script is usually called by the xslt script.
+#!/usr/bin/perl -w -- #  -*- Perl -*-
+#
+# This script runs the Saxon XSLT processor. It relies on a configuration
+# file to identify java versions, class paths, etc.
+#
+# Usage: saxon [opts] input style [output] [params]
+#
+# Options:
 #
-# Usage: saxon [shellopts] src.xml style.xsl output.{xml|html} [styleopts]
+#    -6...     Specifies Saxon 6 or some specific version of Saxon 6
+#    -8...     Specifies Saxon 8 or some specific version of Saxon 8
+#    -8...sa   Specifies "Schema Aware" Saxon. Requires a license.
+#    -8...b    Specifies basic Saxon.
+#    -debug    Debugging
+#    -config   Where is the config file? Defaults to ~/.xmlc
+#    -opts id  Use additional options 'id' from the config file.
+#              The -opts option may be specified more than once
 #
+# Any other options are passed to Saxon unchanged
+#
+# Default version is calculated by looking at the stylesheet. So the
+# -6/-8 option is usually unnecessary.
+
+use strict;
+use English;
+use XML::XPath;
+use XML::XPath::XMLParser;
+
+my $usage = "saxon [opts] input style [output] [params]\n";
+
+my $version = undef;
+my $saxonsa = undef;
+my $debug = 0;
+my $config = "~/.xmlc";
+my @opts = ();
+
+while (@ARGV) {
+    if ($ARGV[0] =~ /^\-6/) {
+       $version = substr($ARGV[0],1);
+       shift @ARGV;
+    } elsif ($ARGV[0] =~ /^\-(8.*?)-?(sa|b)?$/) {
+       $version = $1;
+       $saxonsa = $2 if defined($2);
+       shift @ARGV;
+    } elsif ($ARGV[0] eq '-debug') {
+       $debug = 1;
+       shift @ARGV;
+    } elsif ($ARGV[0] eq '-config') {
+       shift @ARGV;
+       $config = shift @ARGV;
+    } elsif ($ARGV[0] eq '-opts') {
+       shift @ARGV;
+       push(@opts, shift @ARGV);
+    } else {
+       last;
+    }
+}
+
+my @params = ();
+while (@ARGV && $ARGV[$#ARGV] =~ /=/) {
+    unshift (@params, pop @ARGV);
+}
+
+my $input = undef;
+my $style = undef;
+my $output = undef;
+
+my $opt_a = 0;
+foreach my $opt (@ARGV) {
+    $opt_a = 1 if $opt eq '-a';
+    last if $opt_a;
+}
+
+if ($opt_a) {
+    $output = pop @ARGV;
+    $input = pop @ARGV;
+
+    # What if the user didn't specify an output location?
+    if (!defined($input) || $input =~ /^-/) {
+       push (@ARGV, $input) if defined($input);
+       $input = $output;
+       $output = undef;
+    }
+} else {
+    $output = pop @ARGV;
+    $style = pop @ARGV;
+    $input = pop @ARGV;
+
+    # What if the user didn't specify an output location?
+    if (!defined($input) || $input =~ /^-/) {
+       push (@ARGV, $input) if defined($input);
+       $input = $style;
+       $style = $output;
+       $output = undef;
+    }
+}
+
+# Everything else goes to Saxon
+my @saxonopts = @ARGV;
+push (@saxonopts, "-o $output") if defined($output) && $output ne '-';
+
+die $usage if !defined($input) || (!defined($style) && !$opt_a);
+
+if (!defined($version)) {
+    if (defined($style)) {
+       open (F, $style) || die "Cannot open stylesheet: $style\n$usage";
+       read (F, $_, 4096);
+       close (F);
+
+       if (/<\S+:?import-schema/s) {
+           $version = "8" if !defined($version);
+           $saxonsa = "sa" if !defined($saxonsa);
+       } elsif (/<\S+:?stylesheet[^>]*\sversion=.2\.0/s
+                || /<\S+:?transform[^>]*\sversion=.2\.0/s) {
+           $version = "8" if !defined($version);
+           $saxonsa = "b" if !defined($saxonsa) && $version =~ /^8/;
+       } else {
+           $version = "6" if !defined($version);
+       }
+    } else {
+       $style = "";
+       $version = "6" if !defined($version);
+    }
+}
+
+my $optsname = "saxon";
+$optsname .= $saxonsa if defined($saxonsa);
+$optsname .= "-$version";
+
+# Inelegantly, these are used as globals by several functions
+my %seenopts = ();
+my $classname = "";
+my $java = "";
+my @systemprops = ();
+my @javaopts = ();
+my @classpath = ();
+
+$config = (glob($config))[0]; # hack to expand ~/.xmlc to right place
+
+die "Cannot read config: $config\n$usage" if ! -f $config;
+my $xp = XML::XPath->new('filename' => $config);
+my $doc = ($xp->find("/config")->get_nodelist())[0];
+die "Unexpected root element in configuration file.\n" if !$doc;
+
+# Figure out the class path separator before we go any further
+my $cpseparator = $doc->getAttribute('classpath-separator');
+# Default to ';' if it appears in $CLASSPATH, otherwise ':'
+$cpseparator = ($ENV{'CLASSPATH'} =~ /;/ ? ";" : ":")
+    if !defined($cpseparator) or $cpseparator eq '';
+
+foreach my $name (@opts, $optsname) {
+    applyOpts($xp, $name);
+}
+
+$java = "java" if $java eq '';
+
+foreach my $path (reverse split(/$cpseparator/, $ENV{'CLASSPATH'})) {
+    unshift(@classpath, $path);
+}
+
+showVars() if $debug;
+
+die "No classname?\n" if !defined($classname);
+
+my $jopts = join(" ", @javaopts);
+my $jprops = join(" ", @systemprops);
+my $jcp = join($cpseparator, @classpath);
+my $sopts = join(" ", @saxonopts);
+my $sparam = join(" ", @params);
+
+if ($cpseparator eq ';') {
+    # This must be cygwin or some windows flavor, let's try to make it work
+    $jcp =~ s/\//\\\\/sg; # turn / into \\ in classpath paths
+    $jcp =~ s/\;/\\\;/sg; # escape semicolons
+    if (@params) {
+       $sparam = "\"" . join("\" \"", @params) . "\"";
+    } else {
+       $sparam = "";
+    }
+}
+
+print "$java $jopts $classname $sopts $input $style $sparam\n" if $debug;
+exec("$java $jopts -cp $jcp $jprops $classname $sopts $input $style $sparam");
+
+# ============================================================
+
+sub applyOpts {
+    my $xp = shift;
+    my $id = shift;
+
+    # Avoid loops
+    if ($seenopts{$id}) {
+       print STDERR "Skipping $id (already seen)\n" if $debug;
+       return;
+    }
+    $seenopts{$id} = 1;
+
+    my $saxon = ($xp->find("/config/*[\@xml:id='$id']")->get_nodelist())[0];
+
+    die "Config $config does not contain $id.\n" if !defined($saxon);
+
+    print STDERR "Loading $id from $config\n" if $debug;
+
+    $classname = $saxon->getAttribute('class')
+       if $classname eq '';
+
+    $java = $saxon->getAttribute('java')
+       if $java eq '';
+
+    foreach my $path (configPath($saxon, 'classpath', $cpseparator)) {
+       addOpt(\@classpath, $path);
+    }
+
+    foreach my $prop (configArgs($saxon, 'system-property', '-D', '=')) {
+       addOpt(\@systemprops, $prop, '=');
+    }
+
+    foreach my $arg (configArgs($saxon, 'arg', '-', ' ')) {
+       addOpt(\@saxonopts, $arg, ' ');
+    }
+
+    foreach my $opt (configArgs($saxon, 'java-option', '-', '=')) {
+       addOpt(\@javaopts, $opt, ' ');
+    }
+
+    foreach my $param (configArgs($saxon, 'param', '', '=')) {
+       addOpt(\@params, $param, '=');
+    }
+
+    my $extends = $saxon->getAttribute('extends');
+    applyOpts($xp, $extends) if $extends ne '';
+}
+
+sub configArgs {
+    my $context = shift;
+    my $elemname = shift;
+    my $prefix = shift;
+    my $sep = shift;
+    my @opts = ();
+
+    if ($context) {
+       my $args = $context->find($elemname);
+       foreach my $arg ($args->get_nodelist()) {
+           my $name = $arg->getAttribute('name');
+           my $value = $arg->getAttribute('value');
+           if (defined($value) && $value ne '') {
+               push(@opts, "$prefix$name$sep$value");
+           } else {
+               push(@opts, "$prefix$name");
+           }
+       }
+    }
+
+    return @opts;
+}
+
+sub configPath {
+    my $context = shift;
+    my $elemname = shift;
+    my $sep = shift || ":";
+    my @path = ();
+
+    if ($context) {
+       my $args = $context->find($elemname);
+       foreach my $arg ($args->get_nodelist()) {
+           my $dir = $arg->getAttribute('path');
+           if ($dir ne '') {
+               foreach my $f (split(/$sep/, $dir)) {
+                   if (-d $f || -f $f) {
+                       push (@path, $f);
+                   } else {
+                       warn "Invalid path component: $f\n";
+                   }
+               }
+           } else {
+               warn "Invalid path component (no \@dir)\n";
+           }
+       }
+    }
+
+    return @path;
+}
+
+sub addOpt {
+    my $arrayref = shift;
+    my $newopt = shift;
+    my $sep = shift;
+    my $newname = $newopt;
+
+    $newname = $1 if defined($sep) && $newopt =~ /^(.*?)$sep/;
+
+    foreach my $opt (@{$arrayref}) {
+       my $name = $opt;
+       $name = $1 if defined($sep) && $opt =~ /^(.*?)$sep/;
+       return if $name eq $newname;
+    }
+
+    push(@{$arrayref}, $newopt);
+}
+
+sub showVars {
+    print STDERR "config: $config\n";
+    print STDERR "java: $java\n";
+    print STDERR "optsname: $optsname\n";
+    showArr("opts", @opts);
+    showArr("saxon opts", @saxonopts);
+    print STDERR "input: $input\n" if defined($input);
+    print STDERR "style: $style\n" if defined($style);
+    showArr("params", @params);
+    showArr("java opts", @javaopts);
+    showArr("system props", @systemprops);
+    print STDERR "cpseparator: $cpseparator\n";
+    showArr("classpath", @classpath);
+}
+
+sub showArr {
+    my $name = shift;
+    my @arr = @_;
 
-DONE=0
-VERSION=653
-EXTVERSION=643
-DEBUG=0
-XARG=""
-YARG=""
-RARG=""
-MEMORY=""
-VALIDATE=""
-# which parser to use
-PARSER=auto
-
-MYDIR=`dirname $0`
-. $MYDIR/common-functions.sh
-
-# identify the directory for DocBook XSL
-for dir in "/sourceforge/docbook/xsl" \
-           "$MYDIR/xsl" \
-           "/usr/share/sgml/docbook/stylesheet/xsl/nwalsh" ; do
-  if [ -d "$dir" ]; then
-    DOCBOOKXSL="$dir"
-    break
-  fi
-done
-
-while [ "$DONE" = "0" ]; do
-    case $1 in
-       -d)     DEBUG=1;
-               shift;
-               ;;
-       -6*)    VERSION=$1;
-               shift;
-               ;;
-       -7*)    VERSION=$1;
-               shift;
-               ;;
-       -8*)    VERSION=$1;
-               EXTVERSION=8
-               shift;
-               ;;
-       -x)     shift;
-               XARG="-x $1";
-               shift;
-               ;;
-       -y)     shift;
-               YARG="-y $1";
-               shift;
-               ;;
-       -r)     shift;
-               RARG="-r $1";
-               shift;
-               ;;
-        -m)     shift
-               MEMORY="-Xmx$1";
-               shift;
-               ;;
-        -parser) shift
-               PARSER="$1"
-                shift
-               ;;
-        -q)     shift
-               VERBOSE=false
-               ;;
-        -val)   shift
-               VALIDATE=-val
-               ;;
-        -v)     shift
-               VERBOSE=true
-               ;;
-        -X)     shift
-               DOCBOOKXSL="$1";
-               shift;
-               ;;
-       -*)     DONE=1;
-               echo "unexpected argument: $*" 1>&2
-               exit 1
-               ;;
-       *)      DONE=1
-    esac
-done
-
-XMLSRC=$1; shift
-XMLSTY=$1; shift
-OUTPUT=$1; shift
-
-if [ "$OUTPUT" = "-" ]; then
-    OUTPUT="";
-fi
-
-if [ "$OUTPUT" != "" ]; then
-  OUTPUT="-o $OUTPUT"
-fi
-
-if [ ! -d "$DOCBOOKXSL" ]; then
-  echo "DocBook XSL dir '$DOCBOOKXSL' doesn't exist" 1>&2
-  echo "  Try using the '-X <dir>' argument" 1>&2
-  exit 1
-fi
-
-# Toss the leading hyphen
-VERSION=`echo $VERSION | sed -e 's/^-//'`
-
-case $VERSION in
-    8a)
-       VERSION="sa-8.0";
-       ;;
-    8b)
-       VERSION="b8.0";
-       ;;
-    77|751|653|652|651|65|644|643)
-        :                       # handled normally below
-       ;;
-    *) echo "unexpected Saxon version $VERSION" 1>&2
-       exit 1
-       ;;
-esac
-
-case $PARSER in
-    auto|xerces2|xerces1|crimson|native)
-        :                       # handled normally below
-       ;;
-    *) echo "unexpected parser selected, '$PARSER'" 1>&2
-        echo "must be one of 'auto', 'xerces2', 'xerces1', 'crimson', or 'native'" 1>&2
-       exit 1
-       ;;
-esac
-
-
-DOTTEDVERSION=`echo $VERSION | sed -e 's/\([0-9]\)\([0-9]\)/\1.\2/g; s/\([0-9]\)\([0-9]\)/\1.\2/g;'`
-
-##
-## locate saxon.jar
-##
-SAXON=
-for jar in "/usr/local/java/saxon-$DOTTEDVERSION/saxon.jar" \
-           "/usr/local/java/saxon-$DOTTEDVERSION/saxon7.jar" \
-           "/usr/local/java/saxon-$DOTTEDVERSION/saxon8sa.jar" \
-           "/usr/local/java/saxon$DOTTEDVERSION/saxon8sa.jar" \
-           "/usr/local/share/java/saxon-$DOTTEDVERSION/saxon.jar" \
-           "/usr/local/share/java/saxon-$DOTTEDVERSION/saxon7.jar" \
-           "/usr/local/share/java/saxon$DOTTEDVERSION/saxon8sa.jar" \
-           "/usr/share/java/saxon-$DOTTEDVERSION.jar" \
-           "/usr/local/java/saxon/saxon.jar" \
-           "/usr/local/share/java/saxon/saxon.jar" \
-           "/usr/share/java/saxon.jar"; do
-  if [ -f "$jar" -a "$SAXON" = "" ]; then
-    SAXON="$jar"
-    break
-  fi
-done
-if [ ! -f "$SAXON" ]; then
-  echo "warning: cannot locate Saxon JAR file" 1>&2
-fi
-
-##
-## DocBook extensions
-##
-if [ ! "$NDWEXT" ]; then
-  for ext in "$DOCBOOKXSL/extensions/saxon$EXTVERSION/.classes" \
-             "$DOCBOOKXSL/extensions/saxon$EXTVERSION.jar" \
-             "/usr/share/sgml/docbook/stylesheet/xsl/nwalsh/extensions/saxon$EXTVERSION.jar" ; do
-    if [ -d $ext -o -f $ext ]; then
-      NDWEXT=$ext;
-      break
-    fi
-  done
-  if [ ! "$NDWEXT" ]; then
-    echo "warning: cannot locate DocBook XSL Saxon extensions" 1>&2
-  fi
-fi
-
-##
-## Saxon debugging stuff
-##
-if [ "$DEBUG" = "1" ]; then
-  for try in "/usr/local/java/saxon-$DOTTEDVERSION/.classes" \
-             "/usr/local/share/java/saxon-$DOTTEDVERSION/.classes" \
-             "/usr/share/java/saxon-$DOTTEDVERSION/.classes"; do
-    if [ -d "$try" ]; then
-        SAXON="$try:$SAXON"
-    fi
-  done
-fi
-
-##
-## set the desired parser
-##
-## From SAXON documentation:
-##
-## SAXON comes with a bundled XML parser, a modified copy of the
-## AElred parser, adapted to notify comments to the application. SAXON
-## has been tested successfully in the past with Xerces, Lark, SUN
-## Project X, Crimson, Oracle XML, xerces, xml4j, and xp. Use of a
-## SAX2-compliant parser is preferred, as SAX1 does not allow XML
-## comments to be passed to the application. However, SAXON works with
-## either. All the relevant classes must be installed on your Java
-## CLASSPATH.
-##
-## These are the settings which control the parser:
-##   javax.xml.parsers.DocumentBuilderFactory
-##   javax.xml.parsers.SAXParserFactory
-##
-
-# first choice is xerces2
-if [ "$PARSER" = "xerces2" -o "$PARSER" = "auto" ]; then
-  PARSERCLASS=`findxerces2`
-  if [ "$PARSERCLASS" ]; then
-    DBFACTORY="-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"
-    SPFACTORY="-Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl"
-  fi
-fi
-
-# next choice is xerces1
-if [ "$PARSER" = "xerces1" ] || [ ! "$PARSERCLASS" -a "$PARSER" = "auto" ]; then
-  PARSERCLASS=`findxerces1`
-  if [ "$PARSERCLASS" ]; then
-    DBFACTORY="-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"
-    SPFACTORY="-Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl"
-  fi
-fi
-
-# next choice is crimson
-if [ "$PARSER" = "crimson" ] || [ ! "$PARSERCLASS" -a "$PARSER" = "auto" ]; then
-  PARSERCLASS=`findcrimson`
-  if [ "$PARSERCLASS" ]; then
-    DBFACTORY="-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.crimson.jaxp.DocumentBuilderFactoryImpl"
-    SPFACTORY="-Djavax.xml.parsers.SAXParserFactory=org.apache.crimson.jaxp.SAXParserFactoryImpl"
-  fi
-fi
-
-if [ "$PARSER" != "native" -a ! "$PARSERCLASS" ]; then
-  echo "warning: cannot locate an alternate SAX parser, PEs may not work correctly" 1>&2
-fi
-
-##
-## selecting the transformer not handled.  xalan2 can provide this, 
-## javax.xml.transform.TransformerFactory
-##  = org.apache.xalan.processor.TransformerFactoryImpl
-TRANSFACTORY=com.icl.saxon.TransformerFactoryImpl
-
-##
-## optionally replace the URI resolver with the Apache
-## resolver classes
-## FIXME: do we specifically *not* want to use the sun resolver?
-##
-RESOLVER=`findresolver`
-if [ -f "$RESOLVER" -o -d "$RESOLVER" ]; then
-  # use the apache resolver
-  XARG=${XARG:--x org.apache.xml.resolver.tools.ResolvingXMLReader}
-  YARG=${YARG:--y org.apache.xml.resolver.tools.ResolvingXMLReader}
-  RARG=${RARG:--r org.apache.xml.resolver.tools.CatalogResolver}
-fi
-
-CLASSPATH=`fixclasspath "$SAXON:$NDWEXT:$RESOLVER:$PARSERCLASS:$CLASSPATH"`
-
-TFLAG=
-if [ "$DEBUG" != "0" ]; then
-  TFLAG="-T"
-fi
-
-HTTP_PROXY=
-HTTPS_PROXY=
-
-if [ "$http_proxy_host" != "" ]; then
-    HTTP_PROXY="-Dhttp.proxyHost=$http_proxy_host -Dhttp.proxyPort=$http_proxy_port"
-    HTTPS_PROXY="-Dhttps.proxyHost=$http_proxy_host -Dhttps.proxyPort=$http_proxy_port"
-fi
-
-if [ ${VERBOSE} ] && ${VERBOSE}; then
-  echo java $MEMORY -cp $CLASSPATH $DBFACTORY $SPFACTORY \
-      -Djavax.xml.transform.TransformerFactory=$TRANSFACTORY \
-      com.icl.saxon.StyleSheet $TFLAG $VALIDATE \
-      $XARG $YARG $RARG $OUTPUT $XMLSRC $XMLSTY "$@"
-fi
-
-#echo VERSION=$VERSION
-
-if [ "$VERSION" = "sa-8.0" ]; then
-  JAVA_HOME=/usr/local/jdk1.4.2
-  JAVA=$JAVA_HOME/bin/java
-#  exec $JAVA $HTTP_PROXY $HTTPS_PROXY $MEMORY -cp $CLASSPATH $DBFACTORY $SPFACTORY \
-#       -Djavax.xml.transform.TransformerFactory=$TRANSFACTORY \
-#       com.saxonica.Transform $TFLAG $VALIDATE $SAXONOPT \
-#       $XARG $YARG $RARG $OUTPUT $XMLSRC $XMLSTY "$@"
-  CLASSPATH=/usr/local/java/saxonsa-8.0/saxon8sa.jar:/home/ndw/java
-  exec $JAVA $HTTP_PROXY $HTTPS_PROXY $MEMORY -cp $CLASSPATH \
-       com.saxonica.Transform $TFLAG $VALIDATE $SAXONOPT \
-       $OUTPUT $XMLSRC $XMLSTY "$@"
-else if [ "$VERSION" = "b8.0" ]; then
-  JAVA_HOME=/usr/local/jdk1.4.2
-  JAVA=$JAVA_HOME/bin/java
-  CLASSPATH=/usr/local/java/saxonb-8.0/saxon8.jar:/home/ndw/java
-  echo $JAVA $HTTP_PROXY $HTTPS_PROXY $MEMORY -cp $CLASSPATH \
-       net.sf.saxon.Transform $TFLAG $VALIDATE $SAXONOPT \
-       $OUTPUT $XMLSRC $XMLSTY "$@"
-  exec $JAVA $HTTP_PROXY $HTTPS_PROXY $MEMORY -cp $CLASSPATH \
-       net.sf.saxon.Transform $TFLAG $VALIDATE $SAXONOPT \
-       $OUTPUT $XMLSRC $XMLSTY "$@"
-else if [ "$VERSION" = "77" ]; then
-  JAVA_HOME=/usr/local/jdk1.4.2
-  JAVA=$JAVA_HOME/bin/java
-  exec $JAVA $HTTP_PROXY $HTTPS_PROXY $MEMORY -cp $CLASSPATH $DBFACTORY $SPFACTORY \
-       -Djavax.xml.transform.TransformerFactory=$TRANSFACTORY \
-       net.sf.saxon.Transform $TFLAG \
-       $XARG $YARG $RARG $OUTPUT $XMLSRC $XMLSTY "$@"
-else
-  exec java $HTTP_PROXY $HTTPS_PROXY $MEMORY -cp $CLASSPATH $DBFACTORY $SPFACTORY \
-       -Djavax.xml.transform.TransformerFactory=$TRANSFACTORY \
-       com.icl.saxon.StyleSheet $TFLAG \
-       $XARG $YARG $RARG $OUTPUT $XMLSRC $XMLSTY "$@"
-fi
-fi
-fi
\ No newline at end of file
+    if (@arr) {
+       print STDERR "$name:\n";
+       foreach my $p (@arr) {
+           print STDERR "\t$p\n";
+       }
+    }
+}
index 9e8d2304975d7ccfb94b1f7a2d0abca62cc29053..04bebd6dd008cbfe92337883558b586d2e4ebab3 100755 (executable)
-#!/bin/bash
-
-DONE=0
-DEBUG=false                     # not used
-VERSION=2
-MEMORY=""
-USECRIMSON=false
-USERESOLVER=true
-RESOLVERS=""
-
-MYDIR=`dirname $0`
-. $MYDIR/common-functions.sh
-
-while [ "$DONE" = "0" ]; do
-    case $1 in
-        -m)     shift
-               MEMORY="-Xmx$1"
-               shift
-               ;;
-        -crimson)
-               shift
-               USECRIMSON=true
-               ;;
-        -noresolver)
-               shift
-               USERESOLVER=false
-               ;;
-        -d)     shift
-               DEBUG=true
-               ;;
-        -q)     shift
-               VERBOSE=false
-               ;;
-        -v)     shift
-               VERBOSE=true
-               ;;
-        -1)     shift
-               VERSION=1
-               ;;
-        -2*)    VERSION=${1#-}
-                shift
-               ;;
-        -X)     shift
-               DOCBOOKXSL="$1"
-               shift
-               ;;
-       -*)     DONE=1
-               ;;
-       *)      DONE=1
-                ;;
-    esac
-done
-
-
-##
-## optionally replace the SAXParser with crimson
-##
-if $USECRIMSON; then
-  CRIMSON=`findcrimson`
-  if [ ! "$CRIMSON" ]; then
-    echo "crimson requested but cannot be found" 1>&2
-    exit 1
-  else
-    SAXPARSER="-Djavax.xml.parsers.SAXParserFactory=org.apache.crimson.jaxp.SAXParserFactoryImpl"
-  fi
-fi
-
-# FIXME: do this right
-NDWEXT="/sourceforge/docbook/xsl/extensions/xalan2/.classes";
-
-if [ "$VERSION" = "1" ]; then
-  if [ ! "$XALAN" ]; then
-    ##
-    ## find xalan 1
-    ##
-    for path in "${XALANROOT}" \
-                "${XALANROOT}/xalan.jar" \
-                "/usr/local/java/xalan-1.2.2" \
-                "/usr/local/share/java/xalan-1.2.2" \
-                "/usr/share/java/xalan.jar"; do
-      if [ -f "$path" -o -d "$path" ]; then
-        XALAN="$path"
-        break
-      fi
-    done
-    # find bsf
-    for path in "${XALANROOT}/bsf.jar" \
-                "/usr/local/java/bsf.jar" \
-                "/usr/local/share/java/bsf.jar" \
-                "/usr/share/java/bsf.jar"; do
-      if [ -f "$path" ]; then
-        XALAN="$XALAN:$path"
-        break
-      fi
-    done
-  fi
-
-  # resolver isn't supported
-  RESOLVERS=
-
-  ##
-  ## use the appropriate xerces
-  ##
-  XERCES=`findxerces1`
-  if [ ! "$XERCES" ]; then
-    echo "cannot locate Xerces (xerces.jar)" 1>&2
-    exit 1
-    # see http://xml.apache.org/xalan-j/usagepatterns.html#plug
-    # no need to set these
-    # -Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
-    # -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl"
-  fi
-
-else
-  ##
-  ## xalan 2
-  ##
-
-  if [ ! "$XALAN" ]; then
-    STRIKEVERSION=`echo $VERSION | sed -e 's/\([0-9]\)\([0-9]\)/\1_\2/g; s/\([0-9]\)\([0-9D]\)/\1_\2/g;'`
-    DOTVERSION=`echo $VERSION | sed -e 's/\([0-9]\)\([0-9]\)/\1.\2/g; s/\([0-9]\)\([0-9D]\)/\1.\2/g;'`
-    ##
-    ## first find xalan2.jar or directory for compiled classes
-    ##
-    for path in "${XALANROOT}/build/classes" \
-                "${XALANROOT}" \
-                "${XALANROOT}/xalan2.jar" \
-                "${XALANROOT}/bin/xalan2.jar" \
-                "/projects/apache/xml-xalan/java/build/classes" \
-                "/usr/local/java/xalan-j_${STRIKEVERSION}/bin/xalan.jar" \
-                "/usr/local/share/java/xalan-j_${STRIKEVERSION}/bin/xalan.jar" \
-                "/usr/local/share/java/xalan-$STRIKEVERSION.jar" \
-                "/usr/local/share/java/xalan-$DOTVERSION.jar" \
-                "/usr/local/share/java/xalan2.jar" \
-                "/usr/local/java/xalan-$STRIKEVERSION.jar" \
-                "/usr/local/java/xalan-$DOTVERSION.jar" \
-                "/usr/local/java/xalan2.jar" \
-                "/usr/share/java/xalan-$STRIKEVERSION.jar" \
-                "/usr/share/java/xalan-$DOTVERSION.jar" \
-                "/usr/share/java/xalan2.jar"; do
-      if [ -f "$path" -o -d "$path" ]; then
-        if [ ${VERBOSE} ] && ${VERBOSE}; then
-          echo "found xalan2 in $path" 1>&2
-        fi
-        XALAN="$path"
-        break
-      fi
-    done
-    if [ ! "$XALAN" ]; then
-      echo "xalan $VERSION cannot be found" 1>&2
-      exit 1
-    fi
-
-    ##
-    ## we also need xml-apis.jar
-    ##
-    XALANDIR=`dirname $XALAN`
-    for path in "${XALANDIR}/xml-apis.jar" \
-                "${XALANROOT}/xml-apis.jar" \
-                "${XALANROOT}/bin/xml-apis.jar"; do
-      if [ -f "$path" -o -d "$path" ]; then
-        XALAN="${XALAN}:$path"
-        break
-      fi
-    done
-  fi
-
-  ##
-  ## use the appropriate xerces
-  ##
-  XERCES=`findxerces2`
-  if [ ! "$XERCES" ]; then
-    echo "cannot locate Xerces 2" 1>&2
-    exit 1
-  fi
-
-  ##
-  ## resolver
-  ##
-  if $USERESOLVER; then
-    RESOLVER=`findresolver`
-    if [ ${RESOLVER/sun/} != ${RESOLVER} ]; then
-      # guess this is the sun resolver
-      RESOLVERS="-URIRESOLVER com.sun.resolver.tools.CatalogResolver -ENTITYRESOLVER com.sun.resolver.tools.CatalogResolver"
-    else
-      # guess this is the Apache resolver
-      RESOLVERS="-URIRESOLVER org.apache.xml.resolver.tools.CatalogResolver -ENTITYRESOLVER org.apache.xml.resolver.tools.CatalogResolver"
-    fi
-  fi
-fi
-
-RESOLVERS=""
-echo java $MEMORY $SAXPARSER org.apache.xalan.xslt.Process $RESOLVERS $@
-
-CLASSPATH=`fixclasspath "$NDWEXT:$XALAN:$JAXP:$RESOLVER:$XERCES:$CLASSPATH"`
-
-#echo $CLASSPATH
-
-if [ ${VERBOSE} ] && ${VERBOSE}; then
-  echo java $MEMORY $SAXPARSER org.apache.xalan.xslt.Process $RESOLVERS $@
-fi
-exec java -cp $CLASSPATH $MEMORY $SAXPARSER org.apache.xalan.xslt.Process $RESOLVERS $@
+#!/usr/bin/perl -w -- #  -*- Perl -*-
+#
+# This script runs the Xalan XSLT processor. It relies on a configuration
+# file to identify java versions, class paths, etc.
+#
+# Usage: xalan [opts] input style [output] [params]
+#
+# Options:
+#
+#    -2...     Specifies a particular version
+#    -debug    Debugging
+#    -config   Where is the config file? Defaults to ~/.xmlc
+#    -opts id  Use additional options 'id' from the config file.
+#              The -opts option may be specified more than once
+#
+# The default config is "xalan" or "xalan-{version}" if a version
+# is specified. If -xsltc is passed as a Xalan option, then the
+# default config is "xsltc" or "xsltc-{version}"
+#
+# Any other options are passed to Xalan unchanged
 
+use strict;
+use English;
+use XML::XPath;
+use XML::XPath::XMLParser;
+
+my $usage = "xalan [opts] input style [output] [params]\n";
+
+my $version = "";
+my $debug = 0;
+my $config = "~/.xmlc";
+my @opts = ();
+
+while (@ARGV) {
+    if ($ARGV[0] =~ /^-\d/) {
+       $version = substr($ARGV[0],1);
+       shift @ARGV;
+    } elsif ($ARGV[0] eq '-debug') {
+       $debug = 1;
+       shift @ARGV;
+    } elsif ($ARGV[0] eq '-config') {
+       shift @ARGV;
+       $config = shift @ARGV;
+    } elsif ($ARGV[0] eq '-opts') {
+       shift @ARGV;
+       push(@opts, shift @ARGV);
+    } else {
+       last;
+    }
+}
+
+my @params = ();
+while (@ARGV && $ARGV[$#ARGV] =~ /=/) {
+    unshift (@params, pop @ARGV);
+}
+
+my $output = pop @ARGV;
+my $style = pop @ARGV;
+my $input = pop @ARGV;
+
+# What if the user didn't specify an output location?
+if (!defined($input) || $input =~ /^-/) {
+    push (@ARGV, $input) if defined($input);
+    $input = $style;
+    $style = $output;
+    $output = undef;
+}
+
+# Everything else goes to Xalan
+my @xalanopts = @ARGV;
+push (@xalanopts, "-OUT $output") if defined($output) && $output ne '-';
+
+die $usage if !defined($input) || !defined($style);
+
+my $optsname = "xalan";
+foreach my $opt (@xalanopts) {
+    $optsname = "xsltc" if $opt =~ /-xsltc/i;
+}
+$optsname .= "-$version" if $version ne '';
+
+# Inelegantly, these are used as globals by several functions
+my %seenopts = ();
+my $classname = "";
+my $java = "";
+my @systemprops = ();
+my @javaopts = ();
+my @classpath = ();
+
+$config = (glob($config))[0]; # hack to expand ~/.xmlc to right place
+
+die "Cannot read config: $config\n$usage" if ! -f $config;
+my $xp = XML::XPath->new('filename' => $config);
+my $doc = ($xp->find("/config")->get_nodelist())[0];
+die "Unexpected root element in configuration file.\n" if !$doc;
+
+# Figure out the class path separator before we go any further
+my $cpseparator = $doc->getAttribute('classpath-separator');
+# Default to ';' if it appears in $CLASSPATH, otherwise ':'
+$cpseparator = ($ENV{'CLASSPATH'} =~ /;/ ? ";" : ":")
+    if !defined($cpseparator) or $cpseparator eq '';
+
+foreach my $name (@opts, $optsname) {
+    applyOpts($xp, $name);
+}
+
+$java = "java" if $java eq '';
+
+foreach my $path (reverse split(/$cpseparator/, $ENV{'CLASSPATH'})) {
+    unshift(@classpath, $path);
+}
+
+showVars() if $debug;
+
+die "No classname?\n" if !defined($classname);
+
+my $jopts = join(" ", @javaopts);
+my $jprops = join(" ", @systemprops);
+my $jcp = join($cpseparator, @classpath);
+my $xopts = join(" ", @xalanopts);
+
+my $xparam = "";
+foreach my $param (@params) {
+    my $name = "";
+    my $value = "";
+    if ($param =~ /^(.*?)=(.*)$/) {
+       $name = $1;
+       $value = $2;
+    } else {
+       $name = $param;
+    }
+
+    $xparam .= "-PARAM $name \"$value\" ";
+}
+
+if ($cpseparator eq ';') {
+    # This must be cygwin or some windows flavor, let's try to make it work
+    $jcp =~ s/\//\\\\/sg; # turn / into \\ in classpath paths
+    $jcp =~ s/\;/\\\;/sg; # escape semicolons
+    if (@params) {
+       $xparam = "\"" . join("\" \"", @params) . "\"";
+    } else {
+       $xparam = "";
+    }
+}
+
+print "$java $jopts $classname $xopts -IN $input -XSL $style $xparam\n" if $debug;
+exec("$java $jopts -cp $jcp $jprops $classname $xopts -IN $input -XSL $style $xparam");
+
+# ============================================================
+
+sub applyOpts {
+    my $xp = shift;
+    my $id = shift;
+
+    # Avoid loops
+    if ($seenopts{$id}) {
+       print STDERR "Skipping $id (already seen)\n" if $debug;
+       return;
+    }
+    $seenopts{$id} = 1;
+
+    my $node = ($xp->find("/config/*[\@xml:id='$id']")->get_nodelist())[0];
+
+    die "Config $config does not contain $id.\n" if !defined($node);
+
+    print STDERR "Loading $id from $config\n" if $debug;
+
+    $classname = $node->getAttribute('class')
+       if $classname eq '';
+
+    $java = $node->getAttribute('java')
+       if $java eq '';
+
+    foreach my $path (configPath($node, 'classpath', $cpseparator)) {
+       addOpt(\@classpath, $path);
+    }
+
+    foreach my $prop (configArgs($node, 'system-property', '-D', '=')) {
+       addOpt(\@systemprops, $prop, '=');
+    }
+
+    foreach my $arg (configArgs($node, 'arg', '-', ' ')) {
+       addOpt(\@xalanopts, $arg, ' ');
+    }
+
+    foreach my $opt (configArgs($node, 'java-option', '-', '=')) {
+       addOpt(\@javaopts, $opt, ' ');
+    }
+
+    foreach my $param (configArgs($node, 'param', '', '=')) {
+       addOpt(\@params, $param, '=');
+    }
+
+    my $extends = $node->getAttribute('extends');
+    applyOpts($xp, $extends) if $extends ne '';
+}
+
+sub configArgs {
+    my $context = shift;
+    my $elemname = shift;
+    my $prefix = shift;
+    my $sep = shift;
+    my @opts = ();
+
+    if ($context) {
+       my $args = $context->find($elemname);
+       foreach my $arg ($args->get_nodelist()) {
+           my $name = $arg->getAttribute('name');
+           my $value = $arg->getAttribute('value');
+           if (defined($value) && $value ne '') {
+               push(@opts, "$prefix$name$sep$value");
+           } else {
+               push(@opts, "$prefix$name");
+           }
+       }
+    }
+
+    return @opts;
+}
+
+sub configPath {
+    my $context = shift;
+    my $elemname = shift;
+    my $sep = shift || ":";
+    my @path = ();
+
+    if ($context) {
+       my $args = $context->find($elemname);
+       foreach my $arg ($args->get_nodelist()) {
+           my $dir = $arg->getAttribute('path');
+           if ($dir ne '') {
+               foreach my $f (split(/$sep/, $dir)) {
+                   if (-d $f || -f $f) {
+                       push (@path, $f);
+                   } else {
+                       warn "Invalid path component: $f\n";
+                   }
+               }
+           } else {
+               warn "Invalid path component (no \@dir)\n";
+           }
+       }
+    }
+
+    return @path;
+}
+
+sub addOpt {
+    my $arrayref = shift;
+    my $newopt = shift;
+    my $sep = shift;
+    my $newname = $newopt;
+
+    $newname = $1 if defined($sep) && $newopt =~ /^(.*?)$sep/;
+
+    foreach my $opt (@{$arrayref}) {
+       my $name = $opt;
+       $name = $1 if defined($sep) && $opt =~ /^(.*?)$sep/;
+       return if $name eq $newname;
+    }
+
+    push(@{$arrayref}, $newopt);
+}
+
+sub showVars {
+    print STDERR "config: $config\n";
+    print STDERR "java: $java\n";
+    print STDERR "optsname: $optsname\n";
+    showArr("opts", @opts);
+    showArr("xalan opts", @xalanopts);
+    print STDERR "input: $input\n" if defined($input);
+    print STDERR "style: $style\n" if defined($style);
+    if (@params) {
+       print STDERR "params:\n";
+       foreach my $param (@params) {
+           my $name = "";
+           my $value = "";
+           if ($param =~ /^(.*?)=(.*)$/) {
+               $name = $1;
+               $value = $2;
+           } else {
+               $name = $param;
+           }
+
+           print STDERR "\t-PARAM $name \"$value\"\n";
+       }
+    }
+    showArr("java opts", @javaopts);
+    showArr("system props", @systemprops);
+    print STDERR "cpseparator: $cpseparator\n";
+    showArr("classpath", @classpath);
+}
+
+sub showArr {
+    my $name = shift;
+    my @arr = @_;
+
+    if (@arr) {
+       print STDERR "$name:\n";
+       foreach my $p (@arr) {
+           print STDERR "\t$p\n";
+       }
+    }
+}
index 9aea61fba6d0a1833858a5cd34e8b712bc935317..699a697c253358cf49cfd7a8f256cef490d763e7 100755 (executable)
-#!/usr/bin/perl -- # --*-Perl-*--
+#!/bin/bash
 
-use strict;
-use Getopt::Long;
+PROC=saxon
 
-my $defaultProc = $ENV{'XSLTPROC'} || 'saxon';
-my $defaultOpts = $ENV{'XSLTPROCOPTS'} || undef;
+case $1 in
+    -xsltproc) PROC=xsltproc;
+               shift;
+               ;;
+    -xalan)    PROC=xalan
+               shift;
+               ;;
+    -saxon)    PROC=saxon
+               shift;
+               ;;
+esac
 
-my $usage = "Usage: $0 [options] files\n";
+exec `dirname $0`/$PROC "$@"
 
-my %option = ('debug' => 0,
-             'quiet' => 0,
-             'verbose' => 0,
-             'time' => 0,
-             'extensions' => 1,
-             'version' => undef,
-             'memory' => undef,
-             'xml' => undef,
-             'xsl' => undef,
-             'output' => undef,
-             'opts' => $defaultOpts,
-             'processor' => $defaultProc);
 
-my %opt = ();
-&GetOptions(\%opt,
-           'debug+',
-           'quiet+',
-           'verbose',
-           'time',
-           'extensions!',
-           'version=s',
-           'memory=s',
-           'xml=s',
-           'xsl=s',
-           'output=s',
-           'opts=s',
-           'processor=s') || die $usage;
 
-foreach my $key (keys %option) {
-    $option{$key} = $opt{$key} if exists($opt{$key});
-}
-
-my @args  = ();
-my %param = ();
-
-$param{'use.extensions'} = 1 if $option{'extensions'};
-$param{'use.extensions'} = 0 if $option{'processor'} eq 'xsltproc';
-
-while ($_ = shift @ARGV) {
-    if (/^([^\s=]+)=(.*)$/) {
-       $param{$1} = $2;
-    } else {
-       push (@args, $_);
-    }
-}
-
-my $processor = $option{'processor'};
-my $procopt = $option{'opts'};
-my $xmlFile = $option{'xml'} || shift @args;
-my $xslFile = $option{'xsl'} || shift @args;
-my $outFile = $option{'output'} || shift @args;
-my $time    = $option{'time'};
-my $version = $option{'version'};
-my $quiet   = $option{'quiet'};
-my $verbose = $option{'verbose'};
-my $memory  = $option{'memory'};
-my $debug   = $option{'debug'};
-
-my $cmd = "";
-my $exedir;
-($exedir = $0) =~ s/\/[^\/]+$//;
-
-if ($processor eq 'saxon') {
-    $cmd = "$exedir/saxon";
-    $cmd .= " $procopt" if $procopt;
-    $cmd .= " -q" if $quiet;
-    $cmd .= " -v" if $verbose;
-    $cmd .= " -d" if $debug;
-    $cmd .= " -$version" if $version;
-    $cmd .= " -m $memory" if $memory;
-    $cmd .= " $xmlFile $xslFile";
-    $outFile = "-" if $outFile eq '';
-    $cmd .= " $outFile" if $outFile;
-    foreach my $key (keys %param) {
-       $cmd .= " $key=\"" . $param{$key} . "\"";
-    }
-} elsif ($processor eq 'xalan') {
-    $cmd = "$exedir/xalan";
-    $cmd .= " $procopt" if $procopt;
-    $cmd .= " -q" if $quiet;
-    $cmd .= " -v" if $verbose;
-    $cmd .= " -d" if $debug;
-    $cmd .= " -$version" if $version;
-    $cmd .= " -m $memory" if $memory;
-    $cmd .= " -IN $xmlFile -XSL $xslFile";
-    $cmd .= " -OUT $outFile" if $outFile;
-    foreach my $key (keys %param) {
-       $cmd .= " -PARAM $key \"" . $param{$key} . "\"";
-    }
-} elsif ($processor eq 'xsltproc') {
-    $cmd = "$exedir/xsltproc";
-    $cmd .= " $procopt" if $procopt;
-    $cmd .= " -q" if $quiet;
-    $cmd .= " -v" if $verbose;
-    foreach my $key (keys %param) {
-       $cmd .= " -param $key \"'" . $param{$key} . "'\"";
-    }
-    $cmd .= " --output $outFile" if $outFile;
-    $cmd .= " $xslFile $xmlFile";
-} elsif ($processor eq '4xslt') {
-    $cmd = "$exedir/4xslt";
-    $cmd .= " $procopt" if $procopt;
-    $cmd .= " -q" if $quiet;
-    $cmd .= " -v" if $verbose;
-    foreach my $key (keys %param) {
-       $cmd .= " --define=$key=\"" . $param{$key} . "\"";
-    }
-    $cmd .= " --outfile $outFile" if $outFile;
-    $cmd .= " $xmlFile $xslFile";
-} elsif ($processor eq 'xt') {
-    $cmd = "$exedir/xt";
-    $cmd .= " $procopt" if $procopt;
-    $cmd .= " -q" if $quiet;
-    $cmd .= " -v" if $verbose;
-    $cmd .= " $xmlFile $xslFile";
-    $outFile = "-" if $outFile eq '';
-    $cmd .= " $outFile" if $outFile;
-    foreach my $key (keys %param) {
-       $cmd .= " $key=\"" . $param{$key} . "\"";
-    }
-} else {
-    print STDERR "Unknown processor: $processor\n";
-    exit 1;
-}
-
-my $startTime = time();
-if (! $quiet) {
-    print "$cmd\n";
-}
-my $retVal = system($cmd);
-my $endTime = time();
-
-print "Time: ", $endTime - $startTime, "s\n" if $time;
-
-if ($retVal) {
-    exit $retVal / 256;
-} else {
-    exit 0;
-}
index a43efbf6e8a79b9775109b682acd13118645e783..a9c30da1d6eebdbb48a1f853ab8ba7594ace41fb 100755 (executable)
-#!/bin/bash
-
-DONE=0
-DEBUG=0
-QUIET=0
-OPTS=""
-
-while [ "$DONE" = "0" ]; do
-    case $1 in
-       -d)     DEBUG=1;
-               shift;
-               ;;
-        -q)     shift
-               QUIET=1
-               ;;
-       -*)     DONE=1;
-               ;;
-       *)      DONE=1
-               ;;
-    esac
-done
-
-if [ "$DEBUG" = "1" ]; then
-    OPTS="--verbose"
-fi
-
-if [ "$QUIET" = "0" ]; then
-    echo xsltproc $OPTS "$@"
-fi
-
-/usr/bin/xsltproc --catalogs $OPTS "$@"
-
-if [ $? != 0 ]; then
-  echo ""
-  echo FAILED
-  echo ""
-fi
+#!/usr/bin/perl -w -- #  -*- Perl -*-
+#
+# This script runs the xsltproc XSLT processor. It relies on a configuration
+# file to identify the specific executable, parameters, etc.
+#
+# Usage: xsltproc [opts] input style [output] [params]
+#
+# Options:
+#
+#    -2...     Specifies a particular version
+#    -debug    Debugging
+#    -config   Where is the config file? Defaults to ~/.xmlc
+#    -opts id  Use additional options 'id' from the config file.
+#              The -opts option may be specified more than once
+#
+# The default config is "xsltproc" or "xsltproc-{version}" if a version
+# is specified.
+#
+# Any other options are passed to xsltproc unchanged
 
+use strict;
+use English;
+use XML::XPath;
+use XML::XPath::XMLParser;
+
+my $usage = "xsltproc [opts] input style [output] [params]\n";
+
+my $version = "";
+my $debug = 0;
+my $config = "~/.xmlc";
+my @opts = ();
+
+while (@ARGV) {
+    if ($ARGV[0] =~ /^-\d/) {
+       $version = substr($ARGV[0],1);
+       shift @ARGV;
+    } elsif ($ARGV[0] eq '-debug') {
+       $debug = 1;
+       shift @ARGV;
+    } elsif ($ARGV[0] eq '-config') {
+       shift @ARGV;
+       $config = shift @ARGV;
+    } elsif ($ARGV[0] eq '-opts') {
+       shift @ARGV;
+       push(@opts, shift @ARGV);
+    } else {
+       last;
+    }
+}
+
+my @params = ();
+while (@ARGV && $ARGV[$#ARGV] =~ /=/) {
+    unshift (@params, pop @ARGV);
+}
+
+my $output = pop @ARGV;
+my $style = pop @ARGV;
+my $input = pop @ARGV;
+
+# What if the user didn't specify an output location?
+if (!defined($input) || $input =~ /^-/) {
+    push (@ARGV, $input) if defined($input);
+    $input = $style;
+    $style = $output;
+    $output = undef;
+}
+
+# Everything else goes to xsltproc
+my @xsltprocopts = @ARGV;
+push (@xsltprocopts, "-o $output") if defined($output) && $output ne '-';
+
+die $usage if !defined($input) || !defined($style);
+
+my $optsname = "xsltproc";
+$optsname .= "-$version" if $version ne '';
+
+# Inelegantly, these are used as globals by several functions
+my %seenopts = ();
+my $execname = '';
+
+$config = (glob($config))[0]; # hack to expand ~/.xmlc to right place
+
+die "Cannot read config: $config\n$usage" if ! -f $config;
+my $xp = XML::XPath->new('filename' => $config);
+my $doc = ($xp->find("/config")->get_nodelist())[0];
+die "Unexpected root element in configuration file.\n" if !$doc;
+
+foreach my $name (@opts, $optsname) {
+    applyOpts($xp, $name);
+}
+
+showVars() if $debug;
+
+die "No execname?\n" if !defined($execname);
+
+my $xopts = join(" ", @xsltprocopts);
+
+my $xparam = "";
+foreach my $param (@params) {
+    my $name = "";
+    my $value = "";
+    if ($param =~ /^(.*?)=(.*)$/) {
+       $name = $1;
+       $value = $2;
+    } else {
+       $name = $param;
+    }
+
+    $xparam .= "-stringparam $name \"$value\" ";
+}
+
+print "$execname $xopts $style $input\n" if $debug;
+exec("$execname $xopts $style $input");
+
+# ============================================================
+
+sub applyOpts {
+    my $xp = shift;
+    my $id = shift;
+
+    # Avoid loops
+    if ($seenopts{$id}) {
+       print STDERR "Skipping $id (already seen)\n" if $debug;
+       return;
+    }
+    $seenopts{$id} = 1;
+
+    my $node = ($xp->find("/config/*[\@xml:id='$id']")->get_nodelist())[0];
+
+    die "Config $config does not contain $id.\n" if !defined($node);
+
+    print STDERR "Loading $id from $config\n" if $debug;
+
+    $execname = $node->getAttribute('exec')
+       if $execname eq '';
+
+    foreach my $arg (configArgs($node, 'arg', '-', ' ')) {
+       addOpt(\@xsltprocopts, $arg, ' ');
+    }
+
+    foreach my $param (configArgs($node, 'param', '', '=')) {
+       addOpt(\@params, $param, '=');
+    }
+
+    my $extends = $node->getAttribute('extends');
+    applyOpts($xp, $extends) if $extends ne '';
+}
+
+sub configArgs {
+    my $context = shift;
+    my $elemname = shift;
+    my $prefix = shift;
+    my $sep = shift;
+    my @opts = ();
+
+    if ($context) {
+       my $args = $context->find($elemname);
+       foreach my $arg ($args->get_nodelist()) {
+           my $name = $arg->getAttribute('name');
+           my $value = $arg->getAttribute('value');
+           if (defined($value) && $value ne '') {
+               push(@opts, "$prefix$name$sep$value");
+           } else {
+               push(@opts, "$prefix$name");
+           }
+       }
+    }
+
+    return @opts;
+}
+
+sub configPath {
+    my $context = shift;
+    my $elemname = shift;
+    my $sep = shift || ":";
+    my @path = ();
+
+    if ($context) {
+       my $args = $context->find($elemname);
+       foreach my $arg ($args->get_nodelist()) {
+           my $dir = $arg->getAttribute('path');
+           if ($dir ne '') {
+               foreach my $f (split(/$sep/, $dir)) {
+                   if (-d $f || -f $f) {
+                       push (@path, $f);
+                   } else {
+                       warn "Invalid path component: $f\n";
+                   }
+               }
+           } else {
+               warn "Invalid path component (no \@dir)\n";
+           }
+       }
+    }
+
+    return @path;
+}
+
+sub addOpt {
+    my $arrayref = shift;
+    my $newopt = shift;
+    my $sep = shift;
+    my $newname = $newopt;
+
+    $newname = $1 if defined($sep) && $newopt =~ /^(.*?)$sep/;
+
+    foreach my $opt (@{$arrayref}) {
+       my $name = $opt;
+       $name = $1 if defined($sep) && $opt =~ /^(.*?)$sep/;
+       return if $name eq $newname;
+    }
+
+    push(@{$arrayref}, $newopt);
+}
+
+sub showVars {
+    print STDERR "config: $config\n";
+    print STDERR "optsname: $optsname\n";
+    showArr("opts", @opts);
+    showArr("xsltproc opts", @xsltprocopts);
+    print STDERR "input: $input\n" if defined($input);
+    print STDERR "style: $style\n" if defined($style);
+    if (@params) {
+       print STDERR "params:\n";
+       foreach my $param (@params) {
+           my $name = "";
+           my $value = "";
+           if ($param =~ /^(.*?)=(.*)$/) {
+               $name = $1;
+               $value = $2;
+           } else {
+               $name = $param;
+           }
+
+           print STDERR "\t-stringparam $name \"$value\"\n";
+       }
+    }
+}
+
+sub showArr {
+    my $name = shift;
+    my @arr = @_;
+
+    if (@arr) {
+       print STDERR "$name:\n";
+       foreach my $p (@arr) {
+           print STDERR "\t$p\n";
+       }
+    }
+}