File Coverage

blib/lib/Mozilla/Mork.pm
Criterion Covered Total %
statement 3 118 2.5
branch 0 62 0.0
condition 0 7 0.0
subroutine 1 9 11.1
pod 0 8 0.0
total 4 204 1.9


line stmt bran cond sub pod time code
1             package Mozilla::Mork;
2              
3             #use 5.008004;
4             #use strict;
5 1     1   21376 use warnings;
  1         2  
  1         2297  
6              
7             require Exporter;
8              
9             our @ISA = qw(Exporter);
10              
11             # Items to export into callers namespace by default. Note: do not export
12             # names by default without a very good reason. Use EXPORT_OK instead.
13             # Do not simply export all your public functions/methods/constants.
14              
15             # This allows declaration use Mozilla::Mork ':all';
16             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
17             # will save memory.
18             our %EXPORT_TAGS = ( 'all' => [ qw(
19            
20             ) ] );
21              
22             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
23              
24             our @EXPORT = qw(
25            
26             );
27              
28             our $VERSION = '0.01';
29              
30              
31             # Preloaded methods go here.
32             #TODO make private classes of mork munging routines
33              
34             #package mork;
35              
36             ##declare variables
37              
38             my ($verbose, $reference, $file);
39              
40             my (%key_table, %val_table, %row_hash);
41              
42             my ($total, $skipped) = (0, 0);
43              
44             ##initialise variables
45              
46             #set to 0 if you dont want status reports
47              
48             $verbose++;
49              
50              
51             sub new {
52 0     0 0   my $class = shift; # works on @_ by default
53 0           my $file = shift; # If an file has been given to start with
54 0           my $MorkFileInfo = {}; #create a blank hash
55              
56 0           bless $MorkFileInfo, $class;
57              
58             #test that we got the file to parse
59 0 0         unless ($file) { return 0; }
  0            
60             #set the file name in the hash
61 0           $MorkFileInfo->{'file'} = $file;
62             #get a reference to an array of hash's
63              
64 0           $MorkFileInfo->{'results'} = mork_parse_file($file);
65              
66              
67              
68 0           return $MorkFileInfo;
69             }
70              
71             ##################
72             # ReturnReferenceStructure
73             # returns the reference to the array containing the hash's of the data
74             ##################
75             sub ReturnReferenceStructure {
76             #get the ojbect refernce to the instance thats calling us
77 0     0 0   my ($obj) = shift;
78             #return the details as requested above
79 0           return $obj->{'results'};
80             }
81              
82             ################################
83             #address book specific test
84             # probably a better way of doing this is writing a package that inherits
85             # the Mork class and then does this, but for now..
86             #TODO AddressBookTestPrint probably doen't work - test and fix
87             #TODO implement AddressBookTestPrint as a inherited module from Morkto Mork::AddressBook
88             ################################
89             sub AddressBookTestPrint {
90 0     0 0   my ($obj) = shift;
91              
92             #get the first hash of results from the parse
93 0   0       my %array = %{ $obj->{'results'}->[0] }
94             || die "constructor not initialised in Mork.pm. Did you call mork->new()?\n";
95             #construct an array of just the keys of the hash
96              
97 0           my @field_names = sort(keys(%array));
98              
99             #print each of the field headers
100 0           map { print "Field Names: $_\n"; } @field_names;
  0            
101              
102             #test print a couple of values
103 0           print "Record Number 0's First Name is: $array{\"FirstName\"}\n";
104 0           print "Record Number 0's Email is: $array{\"PrimaryEmail\"}\n";
105             }
106              
107             ##########################
108             # dumps the record headers
109             # returns an array of the record headers
110             # assumes that the first record contains all the headers
111             # so far this assumption has proved true
112             ##########################
113             sub ListHeaders
114             {
115 0     0 0   my ($obj) = shift;
116             #get the first hash of results from the parse
117             #having problems with dereferncing, so..
118 0   0       my $results = $obj->{'results'}
119             || die "constructor not initialised in Mork.pm. Did you call mork->new()?\n";
120              
121 0           my @field_names = sort(keys( %{$results->[0]} ));
  0            
122 0           return @field_names;
123             }
124              
125             ##########################
126             # Returns a reference to an array of hashes, the contents of the mork file.
127              
128             # expects filename to process ($file)
129              
130             ##########################
131             sub mork_parse_file
132              
133             {
134              
135             #my ($obj) = shift; #dont need to do this for internal (private class methods)
136             #get the filename
137              
138 0     0 0   my ($file) = shift;
139              
140             #stream the file (gulp all in one go, not iterate over each line)
141              
142 0           local $/ = undef;
143              
144 0           local *IN;
145              
146              
147              
148             ##########################################################################
149              
150             # Define the messy regexen up here
151              
152             ##########################################################################
153              
154              
155              
156 0           my $top_level_comment = qr@//.*\n@;
157              
158              
159              
160 0           my $key_table_re = qr/ < \s* < # "< <"
161              
162             \( a=c \) > # "(a=c)>"
163              
164             (?> ([^>]*) ) > \s* # Grab anything that's not ">"
165              
166             /sx;
167              
168              
169              
170 0           my $value_table_re = qr/ < ( .*?\) )> \s* /sx;
171              
172              
173              
174 0           my $table_re = qr/ \{ -? # "{" or "{-"
175              
176             [\da-f]+ : # hex, ":"
177              
178             (?> .*?\{ ) # Eat up to a {...
179              
180             ((?> .*?\} ) # and then the closing }...
181              
182             (?> .*?\} )) # Finally, grab the table section
183              
184             \s* /six;
185              
186              
187              
188 0           my $row_re = qr/ ( (?> \[ [^]]* \] # "["..."]"
189              
190             \s*)+ ) # Perhaps repeated many times
191              
192             /sx;
193              
194              
195              
196 0           my $section_begin_re = qr/ \@\$\$\{ # "@$${"
197              
198             ([\dA-F]+) # hex
199              
200             \{\@ \s* # "{@"
201              
202             /six;
203              
204              
205              
206 0           my $section_end_re = undef;
207              
208 0           my $section = "top level";
209              
210              
211              
212             ##########################################################################
213              
214             # Read in the file.
215              
216             ##########################################################################
217              
218             #open (IN, "<$file") || error ("$file: $!") || die "Cannot open $file: $!\n";
219              
220 0 0         open (IN, "<$file") || die "Cannot open $file: $!\n";
221 0 0         print STDERR "$0: reading $file...\n" if ($verbose);
222              
223              
224              
225 0           my $body = ;
226              
227 0           close IN;
228              
229              
230              
231 0           $body =~ s/\r\n/\n/gs; # Windows Mozilla uses \r\n
232              
233 0           $body =~ s/\r/\n/gs; # Presumably Mac Mozilla is similarly dumb
234              
235              
236              
237 0           $body =~ s/\\\\/\$5C/gs; # Sometimes backslash is quoted with a
238              
239             # backslash; convert to hex.
240              
241 0           $body =~ s/\\\)/\$29/gs; # close-paren is quoted with a backslash;
242              
243             # convert to hex.
244              
245 0           $body =~ s/\\\n//gs; # backslash at end of line is continuation.
246              
247              
248              
249             ##########################################################################
250              
251             # Figure out what we're looking at, and parse it.
252              
253             ##########################################################################
254              
255              
256              
257 0 0         print STDERR "$0: $file: parsing...\n" if ($verbose);
258              
259              
260              
261 0           pos($body) = 0;
262              
263 0           my $length = length($body);
264              
265              
266              
267 0           while( pos($body) < $length )
268              
269             {
270              
271              
272              
273             # Key table
274              
275 0 0 0       if ( $body =~ m/\G$key_table_re/gc )
    0          
    0          
    0          
    0          
    0          
    0          
276              
277             {
278              
279 0           mork_parse_key_table($file, $section, $1);
280              
281              
282              
283             # Values
284              
285             } elsif ( $body =~ m/\G$value_table_re/gco )
286              
287             {
288              
289 0           mork_parse_value_table($file, $section, $1);
290              
291              
292              
293             # Table
294              
295             } elsif ( $body =~ m/\G$table_re/gco )
296              
297             {
298              
299 0           mork_parse_table($file, $section, $age, $since, $1);
300              
301              
302              
303             # Rows (-> table)
304              
305             } elsif ( $body =~ m/\G$row_re/gco )
306              
307             {
308              
309 0           mork_parse_table($file, $section, $age, $since, $1);
310              
311              
312              
313             # Section begin
314              
315             } elsif ( $body =~ m/\G$section_begin_re/gco )
316              
317             {
318              
319 0           $section = $1;
320              
321 0           $section_end_re = qr/\@\$\$\}$section\}\@\s*/s;
322              
323              
324              
325             # Section end
326              
327             } elsif ( $section_end_re && $body =~ m/\G$section_end_re/gc )
328              
329             {
330              
331 0           $section_end_re = undef;
332              
333 0           $section = "top level";
334              
335              
336              
337             # Comment
338              
339             } elsif ( $body =~ m/\G$top_level_comment/gco )
340              
341             {
342              
343             #no-op
344              
345             }
346              
347             else
348              
349             {
350              
351             #$body =~ m/\G (.{0,300}) /gcsx; print "<$1>\n";
352              
353 0           print("$file: $section: Cannot parse");
354              
355             }
356              
357             }#end of while loop
358              
359              
360              
361 0 0         if($section_end_re)
362              
363             {
364              
365 0           print("$file: Unterminated section $section");
366              
367             }
368              
369              
370              
371              
372              
373 0 0         print STDERR "$0: $file: sorting...\n" if ($verbose);
374              
375              
376              
377             # my @entries = sort { $b->{LastVisitDate} <=>
378              
379             # $a->{LastVisitDate} } values(%row_hash);
380              
381              
382              
383 0           my @entries = values(%row_hash);
384              
385            
386              
387 0 0         print STDERR
388              
389             "$0: $file: done! ($total total, $skipped skipped)\n"
390              
391             if ($verbose);
392              
393              
394              
395             #reset all variables in the left parenthesis
396              
397 0           (%key_table, %val_table, %row_hash, $total, $skipped) = ();
398              
399              
400              
401             #send a reference to the @entries array back to the calling routine
402              
403 0           return \@entries;
404              
405             } # end of mork_parse_file
406              
407              
408              
409             ##########################################################################
410              
411             # parse a row and column table
412              
413             ##########################################################################
414              
415             sub mork_parse_table {
416             #my ($obj) = shift;
417              
418             #get the variables from the calling script
419              
420 0     0 0   my($file, $section, $age, $since, $table_part) = (@_);
421              
422 0 0         print STDERR "\n" if ($verbose);
423              
424              
425              
426             # Assumption: no relevant spaces in values in this section
427              
428 0           $table_part =~ s/\s+//g;
429              
430              
431              
432             # print $table_part; #exit(0);
433              
434              
435              
436             #Grab each complete [...] block
437              
438 0           while( $table_part =~ m/\G [^[]* \[ # find a "["
439              
440             ( [^]]+ ) \] # capture up to "]"
441              
442             /gcx )
443              
444             {
445              
446             #set $_ to the result of the regex (each complete [...] block)
447              
448 0           $_ = $1;
449              
450 0           my %hash;
451              
452             #break up the table - each line cosists of a $id and the rest are records
453              
454 0           my ($id, @cells) = split (m/[()]+/s);
455              
456              
457              
458             #a long way of saying skip the line if there are no records
459              
460             #in the @cells array
461              
462 0 0         next unless scalar(@cells);
463              
464              
465              
466             # Trim junk
467              
468 0           $id =~ s/^-//;
469              
470 0           $id =~ s/:.*//;
471              
472              
473              
474             #check that the $id number we've been given corresponds
475              
476             # to one we pulled out from the key_table index
477              
478 0 0         if($row_hash{$id})
479              
480             {
481              
482             #set %hash to the contents of the anonymous
483              
484             # hash that holds the hash $id
485              
486             # uniquely identifies within %row_hash
487              
488 0           %hash = ( %{$row_hash{$id}} );
  0            
489              
490             } #else
491              
492             #{
493              
494             # the code below is for the history mdb hash,
495              
496             # and not what we want to do here, so I've
497              
498             # shamefully just ommitted it.
499              
500             # %hash = ( 'ID' => $id,
501              
502             #'LastVisitDate' => 0 );
503              
504             #}
505              
506             #TODO write some code that inserts a default value if there isn't one already
507              
508              
509              
510             #having sorted out the right %hash according to the $id which was the
511              
512             #first record of the line, we now interate through all the others
513              
514             # on the line
515              
516             #another bit of Deep Magic which sorts out the cell,
517              
518             # includes some error checking
519              
520 0           foreach (@cells)
521              
522             {
523              
524             #if the record is empty, skip
525              
526 0 0         next unless $_;
527              
528             # extract $keyi, $which, $vali from the result of the regexp
529              
530 0           my ($keyi, $which, $vali) =
531              
532             m/^\^ ([-\dA-F]+)
533              
534             ([\^=])
535              
536             (.*)
537              
538             $/xi;
539              
540              
541              
542 0 0         print ("$file: unparsable cell: $_\n") unless defined ($vali);
543              
544            
545              
546             # If the key isn't in the key table, ignore it
547              
548             #
549              
550 0           my $key = $key_table{$keyi};
551              
552 0 0         next unless defined($key);
553              
554              
555              
556             #IIRC this is the precurser to map() in perl 5.
557              
558             # perl wizards feel free to correct me..
559              
560 0 0         my $val = ($which eq '='
561              
562             ? $vali
563              
564             : $val_table{$vali});
565              
566              
567              
568             #if ($key eq 'LastVisitDate' || $key eq 'FirstVisitDate')
569              
570             #{
571              
572             #$val = int ($val / 1000000); # we don't need milliseconds..
573              
574             #}
575              
576              
577              
578             #add a hash value of the $val we extracted from the table,
579              
580             # relating to the key $key
581              
582 0           $hash{$key} = $val;
583              
584             #print "$id: $key -> $val\n";
585              
586             }
587              
588              
589              
590              
591              
592             # if ($age && ($hash{LastVisitDate} || $since) < $since)
593              
594             # {
595              
596             # print STDERR "$0: $file: skipping old: " .
597              
598             # # "$hash{LastVisitDate} $hash{URL}\n"
599              
600             # if ($verbose);
601              
602             #
603              
604             # $skipped++;
605              
606             # next;
607              
608             # }
609              
610            
611              
612             #showing a blatant disregard for preserving the my of
613              
614             #$total, we treat it as an our()
615              
616             #increment the $total counter so that mork_parse_file()
617              
618             #can print its stats of how many
619              
620             # lines its processed
621              
622 0           $total++;
623              
624             #add a reference to the %hash table we just constructed
625              
626             #of the values in this line
627              
628 0           $row_hash{$id} = \%hash;
629              
630             }
631              
632             }
633             #end of mork_parse_tabl()
634              
635              
636             ##########################################################################
637              
638             # parse a values table
639              
640             ##########################################################################
641              
642              
643              
644             sub mork_parse_value_table {
645             #my ($obj) = shift;
646              
647 0     0 0   my($file, $section, $val_part) = (@_);
648              
649              
650              
651 0 0         return unless $val_part;
652              
653              
654              
655 0           my @pairs = split (m/\(([^\)]+)\)/, $val_part);
656              
657 0           $val_part = undef;
658              
659              
660              
661 0 0         print STDERR "\n" if ($verbose > 3);
662              
663              
664              
665 0           foreach (@pairs) {
666              
667 0 0         next unless (m/[^\s]/s);
668              
669 0           my ($key, $val) = m/([\dA-F]*)[\t\n ]*=[\t\n ]*(.*)/i;
670              
671              
672              
673 0 0         if (! defined ($val)) {
674              
675 0           print STDERR "$0: $file: $section: unparsable val: $_\n";
676              
677 0           next;
678              
679             }
680              
681              
682              
683             # Assume that records are never hexilated; so
684              
685             # don't bother unhexilating if we won't be using Name, etc.
686              
687 0 0         if($val =~ m/\$/) {
688              
689             # Approximate wchar_t -> ASCII and remove NULs
690              
691 0           $val =~ s/\$00//g; # faster if we remove these first
692              
693 0           $val =~ s/\$([\dA-F]{2})/chr(hex($1))/ge;
  0            
694              
695             }
696              
697              
698              
699 0           $val_table{$key} = $val;
700              
701 0 0         print STDERR "$0: $file: $section: val $key = \"$val\"\n"
702              
703             if ($verbose > 3);
704              
705             }
706              
707             } #end of mork_parse_value_table
708              
709              
710              
711              
712             ##########################################################################
713              
714             # parse a key table
715              
716             ##########################################################################
717              
718              
719              
720             sub mork_parse_key_table {
721              
722             #my ($obj) = shift;
723 0     0 0   my ($file, $section, $key_table) = (@_);
724              
725              
726              
727 0 0         print STDERR "\n" if ($verbose > 3);
728              
729 0           $key_table =~ s@\s+//.*$@@gm;
730              
731              
732              
733 0           my @pairs = split (m/\(([^\)]+)\)/s, $key_table);
734              
735 0           $key_table = undef;
736              
737              
738              
739 0           foreach (@pairs) {
740              
741 0 0         next unless (m/[^\s]/s);
742              
743 0           my ($key, $val) = m/([\dA-F]+)\s*=\s*(.*)/i;
744              
745 0 0         error ("$file: $section: unparsable key: $_") unless defined ($val);
746              
747              
748              
749             ## If we're only emitting URLs and dates, don't even bother
750              
751             ## saving the other fields that we aren't interested in.
752              
753             ##
754              
755             #next if (!$show_all_p &&
756              
757             # $val ne 'URL' && $val ne 'LastVisitDate' &&
758              
759             # $val ne 'VisitCount');
760              
761              
762              
763 0           $key_table{$key} = $val;
764              
765 0 0         print STDERR "$0: $file: $section: key $key = \"$val\"\n"
766              
767             if ($verbose > 3);
768              
769             }
770              
771             }
772             #end of mork_parse_key_table()
773              
774              
775             1;
776             __END__