XML::ApplyXSLT::Apache - mod_perl1 interface to XML::ApplyXSLT


XML-ApplyXSLT documentation Contained in the XML-ApplyXSLT distribution.

Index


Code Index:

NAME

Top

XML::ApplyXSLT::Apache - mod_perl1 interface to XML::ApplyXSLT

SYNOPSIS

Top

  PerlModule XML::ApplyXSLT::Apache

  <Directory "/foo">
    SetHandler perl-script
    PerlHandler XML::ApplyXSLT::Apache
  </Directory>

DESCRIPTION

Top

Apache (mod_perl1) interface to XML::ApplyXSLT.

BUGS

Top

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.

Known Issues

No known issues, though see source for TODO and other comments.

SEE ALSO

Top

AxKit (AxKit), for more complex XML mangling needs.

The supporting modules XML::LibXML and XML::LibXSLT.

AUTHOR

Top

Jeremy Mates, <jmates@sial.org>

COPYRIGHT AND LICENSE

Top


XML-ApplyXSLT documentation Contained in the XML-ApplyXSLT distribution.

# $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__