File Coverage

blib/lib/Palm/PalmDoc.pm
Criterion Covered Total %
statement 6 272 2.2
branch 0 100 0.0
condition 0 56 0.0
subroutine 2 17 11.7
pod 12 15 80.0
total 20 460 4.3


line stmt bran cond sub pod time code
1             package Palm::PalmDoc;
2              
3 1     1   700 use strict;
  1         2  
  1         52  
4 1     1   6 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         1  
  1         2968  
5              
6             require Exporter;
7              
8             @ISA = qw(Exporter);
9             @EXPORT = qw();
10             $VERSION = '0.13';
11              
12             # Palm::PalmDoc Constructor
13              
14             sub new {
15 0     0 1   my $proto = shift;
16 0   0       my $class = ref($proto) || $proto;
17 0           my $self = {};
18 0           $self->{TITLE} = "PalmDoc Document";
19 0           $self->{INFILE} = undef;
20 0           $self->{OUTFILE} = undef;
21 0           $self->{INFILEH} = undef;
22 0           $self->{OUTFILEH} = undef;
23 0           $self->{BODY} = undef;
24 0           $self->{COMPRESS} = 0;
25 0           $self->{BLOCK_SIZE} = [];
26 0           $self->{IGNORENL} = 0;
27 0           bless($self,$class);
28 0 0         if (@_)
29 0           { my $ref = shift;
30 0           my %params = ();
31 0 0         if (ref $ref eq 'ARRAY')
32 0           { %params = @{$ref}; }
  0            
33 0 0         if (ref $ref eq 'HASH')
34 0           { %params = %{$ref}; }
  0            
35 0 0         if (ref $ref eq '')
36 0           { unshift @_,$ref;
37 0 0         if (!(@_ % 2))
38 0           { %params = @_; }
39             }
40 0           foreach (keys %params) { my $tkey = uc $_; my $tvalue = $params{$_}; delete $params{$_}; $params{$tkey} = $tvalue; }
  0            
  0            
  0            
  0            
41 0 0         $self->infile($params{INFILE}) if exists $params{INFILE};
42 0 0         $self->outfile($params{OUTFILE}) if exists $params{OUTFILE};
43 0 0         $self->title($params{TITLE}) if exists $params{TITLE};
44 0 0         $self->compression($params{COMPRESS}) if exists $params{COMPRESS};
45 0 0         $self->ignorenl($params{IGNORENL}) if exists $params{IGNORENL};
46 0 0         $self->body($params{BODY}) if exists $params{BODY};
47 0           $self->compressed(0);
48             }
49 0           return $self;
50             }
51              
52             sub body {
53 0     0 1   my $self = shift;
54 0 0         if (@_) {
55 0           $self->{BODY} = shift;
56 0 0         if ($self->ignorenl)
57 0           { my @body = split(/\n/, $self->{BODY});
58 0           my $sep = "";
59 0           $self->{BODY} = "";
60 0           foreach (@body)
61 0 0         { if (/^\s*$/)
62 0           { $self->{BODY} .= "\n";
63 0           $sep = "";
64             } else
65 0           { $self->{BODY} .= "$sep$_";
66 0           $sep = " ";
67             }
68             }
69 0 0         if ($sep eq " ")
70 0           { $self->{BODY} .= "\n"; }
71             }
72 0           $self->length(CORE::length $self->{BODY});
73 0 0 0       if ($self->compression && !$self->compressed) { $self->compressed(1); $self->{BODY} = $self->compr_text($self->{BODY}); }
  0            
  0            
74             }
75 0           return($self->{BODY});
76             }
77              
78             sub length {
79 0     0 0   my $self = shift;
80 0 0         if (@_) { $self->{LENGTH} = shift; }
  0            
81 0           return($self->{LENGTH});
82             }
83              
84             sub title {
85 0     0 1   my $self = shift;
86 0 0         if (@_) { $self->{TITLE} = shift; }
  0            
87 0           return($self->{TITLE});
88             }
89              
90             sub compression {
91 0     0 1   my $self = shift;
92 0 0         if (@_) { $self->{COMPRESS} = shift @_ ? 1 : 0; }
  0 0          
93 0           return($self->{COMPRESS});
94             }
95              
96             sub compressed {
97 0     0 0   my $self = shift;
98 0 0         if (@_) { $self->{COMPRESSED} = shift @_ ? 1 : 0; }
  0 0          
99 0           return($self->{COMPRESSED});
100             }
101              
102             sub ignorenl {
103 0     0 1   my $self = shift;
104 0 0         if (@_) { $self->{IGNORENL} = shift @_ ? 1 : 0; }
  0 0          
105 0           return($self->{IGNORENL});
106             }
107              
108              
109             sub infile {
110 0     0 1   my $self = shift;
111 0 0         if (@_)
112 0           { $self->{INFILE} = shift;
113 0           $self->{INFILE} =~ s/([;\`'\\\|"*~<>^\(\)\[\]\{\}\$\n\r\0\t\s])//g;
114             }
115 0           return($self->{INFILE});
116             }
117              
118             sub outfile {
119 0     0 1   my $self = shift;
120 0 0         if (@_) {
121 0           $self->{OUTFILE} = shift;
122 0           $self->{OUTFILE} =~ s/([;\`'\\\|"*~<>^\(\)\[\]\{\}\$\n\r\0\t\s])//g;
123             }
124 0           return($self->{OUTFILE});
125             }
126              
127             sub parse_from_file {
128 0     0 1   my $self = shift;
129 0 0         $self->infile(shift) if @_;
130 0 0         $self->outfile(shift) if @_;
131             }
132              
133             sub parse_from_filehandle {
134 0     0 1   my $self = shift;
135 0           ($self->{INFILEH},$self->{OUTFILEH}) = @_;
136 0   0       $self->{INFILEH} ||= \*STDIN;
137 0   0       $self->{OUTFILEH} ||= \*STDOUT;
138             }
139              
140             sub read_text {
141 0     0 1   my $self = shift;
142 0 0         if ($self->infile)
143 0 0         { open (IN, "<".$self->infile) || die "Can't open ".$self->infile.": $!\n";
144 0           { local $/ = undef;
  0            
145 0           $self->body();
146             }
147 0           close (IN);
148 0 0 0       if ($self->{INFILEH} && !$self->infile)
149 0           { local $/ = undef;
150 0           $self->body();
151             }
152 0 0 0       $self->{INFILEH} and close($self->{INFILEH}) || die "Can't close input filehandle after reading: $!";
153 0 0 0       if ($self->compression && !$self->compressed) { $self->compressed(1); $self->body($self->compr_text($self->body)); }
  0            
  0            
154 0           return ($self->body);
155 0           } else { return(0); }
156             }
157              
158             sub write_text {
159 0     0 1   my $self = shift;
160 0 0         if ($self->body)
161 0 0         { if ($self->outfile)
162 0 0         { open (OUT,">".$self->outfile) || die "Can't open ".$self->outfile.": $!\n";
163 0           binmode(OUT);
164 0           print OUT $self->pdb_header(),$self->body;
165 0           close (OUT);
166             }
167 0 0 0       if ($self->{OUTFILEH} && !$self->outfile)
168 0           { binmode($self->{OUTFILEH});
169 0           my $foo = $self->{OUTFILEH};
170 0           print $foo $self->pdb_header,$self->body;
171 0 0 0       $self->{OUTFILEH} and close($self->{OUTFILEH}) || die "Can't close output filehandle after reading: $!";
172             }
173 0           return (1);
174 0           } else { return(0); }
175             }
176              
177             sub pdb_header {
178 0     0 1   my $self = shift;
179 0           my $COUNT_BITS = 3;
180 0           my $DISP_BITS = 11;
181 0           my $DOC_CREATOR = "REAd";
182 0           my $DOC_TYPE = "TEXt";
183 0           my $RECORD_SIZE_MAX = 4096; # 4k record size
184 0           my $dmDBNameLength = 32; # 31 chars + 1 null
185              
186 0           my $pdb_rec_offset; # PDB record offset
187 0           my $header_buff = ""; # Temporary buffer to build the headers in.
188 0           my $x;
189             my $y;
190 0           my $pdb_header_size = 78;
191 0           my $pdb_attributes = 0;
192 0           my $pdb_version = 0;
193 0           my $pdb_create_time = 0x11111111; # Palm Desktop demands
194 0           my $pdb_modify_time = 0x11111111; # a timestamp.
195 0           my $pdb_backup_time = 0;
196 0           my $pdb_modificationNumber = 0;
197 0           my $pdb_appInfoID = 0;
198 0           my $pdb_sortInfoID = 0;
199 0           my $pdb_type = $DOC_TYPE;
200 0           my $pdb_creator = $DOC_CREATOR;
201 0           my $pdb_id_seed = 0;
202 0           my $pdb_id_nextRecordList = 0;
203 0           my $pdb_numRecords = (int ($self->length / 4096)) + 2; # +1 for record 0
204             # +1 for fractional part
205            
206 0           my $pdb_header = pack("a32nnNNNNNNa4a4NNn",substr($self->title,0,31)."\0",$pdb_attributes,
207             $pdb_version,$pdb_create_time,
208             $pdb_modify_time,$pdb_backup_time,
209             $pdb_modificationNumber,$pdb_appInfoID,
210             $pdb_sortInfoID,$pdb_type,$pdb_creator,
211             $pdb_id_seed,$pdb_id_nextRecordList,
212             $pdb_numRecords);
213              
214 0 0         if ( (CORE::length $pdb_header) != 78) { die "pdb_header malformed\n"; }
  0            
215              
216 0           my $doc_header_size = 16;
217 0           my $doc_version = 1 + $self->compression;
218 0           my $reserved1 = 0;
219 0           my $doc_doc_size = $self->length;
220 0           my $doc_rec_size = 4096;
221 0           my $doc_num_recs = (int ($self->length / 4096)) + 1;
222 0           my $doc_reserved2 = 0;
223              
224 0           my $doc_header = pack("nnNnnN",$doc_version,$reserved1,$doc_doc_size,
225             $doc_num_recs,$doc_rec_size,$doc_reserved2);
226              
227 0 0         if ( (CORE::length $doc_header) != 16) { die "doc_header malformed\n"; }
  0            
228              
229 0           my $pdb_rec_header_size = 8;
230 0           my $pdb_rec_attributes = 0x40; # We'll fake this, 0x40 = 'dirty'
231 0           my $pdb_rec_uniqueID = 0x3D0; # Simple increment
232              
233 0           my $pdb_rec_header_template = "Nccn";
234              
235 0           $pdb_rec_offset = $pdb_header_size +
236             (($pdb_numRecords)* $pdb_rec_header_size) + 2;
237              
238 0           $header_buff = $pdb_header . pack($pdb_rec_header_template,
239             $pdb_rec_offset, $pdb_rec_attributes,
240             ord('a'),$pdb_rec_uniqueID );
241 0           $pdb_rec_offset += $doc_header_size; # Add offset for doc_header
242              
243 0           for ($x = 0; $x < $pdb_numRecords - 1; $x++) {
244              
245             # if ($x > 0 )
246             # { $self->{BLOCK_SIZE}[$x] = $RECORD_SIZE_MAX; }
247            
248 0           $pdb_rec_offset += $self->{BLOCK_SIZE}[$x];
249 0           ++$pdb_rec_uniqueID;
250 0           $header_buff .= pack($pdb_rec_header_template,$pdb_rec_offset,
251             $pdb_rec_attributes,ord('a'),$pdb_rec_uniqueID);
252             }
253            
254 0           $header_buff .= 0x00 . 0x00;
255              
256 0           $header_buff .= $doc_header;
257              
258 0           return ($header_buff);
259             }
260              
261             sub compr_text {
262 0     0 0   my $self = shift;
263 0           my $total_compr_size = 0; # Final compressed text size
264 0           my $compr_buff = ""; # Temporary output buffer
265 0           my $numrecords = (int($self->{LENGTH} / 4096) +1); # Number of blocks to compress.
266 0           my $x;
267             my $y;
268 0           my $block_offset;
269 0           my $block; # Contains the current 4096 byte block of text
270 0           my $block_len; # Length of current block
271 0           my $index; # Current scan position in block
272 0           my $byte; # Char at index (for space + char compression)
273 0           my $byte2; # Char at index+1
274 0           my $test; # Potentially compressible text for
275             # LZ77 compression.
276              
277 0           my $frag_size; # Current size of above
278 0           my $frag_size2; # Spare for lazy byte compression
279 0           my $test2; # spare for above
280 0           my $test3; # second spare
281 0           my $pos; # Position (in $block) of reference text
282             # for $test
283             # to compress against.
284              
285 0           my $pos2; # spare for above
286 0           my $pos3; # second spare
287 0           my $back; # $index - pos
288 0           my $mask; # Bitwise mask to do LZ77 'magic'
289 0           my $compr_ratio; # Compression ratio
290 0           my $done;
291 0           my $comp_block_offset = 0; # The $compr_buff index
292             # block begins.
293 0           my $FRAG_MAX = 10; # Max LZ77 fragment size
294 0           my $FRAG_MIN = 3; # Min LZ77 fragment size
295 0           my $LAZY_BYTE_FRAG = $FRAG_MAX + $FRAG_MIN - 1;
296              
297            
298 0           $self->{BLOCK_SIZE}[0] = 0; # Record 0 is already written and
299             # is not compressed.
300 0           for ($x = 1; $x <= $numrecords; $x++) {
301              
302 0           $block_offset = ($x - 1) * 4096;
303 0           $block = substr($_[0],$block_offset, 4096);
304 0 0         if ($x >= $numrecords) { # Last block
305 0           $block = substr($block,0,($self->{LENGTH} % 4096));
306              
307             }
308            
309 0           $block_len = CORE::length($block);
310              
311 0           $index = 0;
312              
313 0           while ( $index < $block_len ) {
314              
315 0           $byte = substr($block,$index,1); # Char at $index
316 0 0         if ($byte =~ /[\200-\377]/) { # is high bit set?
317              
318 0           $y = 1; # found at least one!
319              
320 0   0       while ( (substr($block,$index + $y ,1) =~
321             /[\200-\377]/) &&
322             ($y < 8) ) {
323              
324 0           ++$y; # If found, increment counter
325            
326             }
327              
328 0           $compr_buff .= chr($y); # Write escape code
329 0           $compr_buff .= substr($block,$index,$y); # Write text
330 0           $index += $y; # Increment the index
331              
332             } else { # Real compression routines
333              
334 0           $frag_size = $FRAG_MIN; # We don't care about anything less
335              
336 0           $test = substr($block,$index,$frag_size); # pull the current fragment
337 0           $pos = rindex($block, $test, $index - 1); # check against the buffer
338              
339 0 0 0       if ( ($pos > 0) &&
      0        
340             ($index - $pos <= 2047) && # Inside our 2047 byte window
341             ( $index < $block_len - $frag_size) ) {
342              
343 0           for ($y = 4; $y <= $FRAG_MAX; $y++ ) {
344 0           ++$frag_size ;
345 0           $test2 = substr($block,$index,$frag_size);
346 0           $pos2 = rindex($block, $test2, $index - 1);
347 0 0 0       if (($pos2 > 0) &&
      0        
348             ($index - $pos2 <= 2047) &&
349             ($index < $block_len - $frag_size) ) {
350             # found a match!
351 0           $pos = $pos2;
352 0           $test = $test2;
353             } else { # no match, go back
354 0           --$frag_size;
355 0           last;
356            
357             }
358            
359             }
360             # Sanity check
361 0 0         if ($frag_size > $FRAG_MAX)
362 0           { die "frag_size too big!!!: $frag_size\n"; }
363            
364 0           $frag_size2 = $frag_size + 2;
365 0           $test2 = substr($block,$index + 1, $frag_size2);
366 0           $pos2 = rindex($block, $test2, $index - 1);
367 0 0 0       if (($pos2 > 0) &&
      0        
368             ($index - $pos2 <= 2047) &&
369             ($index < $block_len - $frag_size2) ) {
370              
371 0           for ($y = $frag_size2;$y <= $LAZY_BYTE_FRAG;
372             $y++ ) { # Look for more
373 0           ++$frag_size2;
374 0           $test2 = substr($block,$index + 1, $frag_size2);
375 0           $pos2 = rindex($block, $test2, $index - 1);
376 0 0 0       if (($pos2 > 0) &&
      0        
377             ($index - $pos2 <= 2047) &&
378             ($index < $block_len - $frag_size2) ) {
379             # found a match!
380              
381             } else { # no match, go back
382 0           --$frag_size2;
383 0           last;
384            
385             }
386             }
387 0 0         if ($frag_size2 < $LAZY_BYTE_FRAG) {
388              
389 0           $pos = 0;
390 0           $compr_buff .= substr($block,$index,1);
391 0           ++$index;
392             }
393             }
394            
395 0 0         if ($pos > 0) { # Did we abort the compression?
396            
397 0           $back = $index - $pos;
398 0           $mask = 0x8000 | int($frag_size - 3);
399              
400 0           $compr_buff .= pack("n",int($back << 3) | $mask);
401 0           $index += $frag_size;
402             }
403            
404             } else {
405              
406 0           $byte = substr($block,$index,1); # Char at $index
407 0           $byte2 = substr($block,$index + 1,1); # next char as well
408 0 0 0       if ( ($byte eq " ") &&
      0        
409             ($byte2 =~ /[\100-\176]/ ) &&
410             ($index <= $block_len - 1)) {
411             # Got a space + char
412            
413             # Set the high bit
414             # and add to output
415             # buffer.
416 0           $compr_buff .= pack("C", ord ($byte2) | 0x80 );
417 0           $index += 2; # Compressed 2 bytes
418            
419             } else {
420 0           $compr_buff .= $byte; # No compression
421 0           ++$index;
422             }
423             }
424             }
425             }
426              
427 0           $self->{BLOCK_SIZE}[$x] = (CORE::length ($compr_buff)) - $total_compr_size;
428 0           $total_compr_size = CORE::length ($compr_buff);
429              
430             }
431              
432 0           return ($compr_buff);
433             }
434              
435              
436             1;
437             __END__