File Coverage

blib/lib/PostScript/PPD.pm
Criterion Covered Total %
statement 280 308 90.9
branch 105 130 80.7
condition 20 28 71.4
subroutine 43 45 95.5
pod 6 6 100.0
total 454 517 87.8


line stmt bran cond sub pod time code
1             package PostScript::PPD;
2              
3             # use 5.008008;
4 4     4   3253 use strict;
  4         9  
  4         113  
5 4     4   21 use warnings;
  4         6  
  4         108  
6              
7 4     4   2361 use Compress::Zlib qw( gzopen );
  4         254631  
  4         395  
8 4     4   39 use Carp qw( carp croak confess cluck );
  4         9  
  4         258  
9 4     4   2670 use Storable qw( dclone );
  4         13337  
  4         243  
10 4     4   27 use IO::File;
  4         14  
  4         12461  
11              
12             our $VERSION = '0.0400';
13              
14             sub DEBUG () { 0 }
15              
16             ################################################
17             sub new
18             {
19 3     3 1 1903 my( $package, $file ) = @_;
20 3         12 my $self = bless { file => $file }, $package;
21 3 50       20 $self->load if $file;
22 3         9 return $self;
23             }
24              
25             ################################################
26             sub load
27             {
28 11     11 1 908 my( $self, $file ) = @_;
29             croak "Usage: $self->load( [ $file ] );"
30 11 0 33     42 unless $file or $self->{file};
31            
32 11   33     32 $file ||= $self->{file};
33 11 50       29 return unless $file;
34              
35 11         215 delete @{ $self }{ keys %$self };
  11         3559  
36              
37 11         136 local $self->{__read_state};
38 11         59 local $self->{__position} = { file=>$file, line=>0};
39              
40 11         22 eval {
41 11 100       72 if( $file =~ /\.gz$/ ) {
42 2         14 my $gz = gzopen( $file, "rb" );
43 2 50       5153 croak "Unable to read $file: $!" unless $gz;
44 2         6 $self->{file} = $file;
45              
46 2         4 my( $line, $size );
47 2         17 while( $size = $gz->gzreadline( $line ) ) {
48 588         59798 $self->{__position}{line}++;
49 588         1019 $self->__read_line( $line );
50             }
51             }
52             else {
53 9         107 my $fh = IO::File->new( $file );
54 9 50       1072 croak "Unable to read $file: $!" unless $fh;
55 9         32 $self->{file} = $file;
56 9         765 while( <$fh> ) {
57 11352         17662 $self->{__position}{line}++;
58 11352         18919 $self->__read_line( $_ );
59             }
60             }
61             };
62 11 50       746 if( $@ ) {
63 0         0 die "File $self->{__position}{file} line $self->{__position}{line}: $@";
64             }
65             }
66              
67             ################################################
68             sub parse
69             {
70 0     0 1 0 my( $self, $text ) = @_;
71              
72 0         0 delete @{ $self }{ keys %$self };
  0         0  
73              
74 0         0 local $self->{__read_state};
75 0         0 local $self->{__position} = { file=>'string', line=>0};
76 0         0 eval {
77 0         0 foreach my $line ( split /\n/, $text ) {
78 0         0 $self->{__position}{line}++;
79 0         0 $self->__read_line( $line );
80             }
81             };
82 0 0       0 if( $@ ) {
83 0         0 die "String line $self->{__position}{line}: $@";
84             }
85             }
86              
87             ################################################
88             sub __read_line
89             {
90 11940     11940   22370 my( $self, $line ) = @_;
91              
92 11940   100     22355 $self->{__read_state} ||= { state => 0,
93             value => '',
94             key => '',
95             current => [ $self ]
96             };
97 11940         15987 my $S = $self->{__read_state};
98              
99 11940 100       19568 if( $S->{key} ) {
100 1674 100 100     5030 if( $line =~ /^\*/ and $S->{value} =~ /^".*"\s*$/s ) {
101 313         636 $self->__new_tupple;
102             }
103             else {
104 1361         2837 $self->__append( $line );
105 1361         3388 return;
106             }
107             }
108              
109             # comment
110 10579 100       20757 return if $line =~ /^\*%/;
111             # End a multi-line tupple
112 10065 100       17652 if( $line =~ /^\*End\s*$/ ) {
113 374         864 $self->__new_tupple; # missing *End?
114 374         1019 return;
115             }
116              
117             # Start a config group
118 9691 100       16183 if( $line =~ /^\*OpenGroup:\s*(.+)/ ) {
119 28         83 my $name = $1;
120 28         80 $self->__new_tupple; # missing *End?
121 28         77 $self->__new_group( $name );
122 28         121 return;
123             }
124             # End a config group
125 9663 100       15929 if( $line =~ /^\*CloseGroup:\s*(.+)/ ) {
126 28         76 my $name = $1;
127 28         78 $self->__new_tupple; # missing *End?
128 28         90 $self->__end_group( $name );
129 28         81 return;
130             }
131             # Open a UI option
132 9635 100       17272 if( $line =~ /^\*(?:JCL)?OpenUI\s*\*(.+?):\s*(.+)/ ) {
133 161         490 my( $name, $value ) = ( $1, $2 );
134 161         388 $self->__new_tupple; # missing *End?
135 161         463 $self->__new_UI( $name, $value );
136 161         509 return;
137             }
138             # End the UI option
139 9474 100       17066 if( $line =~ /^\*(?:JCL)?CloseUI:?\s*\*(.+)/ ) {
140 161         323 my $name = $1;
141 161         357 $self->__new_tupple; # missing *End?
142 161         378 $self->__end_UI( $name );
143 161         437 return;
144             }
145              
146             # Translation tupple, that contains bad UTF-8 (Gestetner)
147 9313 50       17748 if( $line =~ /^(\*zh_([^:]+)\s+)""$/ ) {
148 0         0 my( $used, $name ) = ( $1, $2 );
149 0         0 $self->__new_tupple; # missing *End?
150 0         0 $S->{key} = $name;
151 0         0 $S->{value} = '';
152 0         0 $self->__new_tupple;
153 0         0 return;
154             }
155             # New tupple
156 9313 100       33227 if( $line =~ /^(\*\s*([^:]+):\s*)/ ) {
157 8867         23834 my( $used, $name ) = ( $1, $2 );
158 8867         18755 $self->__new_tupple; # missing *End?
159              
160 8867         11690 $used = length $used;
161 8867         12260 $S->{key} = $name;
162 8867         12508 $S->{value} = '';
163 8867         15385 local $S->{first} = 1;
164 8867         23469 $self->__append( substr $line, $used );
165 8867         33622 return;
166             }
167 446 50       1739 return unless $line =~ /\S/;
168            
169 0         0 warn "What's with line '$line' at $self->{__position}{file} line $self->{__position}{line}";
170             }
171              
172             ################################################
173             sub __append
174             {
175 10228     10228   22460 my( $self, $line ) = @_;
176              
177 10228         16636 my $S = $self->{__read_state};
178 10228         13236 my $exit = 0;
179 10228 100       18263 $exit = 1 if not $S->{value};
180              
181             # *Something: "honk" <- here
182 10228 100       32825 if( $line =~ m/^"(.*)" *$/ ) {
    100          
    50          
    100          
183 5880         8488 $S->{quoted} = 1;
184 5880         7519 $exit = 1;
185             }
186             # *Something: "honk
187             # " <- here
188             # *End
189             # *Something: "<- here
190             # "
191             # *End
192             elsif( $line =~ m/^"/ ) {
193 441         713 $S->{quoted} = 1;
194 441         1082 $exit = ( $line =~ /" *$/ );
195 441 100       919 $exit = 0 if $S->{first};
196             }
197             # *Something: "
198             # with trailing
199             # "
200             # *End <- here
201             elsif( $line eq "*End\n" ) {
202 0         0 $line = '';
203 0         0 $exit = 1;
204             }
205             # *Something: "
206             # with trailing <- here
207             # "
208             # *End
209             elsif( not $S->{first} ) {
210 1297         3101 $line =~ s/ +$//;
211             }
212              
213             # *Something: "
214             # Ho&& <- here
215             # nk"
216 10228 100       20506 if( $line =~ s/&&\s*$// ) {
217 87         140 $exit = 0;
218             }
219              
220 10228         18594 $S->{value} .= $line;
221              
222 10228 100       19004 if( $exit ) {
223 8554         17266 $self->__new_tupple;
224 8554         13343 return;
225             }
226             }
227              
228             ################################################
229             sub __new_tupple
230             {
231 18486     18486   26384 my( $self ) = @_;
232 18486         25852 my $S = $self->{__read_state};
233 18486 100       33859 return unless $S->{key};
234              
235 8867 100       15396 chomp( $S->{value} ) unless $S->{quoted};
236              
237 8867         13237 my $C = $S->{current}[-1];
238 8867 100       31489 if( $S->{key} =~ /^([^ ]+)\s+(.+(\/.+)?)$/ ) {
239 6603         12892 $self->__new_option( $1, $2, $S->{value} );
240             }
241             else {
242 2264         3851 my $v = $self->__fix_value( $S->{value} );
243 2264         3477 my $k = $S->{key};
244 2264 100       3883 if( $C->{ $k } ) {
245 1487 100       2946 $C->{ $k } = [ $C->{$k} ] unless ref $C->{$k};
246 1487         1828 push @{ $C->{$k} }, $v;
  1487         3184  
247             }
248             else {
249 777         2083 $C->{ $k } = $v;
250             }
251 2264   100     4532 $C->{__sorted} ||= [];
252 2264         3661 $self->__new_key( $k );
253             }
254 8867         13438 $S->{key} = '';
255 8867         11706 $S->{value} = '';
256 8867         13179 $S->{quoted} = 0;
257             }
258              
259             sub __fix_value
260             {
261 8867     8867   14174 my( $self, $v ) = @_;
262 8867 100       31050 if( $v eq 'False' ) {
    100          
263 41         77 return 0;
264             }
265             elsif( $v =~ s/"(.+)"\s*/$1/s ) {
266 3610         6839 $v =~ s/"?/"/g;
267             }
268 8826         34260 return $v;
269             }
270              
271             sub __new_key
272             {
273 9056     9056   13781 my( $self, $key ) = @_;
274 9056         11992 my $S = $self->{__read_state};
275 9056         11727 my $C = $S->{current}[-1];
276 9056 100       19256 push @{ $C->{__sorted} }, $key unless $C->{$key};
  1088         2522  
277             }
278              
279             ################################################
280             sub __new_option
281             {
282 6603     6603   21560 my( $self, $key, $name, $value ) = @_;
283 6603         13140 my( $tname, $text ) = $self->__parse_name( $name );
284 6603         10874 my $S = $self->{__read_state};
285 6603         9109 my $C = $S->{current}[-1];
286              
287 6603         13778 $self->__new_key( $key );
288              
289 6603 100       11538 if( $C->{$key} ) {
290 5751 100       13206 unless( 'HASH' eq ref $C->{$key} ) {
291             # Promote *foo: "something" to *foo _: "something"
292             # wish I had a better name for _
293             $C->{$key} = { '_' => { __name => '_',
294             __text => '_',
295 1         8 value => $C->{$key}
296             }
297             };
298             }
299             }
300             else {
301 852         3523 $C->{$key} = {
302             __sorted => []
303             };
304             }
305 6603         8245 DEBUG and warn "new option key=$key tname=$tname";
306              
307 6603         11174 $C->{$key}{$tname} = { __name => $tname,
308             __text => $text,
309             value => $self->__fix_value( $value )
310             };
311 6603         10096 push @{ $C->{$key}{__sorted} }, $tname;
  6603         17496  
312             }
313              
314             ################################################
315             sub __new_group
316             {
317 28     28   84 my( $self, $name ) = @_;
318 28         78 my( $tname, $text ) = $self->__parse_name( $name );
319 28         161 $self->__push( group => { __name => $tname,
320             __text => $text
321             }
322             );
323             }
324              
325             ################################################
326             sub __end_group
327             {
328 28     28   61 my( $self, $name ) = @_;
329              
330              
331 28         46 my $S = $self->{__read_state};
332 28         54 my $data = $S->{current}[-1];
333 28 50       95 if( 'HASH' eq ref $data ) {
334 28 50       78 if( 'group' ne $data->{__type} ) { # Missing *CloseUI
335 0         0 $self->__pop( $data->{__type}, $data->{__name} );
336             }
337             }
338              
339              
340 28         92 my( $tname, $text ) = $self->__parse_name( $name );
341 28         104 $self->__pop( group => $tname );
342             }
343              
344             ################################################
345             sub __new_UI
346             {
347 161     161   322 my( $self, $name, $type ) = @_;
348 161         378 my( $tname, $text ) = $self->__parse_name( $name );
349 161         699 $self->__push( UI => { __name => $tname,
350             __text => $text,
351             __type => $type
352             }
353             );
354             }
355              
356             ################################################
357             sub __end_UI
358             {
359 161     161   313 my( $self, $name ) = @_;
360 161         339 $self->__pop( UI => $name );
361             }
362              
363             ################################################
364             sub __parse_name
365             {
366 6820     6820   10359 my( $self, $name ) = @_;
367 6820         16240 my @bits = split '/', $name, 2;
368 6820   100     15136 $bits[1] ||= $name;
369 6820         18977 return @bits;
370             }
371              
372             ################################################
373             sub __push
374             {
375 189     189   357 my( $self, $type, $data ) = @_;
376 189         312 $data->{__type} = $type;
377              
378 189         269 my $S = $self->{__read_state};
379 189         293 my $C = $S->{current}[-1];
380 189         579 $C->{$type}{ $data->{__name} } = $data;
381 189         240 push @{ $C->{"__${type}_sorted"} }, $data->{__name};
  189         609  
382              
383             # warn "PUSH $type.$data->{__name}\n";
384 189         676 $self->__new_key( "$type.$data->{__name}" );
385 189         266 push @{ $S->{current} }, $data;
  189         392  
386             }
387              
388             ################################################
389             sub __pop
390             {
391 189     189   376 my( $self, $type, $name ) = @_;
392              
393 189         282 my $S = $self->{__read_state};
394             # warn "POP $type.$name\n";
395              
396             # die "Trying to pop unknown $type $name"
397             # unless $C->{$type}{$name};
398 189         231 my $current = pop @{ $S->{current} };
  189         363  
399 189         417 $name =~ s/\s+$//;
400 189         290 $name =~ s(/.+$)();
401              
402             die "Closing $type $name that was never open"
403 189 50       382 unless $current->{__name};
404              
405             # Missing *CloseUI
406             die "Current $type is $current->{__name}, not $name"
407 189 50       528 unless $current->{__name} eq $name;
408             }
409              
410             ############################################################################
411             ## Introspection
412              
413             our $AUTOLOAD;
414             sub AUTOLOAD
415             {
416 26     26   2798 my $self = shift;
417 26         138 $AUTOLOAD =~ s/^PostScript::PPD:://;
418 26 100       3138 return if $AUTOLOAD eq 'DESTROY';
419 23         77 return $self->get( $self, $AUTOLOAD, @_ );
420             }
421              
422             sub get
423             {
424 31     31 1 88 my( $self, $D, $name, $subkey ) = @_;
425 31 100       99 if( @_ == 2 ) {
426 1         13 $name = $D;
427 1         3 $D = $self;
428             }
429              
430 31 50       89 return unless exists $D->{$name};
431 31         74 my $ret = $D->{$name};
432 31 100       79 if( ref $ret ) {
433 10 100 100     94 if( not $subkey and 'HASH' eq ref $ret and $ret->{"_"} ) {
      100        
434 1         3 $subkey = "_";
435             }
436 10 100       27 if( $subkey ) {
437 2         5 $D = $ret;
438 2         4 $name = $subkey;
439 2         11 $ret = $D->{ $name };
440             }
441 10 100       41 $ret = $self->__mk_subkey( $ret, $D, $name ) if 'HASH' eq ref $ret;
442             }
443 31         136 return $ret;
444             }
445              
446             sub __mk_subkey
447             {
448 9     9   27 my( $self, $value, $parent, $subkey ) = @_;
449 9   33     75 return PostScript::PPD::Subkey->new( $value, ($parent||$self), $subkey );
450             }
451              
452             sub Group
453             {
454 7     7 1 1028 my( $self, $name ) = @_;
455 7 50       28 if( $name eq '_default' ) {
456 0         0 my $ret = dclone $self;
457 0         0 return $self->__mk_subkey( $ret, $self, $name );
458             }
459 7         38 return $self->get( $self->{group}, $name );
460             }
461              
462             sub Groups
463             {
464 2     2 1 325 my( $self ) = @_;
465 2 50       3 my @ret = @{ $self->{__group_sorted}||[] };
  2         11  
466 2 50       7 unshift @ret, '_default' if $self->{__UI_sorted};
467 2 100       8 return @ret if wantarray;
468 1         3 return \@ret;
469             }
470              
471             ############################################################################
472             package PostScript::PPD::Subkey;
473              
474 4     4   39 use strict;
  4         9  
  4         143  
475 4     4   21 use warnings;
  4         12  
  4         201  
476 4     4   24 use Carp;
  4         9  
  4         277  
477 4     4   2667 use Data::Dumper;
  4         25109  
  4         376  
478              
479 4         55 use overload '""' => \&as_string,
480 4     4   32 fallback => 1;
  4         8  
481              
482             sub new
483             {
484 62     62   128 my( $package, $data, $parent, $subkey ) = @_;
485 62         449 my $self = bless { %$data }, $package;
486 62         265 $self->{__parent} = $parent;
487 62         109 $self->{__subkey} = $subkey;
488 62 50       133 confess "Need a subkey" unless defined $subkey;
489 62         136 return $self;
490             }
491              
492             sub default
493             {
494 2     2   6 my( $self ) = @_;
495 2 50       8 die Dumper $self unless $self->{__subkey};
496 2         8 return $self->{__parent}->get( "Default$self->{__subkey}" );
497             }
498              
499             sub as_string
500             {
501 127     127   5845 my( $self ) = @_;
502 127 100       333 return $self->{value} if $self->{value};
503 98         220 return $self;
504             }
505              
506             sub name
507             {
508 23     23   4353 my( $self ) = @_;
509 23         73 return $self->{__name};
510             }
511              
512             sub text
513             {
514 26     26   637 my( $self ) = @_;
515 26         216 return $self->{__text};
516             }
517              
518             sub list
519             {
520 4     4   1010 my( $self ) = @_;
521 4 100       33 return $self->{__sorted} unless wantarray;
522 2         4 return @{ $self->{__sorted} };
  2         17  
523             }
524              
525             sub sorted_list
526             {
527 1     1   710 my( $self ) = @_;
528 70         128 my @ret = sort { $self->{$a}{__text} cmp $self->{$b}{__text} }
529 1         3 @{ $self->{__sorted} };
  1         8  
530             }
531              
532             our $AUTOLOAD;
533             sub AUTOLOAD
534             {
535 71     71   6616 my $self = shift;
536 71         290 $AUTOLOAD =~ s/^PostScript::PPD::Subkey:://;
537 71 100       949 return if $AUTOLOAD eq 'DESTROY';
538 9         42 return $self->get( $self, $AUTOLOAD, @_ );
539             }
540              
541             sub UIs
542             {
543 4     4   1011 my( $self ) = @_;
544 4 50       13 return unless $self->{__UI_sorted};
545 4 100       12 return @{ $self->{__UI_sorted} } if wantarray;
  2         12  
546 2         3 return [ @{ $self->{__UI_sorted} } ];
  2         10  
547             }
548              
549             sub UI
550             {
551 10     10   250 my( $self, $name, $subkey ) = @_;
552 10         35 return $self->get( $self->{UI}, $name, $subkey );
553             }
554              
555             sub get
556             {
557 58     58   794 my( $self, $D, $name, $subkey ) = @_;
558 58 100       147 if( @_ == 2 ) {
559 39         71 $name = $D;
560 39         57 $D = $self;
561             }
562              
563 58 100       154 if( $name =~ s/^UI\.// ) {
564 9         33 $D = $self->{UI};
565             }
566              
567 58 100       143 return unless exists $D->{$name};
568 57         98 my $ret = $D->{$name};
569 57 100       131 if( ref $ret ) {
570 53 50       109 if( $subkey ) {
571 0         0 $ret = $ret->{ $subkey };
572             }
573 53 50       162 $ret = $self->__mk_subkey( $ret, $D, $name ) if 'HASH' eq ref $ret;
574             }
575 57         136 return $ret;
576             }
577              
578             sub __mk_subkey
579             {
580 53     53   118 my( $self, $value, $parent, $subkey ) = @_;
581 53   33     147 return PostScript::PPD::Subkey->new( $value, ($parent||$self), $subkey );
582             }
583              
584             sub Dump
585             {
586 0     0     my( $self ) = @_;
587 0           local $self->{__parent} = $self->{__parent}{__name};
588 0           return Dumper $self;
589             }
590              
591              
592             1;
593             __END__