# $Id: Apache.pm,v 1.16 2006/07/12 02:39:48 jmates Exp $ # # The author disclaims all copyrights and releases this module into the # public domain. # # A mod_perl1 interface to XML::ApplyXSLT for the transformation of XML # documents via XSLT. # # For more documentation, run perldoc(1) on this module. package XML::ApplyXSLT::Apache; use 5.005; use strict; use warnings; our $VERSION = '0.16'; use Apache::Constants qw(:common REDIRECT); use Apache::File (); use Apache::Log (); use Apache::URI (); #use Date::Parse (); use XML::ApplyXSLT (); my $xapply = XML::ApplyXSLT->new; # TODO allow different rules for different areas if need be? # TODO cache mtime on rules file for if-modified calc below or reloading needs? my $rules_file = Apache->server_root_relative('conf/applyxslt-rules'); if ( open my $rfh, "< $rules_file" ) { $xapply->rules($rfh); } else { remark( 'error', "could not load rules file", { errno => $!, file => $rules_file } ); } # TODO way to set these from prefs? $xapply->config_libxml( { load_ext_dtd => 0, expand_entities => 0, complete_attributes => 0 } ); sub handler { my $r = shift; # TODO prolly need a few DECLINES here or have a httpd prefs to avoid # certain areas or types, pre-file-and-rules-parse? #return DECLINED if not defined $r->content_type; my $file = $r->filename; my $uri = Apache::URI->parse($r); #return DECLINED if $r->content_type() eq 'httpd/unix-directory'; # KLUGE work around Apache internal redirect on bare directories if ($r->content_type() eq 'httpd/unix-directory' ) { if ( $r->uri =~ m{/$} ) { return DECLINED; } else { sleep 3; $r->headers_out->set(Location => 'http://sial.org' . $uri->path . '/' ); return REDIRECT; } } my %request_params; my %request_defaults; my %param = $r->args || (); # set style from query string, if possible for my $param (%param) { $request_defaults{style} = $1 if $param eq 'style' and $param{$param} =~ m/ ([A-Za-z0-9_-]+) /x; } my ($port) = $uri->port =~ m/ (\d+) /x; $port = 80 unless defined $port; $request_defaults{site} = $uri->scheme . '://' . $uri->hostname . ( $port != 80 ? ":$port" : '' ); my $doc; unless ( $doc = $xapply->parse($file) ) { remark( 'warn', 'could not parse file', { errno => $xapply->errorstring, file => $file } ); return DECLINED; } my ( $filedata, $defaults, $params ) = $xapply->study( $doc, $file, $r->document_root ); unless ( defined $filedata ) { remark( 'warn', 'no filedata found', { file => $file } ); return DECLINED; } $defaults = {} unless defined $defaults; $params = {} unless defined $params; %$defaults = ( %$filedata, %$defaults, %request_defaults ); # KLUGE nuke filename if DirectoryIndex name (currently index.xml) and # fix slashes so subdir can vanish without // problems $defaults->{filename} =~ s, index\.xml ,,x; $defaults->{filename} = '/' . $defaults->{filename}; # macro expansion as well on XSL params %$params = ( %$params, %request_params ); $params = $xapply->expand( $params, $defaults ); my ( $docref, $details ) = $xapply->transform( $doc, default => $defaults, param => $params ); unless ( defined $docref ) { remark( 'error', 'could not parse document', { errno => $xapply->errorstring, file => $file } ); return DECLINED; } # TODO how handle output encoding? unless ( $details->{'media_type'} ) { remark( 'error', 'no Content-Type for results', { file => $file } ); # TODO might need to return errors instead soas to prevent raw XML # from going at the user? return DECLINED; } $r->content_type( $details->{'media_type'} ); $r->set_content_length( do { use bytes; length $$docref } ); # TODO improve this, need to include mtime of rules and stylesheet ideally # $r->update_mtime( (stat $r->finfo)[9] ); # $r->update_mtime( # Date::Parse::str2time( substr q$Date: 2006/07/12 02:39:48 $, 6 ) ); $r->set_last_modified( ( stat $r->finfo )[9] ); # TODO load this from prefs somehow? if ( $r->protocol =~ /(\d\.\d)/ && $1 >= 1.1 ) { $r->header_out( 'Cache-Control', 'max-age=' . 7 * 24 * 60 * 60 ); } # TODO do the etag stuff here? would need tp MD5 or similar off # of data such as: the document, the stylesheet, and possibly # other fields? (good for when have large or mainly static docs) if ( ( my $rc = $r->meets_conditions ) != OK ) { return $rc; } $r->send_http_header; return OK if $r->header_only; print $$docref; return OK; } sub remark { my $priority = shift; my $message = shift; my $attributes = shift; chomp $message; my $attr_str; if ($attributes) { $attr_str = join ', ', map { $attributes->{$_} ||= ''; "$_=$attributes->{$_}" } sort keys %$attributes; } my $r = Apache->request || Apache->server; $r->log->$priority( $message . ( $attr_str ? ": $attr_str" : '' ) ); return 1; } 1; __END__ =head1 NAME XML::ApplyXSLT::Apache - mod_perl1 interface to XML::ApplyXSLT =head1 SYNOPSIS PerlModule XML::ApplyXSLT::Apache SetHandler perl-script PerlHandler XML::ApplyXSLT::Apache =head1 DESCRIPTION Apache (mod_perl1) interface to L. =head1 BUGS =head2 Reporting Bugs Newer versions of this module may be available from CPAN. If the bug is in the latest version, send a report to the author. Patches that fix problems or add new features are welcome. =head2 Known Issues No known issues, though see source for TODO and other comments. =head1 SEE ALSO L, for more complex XML mangling needs. The supporting modules L and L. =head1 AUTHOR Jeremy Mates, Ejmates@sial.orgE =head1 COPYRIGHT AND LICENSE The author disclaims all copyrights and releases this module into the public domain. =cut