File Coverage

blib/lib/Class/Phrasebook.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Class::Phrasebook;
2              
3 1     1   1056 use strict;
  1         2  
  1         61  
4              
5             our $VERSION = '0.88';
6              
7              
8 1     1   1289 use Term::ANSIColor 1.03 qw(:constants);
  1         12689  
  1         5265  
9 1     1   14 use strict;
  1         10  
  1         47  
10 1     1   2220 use XML::Parser 2.30;
  0            
  0            
11             use Log::NullLogLite 0.2;
12             use bytes;
13             # reset to normal at the end of each line.
14             $Term::ANSIColor::AUTORESET = 1;
15              
16             my $Dictionaries_cache;
17             my $Clean_out_of_scope_dictionaries = 1;
18              
19             #############################################################
20             # new($log, $file_path)
21             #############################################################
22             # the constructor
23             sub new {
24             my $proto = shift; # get the class name
25             my $class = ref($proto) || $proto;
26             my $self = {};
27              
28             $self->{LOG} = shift || new Log::NullLogLite;
29             $self->{FILE_PATH} = shift || "";
30            
31             # we bless already so we can use the method get_xml_path
32             bless ($self, $class);
33              
34             # check that we can find this file
35             $self->{FILE_PATH} = $self->get_xml_path($self->{FILE_PATH});
36             unless ($self->{FILE_PATH}) {
37             return undef;
38             }
39              
40             # get the file name for using as part of the key of the dictionary
41             $self->{FILE_PATH} =~ /[^\/]+$/;
42             $self->{FILE_NAME} = $&;
43             # dictionary key holds a representative key for the dictionary that is
44             # loaded.
45             $self->{DICTIONARY_KEY} = "";
46             $self->{PHRASES} = {};
47             # defaults
48             if (defined($ENV{PHRASEBOOK_AS_IS_BETWEEN_TAGS})) {
49             $self->{AS_IS_BETWEEN_TAGS} = $ENV{PHRASEBOOK_AS_IS_BETWEEN_TAGS};
50             }
51             else {
52             $self->{AS_IS_BETWEEN_TAGS} = 1; # set by default
53             }
54             $self->{REMOVE_NEW_LINES} = 0;
55             return $self;
56             } # of new
57              
58             ##############################
59             # Dictionaries_names_in_cache
60             ##############################
61             sub Dictionaries_names_in_cache {
62             return keys ( % { $Dictionaries_cache } );
63             } # of Dictionaries_names_in_cache
64              
65             ###############
66             # DESTROY
67             ###############
68             sub DESTROY {
69             my $self = shift;
70             if ($self->{DICTIONARY_KEY}) {
71             $Dictionaries_cache->{$self->{DICTIONARY_KEY}}{COUNTER}--;
72             # clean that dictionary from the cache if needed.
73             if ($Dictionaries_cache->{$self->{DICTIONARY_KEY}}{COUNTER} == 0) {
74             if ($Clean_out_of_scope_dictionaries) {
75             delete($Dictionaries_cache->{$self->{DICTIONARY_KEY}});
76             }
77             }
78             }
79              
80             } # of DESTROY
81              
82             #################
83             # file_path
84             #################
85             sub file_path {
86             my $self = shift;
87             if (@_) {
88             $self->{FILE_PATH} = shift;
89              
90             # check that we can find this file
91             $self->{FILE_PATH} = $self->get_xml_path($self->{FILE_PATH});
92             unless ($self->{FILE_PATH}) {
93             return undef;
94             }
95             }
96             return $self->{FILE_PATH};
97             } # of file_path
98              
99             #################
100             # log
101             #################
102             sub log {
103             my $self = shift;
104             if (@_) { $self->{LOG} = shift }
105             return $self->{LOG};
106             } # of log
107              
108             ###################################
109             # clean_out_of_scope_dictionaries
110             ###################################
111             sub clean_out_of_scope_dictionaries {
112             my $proto = shift; # get the class name
113             $Clean_out_of_scope_dictionaries = shift;
114             return $Clean_out_of_scope_dictionaries;
115             } # of clean_out_of_scope_dictionaries
116              
117             #################
118             # dictionary_name
119             #################
120             sub dictionary_name {
121             my $self = shift;
122             if (@_) { $self->{DICTIONARY_NAME} = shift }
123             return $self->{DICTIONARY_NAME};
124             } # of dictionary_name
125              
126             ####################
127             # remove_new_lines
128             ####################
129             sub remove_new_lines {
130             my $self = shift;
131             if (@_) { $self->{REMOVE_NEW_LINES} = shift }
132             return $self->{REMOVE_NEW_LINES};
133             } # of remove_new_lines
134              
135             #####################
136             # as_is_between_tags
137             #####################
138             sub as_is_between_tags {
139             my $self = shift;
140             if (@_) { $self->{AS_IS_BETWEEN_TAGS} = shift }
141             return $self->{AS_IS_BETWEEN_TAGS};
142             } # of as_is_between_tags
143              
144             ####################################
145             # load($dictionary_name)
146             ####################################
147             sub load {
148             my $self = shift;
149             my $requested_dictionary_name = shift || "";
150             # get a unique key that represents this dictionary of that file.
151             my $dictionary_key =
152             $self->{FILE_NAME}."/".$requested_dictionary_name;
153             # if the object already loaded a dictionary, and now it loads other
154             # dictionary, we should reduce the counter of the dictionary that was
155             # loaded till now.
156             if ($self->{DICTIONARY_KEY} &&
157             $self->{DICTIONARY_KEY} ne $dictionary_key) {
158             $Dictionaries_cache->{$self->{DICTIONARY_KEY}}{COUNTER}--;
159              
160             # clean that dictionary from the cache if needed.
161             if ($Dictionaries_cache->{$self->{DICTIONARY_KEY}}{COUNTER} == 0) {
162             if ($Clean_out_of_scope_dictionaries) {
163             delete($Dictionaries_cache->{$self->{DICTIONARY_KEY}});
164             }
165             }
166             }
167             # zero the cache counter for that dictionary if this is the first time
168             # that this dictionary is loaded
169             if (!defined($Dictionaries_cache->{$dictionary_key}) ||
170             !defined($Dictionaries_cache->{$dictionary_key}{COUNTER})) {
171             $Dictionaries_cache->{$dictionary_key}{COUNTER} = 0;
172             }
173             # keep the dictionary key
174             $self->{DICTIONARY_KEY} = $dictionary_key;
175             # and increment the counter of this dictionary
176             $Dictionaries_cache->{$self->{DICTIONARY_KEY}}{COUNTER}++;
177             # the the dictionaries cache keeps the phrases of all the dictionaries
178             if (defined($Dictionaries_cache->{$self->{DICTIONARY_KEY}}) &&
179             defined($Dictionaries_cache->{$self->{DICTIONARY_KEY}}{PHRASES}) &&
180             ref($Dictionaries_cache->{$self->{DICTIONARY_KEY}}{PHRASES})
181             eq "HASH") {
182             $self->{PHRASES} =
183             $Dictionaries_cache->{$self->{DICTIONARY_KEY}}{PHRASES};
184             return 1;
185             }
186            
187             # the load may set the data member DICTIONARY_NAME. On the other hand
188             # if the requested_dictionary_name is not defined, we will try to use
189             # the data member.
190             if ($requested_dictionary_name) {
191             $self->{DICTIONARY_NAME} = $requested_dictionary_name;
192             }
193             else {
194             $requested_dictionary_name = $self->{DICTIONARY_NAME} || "";
195             }
196              
197             my $phrases; # a reference to anonymous hash that will hold all the
198             # phrases
199             my $phrase_name; # the name of the current phrase.
200             my $phrase_value; # the string of the phrase.
201              
202             # the first dictionary is the default one and should be read. this flag
203             # will tell if it was read.
204             my $default_was_read = 0;
205            
206             # this flag will be set to zero after the default dictionary was read. then
207             # it will be set to one when the requested dictionary should be read.
208             my $read_on = 1;
209              
210             # create the XML parser object
211             my $parser = new XML::Parser(ErrorContext => 2);
212             $parser->setHandlers(
213             Start => sub {
214             my $expat = shift;
215             my $element = shift;
216             my %attributes = (@_);
217            
218             # deal with the dictionary element
219             if ($element =~ /dictionary/) {
220             my $dictionary_name = $attributes{name};
221             unless (defined($dictionary_name)) {
222             $self->log()->write("The dictionary element must".
223             " have the name attribute", 4);
224             return 0; # we must have name
225             }
226             # if the default was already read, and the dictionary name
227             # is not the requested one, we should not read on.
228             if ($default_was_read &&
229             $dictionary_name ne $requested_dictionary_name) {
230             $read_on = 0;
231             }
232             # in any other case we should read on
233             else {
234             $read_on = 1;
235             }
236             }
237              
238             # deal with the phrase element
239             if ($element =~ /^phrase$/) {
240             $phrase_name = $attributes{name};
241             unless (defined($phrase_name)) {
242             $self->log()->write("The phrase element must".
243             " have the name attribute", 4);
244             return 0; # we must have name
245             }
246             }
247             if ($self->{AS_IS_BETWEEN_TAGS}) {
248             # we should clean the $phrase_value after the start of the tag
249             # so in the phrase we will have only the text that is between
250             # the phrase tags.
251             $phrase_value = "";
252             }
253             }, # of Start
254            
255             End => sub {
256             my $expat = shift;
257             my $element = shift;
258             if ($element =~ /^dictionary$/i) {
259             $default_was_read = 1;
260             }
261            
262             if ($element =~ /^phrase$/i) {
263             if ($read_on) {
264             $phrases->{$phrase_name} = $phrase_value;
265             $phrase_value = "";
266             }
267             }
268             }, # of End
269            
270             Char => sub {
271             my $expat = shift;
272             my $string = shift;
273             # if $read_on flag is true and the string is not empty we set the
274             # value of the phrase.
275             if ($self->{AS_IS_BETWEEN_TAGS}) {
276             if ($read_on && length($string)) {
277             $phrase_value .= $string;
278             }
279             }
280             else { # this block is here for legacy reasons.
281             if ($read_on && $string =~ /[\S]/) {
282             # if we have already $phrase_value, we should add a
283             # new line to it, before we add the next line.
284             $phrase_value .= "\n" if ($phrase_value);
285             $phrase_value .= $string;
286             }
287             }
288             } # of Char
289             ); # of the parser setHandlers class
290              
291             # open the xml file as a locked file and parse it
292             my $fh = new IO::LockedFile("<".$self->{FILE_PATH});
293             unless ($fh) {
294             $self->log()->write("Could not open ".$self->{FILE_PATH}.
295             " to read.", 4);
296             return 0;
297             }
298             eval { $parser->parse($fh) }; # I use eval because the parse function dies
299             # on parsing error.
300             if ($@) {
301             $self->log()->write("Could not parse the ".$self->{FILE_PATH}.
302             " file: ".$@, 4);
303             return 0; # there was an error in parsing the XML.
304             }
305              
306             $self->{PHRASES} = $phrases;
307             # keep the phrases
308             $Dictionaries_cache->{$self->{DICTIONARY_KEY}}{PHRASES} = $self->{PHRASES};
309              
310             return 1; # success
311             } # of load
312              
313             ###################################################################
314             # $phrase = get($key, { var1 => $value1, var2 => value2 ... })
315             # where $key will be the key to certain phrase, and var1, var2
316             # and so on will be $var1 and $var2 in the definition of that
317             # phrase in the load method above.
318             ###################################################################
319             sub get {
320             my $self = shift;
321             my $key = shift;
322             my $variables = shift;
323            
324             # the DEBUG_PRINTS is controlled by an environment.
325             my $debug_prints = lc($ENV{PHRASEBOOK_DEBUG_PRINTS}) || "";
326            
327             if ($debug_prints) {
328             if ($debug_prints eq "color") {
329             # check that all the variables defined in $variables
330             foreach my $key (keys(%$variables)) {
331             unless (defined($variables->{$key})) {
332             print "[";
333             print GREEN called_by();
334             print "]";
335             print BLUE "[";
336             print RED "$key is not defined";
337             print BLUE "]\n";
338             }
339             }
340             }
341             elsif ($debug_prints eq "html") {
342             # check that all the variables defined in $variables
343             foreach my $key (keys(%$variables)) {
344             unless (defined($variables->{$key})) {
345             print "
["; 
346             print called_by();
347             print "]";
348             print "[";
349             print "$key is not defined";
350             print "]\n";
351             }
352             }
353             }
354             elsif ($debug_prints eq "text") {
355             # check that all the variables defined in $variables
356             foreach my $key (keys(%$variables)) {
357             unless (defined($variables->{$key})) {
358             print "[";
359             print called_by();
360             print "]";
361             print "[";
362             print "$key is not defined";
363             print "]\n";
364             }
365             }
366             }
367             }
368              
369             my $phrase = $self->{PHRASES}{$key};
370             unless (defined($phrase)) {
371             if ($debug_prints) {
372             if ($debug_prints eq "color") {
373             print RED "No phrase for $key\n";
374             }
375             elsif ($debug_prints eq "html") {
376             print "
No phrase for $key". 
377             "\n";
378             }
379             elsif ($debug_prints eq "text") {
380             print "No phrase for $key\n";
381             }
382             }
383             $self->{LOG}->write ("No phrase for ".$key."\n", 3);
384             return undef;
385             }
386              
387             # process the placeholders
388             if ($debug_prints) {
389             $phrase =~
390             s/\$([a-zA-Z0-9_]+)/debug_print_variable($1, $variables)/ge;
391             $phrase =~
392             s/\$\(([a-zA-Z0-9_]+)\)/debug_print_variable($1, $variables)/ge;
393             }
394             $phrase =~ s/\$([a-zA-Z0-9_]+)/$variables->{$1}/g;
395             # also process variables in $(var_name) format.
396             $phrase =~ s/\$\(([a-zA-Z0-9_]+)\)/$variables->{$1}/g;
397              
398             # remove new lines if needed
399             if ($self->{REMOVE_NEW_LINES}) {
400             $phrase =~ s/\n//g;
401             }
402              
403             if ($debug_prints) {
404             if ($debug_prints eq "color") {
405             print "[";
406             print GREEN called_by();
407             print "]";
408             print RED "[";
409             print BLUE $key;
410             print RED "]\n";
411             print $phrase."\n";
412             }
413             elsif ($debug_prints eq "html") {
414             print "
["; 
415             print "".called_by()."";
416             print "]";
417             print "[";
418             print "$key";
419             print "]\n";
420             print $phrase."\n";
421             }
422             elsif ($debug_prints eq "text") {
423             print "[";
424             print called_by();
425             print "]";
426             print "[";
427             print $key;
428             print "]\n";
429             print $phrase."\n";
430             }
431             }
432            
433             unless ($phrase) {
434             if ($debug_prints) {
435             if ($debug_prints eq "color") {
436             print RED "Oops - no phrase for $key !!!\n";
437             }
438             elsif ($debug_prints eq "html") {
439             print "
Oops - no phrase for $key". 
440             "\n";
441             }
442             elsif ($debug_prints eq "text") {
443             print "Oops - no phrase for $key !!!\n";
444             }
445             }
446             }
447             return $phrase;
448             } # of get
449              
450             #######################
451             # called_by
452             #######################
453             sub called_by {
454             my $depth = 2;
455             my $args;
456             my $pack;
457             my $file;
458             my $line;
459             my $subr;
460             my $has_args;
461             my $wantarray;
462             my $evaltext;
463             my $is_require;
464             my $hints;
465             my $bitmask;
466             my @subr;
467             my $str = "";
468             while ($depth < 7) {
469             ($pack, $file, $line, $subr, $has_args, $wantarray,
470             $evaltext, $is_require, $hints, $bitmask) = caller($depth);
471             unless (defined($subr)) {
472             last;
473             }
474             $depth++;
475             $line = "$file:".$line."-->";
476             push(@subr, $line.$subr);
477             }
478             @subr = reverse(@subr);
479             foreach $subr (@subr) {
480             $str .= $subr;
481             $str .= " > ";
482             }
483             $str =~ s/ > $/: /;
484             return $str;
485             } # of called_by
486              
487             #######################################################
488             # is_variables_defined_in_this_line($line, $variables)
489             #######################################################
490             sub is_variables_defined_in_this_line {
491             my $line = shift;
492             my $variables = shift;
493             while ($line =~ /\$([a-zA-Z0-9_]+)/ ) {
494             unless (defined($variables->{$1})) {
495             return 0;
496             }
497             $line = $';
498             }
499             return 1;
500             } # of is_variables_defined_in_this_line
501              
502             ##################
503             # to_string()
504             ##################
505             sub to_string {
506             my $self = shift;
507             my $string = "";
508             foreach my $key (keys(% { $self->{PHRASES} } )) {
509             my $phrase = $self->{PHRASES}{$key};
510             $string .= $key." => \n".$phrase."\n\n";
511             }
512             return $string;
513             } # of to_string
514              
515             #######################
516             # get_xml_path()
517             #######################
518             sub get_xml_path {
519             my $self = shift;
520             my $file = $self->{FILE_PATH};
521              
522             # first deal with absolute path
523             if (is_absolute_path($file)) {
524             if (-e $file) {
525             return $file;
526             }
527             else {
528             $self->{LOG}->write("Cannot find the XML file ".
529             $self->{FILE_PATH}, 4);
530             return undef;
531             }
532             }
533             else {
534             my @dirs = (".", "./lib", "../lib", @INC);
535            
536             foreach my $dir (@dirs) {
537             my $path = $dir."/".$file;
538             if (-e $path) {
539             return $path;
540             }
541             }
542            
543             # we could not find that file, announce it.
544             $self->{LOG}->write("Cannot find the XML file ".
545             $file." in tghe directories: (".
546             join(", ", @INC).")", 4);
547            
548             return undef;
549             }
550             } # of get_xml_path
551              
552             ######################
553             # is_absolute_path
554             ######################
555             sub is_absolute_path {
556             my $path = shift;
557              
558             unless (defined($path)) {
559             return 0;
560             }
561             # the different Operating Systems
562             my %operating_systems = ( "mswin32" => '^(?:[a-zA-Z]:)?[\\\/]+',
563             "cygwin" => '^([A-Za-z]:)|^(\/)',
564             "linux" => '^\/');
565             my $os = lc($^O);
566             my $reg_expression = $operating_systems{$os} ||
567             $operating_systems{'linux'};
568             return $path =~ /$reg_expression/;
569             } # is_absolute_path
570              
571             #########################
572             # debug_print_variable
573             #########################
574             sub debug_print_variable {
575             my $key = shift;
576             my $variables = shift;
577             my $value = $variables->{$key};
578             my $debug_prints = lc($ENV{PHRASEBOOK_DEBUG_PRINTS}) || "";
579             if ($debug_prints eq "color") {
580             print MAGENTA "$key = ";
581             if (defined($value)) {
582             print MAGENTA "$value\n";
583             }
584             else {
585             print RED "undef\n";
586             }
587             }
588             elsif ($debug_prints eq "html") {
589             print "
 $key = "; 
590             if (defined($value)) {
591             print "$value\n";
592             }
593             else {
594             print "undef\n";
595             }
596             }
597             elsif ($debug_prints eq "text") {
598             print "$key = ";
599             if (defined($value)) {
600             print "$value\n";
601             }
602             else {
603             print "undef\n";
604             }
605             }
606             return "\$".$key;
607             } # of debug_print_varibale
608              
609             1; # make perl happy
610              
611             __END__