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   6153 use 5.008009;
  3         11  
  3         116  
4 3     3   18 use warnings;
  3         6  
  3         85  
5 3     3   18 use strict;
  3         5  
  3         87  
6 3     3   15 use Carp;
  3         6  
  3         185  
7 3     3   20 use Regexp::RegGrp::Data;
  3         4  
  3         146  
8              
9             BEGIN {
10 3 50   3   428 if ( $] < 5.010000 ) {
11 0         0 require re;
12 0         0 re->import( 'eval' );
13             }
14             }
15              
16             use constant {
17 3         4171 ESCAPE_BRACKETS => qr~(?<])~,
18             ESCAPE_CHARS => qr~\\.~,
19             BRACKETS => qr~\(~,
20             BACK_REF => qr~(?:\\g?(\d\d*)|\\g\{(\d+)\})~
21 3     3   33 };
  3         7  
22              
23             # =========================================================================== #
24              
25             our $VERSION = '1.002001';
26              
27             sub new {
28 14     14 0 8631 my ( $class, $in_ref ) = @_;
29 14         35 my $self = {};
30              
31 14         45 bless( $self, $class );
32              
33 14 100       64 if ( ref( $in_ref ) ne 'HASH' ) {
34 1         191 carp( 'First argument must be a hashref!' );
35 1         9 return;
36             }
37              
38 13 50       44 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       51 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     63 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         21 my $no = 0;
57              
58 38         53 map {
59 13         39 $no++;
60              
61 38         320 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       124 unless ( $reggrp_data ) {
72 0         0 carp( 'RegGrp No ' . $no . ' in arrayref is malformed!' );
73 0         0 return;
74             }
75              
76 38         98 $self->reggrp_add( $reggrp_data );
77 13         25 } @{$in_ref->{reggrp}};
78              
79 13   66     77 my $restore_pattern = $in_ref->{restore_pattern} || qr~\x01(\d+)\x01~;
80 13         63 $self->{_restore_pattern} = qr/$restore_pattern/;
81              
82 13         19 my $offset = 1;
83 13         17 my $midx = 0;
84              
85             # In perl versions < 5.10 hash %+ doesn't exist, so we have to initialize it
86 38         111 $self->{_re_str} = ( ( $] < 5.010000 ) ? '(?{ %+ = (); })' : '' ) . join(
87             '|',
88             map {
89 13 50       60 my $re = $_->regexp();
90             # Count backref brackets
91 38         52 $re =~ s/${\(ESCAPE_CHARS)}//g;
  38         205  
92 38         56 $re =~ s/${\(ESCAPE_BRACKETS)}//g;
  38         316  
93 38         62 my @nparen = $re =~ /${\(BRACKETS)}/g;
  38         172  
94              
95 38         117 $re = $_->regexp();
96              
97 38         55 my $backref_pattern = '\\g{%d}';
98              
99 38 50       92 if ( $] < 5.010000 ) {
100 0         0 $backref_pattern = '\\%d';
101             }
102              
103 38   66     52 $re =~ s/${\(BACK_REF)}/sprintf( $backref_pattern, $offset + ( $1 || $2 ) )/eg;
  12         101  
  38         253  
104              
105 38         50 my $ret;
106              
107 38 50       71 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         111 $ret = '(?\'_' . $midx++ . '\'' . $re . ')';
115             }
116              
117 38         58 $offset += scalar( @nparen ) + 1;
118              
119 38         155 $ret;
120              
121             } $self->reggrp_array()
122             );
123              
124 13         59 return $self;
125             }
126              
127             # re_str methods
128              
129             sub re_str {
130 26     26 0 30 my $self = shift;
131              
132 26         763 return $self->{_re_str};
133             }
134              
135             # /re_str methods
136              
137             # restore_pattern methods
138              
139             sub restore_pattern {
140 12     12 1 17 my $self = shift;
141              
142 12         86 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 29 my ( $self, $data ) = @_;
151              
152 16         20 push( @{$self->{_store_data}}, $data );
  16         54  
153             }
154              
155             sub store_data_by_idx {
156 8     8 0 19 my ( $self, $idx ) = @_;
157              
158 8         42 return $self->{_store_data}->[$idx];
159             }
160              
161             sub store_data_count {
162 48     48 0 60 my $self = shift;
163              
164 48 100       50 return scalar( @{$self->{_store_data} || []} );
  48         433  
165             }
166              
167             sub flush_stored {
168 17     17 0 8870 my $self = shift;
169              
170 17         60 $self->{_store_data} = [];
171             }
172              
173             # /store_data methods
174              
175             # reggrp methods
176              
177             sub reggrp_add {
178 38     38 0 53 my ( $self, $reggrp ) = @_;
179              
180 38         41 push( @{$self->{_reggrp}}, $reggrp );
  38         157  
181             }
182              
183             sub reggrp_array {
184 13     13 0 18 my $self = shift;
185              
186 13         18 return @{$self->{_reggrp}};
  13         38  
187             }
188              
189             sub reggrp_by_idx {
190 106     106 0 144 my ( $self, $idx ) = @_;
191              
192 106         288 return $self->{_reggrp}->[$idx];
193             }
194              
195             # /reggrp methods
196              
197             sub exec {
198 26     26 0 173 my ( $self, $input, $opts ) = @_;
199              
200 26 50       77 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     146 $opts ||= {};
206              
207 26 50       66 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         30 my $to_process = \'';
213 26         32 my $cont = 'void';
214              
215 26 100       60 if ( defined( wantarray ) ) {
216 13         14 my $tmp_input = ${$input};
  13         27  
217              
218 13         20 $to_process = \$tmp_input;
219 13         29 $cont = 'scalar';
220             }
221             else {
222 13         17 $to_process = $input;
223             }
224              
225 3     3   3478 ${$to_process} =~ s/${\$self->re_str()}/$self->_process( { match_hash => \%+, opts => $opts } )/eg;
  3         1972  
  3         1870  
  26         28  
  26         41  
  106         485  
  26         66  
226              
227             # Return a scalar if requested by context
228 26 100       218 return ${$to_process} if ( $cont eq 'scalar' );
  13         60  
229             }
230              
231             sub _process {
232 106     106   141 my ( $self, $in_ref ) = @_;
233              
234 106         129 my %match_hash = %{$in_ref->{match_hash}};
  106         1032  
235 106         280 my $opts = $in_ref->{opts};
236              
237 106         209 my $match_key = ( keys( %match_hash ) )[0];
238 106         1760 my ( $midx ) = $match_key =~ /^_(\d+)$/;
239 106         189 my $match = $match_hash{$match_key};
240              
241 106         208 my $reggrp = $self->reggrp_by_idx( $midx );
242              
243 106         340 my @submatches = $match =~ $reggrp->regexp();
244 106         358 map { $_ .= ''; } @submatches;
  182         409  
245              
246 106         150 my $ret = $match;
247              
248 106         306 my $replacement = $reggrp->replacement();
249              
250 106 100 100     7035 if (
    100          
251             defined( $replacement ) and
252             not ref( $replacement )
253             ) {
254 50         75 $ret = $replacement;
255             }
256             elsif ( ref( $replacement ) eq 'CODE' ) {
257 48         127 $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         621 my $store = $reggrp->store();
268              
269 106 100       4676 if ( $store ) {
270 16         29 my $tmp_match = $match;
271 16 50       59 if ( not ref( $store ) ) {
    50          
272 0         0 $tmp_match = $store;
273             }
274             elsif ( ref( $store ) eq 'CODE' ) {
275 16         83 $tmp_match = $store->(
276             {
277             match => $match,
278             submatches => \@submatches,
279             opts => $opts
280             }
281             );
282             }
283              
284 16         187 $self->store_data_add( $tmp_match );
285             }
286              
287 106         912 return $ret;
288             };
289              
290             sub restore_stored {
291 4     4 0 32 my ( $self, $input ) = @_;
292              
293 4 50       14 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         8 my $to_process = \'';
299 4         7 my $cont = 'void';
300              
301 4 100       12 if ( defined( wantarray ) ) {
302 2         5 my $tmp_input = ${$input};
  2         3  
303              
304 2         3 $to_process = \$tmp_input;
305 2         6 $cont = 'scalar';
306             }
307             else {
308 2         4 $to_process = $input;
309             }
310              
311             # Here is a while loop, because there could be recursive replacements
312 4         9 while ( ${$to_process} =~ /${\$self->restore_pattern()}/ ) {
  8         13  
  8         21  
313 4         8 ${$to_process} =~ s/${\$self->restore_pattern()}/$self->store_data_by_idx( $1 )/egsm;
  4         13  
  8         24  
  4         11  
314             }
315              
316 4         13 $self->flush_stored();
317              
318             # Return a scalar if requested by context
319 4 100       17 return ${$to_process} if ( $cont eq 'scalar' );
  2         56  
320             }
321              
322             1;
323              
324             __END__