File Coverage

blib/lib/CIPP/Compile/Parser.pm
Criterion Covered Total %
statement 34 36 94.4
branch n/a
condition n/a
subroutine 12 12 100.0
pod n/a
total 46 48 95.8


line stmt bran cond sub pod time code
1             # $Id: Parser.pm,v 1.29 2006/05/16 14:58:54 joern Exp $
2              
3             package CIPP::Compile::Parser;
4              
5 1     1   6 use strict;
  1         1  
  1         33  
6 1     1   5 use Carp;
  1         3  
  1         54  
7 1     1   5 use vars qw ( @ISA );
  1         2  
  1         48  
8              
9 1     1   526 use CIPP::Debug;
  1         2  
  1         29  
10 1     1   632 use CIPP::Compile::Message;
  1         2  
  1         22  
11 1     1   578 use CIPP::Compile::Cache;
  1         3  
  1         28  
12 1     1   614 use CIPP::Compile::PerlCheck;
  1         4  
  1         34  
13              
14 1     1   6 use FileHandle;
  1         2  
  1         6  
15 1     1   395 use File::Basename;
  1         26  
  1         58  
16 1     1   5 use File::Path;
  1         2  
  1         61  
17 1     1   957 use File::Copy;
  1         2415  
  1         49  
18 1     1   1509 use IO::String;
  0            
  0            
19             use Data::Dumper;
20              
21             @ISA = qw ( CIPP::Debug );
22              
23             #---------------------------------------------------------------------
24             # Konstruktor
25             #---------------------------------------------------------------------
26              
27             sub new {
28             my $type = shift; $type->trace_in;
29             my %par = @_;
30             my ($object_type, $project, $mime_type, $lib_path) =
31             @par{'object_type','project','mime_type','lib_path'};
32             my ($program_name, $start_context, $magic_start, $magic_end) =
33             @par{'program_name','start_context','magic_start','magic_end'};
34             my ($no_http_header, $dont_cache, $url_par_delimiter) =
35             @par{'no_http_header','dont_cache','url_par_delimiter'};
36             my ($config_dir, $trunc_ws) =
37             @par{'config_dir','trunc_ws'};
38              
39             confess "Unknown object type '$object_type'"
40             if $object_type ne 'cipp' and
41             $object_type ne 'cipp-html' and
42             $object_type ne 'cipp-inc' and
43             $object_type ne 'cipp-module';
44              
45             confess "Please specify the following parameters:\n".
46             "object_type, project, and program_name.\n".
47             "Got: ".join(', ', keys(%par))."\n"
48             unless $object_type and $project and $program_name;
49            
50             $magic_start ||= '
51             $magic_end ||= '>';
52             $start_context ||= 'html';
53             $url_par_delimiter ||= '&';
54              
55             my $self = bless {
56             object_type => $object_type,
57             start_context => $start_context,
58             magic_start => $magic_start,
59             magic_end => $magic_end,
60             project => $project,
61             program_name => $program_name,
62             lib_path => $lib_path,
63             mime_type => $mime_type,
64             dont_cache => $dont_cache,
65             no_http_header => $no_http_header,
66             url_par_delimiter => $url_par_delimiter,
67             config_dir => $config_dir,
68             trunc_ws => $trunc_ws,
69             perl_code_sref => undef,
70             cache_ok => 0,
71             state => {},
72             used_objects => {},
73             used_objects_by_type => {},
74             used_modules => {},
75             messages => [],
76             context => [ $start_context ],
77             context_data => [ "" ],
78             in_fh => undef,
79             out_fh => undef,
80             tag_stack => [],
81             out_fh_stack => [],
82             command2method => {
83             '#' => 'cmd_comment',
84             '!#' => 'cmd_comment',
85             '' => 'cmd_expression',
86             '!autoprint' => 'cmd_autoprint',
87             '!httpheader' => 'cmd_httpheader',
88             '!profile' => 'cmd_profile',
89             },
90             }, $type;
91              
92             my $norm_name = $self->get_normalized_object_name (
93             name => $program_name
94             );
95            
96             $self->{norm_name} = $norm_name;
97             $self->set_inc_trace ( ":$norm_name:" );
98              
99             return $self;
100             }
101              
102             #---------------------------------------------------------------------
103             # Generator process method return codes
104             #---------------------------------------------------------------------
105              
106             sub RC_SINGLE_TAG { 1 }
107             sub RC_BLOCK_TAG { shift; return {} if not @_;
108             my %par = @_; return \%par; }
109              
110             #---------------------------------------------------------------------
111             # Read only attribute accessors
112             #---------------------------------------------------------------------
113              
114             sub get_project { shift->{project} }
115             sub get_program_name { shift->{program_name} }
116             sub get_norm_name { shift->{norm_name} }
117             sub get_object_type { shift->{object_type} }
118             sub get_start_context { shift->{start_context} }
119             sub get_lib_path { shift->{lib_path} }
120             sub get_config_dir { shift->{config_dir} }
121             sub get_mime_type { shift->{mime_type} }
122             sub get_state { shift->{state} }
123             sub get_command2method { shift->{command2method} }
124             sub get_used_objects { shift->{used_objects} }
125             sub get_used_objects_by_type { shift->{used_objects_by_type} }
126             sub get_no_http_header { shift->{no_http_header} }
127             sub get_used_modules { shift->{used_modules} }
128             sub get_magic_start { shift->{magic_start} }
129             sub get_magic_end { shift->{magic_end} }
130             sub get_trunc_ws { shift->{trunc_ws} }
131             sub get_text_domain {
132             my $self = shift;
133             return $self->{text_domain} if exists $self->{text_domain};
134             return $self->{text_domain} = $self->determine_text_domain;
135             }
136              
137             #---------------------------------------------------------------------
138             # Read and Write attribute accessors
139             #---------------------------------------------------------------------
140              
141             sub get_in_filename { shift->{in_filename} }
142             sub get_out_filename { shift->{out_filename} }
143             sub get_prod_filename { shift->{prod_filename} }
144             sub get_iface_filename { shift->{iface_filename} }
145             sub get_dep_filename { shift->{dep_filename} }
146             sub get_err_filename { shift->{err_filename} }
147             sub get_err_copy_filename { shift->{err_copy_filename} }
148             sub get_http_filename { shift->{http_filename} }
149             sub get_url_par_delimiter { shift->{url_par_delimiter} }
150             sub get_messages { shift->{messages} }
151             sub get_interface_changed { shift->{interface_changed} }
152             sub get_cache_ok { shift->{cache_ok} }
153             sub get_dont_cache { shift->{dont_cache} }
154             sub get_current_tag { shift->{current_tag} }
155             sub get_current_tag_closed { shift->{current_tag_closed} }
156             sub get_current_tag_line_nr { shift->{current_tag_line_nr} }
157             sub get_current_tag_options { shift->{current_tag_options} }
158             sub get_current_tag_options_case { shift->{current_tag_options_case} }
159             sub get_current_tag_options_order { shift->{current_tag_options_order} }
160             sub get_inc_trace { shift->{inc_trace} }
161             sub get_last_text_block { shift->{last_text_block} }
162              
163             sub set_in_filename { shift->{in_filename} = $_[1] }
164             sub set_out_filename { shift->{out_filename} = $_[1] }
165             sub set_prod_filename { shift->{prod_filename} = $_[1] }
166             sub set_iface_filename { shift->{iface_filename} = $_[1] }
167             sub set_dep_filename { shift->{dep_filename} = $_[1] }
168             sub set_err_filename { shift->{err_filename} = $_[1] }
169             sub set_err_copy_filename { shift->{err_copy_filename} = $_[1] }
170             sub set_http_filename { shift->{http_filename} = $_[1] }
171             sub set_url_par_delimiter { shift->{url_par_delimiter} = $_[1] }
172             sub set_messages { shift->{messages} = $_[1] }
173             sub set_interface_changed { shift->{interface_changed} = $_[1] }
174             sub set_cache_ok { shift->{cache_ok} = $_[1] }
175             sub set_dont_cache { shift->{dont_cache} = $_[1] }
176             sub set_current_tag { shift->{current_tag} = $_[1] }
177             sub set_current_tag_closed { shift->{current_tag_closed} = $_[1] }
178             sub set_current_tag_line_nr { shift->{current_tag_line_nr} = $_[1] }
179             sub set_current_tag_options { shift->{current_tag_options} = $_[1] }
180             sub set_current_tag_options_case { shift->{current_tag_options_case} = $_[1] }
181             sub set_current_tag_options_order { shift->{current_tag_options_order}= $_[1] }
182             sub set_inc_trace { shift->{inc_trace} = $_[1] }
183             sub set_last_text_block { shift->{last_text_block} = $_[1] }
184              
185             #---------------------------------------------------------------------
186             # Parser internal methods
187             #---------------------------------------------------------------------
188              
189             sub get_tag_stack { shift->{tag_stack} }
190             sub get_in_fh { shift->{in_fh} }
191             sub get_out_fh { shift->{out_fh} }
192             sub get_out_fh_stack { shift->{out_fh_stack} }
193             sub get_line_nr { shift->{line_nr} }
194             sub get_quote_line_nr { shift->{quote_line_nr} }
195              
196             sub set_tag_stack { shift->{tag_stack} = $_[1] }
197             sub set_in_fh { shift->{in_fh} = $_[1] }
198             sub set_out_fh { shift->{out_fh} = $_[1] }
199             sub set_out_fh_stack { shift->{out_fh_stack} = $_[1] }
200             sub set_line_nr { shift->{line_nr} = $_[1] }
201             sub set_quote_line_nr { shift->{quote_line_nr} = $_[1] }
202              
203             #---------------------------------------------------------------------
204             # These methods must be defined by CIPP::Compile::* classes
205             #---------------------------------------------------------------------
206              
207             sub create_new_parser {
208             die "create_new_parser not implemented";
209             }
210              
211             sub generate_start_program {
212             die "generate_start_program not implemented";
213             }
214              
215             sub generate_project_handler {
216             die "generate_project_handler not implemented";
217             }
218              
219             sub generate_init_request {
220             die "generate_init_request not implemented";
221             }
222              
223             sub get_normalized_object_name {
224             die "normalize_object_name not implemented";
225             }
226              
227             sub get_object_filename {
228             die "get_object_filename not implemented";
229             }
230              
231             sub determine_object_type {
232             die "determine_object_type not implemented";
233             }
234              
235             sub get_object_url {
236             die "get_object_url not implemented";
237             }
238              
239             sub get_object_filenames {
240             die "get_object_filenames not implemented";
241             }
242              
243             sub get_relative_inc_path {
244             die "get_relative_inc_path not implemented";
245             }
246              
247             #---------------------------------------------------------------------
248             # Control methods for processing of CIPP Programs, Includes
249             # and Modules
250             #---------------------------------------------------------------------
251              
252             sub process {
253             my $self = shift; $self->trace_in;
254              
255             # if Cache is clean: nothing to do here
256             return if $self->cache_is_clean;
257              
258             my $object_type = $self->get_object_type;
259              
260             if ( $object_type eq 'cipp' or
261             $object_type eq 'cipp-html' ) {
262             $self->process_program;
263            
264             } elsif ( $object_type eq 'cipp-inc' ) {
265             $self->process_include;
266              
267             } elsif ( $object_type eq 'cipp-module' ) {
268             $self->process_module;
269              
270             } else {
271             croak "Unknown object type '$object_type'";
272              
273             }
274              
275             1;
276             }
277              
278             sub process_program {
279             my $self = shift; $self->trace_in;
280            
281             # open files
282             $self->open_files;
283             return unless $self->get_out_fh and $self->get_in_fh;
284              
285             # process Program, generate code
286             $self->generate_start_program;
287             $self->generate_open_exception_handler;
288             $self->generate_project_handler;
289              
290             # buffer output of the program parser
291             my $buffer_sref = $self->open_output_buffer;
292             $self->parse;
293             $self->close_output_buffer;
294              
295             # write dependencies here, otherwise ->custom_http_header_file
296             # in ->generate_open_request may fail, because it reads
297             # the .dep file
298             $self->write_dependencies;
299              
300             # now we can generate init request
301             # (due to )
302             $self->generate_open_request;
303            
304             # flush the output of the parser to the output file
305             $self->flush_output_buffer ( buffer_sref => $buffer_sref );
306              
307             $self->generate_close_exception_handler;
308             $self->generate_close_request;
309             $self->close_files;
310             $self->perl_error_check;
311             $self->install_file;
312              
313             1;
314             }
315              
316             sub process_module {
317             my $self = shift; $self->trace_in;
318            
319             # open files
320             $self->open_files;
321             return unless $self->get_out_fh and $self->get_in_fh;
322              
323             $self->generate_module_open;
324             $self->parse;
325             $self->generate_module_close;
326             $self->close_files;
327             $self->perl_error_check;
328             $self->install_file;
329             $self->write_dependencies;
330            
331             1;
332             }
333              
334             sub process_include {
335             my $self = shift; $self->trace_in;
336            
337             # open files
338             $self->open_files;
339             return unless $self->get_out_fh and $self->get_in_fh;
340              
341             # buffer output from the parser
342             my $buffer_sref = $self->open_output_buffer;
343             $self->parse;
344             $self->close_output_buffer;
345              
346             # generate the Include header (now the interface is known)
347             $self->generate_include_open;
348              
349             # add result from the parser
350             $self->flush_output_buffer ( buffer_sref => $buffer_sref );
351            
352             # close include
353             $self->generate_include_close;
354             $self->close_files;
355             $self->perl_error_check;
356             $self->install_file;
357              
358             #-------------------------------------------------------------
359             # Now update meta data: interface and dependecy information
360             #-------------------------------------------------------------
361              
362             my $iface_filename = $self->get_iface_filename;
363              
364             # remember atime and mtime of the interface file
365             my ($last_interface_atime, $last_interface_mtime);
366             ($last_interface_atime, $last_interface_mtime) =
367             (stat($iface_filename))[8,9] if -f $iface_filename;
368              
369             # remember old interface (interface file may not exist)
370             my $old_interface = eval { $self->read_include_interface_file };
371            
372             # store (possibly) new Include interface
373             my $new_interface = $self->store_include_interface_file;
374              
375             # update dependencies
376             $self->write_dependencies;
377            
378             # reset timestamps if interfaces are compatible
379             if ( $self->check_interfaces_are_compatible (
380             old_interface => $old_interface,
381             new_interface => $new_interface
382             ) and $last_interface_atime ) {
383             # set back timestamps
384             utime $last_interface_atime, $last_interface_mtime,
385             $iface_filename;
386             }
387            
388             1;
389             }
390              
391             #---------------------------------------------------------------------
392             # Elementary public Parser methods
393             #---------------------------------------------------------------------
394              
395             sub parse {
396             my $self = shift; $self->trace_in;
397            
398             my $in_fh = $self->get_in_fh;
399              
400             # these characters indicate CIPP commands
401             my $magic_start = $self->get_magic_start;
402             my $magic_end = $self->get_magic_end;
403             my $magic_start_length = length($magic_start);
404             my $magic_end_length = length($magic_end);
405              
406             # holds actual read line
407             my $line;
408              
409             # holds actual lines which belongs together
410             my $buffer = "";
411              
412             # state of the parser. the following values are defined:
413             # 'text' : text between CIPP tags
414             # 'tag : we are inside a CIPP tag
415             my $state = 'text';
416             $self->set_current_tag ($state);
417             $self->set_current_tag_line_nr (0);
418              
419             # $start_pos: starting position for searches inside lines
420             # $pos: temporary search position
421             # $quote_pos: position of quote sign
422             # $backslash_pos: position of backslash
423             # $tag_name: name of tag we are currently in
424             my ($start_pos, $pos, $quote_pos, $backslash_pos);
425              
426             # line number counter
427             my $line_nr = 0;
428            
429             READLINE: while ( $line = <$in_fh> ) {
430             $self->set_line_nr (++$line_nr);
431            
432             # skip comments
433             next READLINE if $line =~ m!^\s*#!;
434              
435             $start_pos = 0;
436             PARSELINE: while ( $start_pos < length($line) ) {
437             $self->debug ("nr=$line_nr start_pos=$start_pos state=$state line='$line'");
438             if ( $state eq 'text' ) {
439             # search next CIPP tag
440             $pos = index($line, $magic_start, $start_pos);
441             $self->debug ("text: index ($magic_start, $start_pos) = $pos");
442             if ( -1 == $pos ) {
443             # not found => read next line
444             $buffer .= substr($line, $start_pos);
445             next READLINE;
446             } else {
447             # found => add text beneath $pos to buffer
448             $self->debug (
449             "text: substr(".$start_pos.
450             ",".($pos-$start_pos).")"
451             );
452             $buffer .= substr(
453             $line, $start_pos,
454             $pos-$start_pos
455             );
456             $self->process_text (\$buffer);
457             $start_pos = $pos + $magic_start_length;
458             $buffer = '';
459             $state = 'tag';
460             $self->debug ("set tag line: $line_nr");
461             $self->set_current_tag_line_nr ($line_nr);
462             next PARSELINE;
463             }
464             }
465              
466             if ( $state eq 'tag' ) {
467             # search end of CIPP tag
468             $pos = index($line, $magic_end, $start_pos);
469             $quote_pos = index($line, '"', $start_pos);
470             $backslash_pos = index($line, '\\', $start_pos);
471              
472             $self->debug ("magic_end_pos=$pos quote_pos=$quote_pos");
473              
474             # found a backslash first?
475             if ( $backslash_pos != -1 and
476             ($backslash_pos < $quote_pos or $quote_pos == -1 ) and
477             ($backslash_pos < $pos or $pos == -1 ) ) {
478             # skip next character
479             $buffer .= substr(
480             $line, $start_pos,
481             $backslash_pos-$start_pos+2
482             );
483             $start_pos = $backslash_pos + 2;
484             next PARSELINE;
485             }
486            
487             # found a quote first?
488             if ( $quote_pos != -1 and ( $quote_pos < $pos or $pos == -1 ) ) {
489             $buffer .= substr(
490             $line, $start_pos,
491             $quote_pos-$start_pos+1
492             );
493             $start_pos = $quote_pos+1;
494             $state = 'quote';
495             $self->set_quote_line_nr ($line_nr);
496             next PARSELINE;
497             }
498              
499             $self->debug ("tag: index ($magic_end, $start_pos) = $pos");
500              
501             if ( -1 == $pos ) {
502             # not found => read next line
503             $buffer .= substr($line, $start_pos);
504             next READLINE;
505             } else {
506             $self->debug (
507             "tag: substr(".$start_pos.
508             ",".($pos-$start_pos).")"
509             );
510             $buffer .= substr(
511             $line, $start_pos,
512             $pos-$start_pos
513             );
514             $start_pos = $pos + $magic_end_length;
515              
516             # process this tag
517             $self->parse_tag ($buffer);
518             $buffer = '';
519              
520             $state = 'text';
521             $self->set_current_tag ($state);
522             $self->set_current_tag_line_nr ($line_nr+1);
523              
524             next PARSELINE;
525             }
526             }
527            
528             if ( $state eq 'quote' ) {
529             $quote_pos = index($line, '"', $start_pos);
530             $backslash_pos = index($line, '\\', $start_pos);
531              
532             # found a backslash first?
533             if ( $backslash_pos != -1 and
534             $backslash_pos < $quote_pos ) {
535             # skip next character
536             $buffer .= substr(
537             $line, $start_pos,
538             $backslash_pos-$start_pos+2
539             );
540             $start_pos = $backslash_pos + 2;
541             next PARSELINE;
542             }
543            
544             # found a quote?
545             if ( -1 == $quote_pos ) {
546             $buffer .= substr($line, $start_pos);
547             next READLINE;
548            
549             } else {
550             $buffer .= substr(
551             $line, $start_pos,
552             $quote_pos-$start_pos+1
553             );
554             $start_pos = $quote_pos+1;
555             $state = 'tag';
556             next PARSELINE;
557             }
558             }
559             }
560             }
561            
562             if ( $state eq 'text' ) {
563             $self->process_text (\$buffer);
564              
565             } elsif ( $state eq 'quote' ) {
566             $self->add_message (
567             message => "Double quote not closed.",
568             line_nr => $self->get_quote_line_nr,
569             );
570              
571             } else {
572             $self->add_message (
573             message => "Error parsing CIPP tag.",
574             line_nr => $self->get_current_tag_line_nr,
575             );
576             }
577              
578             my $opened_tag;
579             while ( $opened_tag = $self->pop_tag ) {
580             $self->add_message (
581             line_nr => $opened_tag->{line_nr},
582             message => "Tag not closed.",
583             tag => $opened_tag->{tag},
584             );
585             }
586             }
587              
588             sub parse_variable_option {
589             my $self = shift; $self->trace_in;
590              
591             my $var2name = $self->parse_variable_option_hash (@_);
592            
593             if ( scalar keys %{$var2name} > 1 ) {
594             $self->add_tag_message (
595             message => "More than one variable specified."
596             );
597             return;
598             } else {
599             return (keys %{$var2name})[0];
600             }
601             }
602              
603             my %TYPE2CHAR = (
604             scalar => '$',
605             hash => '%',
606             array => '@'
607             );
608              
609             sub parse_variable_option_hash {
610             my $self = shift; $self->trace_in;
611             my %par = @_;
612             my ($option, $types, $name2var) =
613             @par{'option','types','name2var'};
614            
615             my $type_regex;
616             if ( not $types ) {
617             $type_regex = "[".quotemeta('$@%')."]";
618             } else {
619             $type_regex = "[".
620             quotemeta(join('',map($TYPE2CHAR{$_}, @{$types}))).
621             "]";
622             }
623            
624             my $value = $self->get_current_tag_options->{$option};
625             $value =~ s/^\s*//;
626             $value =~ s/\s*$//;
627              
628             my ($name, $var, @untyped, %var2name, %name2var);
629             foreach $var ( split(/\s*,\s*/, $value) ) {
630             ( $name = $var ) =~ s/^$type_regex//;
631             if ( $name eq $var ) {
632             push @untyped, $var;
633             } else {
634             $name2var{$name} = $var if $name2var;
635             $var2name{$var} = $name if not $name2var;
636             }
637             }
638            
639             $self->add_tag_message (
640             message => "Untyped variables: ".
641             join(', ', @untyped)
642             ) if @untyped;
643            
644             return $name2var ? \%name2var : \%var2name;
645             }
646              
647             sub parse_variable_option_list {
648             my $self = shift; $self->trace_in;
649             my %par = @_;
650             my ($option, $types) = @par{'option','types'};
651            
652             my $type_regex;
653             if ( not $types ) {
654             $type_regex = "[".quotemeta('$@%')."]";
655             } else {
656             $type_regex = "[".
657             quotemeta(join('',map($TYPE2CHAR{$_}, @{$types}))).
658             "]";
659             }
660            
661             my $value = $self->get_current_tag_options->{$option};
662             $value =~ s/^\s*//;
663             $value =~ s/\s*$//;
664              
665             my ($name, $var, @untyped, @var);
666             foreach $var ( split(/\s*,\s*/, $value) ) {
667             ( $name = $var ) =~ s/^$type_regex//;
668             if ( $name eq $var ) {
669             push @untyped, $var;
670             } else {
671             push @var, $var;
672             }
673             }
674            
675             $self->add_tag_message (
676             message => "Untyped variables: ".
677             join(', ', @untyped)
678             ) if @untyped;
679            
680             return \@var;
681             }
682              
683             sub context {
684             my $self = shift; $self->trace_in;
685             return $self->{context}->[@{$self->{context}}-1];
686             }
687              
688             sub push_context {
689             my $self = shift; $self->trace_in;
690             my ($context, $data) = @_;
691            
692             push @{$self->{context}}, $context;
693             push @{$self->{context_data}}, $data;
694              
695             return $context;
696             }
697              
698             sub pop_context {
699             my $self = shift; $self->trace_in;
700             my ($context) = @_;
701            
702             my $context = pop @{$self->{context}};
703             my $data = pop @{$self->{context_data}};
704              
705             return ($context, $data) if wantarray;
706             return $context;
707             }
708              
709             sub check_object_type {
710             my $self = shift; $self->trace_in;
711             my %par = @_;
712             my ($name, $type, $message) = @par{'name','type','message'};
713              
714             $message ||= "Object '$name' is not of type '$type'.";
715              
716             return if not $self->object_exists (
717             name => $name,
718             add_message_if_not => 1
719             );
720              
721             my $object_type = $self->determine_object_type ( name => $name );
722              
723             if ( $object_type ne $type ) {
724             $self->add_tag_message (
725             message => $message
726             );
727             return;
728             }
729            
730             1;
731             }
732              
733             sub object_exists {
734             my $self = shift; $self->trace_in;
735             my %par = @_;
736             my ($name, $add_message_if_not) =
737             @par{'name','add_message_if_not'};
738              
739             my $filename = $self->get_object_filename (
740             name => $name
741             );
742            
743             if ( not defined $filename and $add_message_if_not ) {
744             $self->add_tag_message (
745             message => "Object '$name' not found."
746             );
747             }
748              
749             return defined $filename;
750             }
751              
752             sub query_tag_history {
753             my $self = shift; $self->trace_in;
754             my %par = @_;
755             my ($tag, $steps) = @par{'tag','steps'};
756            
757             $tag ||= $self->get_current_tag;
758            
759             # $steps == 0 => search back to bottom of the stack
760            
761             my $tag_stack = $self->get_tag_stack;
762             my $i = @{$tag_stack} - 1;
763            
764             for (my $i = @{$tag_stack} - 1; $i >= 0 and $steps >= 0; --$i ) {
765             return $tag_stack->[$i]->{data}
766             if $tag_stack->[$i]->{tag} eq $tag;
767             --$steps;
768             }
769            
770             return;
771             }
772              
773             sub check_options {
774             my $self = shift; $self->trace_in;
775             my %par = @_;
776             my ($mandatory, $optional) = @par{'mandatory','optional'};
777            
778             my $options = $self->get_current_tag_options;
779            
780             # check mandatory options
781             my @missing;
782             foreach my $name ( keys %{$mandatory} ) {
783             push @missing, $name if not exists $options->{$name};
784             }
785            
786             # check unknown options
787             my @unknown;
788             if ( not exists $optional->{'*'} ) {
789             foreach my $name ( keys %{$options} ) {
790             push @unknown, $name if not exists $mandatory->{$name} and
791             not exists $optional->{$name};
792             }
793             }
794              
795             my $ok = 1;
796              
797             # an optional => '*', mandatory => {} means: min. 1 parameter
798             # is expected
799             if ( exists $optional->{'*'} and scalar(keys %{$mandatory}) == 0 and
800             scalar(keys%{$options}) == 0 ) {
801             $self->add_tag_message (
802             message => 'Minimum one parameter is required.'
803             );
804             $ok = 0;
805             }
806            
807             if ( @missing ) {
808             $self->add_tag_message (
809             message => 'Missing tag options: '.
810             join(', ', map uc($_), @missing)
811             );
812             $ok = 0;
813             }
814              
815             if ( @unknown ) {
816             $self->add_tag_message (
817             message => 'Unknown tag options: '.
818             join(', ', map uc($_), @unknown)
819             );
820             $ok = 0;
821             }
822              
823             return $ok;
824             }
825              
826             #---------------------------------------------------------------------
827             # These methods manage output buffers
828             #---------------------------------------------------------------------
829              
830             sub open_output_buffer {
831             my $self = shift; $self->trace_in;
832              
833             push @{$self->get_out_fh_stack}, $self->get_out_fh;
834              
835             my $buffer = "";
836             $self->set_out_fh ( IO::String->new($buffer) );
837              
838             return \$buffer;
839             }
840              
841             sub close_output_buffer{
842             my $self = shift; $self->trace_in;
843              
844             my $buffer_fh = $self->get_out_fh;
845              
846             $self->set_out_fh ( pop @{$self->get_out_fh_stack} );
847              
848             return $buffer_fh->string_ref;
849             }
850              
851             sub flush_output_buffer{
852             my $self = shift; $self->trace_in;
853             my %par = @_;
854             my ($buffer_sref) = @par{'buffer_sref'};
855              
856             # flush buffer
857             $self->write ( $$buffer_sref );
858              
859             # free memory
860             $$buffer_sref = "";
861              
862             1;
863             }
864              
865             #---------------------------------------------------------------------
866             # File I/O related methods
867             #---------------------------------------------------------------------
868              
869             sub write {
870             my $self = shift; $self->trace_in;
871             my $fh = $self->get_out_fh;
872             print $fh ref $_ eq 'SCALAR' ? $$_ : $_ for @_;
873             1;
874             }
875              
876             sub writef {
877             my $self = shift; $self->trace_in;
878             my $fh = $self->get_out_fh;
879             printf $fh (@_);
880             1;
881             }
882              
883             sub open_files {
884             my $self = shift; $self->trace_in;
885            
886             my $filename;
887             my $fh;
888              
889             $filename = $self->get_in_filename;
890             $fh = FileHandle->new;
891              
892             if ( open ($fh, $filename) ) {
893             $self->set_in_fh ($fh);
894             } else {
895             $self->add_message (
896             message => "Can't read input file '$filename': $!"
897             );
898             }
899              
900             $filename = $self->get_out_filename;
901             $self->make_path($filename);
902             $fh = FileHandle->new;
903             if ( open ($fh, ">$filename") ) {
904             $self->set_out_fh ($fh);
905             } else {
906             $self->add_message (
907             message => "Can't write output file '$filename': $!"
908             );
909             }
910              
911             1;
912             }
913              
914             sub close_files {
915             my $self = shift; $self->trace_in;
916            
917             close ($self->get_in_fh);
918             close ($self->get_out_fh);
919              
920             1;
921             }
922              
923             sub install_file {
924             my $self = shift; $self->trace_in;
925              
926             if ( $self->has_errors ) {
927             move ($self->get_out_filename, $self->get_err_copy_filename);
928             unlink $self->get_dep_filename;
929             unlink $self->get_iface_filename
930             if $self->get_iface_filename;
931             return;
932             }
933              
934             unlink $self->get_err_copy_filename;
935              
936             my $object_type = $self->get_object_type;
937              
938             if ( $object_type eq 'cipp' ) {
939             chmod 0775, $self->get_out_filename;
940              
941             } elsif ( $object_type eq 'cipp-inc' ) {
942             chmod 0664, $self->get_out_filename;
943              
944             } elsif ( $object_type eq 'cipp-module' ) {
945             my $tmp_module_file = $self->get_out_filename;
946             my $prod_filename;
947             (undef, undef, $prod_filename) = $self->get_object_filenames;
948             $self->set_prod_filename ( $prod_filename );
949              
950             my $prod_dir = dirname($prod_filename);
951             if ( not -d $prod_dir ) {
952             mkpath ([$prod_dir], 0, 0775) or $self->add_message (
953             line_nr => 0,
954             message => "Can't create dir $prod_dir"
955             );
956             }
957              
958             if ( -d $prod_dir and not move ($tmp_module_file, $prod_filename) ) {
959             $self->add_message (
960             line_nr => 0,
961             message => "Can't move '$tmp_module_file' to ".
962             "'$prod_filename': $!"
963             );
964             }
965              
966             } elsif ( $object_type eq 'cipp-html' ) {
967             # ->perl_error_check will execute the generated
968             # perl program and install its output to
969              
970             unlink $self->get_out_filename;
971              
972             } else {
973             confess "Unknown object type '$object_type'";
974             }
975              
976             # delete http_file if no occured
977             if ( not $self->get_state->{http_header_occured} ) {
978             unlink ($self->get_http_filename);
979             }
980              
981             1;
982             }
983              
984             sub make_path {
985             my $self = shift; $self->trace_in;
986            
987             my ($filename) = @_;
988             my $dir = dirname $filename;
989            
990             return if -d $dir;
991              
992             mkpath ($dir, 0, 0770)
993             or confess "can't mkpath '$dir': $!";
994            
995             1;
996             }
997              
998             sub cache_is_clean {
999             my $self = shift;
1000              
1001             return if $self->get_dont_cache;
1002            
1003             my $cache_status = CIPP::Compile::Cache->get_cache_status (
1004             dep_file => $self->get_dep_filename,
1005             if_file => $self->get_iface_filename,
1006             );
1007            
1008             if ( $cache_status eq 'dirty' ) {
1009             $self->set_cache_ok (0);
1010             return;
1011            
1012             } elsif ( $cache_status eq 'clean' ) {
1013             $self->set_cache_ok (1);
1014             return 1;
1015              
1016             } elsif ( $cache_status eq 'cached err' ) {
1017             $self->set_cache_ok (1);
1018             $self->load_cached_errors;
1019             return 1;
1020              
1021             } else {
1022             croak "Unknown cache_status '$cache_status'";
1023             }
1024             }
1025              
1026             sub get_perl_code_sref {
1027             my $self = shift;
1028            
1029             my $sub_filename = $self->get_out_filename;
1030              
1031             return $self->{perl_code_sref}
1032             if defined $self->{perl_code_sref};
1033              
1034             my $fh = FileHandle->new;
1035             open ($fh, $sub_filename) or confess "can't read $sub_filename";
1036             my $perl_code = join ('',<$fh>);
1037             close $fh;
1038              
1039             $self->{perl_code_sref} = \$perl_code;
1040              
1041             return \$perl_code;
1042             }
1043              
1044             sub custom_http_header_file {
1045             my $self = shift;
1046            
1047             my $http_files = CIPP::Compile::Cache->get_custom_http_header_files (
1048             dep_file => $self->get_dep_filename
1049             );
1050              
1051             if ( @{$http_files} > 1 ) {
1052             $self->add_tag_message (
1053             message => "Multiple instances found: ".
1054             join (", ", @{$http_files})
1055             );
1056             return;
1057             }
1058              
1059             if ( @{$http_files} == 1 ) {
1060             return $self->get_relative_inc_path (
1061             filename => $http_files->[0]
1062             );
1063             }
1064              
1065             return;
1066             }
1067              
1068             #---------------------------------------------------------------------
1069             # Dependency related methods
1070             #---------------------------------------------------------------------
1071              
1072             sub add_used_object {
1073             my $self = shift; $self->trace_in;
1074             my %par = @_;
1075             my ($name, $ext, $type, $normalized) =
1076             @par{'name','ext','type','normalized'};
1077            
1078             $ext ||= $type;
1079              
1080             $name = $self->get_normalized_object_name ( name => $name )
1081             if not $normalized;
1082              
1083             $self->get_used_objects->{"$name.$ext:$type"} = 1;
1084             $self->get_used_objects_by_type->{$type}->{$name} = 1;
1085              
1086             1;
1087             }
1088              
1089             sub add_used_module {
1090             my $self = shift; $self->trace_in;
1091             my %par = @_;
1092             my ($name) = @par{'name'};
1093            
1094             $self->get_used_modules->{$name} = 1;
1095              
1096             1;
1097             }
1098              
1099             sub get_module_name {
1100             my $self = shift; $self->trace_in;
1101             return $self->get_state->{module_name};
1102             }
1103              
1104             sub write_dependencies {
1105             my $self = shift; $self->trace_in;
1106              
1107             my $used_includes_href = $self->get_used_objects_by_type->{'cipp-inc'};
1108            
1109             my %entries_hash;
1110             foreach my $name ( keys %{$used_includes_href} ) {
1111             # resolve filenames of this used include
1112             my ($in_filename, $out_filename, $prod_filename,
1113             $dep_filename, $iface_filename, $err_filename,
1114             $http_filename ) =
1115             $self->get_object_filenames (
1116             norm_name => $name,
1117             object_type => 'cipp-inc'
1118             );
1119              
1120             # direct entry of this Include
1121             $entries_hash{$in_filename} =
1122             "$in_filename\t$prod_filename\t$iface_filename\t$http_filename";
1123              
1124             # load transitive dependencies of this Include
1125             # into our entries hash
1126             CIPP::Compile::Cache->load_dep_file_into_entries_hash (
1127             dep_file => $dep_filename,
1128             entries_href => \%entries_hash,
1129             );
1130             }
1131            
1132             CIPP::Compile::Cache->write_dep_file (
1133             src_file => $self->get_in_filename,
1134             dep_file => $self->get_dep_filename,
1135             cache_file => $self->get_prod_filename,
1136             err_file => $self->get_err_filename,
1137             http_file => $self->get_http_filename,
1138             entries_href => \%entries_hash,
1139             );
1140            
1141             if ( $self->has_direct_errors ) {
1142             $self->save_cached_errors;
1143             } else {
1144             unlink ($self->get_err_filename) if -f $self->get_err_filename;
1145             }
1146              
1147             1;
1148             }
1149              
1150             #---------------------------------------------------------------------
1151             # Message and Error handling
1152             #---------------------------------------------------------------------
1153              
1154             sub add_message {
1155             my $self = shift; $self->trace_in;
1156             my %par = @_;
1157              
1158             my ($type, $line_nr, $tag, $message) =
1159             @par{'type','line_nr','tag','message'};
1160            
1161             $type ||= 'cipp_err';
1162             $line_nr ||= $self->get_line_nr;
1163             $tag ||= $self->get_current_tag;
1164              
1165             push @{$self->get_messages}, CIPP::Compile::Message->new (
1166             line_nr => $line_nr,
1167             type => $type,
1168             tag => $tag,
1169             message => $message,
1170             name => $self->get_program_name,
1171             );
1172            
1173             1;
1174             }
1175              
1176             sub add_tag_message {
1177             my $self = shift; $self->trace_in;
1178             my %par = @_;
1179              
1180             my ($type, $message) =
1181             @par{'type','message'};
1182            
1183             $type ||= 'cipp_err';
1184              
1185             push @{$self->get_messages}, CIPP::Compile::Message->new (
1186             line_nr => $self->get_current_tag_line_nr,
1187             type => $type,
1188             tag => $self->get_current_tag,
1189             message => $message,
1190             name => $self->get_program_name,
1191             );
1192            
1193             1;
1194             }
1195              
1196             sub add_message_object {
1197             my $self = shift; $self->trace_in;
1198             my %par = @_;
1199             my ($object) = @par{'object'};
1200            
1201             push @{$self->get_messages}, $object;
1202            
1203             1;
1204             }
1205              
1206             sub has_errors {
1207             my $self = shift; $self->trace_in;
1208             return scalar(@{$self->get_messages});
1209             }
1210              
1211             sub has_direct_errors {
1212             my $self = shift; $self->trace_in;
1213            
1214             return if not $self->has_errors;
1215             return $self->get_normalized_object_name ( name => $self->get_messages->[0]->get_name ) eq
1216             $self->get_norm_name;
1217             }
1218              
1219             sub get_direct_errors {
1220             my $self = shift; $self->trace_in;
1221            
1222             my @direct_errors;
1223             my $name = $self->get_program_name;
1224              
1225             foreach my $err ( @{$self->get_messages} ) {
1226             push @direct_errors, $err
1227             if $err->get_name eq $name;
1228             }
1229            
1230             return \@direct_errors;
1231             }
1232              
1233             sub save_cached_errors {
1234             my $self = shift;
1235            
1236             my $direct_errors = $self->get_direct_errors;
1237             my $fh = FileHandle->new;
1238             open ($fh, "> ".$self->get_err_filename)
1239             or confess "can't write ".$self->get_err_filename;
1240             print $fh Dumper( $direct_errors );
1241             close $fh;
1242              
1243             1;
1244             }
1245              
1246             sub load_cached_errors {
1247             my $self = shift;
1248            
1249             my $err_filename = $self->get_err_filename;
1250             my $VAR1;
1251             do $err_filename;
1252              
1253             $self->set_messages ( do $err_filename );
1254              
1255             1;
1256             }
1257              
1258             #---------------------------------------------------------------------
1259             # Include related methods
1260             #---------------------------------------------------------------------
1261              
1262             sub store_include_interface_file {
1263             my $self = shift; $self->trace_in;
1264              
1265             my $iface_filename = $self->get_iface_filename;
1266             my $interface = $self->get_state->{incinterface};
1267            
1268             $self->make_path ($iface_filename);
1269              
1270             open (OUT, "> $iface_filename")
1271             or die "INCLUDE\tcan't write $iface_filename";
1272            
1273             if ( $interface ) {
1274             print OUT join ("\t", %{$interface->{input}}), "\n";
1275             print OUT join ("\t", %{$interface->{optional}}), "\n";
1276             print OUT join ("\t", %{$interface->{noquote}}), "\n";
1277             print OUT join ("\t", %{$interface->{output}}), "\n";
1278             } else {
1279             print OUT "\n\n\n\n";
1280             }
1281              
1282             close OUT;
1283              
1284             return $interface;
1285             }
1286              
1287             sub read_include_interface_file {
1288             my $self = shift; $self->trace_in;
1289              
1290             my $iface_filename = $self->get_iface_filename;
1291              
1292             my $line;
1293             open (IN, $iface_filename)
1294             or confess "INCLUDE\tCan't load interface file ".
1295             "'$iface_filename'";
1296            
1297             # input parameters
1298             chomp ($line = );
1299             my %input = split("\t", $line);
1300            
1301             # optional parameters
1302             chomp ($line = );
1303             my %optional = split("\t", $line);
1304            
1305             # noquote parameters
1306             chomp ($line = );
1307             my %noquote = split("\t", $line);
1308            
1309             # output parameters
1310             chomp ($line = );
1311             my %output = split("\t", $line);
1312            
1313             # close file
1314             close IN;
1315            
1316             # store and return
1317             return {
1318             input => \%input,
1319             optional => \%optional,
1320             output => \%output,
1321             noquote => \%noquote,
1322             };
1323             }
1324              
1325             sub check_interfaces_are_compatible {
1326             my $self = shift; $self->trace_in;
1327             my %par = @_;
1328             my ($oi, $ni) = @par{'old_interface', 'new_interface'};
1329              
1330             my ($par, $incompatible);
1331            
1332             $self->set_interface_changed (1);
1333            
1334             # 1. incompatible, if we have a new INPUT parameter,
1335             # or type has changed
1336             foreach $par ( keys %{$ni->{input}} ) {
1337             return if $oi->{input}->{$par} ne $ni->{input}->{$par};
1338             }
1339            
1340             # 2. an INPUT parameter was removed, but is no
1341             # optional parameter (of same type)
1342             foreach $par ( keys %{$oi->{input}} ) {
1343             return if $oi->{input}->{$par} ne $ni->{input}->{$par} and
1344             $oi->{input}->{$par} ne $ni->{optional}->{$par};
1345             }
1346            
1347             # 3. removal of an OPTIONAL parameter (or type switch)?
1348             foreach $par ( keys %{$oi->{optional}} ) {
1349             return if $oi->{optional}->{$par} ne $ni->{optional}->{$par};
1350             }
1351            
1352             # 4. removal of an OUTPUT parameter?
1353             foreach $par ( keys %{$oi->{output}} ) {
1354             return if $oi->{output}->{$par} ne $ni->{output}->{$par};
1355             }
1356              
1357             # 5. NOQUOTE differ?
1358             foreach $par ( keys %{$oi->{noquote}}, keys %{$ni->{noquote}} ) {
1359             return if $oi->{noquote}->{$par} ne $ni->{noquote}->{$par};
1360             }
1361            
1362             $self->set_interface_changed (0);
1363              
1364             return 1;
1365             }
1366              
1367             sub interface_is_correct {
1368             my $self = shift; $self->trace_in;
1369             my %par = @_;
1370             my ($include_parser, $input, $output) =
1371             @par{'include_parser','input','output'};
1372              
1373             my $error;
1374              
1375             # load interface information
1376             my $interface = $include_parser->read_include_interface_file;
1377              
1378             # any unknown input parameters?
1379             my @unknown_input;
1380             foreach my $par ( keys %{$input} ) {
1381             if ( not defined $interface->{input}->{$par} and
1382             not defined $interface->{optional}->{$par} ) {
1383             $self->add_tag_message (
1384             message => "Unknown input paramter: $par"
1385             );
1386             $error = 1;
1387             }
1388             }
1389            
1390             # do we miss some parameters?
1391             foreach my $par ( keys %{$interface->{input}} ) {
1392             if ( not defined $input->{$par} ) {
1393             $self->add_tag_message (
1394             message => "Missing input paramter: $interface->{input}->{$par}"
1395             );
1396             $error = 1;
1397             }
1398             }
1399              
1400             # any unknown output parameters?
1401             foreach my $par ( keys %{$output} ) {
1402             if ( not defined $interface->{output}->{$par} ) {
1403             $self->add_tag_message (
1404             message => "Unknown output paramter: $par"
1405             );
1406             $error = 1;
1407             }
1408             }
1409              
1410             return not $error;
1411             }
1412              
1413             #---------------------------------------------------------------------
1414             # Error checking related methods
1415             #---------------------------------------------------------------------
1416              
1417             my ( $perl_check_instance_cnt,
1418             $perl_check_instance );
1419              
1420             sub perl_error_check {
1421             my $self = shift; $self->trace_in;
1422             my %par = @_;
1423             my ($perl_code_sref) = @par{'perl_code_sref'};
1424              
1425             return if not $perl_code_sref and $self->has_errors;
1426              
1427             $perl_code_sref ||= $self->get_perl_code_sref;
1428              
1429             my $src_filename = $self->get_in_filename;
1430             my $sub_filename = $self->get_prod_filename;
1431              
1432             my $pc;
1433             if ( $self->get_object_type eq 'cipp-html' ) {
1434             # code will be executed. we create a single
1435             # instance for this case
1436             $pc = CIPP::Compile::PerlCheck->new;
1437            
1438             } else {
1439             # syntax check only: an instance may check
1440             # several programs
1441             if ( not $perl_check_instance or
1442             $perl_check_instance_cnt == 20 ) {
1443             $perl_check_instance = CIPP::Compile::PerlCheck->new;
1444             $perl_check_instance_cnt = 0;
1445             }
1446             $pc = $perl_check_instance;
1447             ++$perl_check_instance_cnt;
1448             }
1449            
1450             my $dir = dirname $sub_filename;
1451              
1452             $pc->set_directory ( $dir );
1453             $pc->set_lib_path ( $self->get_lib_path );
1454             $pc->set_name ( $self->get_program_name );
1455             $pc->set_config_dir ( $self->get_config_dir );
1456              
1457             my $output_file;
1458             if ( $self->get_object_type eq 'cipp-html' ) {
1459             $output_file = $self->get_prod_filename,
1460             }
1461            
1462             my $msg_lref = $pc->check (
1463             code_sref => $perl_code_sref,
1464             parse_result => 1,
1465             output_file => $output_file
1466             );
1467              
1468             foreach my $msg ( @{$msg_lref} ) {
1469             $self->add_message_object (
1470             object => $msg
1471             );
1472             }
1473              
1474             1;
1475             }
1476              
1477             sub format_debugging_source {
1478             my $self = shift; $self->trace_in;
1479             my %par = @_;
1480             my ($brief) = @par{'brief'};
1481              
1482             my $msg_lref = $self->get_messages;
1483             return if @{$msg_lref} == 0;
1484              
1485             my $line;
1486             my $html = ""; # Scalar für den HTML-Code
1487             my $font = '';
1488              
1489             my $what = $msg_lref->[0]->get_type eq 'perl_err' ?
1490             "Perl Syntax" : "CIPP Syntax";
1491              
1492             $html .= qq{$font}.
1493             qq{There are $what errors:}.
1494             qq{

\n};

1495              
1496             # First generate a list of error messages.
1497             my $nr = 0;
1498             $html .= "
\n"; 
1499             my %anchor;
1500             foreach my $err (@{$msg_lref}) {
1501             my $name = $err->get_name;
1502             my $line = $err->get_line_nr;
1503             my $tag = $err->get_tag;
1504             my $msg = $err->get_message;
1505            
1506             $msg =~ s/
1507            
1508             if ( not defined $anchor{"${name}_$line"} ) {
1509             $html .= qq{};
1510             $anchor{"${name}_$line"} = 1;
1511             }
1512              
1513             $html .= qq{};
1514             if ( $tag eq 'TEXT' ) {
1515             $html .= "$name (line $line): HTML Context: $msg";
1516             } else {
1517             $html .= "$name (line $line): : $msg";
1518             }
1519             $html .= "\n";
1520             ++$nr;
1521             }
1522             $html .= "\n";
1523              
1524             return \$html if $brief;
1525              
1526             # Nun alle betroffenen Objekte extrahieren und dabei die Fehlermeldungen
1527             # in ein Hash umschichten
1528             my %object;
1529             my %error;
1530             my @object;
1531            
1532             my $i_have_an_error = undef;
1533             foreach my $err (@{$msg_lref}) {
1534             my $name = $err->get_name;
1535             my $line = $err->get_line_nr;
1536             my $tag = $err->get_tag;
1537             my $msg = $err->get_message;
1538              
1539             if ( not defined $object{$name} ) {
1540             $object{$name} = $self->get_object_filename ( name => $name );
1541             if ( $name ne $self->{object_name} ) {
1542             push @object, $name;
1543             } else {
1544             $i_have_an_error = 1;
1545             }
1546             }
1547             push @{$error{$name}->{$line}}, $msg;
1548             }
1549              
1550             @object = sort @object;
1551              
1552             unshift @object, $self->{object_name} if $i_have_an_error;
1553            
1554             # Alle betroffenen Objekte einlesen
1555             my %object_source;
1556             my ($object, $filename);
1557             while ( ($object, $filename) = each %object ) {
1558             my $fh = new FileHandle ();
1559             if ( open ($fh, $filename) ) {
1560             local ($_);
1561             while (<$fh>) {
1562             s/&/&/g;
1563             s/
1564             s/>/>/g;
1565             push @{$object_source{$object}}, $_;
1566             }
1567             close $fh;
1568             }
1569             }
1570            
1571             # nun haben wir ein Hash von Listen mit den Quelltextzeilen
1572             $nr = 0;
1573             foreach $object (@object) {
1574             $html .= qq{};
1575             $html .= "


$font

$object

\n"; 
1576             my ($i, $line);
1577             $i = 0;
1578             foreach $line (@{$object_source{$object}}) {
1579             ++$i;
1580             my $color = "red";
1581             if ( defined $error{$object}->{$i} ) {
1582             my $html_msg = "";
1583             my $msg;
1584             foreach $msg (@{$error{$object}->{$i}}) {
1585             if ( $msg eq '__INCLUDE_CALL__' ) {
1586             $color = "green";
1587             next;
1588             }
1589             $html_msg .= "\t$msg\n";
1590             }
1591             $html_msg .= "\n";
1592             $html .= "\n";
1593             if ( $color eq 'red' ) {
1594             # error highlighting
1595             $html .= qq{};
1596             $html .= qq{}.
1597             qq{$i\t}.
1598             qq{$line\n};
1599             } else {
1600             # include reference highlighting
1601             $html .= "$i\t$line\n";
1602             }
1603             $html .= $html_msg;
1604             } else {
1605             $html .= "$i\t$line";
1606             }
1607             }
1608             $html .= "\n";
1609             }
1610            
1611             $html .= "
\n";
1612            
1613             return \$html;
1614             }
1615              
1616             #---------------------------------------------------------------------
1617             # Elementary Private methods for Parsing
1618             #---------------------------------------------------------------------
1619              
1620             sub parse_tag {
1621             my $self = shift; $self->trace_in;
1622             my ($text) = @_;
1623              
1624             # debugging output
1625             my $dbg = $text;
1626             $dbg =~ s/\n/\\n/g;
1627             $self->debug("GOT TAG: '$dbg'\n");
1628            
1629             # extract tag name, tag close marker and tag content
1630             my $magic_start = $self->get_magic_start;
1631             my $magic_end = $self->get_magic_end;
1632              
1633             my ($closed, $tag);
1634             $text =~ s!^\s*(/?)([^\s>]*)\s*!!;
1635             ($closed, $tag) = ($1, lc($2));
1636             $closed = 1 if $closed;
1637              
1638             # check whether we are inside a comment block
1639             return 1 if $self->context eq 'comment' and $tag ne '#'
1640             and $tag ne '!#';
1641              
1642             # parse tag content for options
1643             $text =~ s/\s+$//;
1644             my $closed_immediate = 1 if $text =~ s!/$!!;
1645            
1646             if ( $closed and $closed_immediate ) {
1647             $self->add_message (
1648             message => "Tag closed twice.",
1649             tag => $tag,
1650             line_nr => $self->get_current_tag_line_nr,
1651             );
1652             return;
1653             }
1654            
1655             my ($options, $options_case, $options_order) =
1656             $self->parse_tag_options ($text);
1657              
1658             if ( $options < 0 ) {
1659             if ( $options == -2 ) {
1660             $self->add_message (
1661             message => "Multiple options.",
1662             tag => $tag,
1663             line_nr => $self->get_current_tag_line_nr,
1664             );
1665             } else {
1666             $self->add_message (
1667             message => "Error parsing options.",
1668             tag => $tag,
1669             line_nr => $self->get_current_tag_line_nr,
1670             );
1671             }
1672             return;
1673             }
1674              
1675             $self->debug("TAG=$tag, CLOSED=$closed");
1676              
1677             # check nesting
1678             if ( $closed ) {
1679             my $opened_tag = $self->pop_tag;
1680             if ( not $opened_tag ) {
1681             $tag =~ tr/a-z/A-Z/;
1682             $self->add_message (
1683             line_nr => $self->get_current_tag_line_nr,
1684             message => "Found ${magic_start}/$tag> ".
1685             "without opening it.",
1686             );
1687             return;
1688             }
1689              
1690             if ( $opened_tag->{tag} ne $tag ) {
1691             $tag =~ tr/a-z/A-Z/;
1692             $opened_tag->{tag} =~ tr/a-z/A-Z/;
1693             $self->add_message (
1694             line_nr => $self->get_current_tag_line_nr,
1695             message => "Found ${magic_start}/$tag> ".
1696             "instead of ${magic_start}/".
1697             "$opened_tag->{tag}> opened ".
1698             "at line $opened_tag->{line_nr}.",
1699             );
1700             return;
1701             }
1702            
1703             # give the tag process method state data
1704             # which was generated when processing the
1705             # opening tag
1706             $closed = $opened_tag->{data};
1707             }
1708              
1709             # save information of the current tag
1710             $self->set_current_tag ($tag);
1711             $self->set_current_tag_closed ($closed);
1712             $self->set_current_tag_options ($options);
1713             $self->set_current_tag_options_case ($options_case);
1714             $self->set_current_tag_options_order ($options_order);
1715              
1716             # execute tag handler
1717             my $handler = $self->get_command2method->{$tag};
1718             $handler ||= "cmd_$tag";
1719            
1720             if ( $self->can ($handler) ) {
1721             $self->generate_debugging_code;
1722             my $rc = $self->$handler();
1723             if ( $rc != $self->RC_SINGLE_TAG and not $closed ) {
1724             $self->push_tag (
1725             tag => $tag,
1726             line_nr => $self->get_current_tag_line_nr,
1727             data => $rc,
1728             );
1729             }
1730             if ( $closed_immediate ) {
1731             $self->set_current_tag_closed ($self->pop_tag->{data});
1732             $self->set_current_tag_options ({});
1733             $self->set_current_tag_options_case ({});
1734             $self->set_current_tag_options_order ({});
1735             $self->$handler();
1736             }
1737            
1738             } else {
1739             my $big_tag = uc($tag);
1740             $self->add_message (
1741             tag => $tag,
1742             line_nr => $self->get_current_tag_line_nr,
1743             message => "Unknown CIPP tag: ."
1744             );
1745             }
1746              
1747             1;
1748             }
1749              
1750             sub parse_tag_options {
1751             my $self = shift; $self->trace_in;
1752             my ($options) = @_;
1753              
1754             my %options;
1755             my %options_case;
1756             my @options_order;
1757             return ({},{}) if $options eq '';
1758              
1759             my ($name_var, $name_flag, $value);
1760              
1761             $options =~ s/\\\"/\001/g; # maskiere escapte Quotes
1762             $options =~ s/\\\\/\\/g; # demaskiere escapte \
1763             $options =~ s/^\s+//;
1764             $options .= " ";
1765              
1766             while ( $options ne '' ) {
1767             # Suche 1. Parametername mit Zuweisung
1768             ($name_var) = $options =~ /^([^\s=]+\s*=\s*)/;
1769             # Suche 1. Parametername ohne Zuweisung
1770             ($name_flag) = $options =~ /^([^\s=]+)[^=]/;
1771              
1772             return -1 if not defined $name_var and
1773             not defined $name_flag;
1774              
1775             # Wenn ein " oder < im Parameternamen vorkommt, muß
1776             # ein Syntaxfehler vorliegen
1777              
1778             return -1 if defined $name_var and $name_var =~ /["<]/;
1779             return -1 if defined $name_flag and $name_flag =~ /["<]/;
1780              
1781             # Was wurde gefunden, Zuweisung oder Flag?
1782             if ( defined $name_var ) {
1783             # wir haben eine Zuweisung
1784             my $clear = quotemeta $name_var;
1785             $options =~ s/^$clear//;
1786             $name_var =~ s/\s*=\s*//;
1787             if ( $options =~ /^\"/ ) {
1788             # Parameter ist gequotet!
1789             ($value) = $options =~ /^\"([^\"]*)/;
1790             $options =~ s/\"([^\"]*)\"\s*//;
1791             } else {
1792             # Parameter ist nicht gequotet!
1793             ($value) = $options =~ /^([^\s]*)/;
1794             return -1 if $value eq '';
1795             $options =~ s/^([^\s]*)\s*//;
1796             }
1797             $value =~ tr/\001/\"/;
1798             my $name_case = $name_var;
1799             $name_var = lc($name_var);
1800             if (defined $options{$name_var}) {
1801             return -2;
1802             } else {
1803             $options{$name_var} = $value;
1804             $options_case{$name_var} = $name_case;
1805             push @options_order, $name_case;
1806             }
1807             } else {
1808             # wir haben ein Flag
1809             my $clear = quotemeta $name_flag;
1810             $options =~ s/^$clear\s*//;
1811             my $name_case = $name_flag;
1812             $name_flag = lc($name_flag);
1813             $options{$name_flag} = 1;
1814             $options_case{$name_flag} = $name_case;
1815             push @options_order, $name_case;
1816             }
1817             }
1818              
1819             return (\%options, \%options_case, \@options_order);
1820             }
1821              
1822             sub push_tag {
1823             my $self = shift; $self->trace_in;
1824             my %par = @_;
1825            
1826             push @{$self->get_tag_stack}, \%par;
1827            
1828             return \%par;
1829             }
1830              
1831             sub pop_tag {
1832             my $self = shift; $self->trace_in;
1833             my ($context) = @_;
1834            
1835             return pop @{$self->get_tag_stack};
1836             }
1837              
1838              
1839             1;