File Coverage

blib/lib/XML/Filter/Conditional.pm
Criterion Covered Total %
statement 110 110 100.0
branch 47 52 90.3
condition 42 42 100.0
subroutine 16 16 100.0
pod 7 8 87.5
total 222 228 97.3


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2007,2009 -- leonerd@leonerd.org.uk
5              
6             package XML::Filter::Conditional;
7              
8 6     6   255312 use strict;
  6         16  
  6         216  
9 6     6   31 use warnings;
  6         12  
  6         212  
10 6     6   31 use base qw( XML::SAX::Base );
  6         11  
  6         9993  
11              
12 6     6   143184 use Carp;
  6         217  
  6         2109  
13              
14             our $VERSION = '0.05';
15              
16             =head1 NAME
17              
18             C - an XML SAX filter for conditionally ignoring XML
19             content
20              
21             =head1 SYNOPSIS
22              
23             CODE:
24              
25             package My::XML::Filter;
26             use base qw( XML::Filter::Conditional );
27              
28             sub store_switch
29             {
30             my $self = shift;
31             my ( $e ) = @_;
32              
33             my $ename = $e->{Attributes}{'{}env'}{Value};
34             return $ENV{$ename};
35             }
36              
37             sub eval_case
38             {
39             my $self = shift;
40             my ( $value, $e ) = @_;
41              
42             return $value eq $e->{Attributes}{'{}value'}{Value};
43             }
44              
45             XML:
46              
47            
48            
49             Hello there, root user
50             Hello there, mail user
51             Hello, whoever you are
52            
53            
54              
55             =head1 DESCRIPTION
56              
57             This module provides an abstract base class to implement a PerlSAX filter
58             which conditionally ignores part of the XML content. The base class provides
59             the implememtation of actually surpressing SAX events for filtering purposes,
60             and delegates the evaluation of matches to the subclassed instance.
61              
62             The evaluation of the matches is performed by the abstract methods
63             C and C; see their detail below.
64              
65             =cut
66              
67             =head1 CONSTRUCTOR
68              
69             =cut
70              
71             =head2 $filter = XML::Filter::Conditional->new( %opts )
72              
73             Takes the following options:
74              
75             =over 8
76              
77             =item Handler => OBJECT
78              
79             The PerlSAX handler (or another filter) that will receive the PerlSAX events
80             from this filter.
81              
82             =item SwitchTag => STRING or REGEXP
83              
84             =item CaseTag => STRING or REGEXP
85              
86             =item OtherwiseTag => STRING or REGEXP
87              
88             Changes the tag names used for the C, C and C
89             elements. Can be precompiled regexp values instead of literal strings. The
90             values will be matched against the local name of the tag only, ignoring any
91             namespace prefix.
92              
93             =item NamespaceURI => STRING
94              
95             If present, the tags will only be recognised if they are part of the given
96             namespace. Defaults to the empty string, meaning tags will only be recognised
97             if they do not have a namespace prefix, and no default namespace was defined
98             for the document.
99              
100             =item MatchAll => BOOLEAN
101              
102             Determines whether all of the matching C<< >> elements will be used, or
103             only the first one that matches. By default, only the first matching one will
104             be used.
105              
106             =back
107              
108             =cut
109              
110             sub new
111             {
112 8     8 1 6746 my $class = shift;
113 8         39 my %opts = @_;
114              
115             # Check that the abstract methods are implemented
116              
117 8 100       155 $class->can( "store_switch" ) or
118             croak "$class must provide ->store_switch()";
119 7 50       55 $class->can( "eval_case" ) or
120             croak "$class must provide ->eval_case()";
121              
122 7   100     74 my $switchtag = delete $opts{SwitchTag} || "switch";
123 7   100     51 my $casetag = delete $opts{CaseTag} || "case";
124 7   100     44 my $otherwisetag = delete $opts{OtherwiseTag} || "otherwise";
125              
126 7   100     49 $opts{NamespaceURI} ||= "";
127              
128 7         97 my $self = $class->SUPER::new( %opts );
129              
130 7         614 $self->{switch_stack} = [];
131              
132 7 50       157 $self->{switchre} = ref($switchtag) eq "Regexp" ?
133             $switchtag :
134             qr/^\Q$switchtag\E$/;
135              
136 7 50       172 $self->{casere} = ref($casetag) eq "Regexp" ?
137             $switchtag :
138             qr/^\Q$casetag\E$/;
139              
140 7 50       109 $self->{otherwisere} = ref($otherwisetag) eq "Regexp" ?
141             $otherwisetag :
142             qr/^\Q$otherwisetag\E$/;
143              
144 7         37 return $self;
145             }
146              
147             # We'll keep a little state machine
148             #
149             # outside
150             # in hot
151             # in cold
152             #
153              
154 6     6   43 use constant STATE_SWITCH_OUTSIDE => 1;
  6         15  
  6         474  
155 6     6   105 use constant STATE_SWITCH_INHOT => 2;
  6         13  
  6         308  
156 6     6   33 use constant STATE_SWITCH_INCOLD => 3;
  6         11  
  6         6449  
157              
158             # Define an exception subclass
159             @XML::Filter::Conditional::Exception::ISA = qw( XML::SAX::Exception );
160              
161             sub throw_exception
162             {
163 4     4 0 8 my $self = shift;
164 4         5 my ( $message ) = @_;
165              
166 4         14 my %args = ( Message => $message );
167              
168 4 50       14 if( defined( my $locator = $self->{Locator} ) ) {
169 4         42 $args{$_} = $locator->{$_} for (qw( LineNumber ColumnNumber ));
170             }
171              
172 4         224 XML::Filter::Conditional::Exception->throw( %args );
173             }
174              
175             sub set_document_locator
176             {
177 12     12 1 538783 my $self = shift;
178 12         33 my ( $locator ) = @_;
179              
180 12         42 $self->{Locator} = $locator;
181              
182 12         335 $self->SUPER::set_document_locator( $locator );
183             }
184              
185             sub start_element
186             {
187 58     58 1 35148 my $self = shift;
188 58         90 my ( $e ) = @_;
189              
190 58         108 my $name = $e->{LocalName};
191              
192 58   100     320 my $right_namespace = ( ($e->{NamespaceURI}||"") eq $self->{NamespaceURI} );
193              
194 58 100 100     3797 if( $right_namespace and $name =~ $self->{switchre} ) {
    100 100        
    100 100        
195 9         38 push @{ $self->{switch_stack} },
  9         52  
196             {
197             state => STATE_SWITCH_OUTSIDE,
198             didcase => 0,
199             };
200              
201 9         25 $self->{switch_state} = $self->{switch_stack}[-1];
202              
203 9         50 $self->{switch_state}{cond} = $self->store_switch( $e );
204              
205 9         521 return; # EAT
206             }
207             elsif( $right_namespace and $name =~ $self->{casere} ) {
208 18         73 my $state = $self->{switch_state}{state};
209              
210 18 100       63 defined $state or
211             $self->throw_exception( "Found a <$name> element outside of a containing switch" );
212              
213 17 100       63 $state == STATE_SWITCH_OUTSIDE or
214             $self->throw_exception( "Found a <$name> element nested within another" );
215              
216 16 100 100     122 if( $self->{MatchAll} or !$self->{switch_state}{didcase} ) {
217 11 100       66 if( $self->eval_case( $self->{switch_state}{cond}, $e ) ) {
218 7         204 $self->{switch_state}{state} = STATE_SWITCH_INHOT;
219 7         27 return; # EAT
220             }
221             }
222              
223 9         213 $self->{switch_state}{state} = STATE_SWITCH_INCOLD;
224 9         32 return; # EAT
225             }
226             elsif( $right_namespace and $name =~ $self->{otherwisere} ) {
227 7         19 my $state = $self->{switch_state}{state};
228              
229 7 100       34 defined $state or
230             $self->throw_exception( "Found a <$name> element outside of a containing switch" );
231              
232 6 100       27 $state == STATE_SWITCH_OUTSIDE or
233             $self->throw_exception( "Found a <$name> element nested within another" );
234              
235             # Treat it like a case which might be true
236              
237 5 100       23 if( !$self->{switch_state}{didcase} ) {
238 3         8 $self->{switch_state}{state} = STATE_SWITCH_INHOT;
239 3         12 return; # EAT
240             }
241              
242 2         5 $self->{switch_state}{state} = STATE_SWITCH_INCOLD;
243 2         7 return; # EAT
244             }
245             else {
246 24         54 my $state = $self->{switch_state}{state};
247 24 100 100     100 if( defined $state and $state == STATE_SWITCH_INCOLD ) {
248 1         5 return; # EAT
249             }
250             }
251              
252 23         160 return $self->SUPER::start_element( $e );
253             }
254              
255             sub end_element
256             {
257 46     46 1 5967 my $self = shift;
258 46         64 my ( $e ) = @_;
259              
260 46         74 my $name = $e->{LocalName};
261              
262 46   100     227 my $right_namespace = ( ($e->{NamespaceURI}||"") eq $self->{NamespaceURI} );
263              
264 46         77 my $state = $self->{switch_state}{state};
265              
266 46 100 100     900 if( $right_namespace and $name =~ $self->{switchre} ) {
    100 100        
    100 100        
267 7         14 pop @{ $self->{switch_stack} };
  7         15  
268 7         19 $self->{switch_state} = $self->{switch_stack}[-1];
269              
270 7         28 return; # EAT
271             }
272             elsif( $right_namespace and $name =~ $self->{casere} ) {
273 15 100       51 if( $state == STATE_SWITCH_INHOT ) {
274 6         14 $self->{switch_state}{didcase} = 1;
275             }
276              
277 15         29 $self->{switch_state}{state} = STATE_SWITCH_OUTSIDE;
278              
279 15         47 return; # EAT
280             }
281             elsif( $right_namespace and $name =~ $self->{otherwisere} ) {
282 4         25 return; # EAT
283             }
284             else {
285 20 100 100     76 return if( defined $state and $state == STATE_SWITCH_INCOLD );
286             }
287              
288 19         146 return $self->SUPER::end_element( $e );
289             }
290              
291             sub _surpress
292             {
293 81     81   101 my $self = shift;
294              
295 81         194 my $state = $self->{switch_state}{state};
296              
297 81 100 100     323 if( defined $state and $state == STATE_SWITCH_INCOLD ) {
298 13         57 return 1;
299             }
300              
301 68         176 return 0;
302             }
303              
304             sub characters
305             {
306 75     75 1 4509 my $self = shift;
307 75         108 my ( $e ) = @_;
308              
309 75 100       204 return if $self->_surpress;
310              
311 64         263 return $self->SUPER::characters( $e );
312             }
313              
314             sub comment
315             {
316 3     3 1 480 my $self = shift;
317 3         5 my ( $e ) = @_;
318              
319 3 100       10 return if $self->_surpress;
320              
321 2         33 return $self->SUPER::comment( $e );
322             }
323              
324             sub processing_instruction
325             {
326 3     3 1 585 my $self = shift;
327 3         5 my ( $e ) = @_;
328              
329 3 100       8 return if $self->_surpress;
330              
331 2         38 return $self->SUPER::processing_instruction( $e );
332             }
333              
334             # Keep perl happy; keep Britain tidy
335             1;
336              
337             __END__