File Coverage

blib/lib/PostScript/PPD.pm
Criterion Covered Total %
statement 280 310 90.3
branch 105 130 80.7
condition 20 28 71.4
subroutine 43 46 93.4
pod 6 6 100.0
total 454 520 87.3


line stmt bran cond sub pod time code
1             package PostScript::PPD;
2              
3             # use 5.008008;
4 4     4   3355 use strict;
  4         10  
  4         112  
5 4     4   19 use warnings;
  4         8  
  4         130  
6              
7 4     4   2474 use Compress::Zlib qw( gzopen );
  4         255934  
  4         352  
8 4     4   43 use Carp qw( carp croak confess cluck );
  4         10  
  4         233  
9 4     4   2733 use Storable qw( dclone );
  4         13023  
  4         252  
10 4     4   28 use IO::File;
  4         10  
  4         12382  
11              
12             our $VERSION = '0.0402';
13              
14             sub DEBUG () { 0 }
15              
16             ################################################
17             sub new
18             {
19 3     3 1 1933 my( $package, $file ) = @_;
20 3         12 my $self = bless { file => $file }, $package;
21 3 50       13 $self->load if $file;
22 3         9 return $self;
23             }
24              
25             ################################################
26             sub load
27             {
28 11     11 1 947 my( $self, $file ) = @_;
29             croak "Usage: $self->load( [ $file ] );"
30 11 0 33     38 unless $file or $self->{file};
31            
32 11   33     30 $file ||= $self->{file};
33 11 50       51 return unless $file;
34              
35 11         202 delete @{ $self }{ keys %$self };
  11         3327  
36              
37 11         172 local $self->{__read_state};
38 11         50 local $self->{__position} = { file=>$file, line=>0};
39              
40 11         22 eval {
41 11 100       52 if( $file =~ /\.gz$/ ) {
42 2         22 my $gz = gzopen( $file, "rb" );
43 2 50       6047 croak "Unable to read $file: $!" unless $gz;
44 2         6 $self->{file} = $file;
45              
46 2         4 my( $line, $size );
47 2         11 while( $size = $gz->gzreadline( $line ) ) {
48 588         59910 $self->{__position}{line}++;
49 588         1067 $self->__read_line( $line );
50             }
51             }
52             else {
53 9         84 my $fh = IO::File->new( $file );
54 9 50       1172 croak "Unable to read $file: $!" unless $fh;
55 9         31 $self->{file} = $file;
56 9         759 while( <$fh> ) {
57 11352         17996 $self->{__position}{line}++;
58 11352         19405 $self->__read_line( $_ );
59             }
60             }
61             };
62 11 50       806 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   22428 my( $self, $line ) = @_;
91              
92 11940   100     23039 $self->{__read_state} ||= { state => 0,
93             value => '',
94             key => '',
95             current => [ $self ]
96             };
97 11940         16564 my $S = $self->{__read_state};
98              
99 11940 100       20224 if( $S->{key} ) {
100 1674 100 100     5182 if( $line =~ /^\*/ and $S->{value} =~ /^".*"\s*$/s ) {
101 313         644 $self->__new_tupple;
102             }
103             else {
104 1361         2877 $self->__append( $line );
105 1361         3328 return;
106             }
107             }
108              
109             # comment
110 10579 100       21241 return if $line =~ /^\*%/;
111             # End a multi-line tupple
112 10065 100       17390 if( $line =~ /^\*End\s*$/ ) {
113 374         824 $self->__new_tupple; # missing *End?
114 374         1007 return;
115             }
116              
117             # Start a config group
118 9691 100       17226 if( $line =~ /^\*OpenGroup:\s*(.+)/ ) {
119 28         65 my $name = $1;
120 28         64 $self->__new_tupple; # missing *End?
121 28         78 $self->__new_group( $name );
122 28         94 return;
123             }
124             # End a config group
125 9663 100       16826 if( $line =~ /^\*CloseGroup:\s*(.+)/ ) {
126 28         68 my $name = $1;
127 28         72 $self->__new_tupple; # missing *End?
128 28         76 $self->__end_group( $name );
129 28         85 return;
130             }
131             # Open a UI option
132 9635 100       17877 if( $line =~ /^\*(?:JCL)?OpenUI\s*\*(.+?):\s*(.+)/ ) {
133 161         461 my( $name, $value ) = ( $1, $2 );
134 161         379 $self->__new_tupple; # missing *End?
135 161         491 $self->__new_UI( $name, $value );
136 161         483 return;
137             }
138             # End the UI option
139 9474 100       16079 if( $line =~ /^\*(?:JCL)?CloseUI:?\s*\*(.+)/ ) {
140 161         353 my $name = $1;
141 161         365 $self->__new_tupple; # missing *End?
142 161         375 $self->__end_UI( $name );
143 161         407 return;
144             }
145              
146             # Translation tupple, that contains bad UTF-8 (Gestetner)
147 9313 50       17698 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       33018 if( $line =~ /^(\*\s*([^:]+):\s*)/ ) {
157 8867         23906 my( $used, $name ) = ( $1, $2 );
158 8867         19764 $self->__new_tupple; # missing *End?
159              
160 8867         11492 $used = length $used;
161 8867         12587 $S->{key} = $name;
162 8867         12493 $S->{value} = '';
163 8867         15250 local $S->{first} = 1;
164 8867         23998 $self->__append( substr $line, $used );
165 8867         34113 return;
166             }
167 446 50       1772 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   22751 my( $self, $line ) = @_;
176              
177 10228         16637 my $S = $self->{__read_state};
178 10228         13968 my $exit = 0;
179 10228 100       18088 $exit = 1 if not $S->{value};
180              
181             # *Something: "honk" <- here
182 10228 100       32880 if( $line =~ m/^"(.*)" *$/ ) {
    100          
    50          
    100          
183 5880         8771 $S->{quoted} = 1;
184 5880         7529 $exit = 1;
185             }
186             # *Something: "honk
187             # " <- here
188             # *End
189             # *Something: "<- here
190             # "
191             # *End
192             elsif( $line =~ m/^"/ ) {
193 441         654 $S->{quoted} = 1;
194 441         1128 $exit = ( $line =~ /" *$/ );
195 441 100       953 $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         3216 $line =~ s/ +$//;
211             }
212              
213             # *Something: "
214             # Ho&& <- here
215             # nk"
216 10228 100       20117 if( $line =~ s/&&\s*$// ) {
217 87         135 $exit = 0;
218             }
219              
220 10228         19442 $S->{value} .= $line;
221              
222 10228 100       17980 if( $exit ) {
223 8554         17414 $self->__new_tupple;
224 8554         13506 return;
225             }
226             }
227              
228             ################################################
229             sub __new_tupple
230             {
231 18486     18486   27967 my( $self ) = @_;
232              
233 18486         25510 my $S = $self->{__read_state};
234 18486 100       33991 return unless $S->{key};
235              
236 8867 100       15296 chomp( $S->{value} ) unless $S->{quoted};
237              
238 8867         12585 my $C = $S->{current}[-1];
239 8867 100       31451 if( $S->{key} =~ /^([^ ]+)\s+(.+(\/.+)?)$/ ) {
240 6603         15465 $self->__new_option( $1, $2, $S->{value} );
241             }
242             else {
243 2264         4423 my $v = $self->__fix_value( $S->{value} );
244 2264         3526 my $k = $S->{key};
245 2264 100       3983 if( $C->{ $k } ) {
246 1487 100       3016 $C->{ $k } = [ $C->{$k} ] unless ref $C->{$k};
247 1487         1830 push @{ $C->{$k} }, $v;
  1487         3340  
248             }
249             else {
250 777         1972 $C->{ $k } = $v;
251             }
252 2264   100     4670 $C->{__sorted} ||= [];
253 2264         4091 $self->__new_key( $k );
254             }
255 8867         14005 $S->{key} = '';
256 8867         11838 $S->{value} = '';
257 8867         12956 $S->{quoted} = 0;
258             }
259              
260             sub __fix_value
261             {
262 8867     8867   14448 my( $self, $v ) = @_;
263 8867 100       40095 if( $v eq 'False' ) {
    100          
264 41         84 return 0;
265             }
266             elsif( $v =~ s/"(.*)"\s*/$1/s ) {
267 6889         11664 $v =~ s/"?/"/g;
268             }
269 8826         35574 return $v;
270             }
271              
272             sub __new_key
273             {
274 9056     9056   13720 my( $self, $key ) = @_;
275 9056         13073 my $S = $self->{__read_state};
276 9056         12373 my $C = $S->{current}[-1];
277 9056 100       19410 push @{ $C->{__sorted} }, $key unless exists $C->{$key};
  1041         2447  
278             }
279              
280             ################################################
281             sub __new_option
282             {
283 6603     6603   21318 my( $self, $key, $name, $value ) = @_;
284 6603         13524 my( $tname, $text ) = $self->__parse_name( $name );
285 6603         11167 my $S = $self->{__read_state};
286 6603         9327 my $C = $S->{current}[-1];
287              
288 6603         14313 $self->__new_key( $key );
289              
290 6603 100       11725 if( $C->{$key} ) {
291 5751 100       12862 unless( 'HASH' eq ref $C->{$key} ) {
292             # Promote *foo: "something" to *foo _: "something"
293             # wish I had a better name for _
294             $C->{$key} = { '_' => { __name => '_',
295             __text => '_',
296 1         11 value => $C->{$key}
297             }
298             };
299             }
300             }
301             else {
302 852         3200 $C->{$key} = {
303             __sorted => []
304             };
305             }
306 6603         8204 DEBUG and warn "new option key=$key tname=$tname";
307              
308 6603         12638 $C->{$key}{$tname} = { __name => $tname,
309             __text => $text,
310             value => $self->__fix_value( $value )
311             };
312 6603         10216 push @{ $C->{$key}{__sorted} }, $tname;
  6603         17604  
313             }
314              
315             ################################################
316             sub __new_group
317             {
318 28     28   91 my( $self, $name ) = @_;
319 28         82 my( $tname, $text ) = $self->__parse_name( $name );
320 28         152 $self->__push( group => { __name => $tname,
321             __text => $text
322             }
323             );
324             }
325              
326             ################################################
327             sub __end_group
328             {
329 28     28   62 my( $self, $name ) = @_;
330              
331              
332 28         46 my $S = $self->{__read_state};
333 28         81 my $data = $S->{current}[-1];
334 28 50       88 if( 'HASH' eq ref $data ) {
335 28 50       73 if( 'group' ne $data->{__type} ) { # Missing *CloseUI
336 0         0 $self->__pop( $data->{__type}, $data->{__name} );
337             }
338             }
339              
340              
341 28         79 my( $tname, $text ) = $self->__parse_name( $name );
342 28         81 $self->__pop( group => $tname );
343             }
344              
345             ################################################
346             sub __new_UI
347             {
348 161     161   333 my( $self, $name, $type ) = @_;
349 161         284 my( $tname, $text ) = $self->__parse_name( $name );
350 161         672 $self->__push( UI => { __name => $tname,
351             __text => $text,
352             __option => $type
353             }
354             );
355             }
356              
357             ################################################
358             sub __end_UI
359             {
360 161     161   267 my( $self, $name ) = @_;
361 161         352 $self->__pop( UI => $name );
362             }
363              
364             ################################################
365             sub __parse_name
366             {
367 6820     6820   10488 my( $self, $name ) = @_;
368 6820         16130 my @bits = split '/', $name, 2;
369 6820   100     14752 $bits[1] ||= $name;
370 6820         19649 return @bits;
371             }
372              
373             ################################################
374             sub __push
375             {
376 189     189   394 my( $self, $type, $data ) = @_;
377 189         329 $data->{__type} = $type;
378              
379 189         272 my $S = $self->{__read_state};
380 189         278 my $C = $S->{current}[-1];
381 189         515 $C->{$type}{ $data->{__name} } = $data;
382 189         286 push @{ $C->{"__${type}_sorted"} }, $data->{__name};
  189         612  
383              
384             # warn "PUSH $type.$data->{__name}\n";
385 189         772 $self->__new_key( "$type.$data->{__name}" );
386 189         284 push @{ $S->{current} }, $data;
  189         451  
387             }
388              
389             ################################################
390             sub __pop
391             {
392 189     189   344 my( $self, $type, $name ) = @_;
393              
394 189         284 my $S = $self->{__read_state};
395             # warn "POP $type.$name\n";
396              
397             # die "Trying to pop unknown $type $name"
398             # unless $C->{$type}{$name};
399 189         256 my $current = pop @{ $S->{current} };
  189         339  
400 189         420 $name =~ s/\s+$//;
401 189         283 $name =~ s(/.+$)();
402              
403             die "Closing $type $name that was never open"
404 189 50       754 unless $current->{__name};
405              
406             # Missing *CloseUI
407             die "Current $type is $current->{__name}, not $name"
408 189 50       514 unless $current->{__name} eq $name;
409             }
410              
411             ############################################################################
412             ## Introspection
413              
414             our $AUTOLOAD;
415             sub AUTOLOAD
416             {
417 26     26   2343 my $self = shift;
418 26         128 $AUTOLOAD =~ s/^PostScript::PPD:://;
419 26 100       2962 return if $AUTOLOAD eq 'DESTROY';
420 23         67 return $self->get( $self, $AUTOLOAD, @_ );
421             }
422              
423             sub get
424             {
425 31     31 1 74 my( $self, $D, $name, $subkey ) = @_;
426 31 100       74 if( @_ == 2 ) {
427 1         3 $name = $D;
428 1         2 $D = $self;
429             }
430              
431 31 50       93 return unless exists $D->{$name};
432 31         58 my $ret = $D->{$name};
433 31 100       69 if( ref $ret ) {
434 10 100 100     72 if( not $subkey and 'HASH' eq ref $ret and $ret->{"_"} ) {
      100        
435 1         3 $subkey = "_";
436             }
437 10 100       24 if( $subkey ) {
438 2         4 $D = $ret;
439 2         4 $name = $subkey;
440 2         5 $ret = $D->{ $name };
441             }
442 10 100       43 $ret = $self->__mk_subkey( $ret, $D, $name ) if 'HASH' eq ref $ret;
443             }
444 31         140 return $ret;
445             }
446              
447             sub __mk_subkey
448             {
449 9     9   24 my( $self, $value, $parent, $subkey ) = @_;
450 9   33     72 return PostScript::PPD::Subkey->new( $value, ($parent||$self), $subkey );
451             }
452              
453             sub Group
454             {
455 7     7 1 1594 my( $self, $name ) = @_;
456 7 50       27 if( $name eq '_default' ) {
457 0         0 my $ret = dclone $self;
458 0         0 return $self->__mk_subkey( $ret, $self, $name );
459             }
460 7         31 return $self->get( $self->{group}, $name );
461             }
462              
463             sub Groups
464             {
465 2     2 1 285 my( $self ) = @_;
466 2 50       4 my @ret = @{ $self->{__group_sorted}||[] };
  2         10  
467 2 50       8 unshift @ret, '_default' if $self->{__UI_sorted};
468 2 100       8 return @ret if wantarray;
469 1         3 return \@ret;
470             }
471              
472             ############################################################################
473             package PostScript::PPD::Subkey;
474              
475 4     4   35 use strict;
  4         20  
  4         104  
476 4     4   29 use warnings;
  4         17  
  4         154  
477 4     4   22 use Carp;
  4         9  
  4         289  
478 4     4   2823 use Data::Dumper;
  4         24542  
  4         301  
479              
480 4         45 use overload '""' => \&as_string,
481 4     4   32 fallback => 1;
  4         9  
482              
483             sub new
484             {
485 62     62   122 my( $package, $data, $parent, $subkey ) = @_;
486 62         391 my $self = bless { %$data }, $package;
487 62         241 $self->{__parent} = $parent;
488 62         111 $self->{__subkey} = $subkey;
489 62 50       132 confess "Need a subkey" unless defined $subkey;
490 62         181 return $self;
491             }
492              
493             sub default
494             {
495 2     2   4 my( $self ) = @_;
496 2 50       8 die Dumper $self unless $self->{__subkey};
497 2         9 return $self->{__parent}->get( "Default$self->{__subkey}" );
498             }
499              
500             sub as_string
501             {
502 127     127   5953 my( $self ) = @_;
503 127 100       318 return $self->{value} if $self->{value};
504 98         249 return $self;
505             }
506              
507             sub name
508             {
509 23     23   4455 my( $self ) = @_;
510 23         78 return $self->{__name};
511             }
512              
513             sub text
514             {
515 26     26   638 my( $self ) = @_;
516 26         173 return $self->{__text};
517             }
518              
519             sub option
520             {
521 0     0   0 my( $self ) = @_;
522 0         0 return $self->{__option};
523             }
524              
525              
526              
527             sub list
528             {
529 4     4   981 my( $self ) = @_;
530 4 100       21 return $self->{__sorted} unless wantarray;
531 2         2 return @{ $self->{__sorted} };
  2         16  
532             }
533              
534             sub sorted_list
535             {
536 1     1   683 my( $self ) = @_;
537 70         124 my @ret = sort { $self->{$a}{__text} cmp $self->{$b}{__text} }
538 1         3 @{ $self->{__sorted} };
  1         7  
539             }
540              
541             our $AUTOLOAD;
542             sub AUTOLOAD
543             {
544 71     71   6804 my $self = shift;
545 71         284 $AUTOLOAD =~ s/^PostScript::PPD::Subkey:://;
546 71 100       781 return if $AUTOLOAD eq 'DESTROY';
547 9         30 return $self->get( $self, $AUTOLOAD, @_ );
548             }
549              
550             sub UIs
551             {
552 4     4   987 my( $self ) = @_;
553 4 50       12 return unless $self->{__UI_sorted};
554 4 100       9 return @{ $self->{__UI_sorted} } if wantarray;
  2         9  
555 2         2 return [ @{ $self->{__UI_sorted} } ];
  2         8  
556             }
557              
558             sub UI
559             {
560 10     10   256 my( $self, $name, $subkey ) = @_;
561 10         29 return $self->get( $self->{UI}, $name, $subkey );
562             }
563              
564             sub get
565             {
566 58     58   877 my( $self, $D, $name, $subkey ) = @_;
567 58 100       138 if( @_ == 2 ) {
568 39         59 $name = $D;
569 39         59 $D = $self;
570             }
571              
572 58 100       153 if( $name =~ s/^UI\.// ) {
573 9         21 $D = $self->{UI};
574             }
575              
576 58 100       138 return unless exists $D->{$name};
577 57         101 my $ret = $D->{$name};
578 57 100       122 if( ref $ret ) {
579 53 50       107 if( $subkey ) {
580 0         0 $ret = $ret->{ $subkey };
581             }
582 53 50       152 $ret = $self->__mk_subkey( $ret, $D, $name ) if 'HASH' eq ref $ret;
583             }
584 57         142 return $ret;
585             }
586              
587             sub __mk_subkey
588             {
589 53     53   106 my( $self, $value, $parent, $subkey ) = @_;
590 53   33     146 return PostScript::PPD::Subkey->new( $value, ($parent||$self), $subkey );
591             }
592              
593             sub Dump
594             {
595 0     0     my( $self ) = @_;
596 0           local $self->{__parent} = $self->{__parent}{__name};
597 0           return Dumper $self;
598             }
599              
600              
601             1;
602             __END__