File Coverage

blib/lib/Regexp/RegGrp.pm
Criterion Covered Total %
statement 144 163 88.3
branch 31 44 70.4
condition 10 14 71.4
subroutine 21 21 100.0
pod 1 12 8.3
total 207 254 81.5


line stmt bran cond sub pod time code
1             package Regexp::RegGrp;
2              
3 3     3   3059 use 5.008009;
  3         8  
4 3     3   9 use warnings;
  3         3  
  3         61  
5 3     3   9 use strict;
  3         3  
  3         42  
6 3     3   12 use Carp;
  3         2  
  3         133  
7 3     3   12 use Regexp::RegGrp::Data;
  3         3  
  3         122  
8              
9             BEGIN {
10 3 50   3   317 if ( $] < 5.010000 ) {
11 0         0 require re;
12 0         0 re->import( 'eval' );
13             }
14             }
15              
16             use constant {
17 3         2197 ESCAPE_BRACKETS => qr~(?<])~,
18             ESCAPE_CHARS => qr~\\.~,
19             BRACKETS => qr~\(~,
20             BACK_REF => qr~(?:\\g?(\d\d*)|\\g\{(\d+)\})~
21 3     3   19 };
  3         3  
22              
23             # =========================================================================== #
24              
25             our $VERSION = '2.01';
26              
27             sub new {
28 14     14 0 5127 my ( $class, $in_ref ) = @_;
29 14         15 my $self = {};
30              
31 14         17 bless( $self, $class );
32              
33 14 100       35 if ( ref( $in_ref ) ne 'HASH' ) {
34 1         151 carp( 'First argument must be a hashref!' );
35 1         7 return;
36             }
37              
38 13 50       22 unless ( exists( $in_ref->{reggrp} ) ) {
39 0         0 carp( 'Key "reggrp" does not exist in input hashref!' );
40 0         0 return;
41             }
42              
43 13 50       22 if ( ref( $in_ref->{reggrp} ) ne 'ARRAY' ) {
44 0         0 carp( 'Value for key "reggrp" must be an arrayref!' );
45 0         0 return;
46             }
47              
48 13 50 66     29 if (
49             ref( $in_ref->{restore_pattern} ) and
50             ref( $in_ref->{restore_pattern} ) ne 'Regexp'
51             ) {
52 0         0 carp( 'Value for key "restore_pattern" must be a scalar or regexp!' );
53 0         0 return;
54             }
55              
56 13         9 my $no = 0;
57              
58             map {
59 38         28 $no++;
60              
61             my $reggrp_data = Regexp::RegGrp::Data->new(
62             {
63             regexp => $_->{regexp},
64             replacement => $_->{replacement},
65             store => $_->{store},
66             modifier => $_->{modifier},
67             restore_pattern => $in_ref->{restore_pattern}
68             }
69 38         133 );
70              
71 38 50       84 unless ( $reggrp_data ) {
72 0         0 carp( 'RegGrp No ' . $no . ' in arrayref is malformed!' );
73 0         0 return;
74             }
75              
76 38         43 $self->reggrp_add( $reggrp_data );
77 13         9 } @{$in_ref->{reggrp}};
  13         26  
78              
79 13   66     41 my $restore_pattern = $in_ref->{restore_pattern} || qr~\x01(\d+)\x01~;
80 13         26 $self->{_restore_pattern} = qr/$restore_pattern/;
81              
82 13         11 my $offset = 1;
83 13         12 my $midx = 0;
84              
85             # In perl versions < 5.10 hash %+ doesn't exist, so we have to initialize it
86             $self->{_re_str} = ( ( $] < 5.010000 ) ? '(?{ %+ = (); })' : '' ) . join(
87             '|',
88             map {
89 13 50       25 my $re = $_->regexp();
  38         71  
90             # Count backref brackets
91 38         30 $re =~ s/${\(ESCAPE_CHARS)}//g;
  38         135  
92 38         30 $re =~ s/${\(ESCAPE_BRACKETS)}//g;
  38         168  
93 38         28 my @nparen = $re =~ /${\(BRACKETS)}/g;
  38         105  
94              
95 38         68 $re = $_->regexp();
96              
97 38         32 my $backref_pattern = '\\g{%d}';
98              
99 38 50       56 if ( $] < 5.010000 ) {
100 0         0 $backref_pattern = '\\%d';
101             }
102              
103 38   66     26 $re =~ s/${\(BACK_REF)}/sprintf( $backref_pattern, $offset + ( $1 || $2 ) )/eg;
  12         70  
  38         135  
104              
105 38         30 my $ret;
106              
107 38 50       47 if ( $] < 5.010000 ) {
108             # In perl versions < 5.10 we need to fill %+ hash manually
109             # perl 5.8 doesn't reset the %+ hash correctly if there are zero-length submatches
110             # so this is also done here
111 0         0 $ret = '(' . $re . ')' . '(?{ %+ = ( \'_' . $midx++ . '\' => $^N ); })';
112             }
113             else {
114 38         60 $ret = '(?\'_' . $midx++ . '\'' . $re . ')';
115             }
116              
117 38         35 $offset += scalar( @nparen ) + 1;
118              
119 38         85 $ret;
120              
121             } $self->reggrp_array()
122             );
123              
124 13         30 return $self;
125             }
126              
127             # re_str methods
128              
129             sub re_str {
130 26     26 0 23 my $self = shift;
131              
132 26         333 return $self->{_re_str};
133             }
134              
135             # /re_str methods
136              
137             # restore_pattern methods
138              
139             sub restore_pattern {
140 12     12 1 7 my $self = shift;
141              
142 12         48 return $self->{_restore_pattern};
143             }
144              
145             # /restore_pattern methods
146              
147             # store_data methods
148              
149             sub store_data_add {
150 16     16 0 16 my ( $self, $data ) = @_;
151              
152 16         11 push( @{$self->{_store_data}}, $data );
  16         27  
153             }
154              
155             sub store_data_by_idx {
156 8     8 0 13 my ( $self, $idx ) = @_;
157              
158 8         23 return $self->{_store_data}->[$idx];
159             }
160              
161             sub store_data_count {
162 48     48 0 39 my $self = shift;
163              
164 48 100       29 return scalar( @{$self->{_store_data} || []} );
  48         201  
165             }
166              
167             sub flush_stored {
168 17     17 0 4976 my $self = shift;
169              
170 17         30 $self->{_store_data} = [];
171             }
172              
173             # /store_data methods
174              
175             # reggrp methods
176              
177             sub reggrp_add {
178 38     38 0 34 my ( $self, $reggrp ) = @_;
179              
180 38         76 push( @{$self->{_reggrp}}, $reggrp );
  38         78  
181             }
182              
183             sub reggrp_array {
184 13     13 0 9 my $self = shift;
185              
186 13         9 return @{$self->{_reggrp}};
  13         20  
187             }
188              
189             sub reggrp_by_idx {
190 106     106 0 79 my ( $self, $idx ) = @_;
191              
192 106         149 return $self->{_reggrp}->[$idx];
193             }
194              
195             # /reggrp methods
196              
197             sub exec {
198 26     26 0 114 my ( $self, $input, $opts ) = @_;
199              
200 26 50       59 if ( ref( $input ) ne 'SCALAR' ) {
201 0         0 carp( 'First argument in Regexp::RegGrp->exec must be a scalarref!' );
202 0         0 return undef;
203             }
204              
205 26   50     75 $opts ||= {};
206              
207 26 50       43 if ( ref( $opts ) ne 'HASH' ) {
208 0         0 carp( 'Second argument in Regexp::RegGrp->exec must be a hashref!' );
209 0         0 return undef;
210             }
211              
212 26         14 my $to_process = \'';
213 26         21 my $cont = 'void';
214              
215 26 100       35 if ( defined( wantarray ) ) {
216 13         7 my $tmp_input = ${$input};
  13         15  
217              
218 13         12 $to_process = \$tmp_input;
219 13         13 $cont = 'scalar';
220             }
221             else {
222 13         11 $to_process = $input;
223             }
224              
225 3     3   1152 ${$to_process} =~ s/${\$self->re_str()}/$self->_process( { match_hash => \%+, opts => $opts } )/eg;
  3         940  
  3         1129  
  26         23  
  26         26  
  106         209  
  26         30  
226              
227             # Return a scalar if requested by context
228 26 100       90 return ${$to_process} if ( $cont eq 'scalar' );
  13         31  
229             }
230              
231             sub _process {
232 106     106   89 my ( $self, $in_ref ) = @_;
233              
234 106         68 my %match_hash = %{$in_ref->{match_hash}};
  106         578  
235 106         196 my $opts = $in_ref->{opts};
236              
237 106         115 my $match_key = ( keys( %match_hash ) )[0];
238 106         237 my ( $midx ) = $match_key =~ /^_(\d+)$/;
239 106         98 my $match = $match_hash{$match_key};
240              
241 106         118 my $reggrp = $self->reggrp_by_idx( $midx );
242              
243 106         185 my @submatches = $match =~ $reggrp->regexp();
244 106         126 map { $_ .= ''; } @submatches;
  182         230  
245              
246 106         83 my $ret = $match;
247              
248 106         157 my $replacement = $reggrp->replacement();
249              
250 106 100 100     372 if (
    100          
251             defined( $replacement ) and
252             not ref( $replacement )
253             ) {
254 50         43 $ret = $replacement;
255             }
256             elsif ( ref( $replacement ) eq 'CODE' ) {
257 48         68 $ret = $replacement->(
258             {
259             match => $match,
260             submatches => \@submatches,
261             opts => $opts,
262             store_index => $self->store_data_count()
263             }
264             );
265             }
266              
267 106         337 my $store = $reggrp->store();
268              
269 106 100       141 if ( $store ) {
270 16         11 my $tmp_match = $match;
271 16 50       31 if ( not ref( $store ) ) {
    50          
272 0         0 $tmp_match = $store;
273             }
274             elsif ( ref( $store ) eq 'CODE' ) {
275 16         38 $tmp_match = $store->(
276             {
277             match => $match,
278             submatches => \@submatches,
279             opts => $opts
280             }
281             );
282             }
283              
284 16         88 $self->store_data_add( $tmp_match );
285             }
286              
287 106         454 return $ret;
288             };
289              
290             sub restore_stored {
291 4     4 0 15 my ( $self, $input ) = @_;
292              
293 4 50       8 if ( ref( $input ) ne 'SCALAR' ) {
294 0         0 carp( 'First argument in Regexp::RegGrp->restore must be a scalarref!' );
295 0         0 return undef;
296             }
297              
298 4         4 my $to_process = \'';
299 4         3 my $cont = 'void';
300              
301 4 100       5 if ( defined( wantarray ) ) {
302 2         3 my $tmp_input = ${$input};
  2         2  
303              
304 2         2 $to_process = \$tmp_input;
305 2         2 $cont = 'scalar';
306             }
307             else {
308 2         3 $to_process = $input;
309             }
310              
311             # Here is a while loop, because there could be recursive replacements
312 4         4 while ( ${$to_process} =~ /${\$self->restore_pattern()}/ ) {
  8         7  
  8         9  
313 4         4 ${$to_process} =~ s/${\$self->restore_pattern()}/$self->store_data_by_idx( $1 )/egsm;
  4         4  
  8         11  
  4         5  
314             }
315              
316 4         7 $self->flush_stored();
317              
318             # Return a scalar if requested by context
319 4 100       9 return ${$to_process} if ( $cont eq 'scalar' );
  2         25  
320             }
321              
322             1;
323              
324             __END__