File Coverage

blib/lib/PYX/Parser.pm
Criterion Covered Total %
statement 82 99 82.8
branch 28 40 70.0
condition 5 12 41.6
subroutine 12 13 92.3
pod 5 5 100.0
total 132 169 78.1


line stmt bran cond sub pod time code
1             package PYX::Parser;
2              
3 8     8   218910 use strict;
  8         91  
  8         255  
4 8     8   40 use warnings;
  8         14  
  8         221  
5              
6 8     8   1560 use Class::Utils qw(set_params);
  8         29142  
  8         292  
7 8     8   3959 use Encode qw(decode);
  8         67536  
  8         531  
8 8     8   52 use Error::Pure qw(err);
  8         14  
  8         291  
9 8     8   43 use Readonly;
  8         14  
  8         9605  
10              
11             # Constants.
12             Readonly::Scalar my $EMPTY_STR => q{};
13              
14             our $VERSION = 0.09;
15              
16             # Constructor.
17             sub new {
18 8     8 1 4931 my ($class, @params) = @_;
19 8         26 my $self = bless {}, $class;
20              
21             # Parse callbacks.
22             $self->{'callbacks'} = {
23             'attribute' => undef,
24             'comment' => undef,
25             'data' => undef,
26             'end_element' => undef,
27             'final' => undef,
28             'init' => undef,
29             'instruction' => undef,
30             'rewrite' => undef,
31             'start_element' => undef,
32             'other' => undef,
33             },
34              
35             # Input encoding.
36 8         125 $self->{'input_encoding'} = 'utf-8';
37              
38             # Non parser options.
39 8         21 $self->{'non_parser_options'} = {};
40              
41             # Output encoding.
42 8         22 $self->{'output_encoding'} = 'utf-8';
43              
44             # Output handler.
45 8         23 $self->{'output_handler'} = \*STDOUT;
46              
47             # Output rewrite.
48 8         13 $self->{'output_rewrite'} = 0;
49              
50             # Process params.
51 8         48 set_params($self, @params);
52              
53             # Check output handler.
54 6 50 33     174 if (defined $self->{'output_handler'}
55             && ref $self->{'output_handler'} ne 'GLOB') {
56              
57 0         0 err 'Bad output handler.';
58             }
59              
60             # Processing line.
61 6         18 $self->{'_line'} = $EMPTY_STR;
62              
63             # Object.
64 6         19 return $self;
65             }
66              
67             # Get actual parsing line.
68             sub line {
69 0     0 1 0 my $self = shift;
70              
71 0         0 return $self->{'_line'};
72             }
73              
74             # Parse PYX text or array of PYX text.
75             sub parse {
76 1     1 1 446 my ($self, $pyx, $out) = @_;
77              
78 1 50       6 if (! defined $out) {
79 1         3 $out = $self->{'output_handler'};
80             }
81              
82             # Input data.
83 1         2 my @text;
84 1 50       4 if (ref $pyx eq 'ARRAY') {
85 0         0 @text = @{$pyx};
  0         0  
86             } else {
87 1         20 @text = split /\n/ms, $pyx;
88             }
89              
90             # Parse.
91 1 50       17 if ($self->{'callbacks'}->{'init'}) {
92 0         0 &{$self->{'callbacks'}->{'init'}}($self);
  0         0  
93             }
94 1         7 foreach my $line (@text) {
95 7         17 $self->_parse($line, $out);
96             }
97 1 50       6 if ($self->{'callbacks'}->{'final'}) {
98 0         0 &{$self->{'callbacks'}->{'final'}}($self);
  0         0  
99             }
100              
101 1         148 return;
102             }
103              
104             # Parse file with PYX data.
105             sub parse_file {
106 16     16 1 20158 my ($self, $input_file, $out) = @_;
107              
108 16         730 open my $inf, '<', $input_file;
109 16         96 $self->parse_handler($inf, $out);
110 16         156 close $inf;
111              
112 16         549 return;
113             }
114              
115             # Parse PYX handler.
116             sub parse_handler {
117 16     16 1 46 my ($self, $input_file_handler, $out) = @_;
118              
119 16 50 33     140 if (! $input_file_handler || ref $input_file_handler ne 'GLOB') {
120 0         0 err 'No input handler.';
121             }
122 16 50       107 if (! defined $out) {
123 16         37 $out = $self->{'output_handler'};
124             }
125 16 50       44 if ($self->{'callbacks'}->{'init'}) {
126 0         0 &{$self->{'callbacks'}->{'init'}}($self);
  0         0  
127             }
128 16         431 while (my $line = <$input_file_handler>) {
129 39         101 chomp $line;
130 39         132 $line = decode($self->{'input_encoding'}, $line);
131 39         5625 $self->_parse($line, $out);
132             }
133 16 50       80 if ($self->{'callbacks'}->{'final'}) {
134 0         0 &{$self->{'callbacks'}->{'final'}}($self);
  0         0  
135             }
136              
137 16         31 return;
138             }
139              
140             # Parse text string.
141             sub _parse {
142 46     46   112 my ($self, $line, $out) = @_;
143              
144 46         82 $self->{'_line'} = $line;
145 46         316 my ($type, $value) = $line =~ m/\A([A()\?\-_])(.*)\Z/;
146 46 100       132 if (! $type) {
147 4         16 $type = 'X';
148             }
149              
150             # Attribute.
151 46 100       280 if ($type eq 'A') {
    100          
    100          
    100          
    100          
    100          
152 7         64 my ($att, $attval) = $line =~ m/\AA([^\s]+)\s*(.*)\Z/;
153 7         26 $self->_is_sub('attribute', $out, $att, $attval);
154              
155             # Start of element.
156             } elsif ($type eq '(') {
157 10         27 $self->_is_sub('start_element', $out, $value);
158              
159             # End of element.
160             } elsif ($type eq ')') {
161 6         24 $self->_is_sub('end_element', $out, $value);
162              
163             # Data.
164             } elsif ($type eq '-') {
165 7         22 $self->_is_sub('data', $out, $value);
166              
167             # Instruction.
168             } elsif ($type eq '?') {
169 6         53 my ($target, $data) = $line =~ m/\A\?([^\s]+)\s*(.*)\Z/;
170 6         24 $self->_is_sub('instruction', $out, $target, $data);
171              
172             # Comment.
173             } elsif ($type eq '_') {
174 6         18 $self->_is_sub('comment', $out, $value);
175              
176             # Others.
177             } else {
178 4 50       21 if ($self->{'callbacks'}->{'other'}) {
179 4         15 &{$self->{'callbacks'}->{'other'}}($self, $line);
  4         13  
180             } else {
181 0         0 err "Bad PYX line '$line'.";
182             }
183             }
184              
185 46         3694 return;
186             }
187              
188             # Helper to defined callbacks.
189             sub _is_sub {
190 42     42   113 my ($self, $key, $out, @values) = @_;
191              
192             # Callback with name '$key'.
193 42 100 66     270 if (exists $self->{'callbacks'}->{$key}
    50 33        
    50          
194             && ref $self->{'callbacks'}->{$key} eq 'CODE') {
195              
196 24         47 &{$self->{'callbacks'}->{$key}}($self, @values);
  24         86  
197              
198             # Rewrite callback.
199             } elsif (exists $self->{'callbacks'}->{'rewrite'}
200             && ref $self->{'callbacks'}->{'rewrite'} eq 'CODE') {
201              
202 0         0 &{$self->{'callbacks'}->{'rewrite'}}($self, $self->{'_line'});
  0         0  
203              
204             # Raw output to output file handler.
205             } elsif ($self->{'output_rewrite'}) {
206             my $encoded_line = Encode::encode(
207             $self->{'output_encoding'},
208 18         42 $self->{'_line'},
209             );
210 18         729 print {$out} $encoded_line, "\n";
  18         532  
211             }
212              
213 42         24274 return;
214             }
215              
216             1;
217              
218             __END__