From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

# /=====================================================================\ #
# | LaTeXML::Post::XSLT | #
# | Postprocessor for XSL Transform | #
# |=====================================================================| #
# | Part of LaTeXML: | #
# | Public domain software, produced as part of work done by the | #
# | United States Government & not subject to copyright in the US. | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
# \=========================================================ooo==U==ooo=/ #
use strict;
# Useful Options:
# stylesheet : path to XSLT stylesheet.
# noresources: removes resource requests so CSS/Javascript/etc
# are not copied, and they are not included in XSLT.
# parameters : hash of parameters to pass to stylesheet.
# Among which:
# CSS is a '|' separated list of paths
# ICON a favicon
# resource_directory a directory under top-level to put resources (css, js, etc)
# searchpaths : list of paths to search
sub new {
my ($class, %options) = @_;
my $self = $class->SUPER::new(%options);
my $stylesheet = $options{stylesheet};
Error('expected', 'stylesheet', undef, "No stylesheet specified!") unless $stylesheet;
if (!ref $stylesheet) {
$$self{name} .= '[using ' . $stylesheet . ']';
my $pathname = pathname_find($stylesheet,
types => ['xsl'], installation_subdir => 'resources/XSLT',
paths => $$self{searchpaths} || ['.']);
Error('missing-file', $stylesheet, undef, "No stylesheet '$stylesheet' found!")
unless $pathname && -f $pathname;
$stylesheet = $pathname; }
$stylesheet = $stylesheet && LaTeXML::Common::XML::XSLT->new($stylesheet);
if ((!ref $stylesheet) || !($stylesheet->can('transform'))) {
Error('expected', 'stylesheet', undef, "Stylesheet '$stylesheet' is not a usable stylesheet!"); }
XML::LibXSLT->max_depth(1000);
$$self{stylesheet} = $stylesheet;
my %params = ();
%params = %{ $options{parameters} } if $options{parameters};
$$self{parameters} = {%params};
$$self{noresources} = $options{noresources};
$$self{resource_directory} = $options{resource_directory}; # ???
return $self; }
sub process {
my ($self, $doc, $root) = @_;
return unless $$self{stylesheet};
# # Set up the Stylesheet parameters; making pathname parameters relative to document
my %params = %{ $$self{parameters} };
# Deal with any resources embedded within the document
if (my @resnodes = $doc->findnodes('//ltx:resource[@src]')) {
if ($$self{noresources}) {
$doc->removeNodes(@resnodes); }
else {
foreach my $node (@resnodes) {
my $src = $node->getAttribute('src');
my $path = $self->copyResource($doc, $src, $node->getAttribute('type'));
$node->setAttribute(src => pathname_to_url($path)) unless $path eq $src; } } }
if (my $css = $params{CSS}) {
$params{CSS} = '"' . join('|', map { $self->copyResource($doc, $_, 'text/css') } @$css) . '"'; }
if (my $js = $params{JAVASCRIPT}) {
$params{JAVASCRIPT}
= '"' . join('|', map { $self->copyResource($doc, $_, 'text/javascript') } @$js) . '"'; }
if (my $icon = $params{ICON}) {
# Hmm.... what type? could be various image types
$params{ICON} = '"' . $self->copyResource($doc, $icon, undef) . '"'; }
my $newdoc = $doc->new($$self{stylesheet}->transform($doc->getDocument, %params));
return $newdoc; }
my $RESOURCE_INFO = { # [CONSTANT]
'text/css' => { extension => 'css', subdir => 'resources/CSS' },
'text/javascript' => { extension => 'js', subdir => 'resources/javascript' }
};
# Copy a resource file, if found, and return the relative path to it
# (in case it has been adjusted to be local to the destination document)
sub copyResource {
my ($self, $doc, $reqsrc, $type) = @_;
my $ext = $type && $$RESOURCE_INFO{$type}{extension};
my $resdir = $type && $$RESOURCE_INFO{$type}{subdir};
my @searchpaths = ($doc->getSearchPaths, ($$self{searchpaths} ? @{ $$self{searchpaths} } : ()));
# If the $reqsrc is a URL, no need to copy the resource, or modify the path to it.
if (pathname_is_url($reqsrc)) {
return $reqsrc; }
# Else find the file somewhere; in user's source or in distribution
elsif (my $path = pathname_find($reqsrc, ($ext ? (types => [$ext]) : ()),
paths => [@searchpaths],
($resdir ? (installation_subdir => $resdir) : ()))) {
my $dest;
my ($dir, $name, $ex) = pathname_split($path);
# If a resource directory has been specified,
if (my $rd = $$self{resource_directory}) {
# Put resources in resource_directory, relative to site_directory
$dest = pathname_absolute(pathname_make(dir => $rd, name => $name, type => $ex),
$doc->getSiteDirectory); }
# Otherise, make an attempt to preserve the relative path to the requested resource
# Ie. same path from dest doc to copied resource, as from original doc to source resource.
# Get the path relative to user's source, and simulate that path in the destination
else {
my $relpath = pathname_relative($reqsrc, $doc->getSourceDirectory);
$dest = pathname_absolute($relpath, $doc->getSiteDirectory);
# Now IFF that is a valid relative path WITHIN the site directory, we'll use it.
# Otherwise, we'll place at toplevel in the destination.
if (!pathname_is_contained($dest, $doc->getSiteDirectory)) {
$dest = pathname_make(dir => $doc->getSiteDirectory, name => $name, type => $ex); } }
# Now, copy (unless in same place! happens a lot during testing!!!!)
pathname_copy($path, $dest) unless $path eq $dest;
# and return the relative path from the dest doc to the resource
return pathname_relative($dest, $doc->getDestinationDirectory); }
else {
Warn('missing_file', $reqsrc, $self,
"Couldn't find resource file $reqsrc in paths " . join(',', @searchpaths));
return $reqsrc; } }
# ================================================================================
1;