File Coverage

blib/lib/CGI/XMLApplication.pm
Criterion Covered Total %
statement 17 216 7.8
branch 0 80 0.0
condition 0 26 0.0
subroutine 5 36 13.8
pod 23 33 69.7
total 45 391 11.5


line stmt bran cond sub pod time code
1             package CGI::XMLApplication;
2              
3             # ################################################################
4             #
5             # (c) 2001 Christian Glahn
6             #
7             # This code is free software; you can redistribute it and/or
8             # modify it under the same terms as Perl itself.
9             #
10             # ################################################################
11              
12             ##
13             # CGI::XMLApplication - Application Module for CGI scripts
14              
15             # ################################################################
16             # module loading and global variable initializing
17             # ################################################################
18 1     1   7418 use strict;
  1         3  
  1         35  
19              
20 1     1   18970 use CGI;
  1         47674  
  1         8  
21 1     1   87 use Carp;
  1         7  
  1         3354  
22             #use Data::Dumper;
23              
24             # ################################################################
25             # inheritance
26             # ################################################################
27             @CGI::XMLApplication::ISA = qw( CGI );
28              
29             # ################################################################
30              
31             $CGI::XMLApplication::VERSION = "1.1.5";
32              
33             # ################################################################
34             # general configuration
35             # ################################################################
36              
37             # some hardcoded error messages, the application has always, e.g.
38             # to tell that a stylesheet is missing
39             @CGI::XMLApplication::panic = (
40             'No Stylesheet specified! ',
41             'Stylesheet is not available! ',
42             'Event not implemented',
43             'Application Error',
44             );
45              
46             # The Debug Level for verbose error messages
47             $CGI::XMLApplication::DEBUG = 0;
48              
49             # ################################################################
50             # methods
51             # ################################################################
52             sub new {
53 1     1 1 111 my $class = shift;
54 1         13 my $self = $class->SUPER::new( @_ );
55 1         245 bless $self, $class;
56              
57 1         6 $self->{XML_CGIAPP_HANDLER_} = [$self->registerEvents()];
58 1         3 $self->{XML_CGIAPP_STYLESHEET_} = [];
59 1         4 $self->{XML_CGIAPP_STYLESDIR_} = '';
60              
61 1         2 return $self;
62             }
63              
64             # ################################################################
65             # straight forward coded methods
66              
67             # application related ############################################
68             # both functions are only for backward compatibilty with older scripts
69             sub debug_msg {
70 0     0 0 0 my $level = shift;
71 0 0 0     0 if ( $level <= $CGI::XMLApplication::DEBUG && scalar @_ ) {
72 0         0 my ($module, undef, $line) = caller(1);
73 0         0 warn "[$module; line: $line] ", join(' ', @_) , "\n";
74             }
75             }
76              
77             ##
78             # dummy functions
79             #
80             # each function is required to be overwritten by any class inheritated
81 1     1 1 5 sub registerEvents { return (); }
82              
83             # all following function will recieve the context, too
84 0     0 1   sub getDOM { return undef; }
85 0     0 0   sub requestDOM { return undef; } # old style use getDOM!
86              
87 0     0 0   sub getStylesheetString { return ""; } # return a XSL String
88 0     0 1   sub getStylesheet { return ""; } # returns either name of a stylesheetfile or the xsl DOM
89 0     0 1   sub selectStylesheet { return ""; } # old style getStylesheet
90              
91 0     0 0   sub getXSLParameter { return (); } # should return a plain hash of parameters passed to xsl
92 0     0 1   sub setHttpHeader { return (); } # should return a hash of header
93              
94             sub skipSerialization{
95 0     0 1   my $self = shift;
96 0 0         $self->{CGI_XMLAPP_SKIP_TRANSFORM} = shift if scalar @_;
97 0           return $self->{CGI_XMLAPP_SKIP_TRANSFORM};
98             }
99              
100             # returns boolean
101             sub passthru {
102 0     0 1   my $self = shift;
103 0 0         if ( scalar @_ ) {
    0          
104 0           $self->{CGI_XMLAPP_PASSXML} = shift;
105 0           $self->delete( 'passthru' ); # delete any passthru parameter
106             }
107             elsif ( defined $self->param( "passthru" ) ) {
108 0           $self->{CGI_XMLAPP_PASSXML} = 1 ;
109 0           $self->delete( 'passthru' );
110             }
111 0           return $self->{CGI_XMLAPP_PASSXML};
112             }
113              
114             sub redirectToURI {
115 0     0 0   my $self = shift;
116 0 0         $self->{CGI_XMLAPP_REDIRECT} = shift if scalar @_;
117 0           return $self->{CGI_XMLAPP_REDIRECT};
118             }
119              
120             # ################################################################
121             # content related functions
122              
123             # stylesheet directory information ###############################
124 0     0 1   sub setStylesheetDir { $_[0]->{XML_CGIAPP_STYLESDIR_} = $_[1];}
125 0     0 1   sub setStylesheetPath { $_[0]->{XML_CGIAPP_STYLESDIR_} = $_[1];}
126 0     0 0   sub getStylesheetDir { $_[0]->{XML_CGIAPP_STYLESDIR_}; }
127 0     0 1   sub getStylesheetPath { $_[0]->{XML_CGIAPP_STYLESDIR_}; }
128              
129             # event control ###################################################
130              
131 0     0 0   sub addEvent { my $s=shift; push @{$s->{XML_CGIAPP_HANDLER_}}, @_;}
  0            
  0            
132              
133 0     0 0   sub getEventList { @{ $_[0]->{XML_CGIAPP_HANDLER_} }; }
  0            
134 0     0 1   sub testEvent { return $_[0]->checkPush( $_[0]->getEventList() ); }
135              
136             sub deleteEvent {
137 0     0 0   my $self = shift;
138 0 0         if ( scalar @_ ){ # delete explicit events
139 0           foreach ( @_ ) {
140 0           debug_msg( 8, "[XML::CGIApplication] delete event $_" );
141 0           $self->delete( $_ );
142 0           $self->delete( $_.'.x' );
143 0           $self->delete( $_.'.y' );
144             }
145             }
146             else { # delete all
147 0           foreach ( @{ $self->{XML_CGIAPP_HANDLER_} } ){
  0            
148 0           debug_msg( 8, "delete event $_" );
149 0           $self->delete( $_ );
150 0           $self->delete( $_.'.x' );
151 0           $self->delete( $_.'.y' );
152             }
153             }
154             }
155              
156             sub sendEvent {
157 0     0 1   debug_msg( 10, "send event " . $_[1] );
158 0           $_[0]->deleteEvent();
159 0           $_[0]->param( -name=>$_[1] , -value=>1 );
160             }
161              
162             # error handling #################################################
163             # for internal use only ...
164 0     0 1   sub setPanicMsg { $_[0]->{XML_CGIAPP_PANIC_} = $_[1] }
165 0     0 1   sub getPanicMsg { $_[0]->{XML_CGIAPP_PANIC_} }
166              
167             # ################################################################
168             # predefined events
169              
170             # default event handler prototypes
171       0 1   sub event_init {}
172       0 1   sub event_exit {}
173 0     0 1   sub event_default { return 0 }
174              
175             # ################################################################
176             # CGI specific helper functions
177              
178             # this is required by the eventhandling
179             sub checkPush {
180 0     0 1   my $self = shift;
181             my ( $pushed ) = grep {
182 0 0         defined $self->param( $_ ) || defined $self->param( $_.'.x')
  0            
183             } @_;
184 0 0         $pushed =~ s/\.x$//i if defined $pushed;
185 0           return $pushed;
186             }
187              
188             # helper functions which were missing in CGI.pm
189             sub checkFields{
190 0     0 1   my $self = shift;
191             my @missing = grep {
192 0   0       not length $self->param( $_ ) || $self->param( $_ ) =~ /^\s*$/
  0            
193             } @_;
194 0 0         return wantarray ? @missing : ( scalar(@missing) > 0 ? undef : 1 );
    0          
195             }
196              
197             sub getParamHash {
198 0     0 1   my $self = shift;
199 0           my $ptrHash = $self->Vars;
200 0           my $ptrRV = {};
201              
202 0           foreach my $k ( keys( %{$ptrHash} ) ){
  0            
203 0 0 0       next unless exists $ptrHash->{$_} && $ptrHash->{$_} !~ /^[\s\0]*$/;
204 0           $ptrRV->{$k} = $ptrHash->{$k};
205             }
206              
207 0 0         return wantarray ? %{$ptrRV} : $ptrRV;
  0            
208             }
209              
210             # ################################################################
211             # application related methods
212             # ################################################################
213             # algorithm should be
214             # event registration
215             # app init
216             # event handling
217             # app exit
218             # serialization and output
219             # error handling
220             sub run {
221 0     0 1   my $self = shift;
222 0           my $sid = -1;
223 0 0 0       my $ctxt = (!@_ or scalar(@_) > 1) ? {@_} : shift; # nothing, hash or context object
224              
225 0           $self->event_init($ctxt);
226              
227 0 0         if ( my $n = $self->testEvent($ctxt) ) {
228 0 0         if ( my $func = $self->can('event_'.$n) ) {
229 0           $sid = $self->$func($ctxt);
230             }
231             else {
232 0           $sid = -3;
233             }
234             }
235              
236 0 0         if ( $sid == -1 ){
237 0           $sid = $self->event_default($ctxt);
238             }
239              
240 0           $self->event_exit($ctxt);
241              
242             # if we allready panic, don't try to render
243 0 0         if ( $sid >= 0 ) {
244             # check if we wanna redirect
245 0 0         if ( my $uri = $self->redirectToURI() ) {
    0          
246 0           my %h = $self->setHttpHeader( $ctxt );
247 0           $h{-uri} = $uri;
248 0           print $self->SUPER::redirect( %h ) . "\n\n";
249             }
250             elsif ( not $self->skipSerialization() ) {
251             # sometimes it is nessecary to skip the serialization
252             # eg. due passing binary data.
253 0           $sid = $self->serialization( $ctxt );
254             }
255             }
256              
257 0           $self->panic( $sid, $ctxt );
258             }
259              
260             sub serialization {
261             # i require both modules here, so one can implement his own
262             # serialization
263 0     0 0   require XML::LibXML;
264 0           require XML::LibXSLT;
265              
266 0           my $self = shift;
267 0           my $ctxt = shift;
268 0           my $id;
269              
270 0           my %header = $self->setHttpHeader( $ctxt );
271              
272 0           my $xml_doc = $self->getDOM( $ctxt );
273 0 0         if ( not defined $xml_doc ) {
274 0           debug_msg( 10, "use old style interface");
275 0           $xml_doc = $self->requestDOM( $ctxt );
276             }
277             # if still no document is available
278 0 0         if ( not defined $xml_doc ) {
279 0           debug_msg( 10, "no DOM defined; use empty DOM" );
280 0           $xml_doc = XML::LibXML::Document->new;
281             # the following line is to keep xpath.c quiet!
282 0           $xml_doc->setDocumentElement( $xml_doc->createElement( "dummy" ) );
283             }
284              
285 0 0 0       if( defined $self->passthru() && $self->passthru() == 1 ) {
286             # this is a useful feature for DOM debugging
287 0           debug_msg( 10, "attempt to pass the DOM to the client" );
288 0           $header{-type} = 'text/xml';
289 0           print $self->header( %header );
290 0           print $xml_doc->toString();
291 0           return 0;
292             }
293              
294 0           my $stylesheet = $self->getStylesheet( $ctxt );
295              
296 0           my ( $xsl_dom, $style, $res );
297 0           my $parser = XML::LibXML->new();
298 0           my $xslt = XML::LibXSLT->new();
299              
300 0 0 0       if ( ref( $stylesheet ) ) {
    0          
301 0           debug_msg( 5, "stylesheet is reference" );
302 0           $xsl_dom = $stylesheet;
303             }
304             elsif ( -f $stylesheet && -r $stylesheet ) {
305 0           debug_msg( 5, "filename is $stylesheet" );
306 0           eval {
307 0           $xsl_dom = $parser->parse_file( $stylesheet );
308             };
309 0 0         if ( $@ ) {
310 0           debug_msg( 3, "Corrupted Stylesheet:\n broken XML\n". $@ );
311 0           $self->setPanicMsg( "Corrupted document:\n broken XML\n". $@ );
312 0           return -2;
313             }
314             }
315             else {
316             # first test the new style interface
317 0           my $xslstring = $self->getStylesheetString( $ctxt );
318 0 0         if ( length $xslstring ) {
319 0           debug_msg( 5, "stylesheet is xml string" );
320 0           eval { $xsl_dom = $parser->parse_string( $xslstring ); };
  0            
321 0 0 0       if ( $@ || not defined $xsl_dom ) {
322             # the parse failed !!!
323 0           debug_msg( 3, "Corrupted Stylesheet String:\n". $@ ."\n" );
324 0           $self->setPanicMsg( "Corrupted Stylesheet String:\n". $@ );
325 0           return -2;
326             }
327             }
328             else {
329             # now test old style interface
330             # will be removed with the next major release
331              
332 0           debug_msg( 5, "old style interface to select the stylesheet" );
333 0           $stylesheet = $self->selectStylesheet( $ctxt );
334 0 0 0       if ( ref( $stylesheet ) ) {
    0          
335 0           debug_msg( 5, "stylesheet is reference" );
336 0           $xsl_dom = $stylesheet;
337             }
338             elsif ( -f $stylesheet && -r $stylesheet ) {
339 0           debug_msg( 5, "filename is $stylesheet" );
340 0           eval {
341 0           $xsl_dom = $parser->parse_file( $stylesheet );
342             };
343 0 0         if ( $@ ) {
344 0           debug_msg( 3, "Corrupted Stylesheet:\n broken XML\n". $@ );
345 0           $self->setPanicMsg( "Corrupted document:\n broken XML\n". $@ );
346 0           return -2;
347             }
348             }
349             else {
350 0           debug_msg( 2 , "panic stylesheet file $stylesheet does not exist" );
351 0           $self->setPanicMsg( "$stylesheet" );
352 0 0         return length $stylesheet ? -2 : -1 ;
353             }
354             }
355             }
356              
357 0           eval {
358 0           $style = $xslt->parse_stylesheet( $xsl_dom );
359             # $style = $xslt->parse_stylesheet_file( $file );
360             };
361 0 0         if( $@ ) {
362 0           debug_msg( 3, "Corrupted Stylesheet:\n". $@ ."\n" );
363 0           $self->setPanicMsg( "Corrupted Stylesheet:\n". $@ );
364 0           return -2;
365             }
366              
367 0           my %xslparam = $self->getXSLParameter( $ctxt );
368 0           eval {
369             # first do special xpath encoding of the parameter
370 0 0 0       if ( %xslparam && scalar( keys %xslparam ) > 0 ) {
371 0           my @list;
372 0           foreach my $key ( keys %xslparam ) {
373             # check for multivalued parameters stored in a \0 separated string by CGI.pm :-/
374 0 0         if ( $xslparam{$key} =~ /\0/ ) {
375 0           push @list, $key, (split("\0",$xslparam{$key}))[-1];
376             }
377             else {
378 0           push @list, $key, $xslparam{$key};
379             }
380             }
381 0           $res = $style->transform( $xml_doc,
382             XML::LibXSLT::xpath_to_string(@list)
383             );
384             }
385             else {
386 0           $res = $style->transform( $xml_doc );
387             }
388             };
389 0 0         if( $@ ) {
390 0           debug_msg( 3, "Broken Transformation:\n". $@ ."\n" );
391 0           $self->setPanicMsg( "Broken Transformation:\n". $@ );
392 0           return -2;
393             }
394              
395             # override content-type with the correct content-type
396             # of the style (is this ok?)
397 0           $header{-type} = $style->media_type;
398 0           $header{-charset} = $style->output_encoding;
399              
400 0           debug_msg( 10, "serialization do output" );
401             # we want nice xhtml and since the output_string does not the
402             # right job
403 0           my $out_string= undef;
404              
405 0           debug_msg( 9, "serialization get output string" );
406 0           eval {
407 0           $out_string = $style->output_string( $res );
408             };
409 0           debug_msg( 10, "serialization rendered output" );
410 0 0         if ( $@ ) {
411 0           debug_msg( 3, "Corrupted Output:\n", $@ , "\n" );
412 0           $self->setPanicMsg( "Corrupted Output:\n". $@ );
413 0           return -2;
414             }
415             else {
416             # do the output
417 0           print $self->header( %header );
418 0           print $out_string;
419 0           debug_msg( 10, "output printed" );
420             }
421              
422 0           return 0;
423             }
424              
425             sub panic {
426 0     0 1   my ( $self, $pid ) = @_;
427 0 0         return unless $pid < 0;
428 0           $pid++;
429 0           $pid*=-1;
430              
431 0           my $str = "Application Panic: ";
432 0           $str = "PANIC $pid :" . $CGI::XMLApplication::panic[$pid] ;
433             # this is nice for debugging from logfiles...
434 0           $str = $self->b( $str ) . "
\n";
435 0           $str .= $self->pre( $self->getPanicMsg() );
436 0           $str .= "Please Contact the Systemadminstrator
\n";
437              
438 0           debug_msg( 1, "$str" );
439              
440 0 0         if ( $CGI::XMLApplication::Quiet == 1 ) {
441 0           $str = "Application Panic";
442             }
443 0 0         if ( $CGI::XMLApplication::Quiet == 2 ) {
444 0           $str = "";
445             }
446              
447 0 0         my $status = $pid < 3 ? 404 : 500; # default is the application error ...
448 0           print $self->header( -status => $status ) , $str ,"\n";
449              
450             }
451              
452             1;
453             # ################################################################
454             __END__