File Coverage

blib/lib/PYX/Parser.pm
Criterion Covered Total %
statement 69 97 71.1
branch 24 40 60.0
condition 5 12 41.6
subroutine 11 13 84.6
pod 5 5 100.0
total 114 167 68.2


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