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 !HTTPHEADER>) | ||||||
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 !HTTPHEADER> 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 !HTTPHEADER> 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/</g; | ||||||
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): $tag>: $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/</g; | ||||||
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: $big_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; |