File Coverage

blib/lib/XML/Filter/Mode.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package XML::Filter::Mode;
2              
3             $VERSION = 0.02;
4              
5             =head1 NAME
6              
7             XML::Filter::Mode - Filter out all chunks not in the current mode.
8              
9             =head1 SYNOPSIS
10              
11             use XML::Filter::Mode;
12             use strict;
13              
14             my $filter = XML::Filter::Mode->new( Modes => "a,b,c" );
15             my $filter = XML::Filter::Mode->new( Modes => [qw( a b c )] );
16              
17             ## To inspect the modes:
18             my @modes = $filter->modes;
19              
20             ## To change the modes:
21             $h->modes( qw( d e ) );
22              
23             =head1 DESCRIPTION
24              
25             Filters portions of documents based on a C attribute.
26              
27             I use this to have XML documents that can be read in several modes, for
28             instance "test", "demo" and normal (ie not test or demo), or "C",
29             "Bytecraft_C", "Perl".
30              
31             Mode names must contain only alphanumerics and "_" (ie match Perl's
32             \w regexp assertion).
33              
34             The filter is given a comma separated list of modes. Each element in
35             the XML document may have a mode="" attribute that gives a mode
36             expression. If there is no mode attribute or it is empty or the mode
37             expression matches the list of modes, then the element is accepted.
38             Otherwise it and all of its children are cut from the document.
39              
40             The mode expression is a boolean expression using the operators C<&>
41             (which unfortunately must be escaped as "&"),
42             C<|>, C<,> to build mode matching expressions from a list
43             Parentheses may be used to group operations. of words. C<,> and <|>
44             are synonyms.
45              
46             C may be used as a prefix negation operator, so C means "unless
47             mode a".
48              
49             Examples:
50              
51             Modes mode="..." Action
52             Enabled Value
53             ===== ========== ======
54             (none) "" pass
55              
56             a "" pass
57             a "a" pass
58             a "a" pass
59             a,b "a" pass
60             a "a,b" pass
61             b "a,b" pass
62             a,b "a,b" pass
63             b "!a,b" pass
64             a,b "a b" pass
65              
66             (none) "b" cut
67             a "b" cut
68             a "a&b" cut
69             b "a&b" cut
70             a "!a,b" cut
71             a "!a" cut
72              
73             =head1 METHODS
74              
75             =over
76              
77             =cut
78              
79 1     1   7082 use XML::SAX::Base;
  1         27141  
  1         46  
80             @ISA = qw( XML::SAX::Base );
81              
82 1     1   14 use strict;
  1         2  
  1         43  
83 1     1   1825 use XML::SAX::EventMethodMaker qw( compile_missing_methods sax_event_names );
  0            
  0            
84              
85             =item new
86              
87             my $filter = XML::Filter::Mode->new( Modes => \@modes );
88              
89             where $modes is a comma separated list of mode names and @modes is
90             a list of mode names.
91              
92             =cut
93              
94             sub new {
95             my $class = ref $_[0] ? ref shift : shift;
96             my $self = $class->SUPER::new( @_ );
97              
98             $self->modes( defined $self->{Modes} ? $self->{Modes} : "" );
99              
100             return $self;
101             }
102              
103             =item modes
104              
105             $filter->modes( "test,debug" );
106             $filter->modes( qw( test debug ) );
107             my @modes = $filter->modes;
108              
109             Sets/gets the modes to be active during parse. Note that the comma
110             is the only separator allowed, although whitespace may surround it.
111             This is not the same comma as used in the mode="" attribute values,
112             this comma is just a list separator, that one is
113              
114             Pass in an undef to clear the list.
115              
116             Returns a list of mode names.
117              
118             =cut
119              
120             sub modes {
121             my $self = shift;
122             $self->{Modes} =
123             join( ",",
124             grep length,
125             map split( /\s*,\s*/ ),
126             grep defined,
127             map ref $_ ? @$_ : $_, @_
128             ) if @_;
129             return split /,/, $self->{Modes} if defined wantarray;
130             }
131              
132              
133             sub modes_string {
134             return shift->{Modes};
135             }
136              
137              
138             sub start_document {
139             my $self = shift;
140             $self->{Cutting} = 0;
141             $self->{CuttingStack} = [];
142              
143             $self->SUPER::start_document( @_ );
144             }
145              
146             my %mode_subs;
147              
148             sub start_element {
149             my $self = shift;
150             my ( $elt ) = @_;
151              
152             push @{$self->{CuttingStack}}, $self->{Cutting};
153             return if $self->{Cutting};
154              
155             my $modes = $self->modes_string;
156             $self->{Cutting} ||= do {
157             exists $elt->{Attributes}->{"{}mode"}
158             && length $elt->{Attributes}->{"{}mode"}->{Value}
159             ? do {
160             my $mode_attr = $elt->{Attributes}->{"{}mode"}->{Value};
161             my $cutting_sub = $mode_subs{$mode_attr} ||= do {
162             ## TODO: use a real parser here to improve
163             ## error reporting and reject all invalid
164             ## mode expressions. This is BALGE for now.
165             my $mode_expr = $mode_attr;
166             $mode_expr =~ s{&}{&&}g;
167             $mode_expr =~ s{[|,]}{||}g;
168             $mode_expr =~ s{(\w+)}{ /\\b$1\\b/ }g;
169             ## TODO: report line, column and element name?
170             eval "sub { local \$_ = \$_[0]; !( $mode_expr ) }"
171             or die qq{$@ compiling mode="$mode_attr"\n};
172             };
173             $cutting_sub->( $modes );
174             }
175             : 0;
176             };
177              
178             $self->SUPER::start_element( @_ ) unless $self->{Cutting};
179             }
180              
181             sub end_element {
182             my $self = shift;
183              
184             $self->SUPER::end_element( @_ ) unless $self->{Cutting};
185             $self->{Cutting} = pop @{$self->{CuttingStack}};
186             }
187              
188             compile_missing_methods __PACKAGE__, <<'END_HANDLER', sax_event_names;
189             #line 0 XML::Filter::Mode::()
190             sub {
191             my $self = shift;
192             $self->SUPER::( @_ ) unless $self->{Cutting};
193             }
194             END_HANDLER
195              
196             =back
197              
198             =head1 LIMITATIONS
199              
200             The modes passed in are a list and the attributes in the document are
201             an expression. Some applications might prefer the reverse, so the
202             user could say "give me elements for ( A and B ) or C or something. But
203             we can address that when we get there.
204              
205             =head1 COPYRIGHT
206              
207             Copyright 2003, R. Barrie Slaymaker, Jr., All Rights Reserved
208              
209             =head1 LICENSE
210              
211             You may use this module under the terms of the BSD, Artistic, or GPL licenses,
212             any version.
213              
214             =head1 AUTHOR
215              
216             Barrie Slaymaker
217              
218             =cut
219              
220             1;