#!/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 # -c14n Canonicalize output using xmllint # -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 = (); my $c14n = 0; my $redirect = ""; 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 '-c14n') { shift @ARGV; $c14n = 1; } 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; # xsltproc output sometimes contains unnecessary namespace nodes; # piping it through "xmllint --c14n" to canonicalize the output # removes those my @xmllintopts = ("--c14n"); 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; my $xsltprocexec; foreach my $name (@opts, $optsname) { $xsltprocexec = applyOpts($xp, $name); } showVars() if $debug; die "No execname?\n" if !defined($execname); my $xopts = join(" ", @xsltprocopts); my $xlintopts = join(" ", @xmllintopts); my $xparam = ""; foreach my $param (@params) { my $name = ""; my $value = ""; if ($param =~ /^(.*?)=(.*)$/) { $name = $1; $value = $2; } else { $name = $param; } $xparam .= "--stringparam $name \"$value\" "; } # note that there is a bug in the -o option in xsltproc; if you # do, for example, "xsltproc -o /dev/null foo.xsl bar.xml" with a # stylesheet that has exsl:document or saxon:output, xsltproc # tries to write the output in the /dev directory; so we need to # redirect the output instead of using xsltproc's -o option $redirect = " > $output" if defined($output) && $output ne '-'; if ($c14n) { my $xmllintpipe = " | " . applyOpts($xp, 'xmllint') . " " . $xlintopts; $redirect = $xmllintpipe . " - " . $redirect; } print "$xsltprocexec $xopts $xparam $style $input$redirect\n" if $debug; exec("$xsltprocexec $xopts $xparam $style $input$redirect"); # ============================================================ 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; my $execname = $node->getAttribute('exec') if $execname eq ''; if ($id eq "xsltproc") { foreach my $arg (configArgs($node, 'arg', '--', ' ')) { addOpt(\@xsltprocopts, $arg, ' '); } } elsif ($id eq "xmllint") { foreach my $arg (configArgs($node, 'arg', '--', ' ')) { addOpt(\@xmllintopts, $arg, ' '); } } foreach my $param (configArgs($node, 'param', '', '=')) { addOpt(\@params, $param, '='); } my $extends = $node->getAttribute('extends'); applyOpts($xp, $extends) if $extends ne ''; return $execname; } 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); showArr("xmllint opts", @xmllintopts) if $c14n; print STDERR "input: $input\n" if defined($input); print STDERR "output: $output\n" if defined($output); print STDERR "c14n: enabled\n" if $c14n; 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"; } } }