File Coverage

blib/lib/Regexp/RegGrp.pm
Criterion Covered Total %
statement 145 164 88.4
branch 31 44 70.4
condition 10 14 71.4
subroutine 21 21 100.0
pod 1 12 8.3
total 208 255 81.5


line stmt bran cond sub pod time code
1             package Regexp::RegGrp;
2              
3 3     3   3821 use 5.008009;
  3         10  
  3         125  
4 3     3   16 use warnings;
  3         4  
  3         68  
5 3     3   14 use strict;
  3         4  
  3         74  
6 3     3   12 use Carp;
  3         3  
  3         170  
7 3     3   14 use Regexp::RegGrp::Data;
  3         4  
  3         269  
8              
9             BEGIN {
10 3 50   3   368 if ( $] < 5.010000 ) {
11 0         0 require re;
12 0         0 re->import( 'eval' );
13             }
14             }
15              
16             use constant {
17 3         2967 ESCAPE_BRACKETS => qr~(?<])~,
18             ESCAPE_CHARS => qr~\\.~,
19             BRACKETS => qr~\(~,
20             BACK_REF => qr~(?:\\g?(\d\d*)|\\g\{(\d+)\})~
21 3     3   30 };
  3         4  
22              
23             # =========================================================================== #
24              
25             our $VERSION = '2.00';
26              
27             sub new {
28 14     14 0 14723 my ( $class, $in_ref ) = @_;
29 14         31 my $self = {};
30              
31 14         42 bless( $self, $class );
32              
33 14 100       61 if ( ref( $in_ref ) ne 'HASH' ) {
34 1         343 carp( 'First argument must be a hashref!' );
35 1         20 return;
36             }
37              
38 13 50       32 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       28 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     34 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         15 my $no = 0;
57              
58 38         38 map {
59 13         26 $no++;
60              
61 38         188 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             );
70              
71 38 50       88 unless ( $reggrp_data ) {
72 0         0 carp( 'RegGrp No ' . $no . ' in arrayref is malformed!' );
73 0         0 return;
74             }
75              
76 38         61 $self->reggrp_add( $reggrp_data );
77 13         14 } @{$in_ref->{reggrp}};
78              
79 13   66     52 my $restore_pattern = $in_ref->{restore_pattern} || qr~\x01(\d+)\x01~;
80 13         33 $self->{_restore_pattern} = qr/$restore_pattern/;
81              
82 13         12 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 38         76 $self->{_re_str} = ( ( $] < 5.010000 ) ? '(?{ %+ = (); })' : '' ) . join(
87             '|',
88             map {
89 13 50       88 my $re = $_->regexp();
90             # Count backref brackets
91 38         32 $re =~ s/${\(ESCAPE_CHARS)}//g;
  38         166  
92 38         38 $re =~ s/${\(ESCAPE_BRACKETS)}//g;
  38         202  
93 38         37 my @nparen = $re =~ /${\(BRACKETS)}/g;
  38         125  
94              
95 38         74 $re = $_->regexp();
96              
97 38         36 my $backref_pattern = '\\g{%d}';
98              
99 38 50       71 if ( $] < 5.010000 ) {
100 0         0 $backref_pattern = '\\%d';
101             }
102              
103 38   66     36 $re =~ s/${\(BACK_REF)}/sprintf( $backref_pattern, $offset + ( $1 || $2 ) )/eg;
  12         96  
  38         172  
104              
105 38         33 my $ret;
106              
107 38 50       51 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         74 $ret = '(?\'_' . $midx++ . '\'' . $re . ')';
115             }
116              
117 38         42 $offset += scalar( @nparen ) + 1;
118              
119 38         102 $ret;
120              
121             } $self->reggrp_array()
122             );
123              
124 13         38 return $self;
125             }
126              
127             # re_str methods
128              
129             sub re_str {
130 26     26 0 25 my $self = shift;
131              
132 26         541 return $self->{_re_str};
133             }
134              
135             # /re_str methods
136              
137             # restore_pattern methods
138              
139             sub restore_pattern {
140 12     12 1 12 my $self = shift;
141              
142 12         68 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 21 my ( $self, $data ) = @_;
151              
152 16         12 push( @{$self->{_store_data}}, $data );
  16         39  
153             }
154              
155             sub store_data_by_idx {
156 8     8 0 14 my ( $self, $idx ) = @_;
157              
158 8         31 return $self->{_store_data}->[$idx];
159             }
160              
161             sub store_data_count {
162 48     48 0 46 my $self = shift;
163              
164 48 100       32 return scalar( @{$self->{_store_data} || []} );
  48         273  
165             }
166              
167             sub flush_stored {
168 17     17 0 6678 my $self = shift;
169              
170 17         44 $self->{_store_data} = [];
171             }
172              
173             # /store_data methods
174              
175             # reggrp methods
176              
177             sub reggrp_add {
178 38     38 0 39 my ( $self, $reggrp ) = @_;
179              
180 38         30 push( @{$self->{_reggrp}}, $reggrp );
  38         95  
181             }
182              
183             sub reggrp_array {
184 13     13 0 12 my $self = shift;
185              
186 13         12 return @{$self->{_reggrp}};
  13         29  
187             }
188              
189             sub reggrp_by_idx {
190 106     106 0 98 my ( $self, $idx ) = @_;
191              
192 106         246 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       63 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     80 $opts ||= {};
206              
207 26 50       44 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         25 my $to_process = \'';
213 26         22 my $cont = 'void';
214              
215 26 100       39 if ( defined( wantarray ) ) {
216 13         11 my $tmp_input = ${$input};
  13         22  
217              
218 13         14 $to_process = \$tmp_input;
219 13         13 $cont = 'scalar';
220             }
221             else {
222 13         13 $to_process = $input;
223             }
224              
225 3     3   1847 ${$to_process} =~ s/${\$self->re_str()}/$self->_process( { match_hash => \%+, opts => $opts } )/eg;
  3         1480  
  3         1974  
  26         39  
  26         27  
  106         278  
  26         39  
226              
227             # Return a scalar if requested by context
228 26 100       128 return ${$to_process} if ( $cont eq 'scalar' );
  13         37  
229             }
230              
231             sub _process {
232 106     106   112 my ( $self, $in_ref ) = @_;
233              
234 106         100 my %match_hash = %{$in_ref->{match_hash}};
  106         645  
235 106         183 my $opts = $in_ref->{opts};
236              
237 106         137 my $match_key = ( keys( %match_hash ) )[0];
238 106         276 my ( $midx ) = $match_key =~ /^_(\d+)$/;
239 106         123 my $match = $match_hash{$match_key};
240              
241 106         142 my $reggrp = $self->reggrp_by_idx( $midx );
242              
243 106         212 my @submatches = $match =~ $reggrp->regexp();
244 106         192 map { $_ .= ''; } @submatches;
  182         290  
245              
246 106         84 my $ret = $match;
247              
248 106         193 my $replacement = $reggrp->replacement();
249              
250 106 100 100     450 if (
    100          
251             defined( $replacement ) and
252             not ref( $replacement )
253             ) {
254 50         49 $ret = $replacement;
255             }
256             elsif ( ref( $replacement ) eq 'CODE' ) {
257 48         87 $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         429 my $store = $reggrp->store();
268              
269 106 100       160 if ( $store ) {
270 16         17 my $tmp_match = $match;
271 16 50       40 if ( not ref( $store ) ) {
    50          
272 0         0 $tmp_match = $store;
273             }
274             elsif ( ref( $store ) eq 'CODE' ) {
275 16         50 $tmp_match = $store->(
276             {
277             match => $match,
278             submatches => \@submatches,
279             opts => $opts
280             }
281             );
282             }
283              
284 16         109 $self->store_data_add( $tmp_match );
285             }
286              
287 106         565 return $ret;
288             };
289              
290             sub restore_stored {
291 4     4 0 18 my ( $self, $input ) = @_;
292              
293 4 50       11 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         5 my $to_process = \'';
299 4         4 my $cont = 'void';
300              
301 4 100       7 if ( defined( wantarray ) ) {
302 2         3 my $tmp_input = ${$input};
  2         3  
303              
304 2         3 $to_process = \$tmp_input;
305 2         4 $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         8  
  8         13  
313 4         5 ${$to_process} =~ s/${\$self->restore_pattern()}/$self->store_data_by_idx( $1 )/egsm;
  4         17  
  8         13  
  4         9  
314             }
315              
316 4         8 $self->flush_stored();
317              
318             # Return a scalar if requested by context
319 4 100       14 return ${$to_process} if ( $cont eq 'scalar' );
  2         33  
320             }
321              
322             1;
323              
324             __END__