File Coverage

blib/lib/PYX/Parser.pm
Criterion Covered Total %
statement 71 99 71.7
branch 24 40 60.0
condition 5 12 41.6
subroutine 11 13 84.6
pod 5 5 100.0
total 116 169 68.6


line stmt bran cond sub pod time code
1             package PYX::Parser;
2              
3 7     7   241415 use strict;
  7         106  
  7         202  
4 7     7   36 use warnings;
  7         20  
  7         270  
5              
6 7     7   1502 use Class::Utils qw(set_params);
  7         81940  
  7         233  
7 7     7   3261 use Encode qw(decode);
  7         53507  
  7         475  
8 7     7   51 use Error::Pure qw(err);
  7         15  
  7         291  
9 7     7   83 use Readonly;
  7         13  
  7         7443  
10              
11             # Constants.
12             Readonly::Scalar my $EMPTY_STR => q{};
13              
14             our $VERSION = 0.08;
15              
16             # Constructor.
17             sub new {
18 7     7 1 5454 my ($class, @params) = @_;
19 7         20 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 7         119 $self->{'input_encoding'} = 'utf-8';
37              
38             # Non parser options.
39 7         20 $self->{'non_parser_options'} = {};
40              
41             # Output encoding.
42 7         16 $self->{'output_encoding'} = 'utf-8';
43              
44             # Output handler.
45 7         17 $self->{'output_handler'} = \*STDOUT;
46              
47             # Output rewrite.
48 7         15 $self->{'output_rewrite'} = 0;
49              
50             # Process params.
51 7         50 set_params($self, @params);
52              
53             # Check output handler.
54 5 50 33     114 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 5         15 $self->{'_line'} = $EMPTY_STR;
62              
63             # Object.
64 5         14 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 0     0 1 0 my ($self, $pyx, $out) = @_;
77              
78 0 0       0 if (! defined $out) {
79 0         0 $out = $self->{'output_handler'};
80             }
81              
82             # Input data.
83 0         0 my @text;
84 0 0       0 if (ref $pyx eq 'ARRAY') {
85 0         0 @text = @{$pyx};
  0         0  
86             } else {
87 0         0 @text = split /\n/ms, $pyx;
88             }
89              
90             # Parse.
91 0 0       0 if ($self->{'callbacks'}->{'init'}) {
92 0         0 &{$self->{'callbacks'}->{'init'}}($self);
  0         0  
93             }
94 0         0 foreach my $line (@text) {
95 0         0 $self->_parse($line, $out);
96             }
97 0 0       0 if ($self->{'callbacks'}->{'final'}) {
98 0         0 &{$self->{'callbacks'}->{'final'}}($self);
  0         0  
99             }
100              
101 0         0 return;
102             }
103              
104             # Parse file with PYX data.
105             sub parse_file {
106 16     16 1 22288 my ($self, $input_file, $out) = @_;
107              
108 16         689 open my $inf, '<', $input_file;
109 16         88 $self->parse_handler($inf, $out);
110 16         152 close $inf;
111              
112 16         473 return;
113             }
114              
115             # Parse PYX handler.
116             sub parse_handler {
117 16     16 1 48 my ($self, $input_file_handler, $out) = @_;
118              
119 16 50 33     138 if (! $input_file_handler || ref $input_file_handler ne 'GLOB') {
120 0         0 err 'No input handler.';
121             }
122 16 50       84 if (! defined $out) {
123 16         40 $out = $self->{'output_handler'};
124             }
125 16 50       52 if ($self->{'callbacks'}->{'init'}) {
126 0         0 &{$self->{'callbacks'}->{'init'}}($self);
  0         0  
127             }
128 16         266 while (my $line = <$input_file_handler>) {
129 39         92 chomp $line;
130 39         134 $line = decode($self->{'input_encoding'}, $line);
131 39         4885 $self->_parse($line, $out);
132             }
133 16 50       65 if ($self->{'callbacks'}->{'final'}) {
134 0         0 &{$self->{'callbacks'}->{'final'}}($self);
  0         0  
135             }
136              
137 16         33 return;
138             }
139              
140             # Parse text string.
141             sub _parse {
142 39     39   82 my ($self, $line, $out) = @_;
143              
144 39         64 $self->{'_line'} = $line;
145 39         241 my ($type, $value) = $line =~ m/\A([A()\?\-_])(.*)\Z/;
146 39 100       102 if (! $type) {
147 3         7 $type = 'X';
148             }
149              
150             # Attribute.
151 39 100       168 if ($type eq 'A') {
    100          
    100          
    100          
    100          
    100          
152 6         38 my ($att, $attval) = $line =~ m/\AA([^\s]+)\s*(.*)\Z/;
153 6         78 $self->_is_sub('attribute', $out, $att, $attval);
154              
155             # Start of element.
156             } elsif ($type eq '(') {
157 9         29 $self->_is_sub('start_element', $out, $value);
158              
159             # End of element.
160             } elsif ($type eq ')') {
161 5         13 $self->_is_sub('end_element', $out, $value);
162              
163             # Data.
164             } elsif ($type eq '-') {
165 6         18 $self->_is_sub('data', $out, $value);
166              
167             # Instruction.
168             } elsif ($type eq '?') {
169 5         35 my ($target, $data) = $line =~ m/\A\?([^\s]+)\s*(.*)\Z/;
170 5         19 $self->_is_sub('instruction', $out, $target, $data);
171              
172             # Comment.
173             } elsif ($type eq '_') {
174 5         17 $self->_is_sub('comment', $out, $value);
175              
176             # Others.
177             } else {
178 3 50       14 if ($self->{'callbacks'}->{'other'}) {
179 3         7 &{$self->{'callbacks'}->{'other'}}($self, $line);
  3         11  
180             } else {
181 0         0 err "Bad PYX line '$line'.";
182             }
183             }
184              
185 39         2532 return;
186             }
187              
188             # Helper to defined callbacks.
189             sub _is_sub {
190 36     36   83 my ($self, $key, $out, @values) = @_;
191              
192             # Callback with name '$key'.
193 36 100 66     235 if (exists $self->{'callbacks'}->{$key}
    50 33        
    50          
194             && ref $self->{'callbacks'}->{$key} eq 'CODE') {
195              
196 18         24 &{$self->{'callbacks'}->{$key}}($self, @values);
  18         53  
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         55 $self->{'_line'},
209             );
210 18         760 print {$out} $encoded_line, "\n";
  18         568  
211             }
212              
213 36         16701 return;
214             }
215              
216             1;
217              
218             __END__