package Apache::AxKit::Language::YPathScript; use strict; =head1 NAME Apache::AxKit::Language::YPathScript - An XML Stylesheet Language =head1 SYNOPSIS AxAddStyleMap "application/x-xpathscript => \ Apache::AxKit::Language::YPathScript" =head1 DESCRIPTION YPathScript is a fork of the original AxKit's XPathScript using XML::XPathScript as its transforming engine. As it is mostly backward compatible with the classic Axkit XPathScript module, the definitive reference for XPathScript, located at http://axkit.org/docs/xpathscript/guide.dkb, also applies to YPathScript, excepts for the differences listed in the sections below. =head1 PRE-DEFINED STYLESHEET VARIABLES AND FUNCTIONS =head2 VARIABLES =over =item $r A copy of the Apache::AxKit::request object -- which is itself a wrapper around the Apache::request object -- tied to the current document. <% %args = $r->args() %>

args: <%= join ' : ', map "$_ => $args{$_}", keys %args %>

=back =head2 FUNCTIONS =over =item $node = XML::XPathScript::current->document( $uri ) Fetch the xml document located at $uri and return it as a dom node. =back =cut use Apache; use Apache::File; use Apache::AxKit::Provider; use Apache::AxKit::Language; use Apache::AxKit::Cache; use Apache::AxKit::Exception; use Apache::AxKit::CharsetConv; use XML::XPathScript; use vars qw( @ISA $VERSION $stash ); @ISA = qw/ Apache::AxKit::Language XML::XPathScript /; $VERSION = '1.53'; =head2 Functions =over =item $xps = new Apache::AxKit::Language::YPathScript($xml_provider, $style_provider) Construct a new YPathScript language interpreter out of the provided providers. =cut sub new { my( $class, $xml_provider, $style_provider ) = @_; my $self = XML::XPathScript::new( $class, xml_provider => $xml_provider, style_provider => $style_provider ); return $self; } =item $rc = handler( $class, $request, $xml_provider, $style_provider ) The handler function called by Apache. =cut sub handler { AxKit::Debug( 10, "this is Apache::AxKit::Language::YPathScript version $VERSION" ); my ( $class, $r, $xml_provider, $style_provider) = @_; my $xps = new Apache::AxKit::Language::YPathScript( $xml_provider, $style_provider ); $xps->{local_ent_handler} = $xml_provider->get_ext_ent_handler(); AxKit::Debug(6, "YPathScript: Getting XML Source"); # try to find the XML document my $xml = $r->pnotes('dom_tree') # dom_tree is an XML::LibXML DOM || $r->pnotes('xml_string') || get_source_tree($xml_provider); AxKit::Debug(7, "XML retrieved: $xml\n"); $xps->set_xml( $xml ); # $xpath->set_context($source_tree); what does this do? AxKit::Debug( 6, "Recompiling stylesheet\n" ); $xps->set_stylesheet( get_source_tree($style_provider) ); #$xps->get_stylesheet( $style_provider ); AxKit::Debug(7, "Running YPathScript script\n"); local $^W; $xps->compile( '$r' ); return $xps->process( '', $r ); } =item $file_content = I =item $file_content = I Overloaded from XML::XPathScript in order to provide URI-based stylesheet inclusions: $filename may now be any AxKit URI. The AxKit language class drops support for plain filenames that exists in the ancestor class: this means that include directives like in existing stylesheets should be turned into in order to work with AxKit. =cut sub include_file { my ($self, $filename, @includestack) = @_; my $provider = $self->{xml_provider}; AxKit::Debug(10, "YPathScript: entering include_file ($filename)"); # return if already included my $key = $provider->key(); return '' if grep $_ eq $filename, @{$stash->{$key}{includes}}; push @{$stash->{$key}{includes}}, $filename; my $apache = $provider->apache_request; my $sub = $apache->lookup_uri( $filename ); local $AxKit::Cfg = Apache::AxKit::ConfigReader->new( $sub ); my $inc_provider = Apache::AxKit::Provider->new_style_provider( $sub ); AxKit::Debug( 10, "File: $filename, Sub: $sub, $inc_provider: $inc_provider" ); my $contents; eval { my $fh = $inc_provider->get_fh(); local $/; $contents = <$fh>; }; if ($@) { eval{ $contents = ${ $inc_provider->get_strref() } }; if( $@ ){ AxKit::Debug( 10, "couldn't include $filename" ) } } my $r = AxKit::Apache->request(); if (my $charset = $r->dir_config('AxOutputCharset')) { AxKit::Debug(8, "XPS: got charset: $charset"); my $map = Apache::AxKit::CharsetConv->new($charset, "utf-8") or die "No such charset: $charset"; $contents = $map->convert($contents); } $stash->{$key}{includes} = []; AxKit::Debug(10, "YPathScript: extracting from '$key' contents: $contents\n"); return $self->extract( $inc_provider, @includestack ); } =item $doc = get_source_tree( $xml_provider ) Read an XML document from the provider and return it as a string. =cut sub get_source_tree { my $provider = shift; my $xml; AxKit::Debug(7, "YPathScript: reparsing file"); eval { my $fh = $provider->get_fh(); local $/; $xml = <$fh>; close $fh; }; # didn't work? try get_strref $xml = $provider->get_strref() if $@; AxKit::Debug(7, "YPathScript: Returning source tree"); return $xml; } =item $string = I Retrieve and return the $stylesheet (which can be a filehandler or a string) as a string. =cut sub read_stylesheet { my ( $self, $stylesheet ) = @_; my $contents; if( ref( $stylesheet ) ) { eval { my $fh = $stylesheet->get_fh(); local $/; $contents = <$fh>; }; $self->debug( 7, "wasn't able to extract $stylesheet: $@" ) if $@; } else { $contents = $stylesheet; # it's a string } my $r = AxKit::Apache->request(); if (my $charset = $r->dir_config('AxOutputCharset')) { $self->debug(8, "XPS: got charset: $charset"); my $map = Apache::AxKit::CharsetConv->new($charset, "utf-8") or $self->die( "No such charset: $charset" ); $contents = $map->convert($contents); } return $contents; } =item $self->debug( $level, $message ) Print $message if the requested debug level is equal or smaller than $level. =cut sub debug{ shift; AxKit::Debug( @_ ) } =item $self->die( $suicide_note ) Print the $suicide_note and exit; =cut sub die{ die @_ } =item $nodeset = $self->document( $uri ) Read XML document located at $uri, parse it and return it in a node object. The $uri can be specified using the regular schemes ('http://foo.org/bar.xml', 'ftp://foo.org/bar.xml'), or the Axkit scheme ('axkit://baz.xml'), or as a local file ('/home/web/foo.xml', './foo.xml' ). =cut sub document { # warn "Document function called\n"; return unless $Apache::AxKit::Language::YPathScript::local_ent_handler; my( $self, $uri ) = @_; my( $results, $parser ); if( $XML::XPathScript::XML_parser eq 'XML::XPath' ) { my $xml_parser = XML::Parser->new( ErrorContext => 2, Namespaces => $XML::XPath::VERSION < 1.07 ? 1 : 0, # ParseParamEnt => 1, ); $parser = XML::XPath::XMLParser->new(parser => $xml_parser); $results = XML::XPath::NodeSet->new(); } else { $parser = XML::LibXML->new(); $results = XML::LibXML::Node->new(); } my $newdoc; if ($uri =~ /^axkit:/) { $newdoc = $parser->parse( AxKit::get_axkit_uri($uri) ); } elsif ($uri =~ /^\w\w+:/) { # assume it's scheme://foo uri eval { $self->debug( 5, "trying to parse $uri" ); $newdoc = $parser->parse( $Apache::AxKit::Language::YPathScript::local_ent_handler->( undef, undef, $uri ) ); $self->debug( 5, warn "Parsed OK into $newdoc\n" ); }; if (my $E = $@) { if ($E->isa('Apache::AxKit::Exception::IO')) { AxKit::Debug(2, $E); } else { throw Apache::AxKit::Exception::Error(-text => "Parse of '$uri' failed: $E"); }; } } else { AxKit::Debug(3, "Parsing local: $uri\n"); # create a subrequest, so we get the right AxKit::Cfg for the URI my $sub = AxKit::Apache->request->lookup_uri($uri); local $AxKit::Cfg = Apache::AxKit::ConfigReader->new($sub); my $provider = Apache::AxKit::Provider->new_content_provider($sub); $newdoc = $parser->parse( xml => get_source_tree($provider) ); } $results->push($newdoc) if $newdoc; $self->debug(8, "YPathScript: document() returning"); return $results; } 'Apache::AxKit::Language::YPathScript'; __END__ =back =head1 BUGS Please send bug reports to , or via the web interface at http://rt.cpan.org/Public/Dist/Display.html?Name=XML-XPathScript . =head1 AUTHOR Yanick Champoux Original Axkit::Apache::AxKit::Language module by Matt Sergeant