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   3363 use strict;
  4         8  
  4         120  
5 4     4   23 use warnings;
  4         7  
  4         106  
6              
7 4     4   2405 use Compress::Zlib qw( gzopen );
  4         250500  
  4         350  
8 4     4   38 use Carp qw( carp croak confess cluck );
  4         9  
  4         221  
9 4     4   2653 use Storable qw( dclone );
  4         12792  
  4         239  
10 4     4   30 use IO::File;
  4         9  
  4         12900  
11              
12             our $VERSION = '0.0401';
13              
14             sub DEBUG () { 0 }
15              
16             ################################################
17             sub new
18             {
19 3     3 1 1749 my( $package, $file ) = @_;
20 3         12 my $self = bless { file => $file }, $package;
21 3 50       11 $self->load if $file;
22 3         9 return $self;
23             }
24              
25             ################################################
26             sub load
27             {
28 11     11 1 922 my( $self, $file ) = @_;
29             croak "Usage: $self->load( [ $file ] );"
30 11 0 33     39 unless $file or $self->{file};
31            
32 11   33     30 $file ||= $self->{file};
33 11 50       379 return unless $file;
34              
35 11         148 delete @{ $self }{ keys %$self };
  11         2831  
36              
37 11         110 local $self->{__read_state};
38 11         49 local $self->{__position} = { file=>$file, line=>0};
39              
40 11         20 eval {
41 11 100       49 if( $file =~ /\.gz$/ ) {
42 2         13 my $gz = gzopen( $file, "rb" );
43 2 50       5213 croak "Unable to read $file: $!" unless $gz;
44 2         6 $self->{file} = $file;
45              
46 2         5 my( $line, $size );
47 2         8 while( $size = $gz->gzreadline( $line ) ) {
48 588         59281 $self->{__position}{line}++;
49 588         1035 $self->__read_line( $line );
50             }
51             }
52             else {
53 9         104 my $fh = IO::File->new( $file );
54 9 50       1010 croak "Unable to read $file: $!" unless $fh;
55 9         28 $self->{file} = $file;
56 9         757 while( <$fh> ) {
57 11352         17769 $self->{__position}{line}++;
58 11352         18967 $self->__read_line( $_ );
59             }
60             }
61             };
62 11 50       753 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   22453 my( $self, $line ) = @_;
91              
92 11940   100     22723 $self->{__read_state} ||= { state => 0,
93             value => '',
94             key => '',
95             current => [ $self ]
96             };
97 11940         16526 my $S = $self->{__read_state};
98              
99 11940 100       19533 if( $S->{key} ) {
100 1674 100 100     5215 if( $line =~ /^\*/ and $S->{value} =~ /^".*"\s*$/s ) {
101 313         602 $self->__new_tupple;
102             }
103             else {
104 1361         2803 $self->__append( $line );
105 1361         3197 return;
106             }
107             }
108              
109             # comment
110 10579 100       21177 return if $line =~ /^\*%/;
111             # End a multi-line tupple
112 10065 100       17540 if( $line =~ /^\*End\s*$/ ) {
113 374         837 $self->__new_tupple; # missing *End?
114 374         1052 return;
115             }
116              
117             # Start a config group
118 9691 100       15905 if( $line =~ /^\*OpenGroup:\s*(.+)/ ) {
119 28         64 my $name = $1;
120 28         87 $self->__new_tupple; # missing *End?
121 28         79 $self->__new_group( $name );
122 28         82 return;
123             }
124             # End a config group
125 9663 100       16081 if( $line =~ /^\*CloseGroup:\s*(.+)/ ) {
126 28         62 my $name = $1;
127 28         64 $self->__new_tupple; # missing *End?
128 28         93 $self->__end_group( $name );
129 28         86 return;
130             }
131             # Open a UI option
132 9635 100       17246 if( $line =~ /^\*(?:JCL)?OpenUI\s*\*(.+?):\s*(.+)/ ) {
133 161         453 my( $name, $value ) = ( $1, $2 );
134 161         356 $self->__new_tupple; # missing *End?
135 161         426 $self->__new_UI( $name, $value );
136 161         483 return;
137             }
138             # End the UI option
139 9474 100       16334 if( $line =~ /^\*(?:JCL)?CloseUI:?\s*\*(.+)/ ) {
140 161         337 my $name = $1;
141 161         352 $self->__new_tupple; # missing *End?
142 161         375 $self->__end_UI( $name );
143 161         395 return;
144             }
145              
146             # Translation tupple, that contains bad UTF-8 (Gestetner)
147 9313 50       17869 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       32695 if( $line =~ /^(\*\s*([^:]+):\s*)/ ) {
157 8867         23937 my( $used, $name ) = ( $1, $2 );
158 8867         18787 $self->__new_tupple; # missing *End?
159              
160 8867         11887 $used = length $used;
161 8867         12641 $S->{key} = $name;
162 8867         11948 $S->{value} = '';
163 8867         14965 local $S->{first} = 1;
164 8867         25043 $self->__append( substr $line, $used );
165 8867         33289 return;
166             }
167 446 50       1725 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   22883 my( $self, $line ) = @_;
176              
177 10228         16569 my $S = $self->{__read_state};
178 10228         13432 my $exit = 0;
179 10228 100       18738 $exit = 1 if not $S->{value};
180              
181             # *Something: "honk" <- here
182 10228 100       33027 if( $line =~ m/^"(.*)" *$/ ) {
    100          
    50          
    100          
183 5880         8928 $S->{quoted} = 1;
184 5880         7625 $exit = 1;
185             }
186             # *Something: "honk
187             # " <- here
188             # *End
189             # *Something: "<- here
190             # "
191             # *End
192             elsif( $line =~ m/^"/ ) {
193 441         689 $S->{quoted} = 1;
194 441         1087 $exit = ( $line =~ /" *$/ );
195 441 100       921 $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         3196 $line =~ s/ +$//;
211             }
212              
213             # *Something: "
214             # Ho&& <- here
215             # nk"
216 10228 100       20759 if( $line =~ s/&&\s*$// ) {
217 87         134 $exit = 0;
218             }
219              
220 10228         19614 $S->{value} .= $line;
221              
222 10228 100       17525 if( $exit ) {
223 8554         17898 $self->__new_tupple;
224 8554         13138 return;
225             }
226             }
227              
228             ################################################
229             sub __new_tupple
230             {
231 18486     18486   27021 my( $self ) = @_;
232              
233 18486         26104 my $S = $self->{__read_state};
234 18486 100       33642 return unless $S->{key};
235              
236 8867 100       15570 chomp( $S->{value} ) unless $S->{quoted};
237              
238 8867         13044 my $C = $S->{current}[-1];
239 8867 100       31824 if( $S->{key} =~ /^([^ ]+)\s+(.+(\/.+)?)$/ ) {
240 6603         13612 $self->__new_option( $1, $2, $S->{value} );
241             }
242             else {
243 2264         3884 my $v = $self->__fix_value( $S->{value} );
244 2264         3503 my $k = $S->{key};
245 2264 100       3900 if( $C->{ $k } ) {
246 1487 100       3059 $C->{ $k } = [ $C->{$k} ] unless ref $C->{$k};
247 1487         1874 push @{ $C->{$k} }, $v;
  1487         3115  
248             }
249             else {
250 777         1875 $C->{ $k } = $v;
251             }
252 2264   100     4557 $C->{__sorted} ||= [];
253 2264         3732 $self->__new_key( $k );
254             }
255 8867         13475 $S->{key} = '';
256 8867         11826 $S->{value} = '';
257 8867         13087 $S->{quoted} = 0;
258             }
259              
260             sub __fix_value
261             {
262 8867     8867   14654 my( $self, $v ) = @_;
263 8867 100       39196 if( $v eq 'False' ) {
    100          
264 41         81 return 0;
265             }
266             elsif( $v =~ s/"(.*)"\s*/$1/s ) {
267 6889         11775 $v =~ s/"?/"/g;
268             }
269 8826         34010 return $v;
270             }
271              
272             sub __new_key
273             {
274 9056     9056   13845 my( $self, $key ) = @_;
275 9056         12144 my $S = $self->{__read_state};
276 9056         11834 my $C = $S->{current}[-1];
277 9056 100       19023 push @{ $C->{__sorted} }, $key unless exists $C->{$key};
  1041         2407  
278             }
279              
280             ################################################
281             sub __new_option
282             {
283 6603     6603   21385 my( $self, $key, $name, $value ) = @_;
284 6603         12836 my( $tname, $text ) = $self->__parse_name( $name );
285 6603         10975 my $S = $self->{__read_state};
286 6603         9126 my $C = $S->{current}[-1];
287              
288 6603         14352 $self->__new_key( $key );
289              
290 6603 100       11994 if( $C->{$key} ) {
291 5751 100       12796 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         17 value => $C->{$key}
297             }
298             };
299             }
300             }
301             else {
302 852         3409 $C->{$key} = {
303             __sorted => []
304             };
305             }
306 6603         8731 DEBUG and warn "new option key=$key tname=$tname";
307              
308 6603         11299 $C->{$key}{$tname} = { __name => $tname,
309             __text => $text,
310             value => $self->__fix_value( $value )
311             };
312 6603         10431 push @{ $C->{$key}{__sorted} }, $tname;
  6603         17190  
313             }
314              
315             ################################################
316             sub __new_group
317             {
318 28     28   62 my( $self, $name ) = @_;
319 28         67 my( $tname, $text ) = $self->__parse_name( $name );
320 28         139 $self->__push( group => { __name => $tname,
321             __text => $text
322             }
323             );
324             }
325              
326             ################################################
327             sub __end_group
328             {
329 28     28   72 my( $self, $name ) = @_;
330              
331              
332 28         42 my $S = $self->{__read_state};
333 28         44 my $data = $S->{current}[-1];
334 28 50       82 if( 'HASH' eq ref $data ) {
335 28 50       69 if( 'group' ne $data->{__type} ) { # Missing *CloseUI
336 0         0 $self->__pop( $data->{__type}, $data->{__name} );
337             }
338             }
339              
340              
341 28         92 my( $tname, $text ) = $self->__parse_name( $name );
342 28         73 $self->__pop( group => $tname );
343             }
344              
345             ################################################
346             sub __new_UI
347             {
348 161     161   301 my( $self, $name, $type ) = @_;
349 161         286 my( $tname, $text ) = $self->__parse_name( $name );
350 161         691 $self->__push( UI => { __name => $tname,
351             __text => $text,
352             __option => $type
353             }
354             );
355             }
356              
357             ################################################
358             sub __end_UI
359             {
360 161     161   288 my( $self, $name ) = @_;
361 161         326 $self->__pop( UI => $name );
362             }
363              
364             ################################################
365             sub __parse_name
366             {
367 6820     6820   10473 my( $self, $name ) = @_;
368 6820         16158 my @bits = split '/', $name, 2;
369 6820   100     15147 $bits[1] ||= $name;
370 6820         18739 return @bits;
371             }
372              
373             ################################################
374             sub __push
375             {
376 189     189   341 my( $self, $type, $data ) = @_;
377 189         344 $data->{__type} = $type;
378              
379 189         265 my $S = $self->{__read_state};
380 189         274 my $C = $S->{current}[-1];
381 189         533 $C->{$type}{ $data->{__name} } = $data;
382 189         265 push @{ $C->{"__${type}_sorted"} }, $data->{__name};
  189         565  
383              
384             # warn "PUSH $type.$data->{__name}\n";
385 189         653 $self->__new_key( "$type.$data->{__name}" );
386 189         286 push @{ $S->{current} }, $data;
  189         405  
387             }
388              
389             ################################################
390             sub __pop
391             {
392 189     189   360 my( $self, $type, $name ) = @_;
393              
394 189         269 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         242 my $current = pop @{ $S->{current} };
  189         377  
400 189         391 $name =~ s/\s+$//;
401 189         285 $name =~ s(/.+$)();
402              
403             die "Closing $type $name that was never open"
404 189 50       406 unless $current->{__name};
405              
406             # Missing *CloseUI
407             die "Current $type is $current->{__name}, not $name"
408 189 50       494 unless $current->{__name} eq $name;
409             }
410              
411             ############################################################################
412             ## Introspection
413              
414             our $AUTOLOAD;
415             sub AUTOLOAD
416             {
417 26     26   2505 my $self = shift;
418 26         133 $AUTOLOAD =~ s/^PostScript::PPD:://;
419 26 100       2978 return if $AUTOLOAD eq 'DESTROY';
420 23         69 return $self->get( $self, $AUTOLOAD, @_ );
421             }
422              
423             sub get
424             {
425 31     31 1 80 my( $self, $D, $name, $subkey ) = @_;
426 31 100       75 if( @_ == 2 ) {
427 1         4 $name = $D;
428 1         3 $D = $self;
429             }
430              
431 31 50       79 return unless exists $D->{$name};
432 31         61 my $ret = $D->{$name};
433 31 100       70 if( ref $ret ) {
434 10 100 100     73 if( not $subkey and 'HASH' eq ref $ret and $ret->{"_"} ) {
      100        
435 1         3 $subkey = "_";
436             }
437 10 100       25 if( $subkey ) {
438 2         4 $D = $ret;
439 2         5 $name = $subkey;
440 2         5 $ret = $D->{ $name };
441             }
442 10 100       42 $ret = $self->__mk_subkey( $ret, $D, $name ) if 'HASH' eq ref $ret;
443             }
444 31         135 return $ret;
445             }
446              
447             sub __mk_subkey
448             {
449 9     9   27 my( $self, $value, $parent, $subkey ) = @_;
450 9   33     69 return PostScript::PPD::Subkey->new( $value, ($parent||$self), $subkey );
451             }
452              
453             sub Group
454             {
455 7     7 1 1084 my( $self, $name ) = @_;
456 7 50       67 if( $name eq '_default' ) {
457 0         0 my $ret = dclone $self;
458 0         0 return $self->__mk_subkey( $ret, $self, $name );
459             }
460 7         32 return $self->get( $self->{group}, $name );
461             }
462              
463             sub Groups
464             {
465 2     2 1 296 my( $self ) = @_;
466 2 50       4 my @ret = @{ $self->{__group_sorted}||[] };
  2         11  
467 2 50       6 unshift @ret, '_default' if $self->{__UI_sorted};
468 2 100       9 return @ret if wantarray;
469 1         3 return \@ret;
470             }
471              
472             ############################################################################
473             package PostScript::PPD::Subkey;
474              
475 4     4   43 use strict;
  4         10  
  4         130  
476 4     4   23 use warnings;
  4         11  
  4         163  
477 4     4   24 use Carp;
  4         8  
  4         253  
478 4     4   2546 use Data::Dumper;
  4         24125  
  4         277  
479              
480 4         45 use overload '""' => \&as_string,
481 4     4   31 fallback => 1;
  4         8  
482              
483             sub new
484             {
485 62     62   127 my( $package, $data, $parent, $subkey ) = @_;
486 62         400 my $self = bless { %$data }, $package;
487 62         256 $self->{__parent} = $parent;
488 62         107 $self->{__subkey} = $subkey;
489 62 50       123 confess "Need a subkey" unless defined $subkey;
490 62         140 return $self;
491             }
492              
493             sub default
494             {
495 2     2   6 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   5881 my( $self ) = @_;
503 127 100       312 return $self->{value} if $self->{value};
504 98         232 return $self;
505             }
506              
507             sub name
508             {
509 23     23   4349 my( $self ) = @_;
510 23         78 return $self->{__name};
511             }
512              
513             sub text
514             {
515 26     26   644 my( $self ) = @_;
516 26         175 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   975 my( $self ) = @_;
530 4 100       21 return $self->{__sorted} unless wantarray;
531 2         4 return @{ $self->{__sorted} };
  2         19  
532             }
533              
534             sub sorted_list
535             {
536 1     1   2236 my( $self ) = @_;
537 70         128 my @ret = sort { $self->{$a}{__text} cmp $self->{$b}{__text} }
538 1         2 @{ $self->{__sorted} };
  1         9  
539             }
540              
541             our $AUTOLOAD;
542             sub AUTOLOAD
543             {
544 71     71   6625 my $self = shift;
545 71         278 $AUTOLOAD =~ s/^PostScript::PPD::Subkey:://;
546 71 100       767 return if $AUTOLOAD eq 'DESTROY';
547 9         32 return $self->get( $self, $AUTOLOAD, @_ );
548             }
549              
550             sub UIs
551             {
552 4     4   957 my( $self ) = @_;
553 4 50       13 return unless $self->{__UI_sorted};
554 4 100       13 return @{ $self->{__UI_sorted} } if wantarray;
  2         8  
555 2         3 return [ @{ $self->{__UI_sorted} } ];
  2         24  
556             }
557              
558             sub UI
559             {
560 10     10   250 my( $self, $name, $subkey ) = @_;
561 10         29 return $self->get( $self->{UI}, $name, $subkey );
562             }
563              
564             sub get
565             {
566 58     58   769 my( $self, $D, $name, $subkey ) = @_;
567 58 100       132 if( @_ == 2 ) {
568 39         61 $name = $D;
569 39         88 $D = $self;
570             }
571              
572 58 100       165 if( $name =~ s/^UI\.// ) {
573 9         68 $D = $self->{UI};
574             }
575              
576 58 100       144 return unless exists $D->{$name};
577 57         91 my $ret = $D->{$name};
578 57 100       127 if( ref $ret ) {
579 53 50       111 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         151 return $ret;
585             }
586              
587             sub __mk_subkey
588             {
589 53     53   117 my( $self, $value, $parent, $subkey ) = @_;
590 53   33     150 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__