File Coverage

blib/lib/Perl/SAX.pm
Criterion Covered Total %
statement 89 117 76.0
branch 24 60 40.0
condition 2 9 22.2
subroutine 20 27 74.0
pod 7 9 77.7
total 142 222 63.9


line stmt bran cond sub pod time code
1             package Perl::SAX;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Perl::SAX - Generate SAX events for perl source code (incomplete)
8              
9             =head1 DESCRIPTION
10              
11             With the completion of L and the potential creation of a viable
12             refactoring Perl editor, there has been renewed interest in parsing perl
13             source code and "Doing Stuff" with it.
14              
15             It was felt (actually, it was demanded) that there should be some sort of
16             event mechanism that could go through a chunk of perl source code and emit
17             events that would be handled by a variety of methods.
18              
19             Rather than invent my own, it was much easier to hijack SAX for this
20             purpose.
21              
22             C is the result of this need. Starting with a single object of any
23             type descended from L, C will generate a stream of SAX
24             events.
25              
26             For the sake of compatibility with SAX as a whole, and in the spirit of not
27             dictating the default behaviour based on any one use of this event stream,
28             the stream of events will be such that it can be passed to L
29             and a "PerlML" file will be spat out.
30              
31             This provides the highest level of detail, and allows for a variety of
32             different potential uses, relating to both the actual and lexical content
33             inside of perl source code.
34              
35             =head2 Perl::SAX is just a SAX Driver
36              
37             Please note that C is B a SAX Driver. It cannot be used
38             as a SAX Filter or some other form of SAX Handler, and will die fatally if
39             you try, as soon as it recieves a C event.
40              
41             To restart Perl::SAX only B events, it cannot consume them.
42              
43             =head2 Current State of Completion
44              
45             This basic first working version is being uploaded to support the creation
46             of an L rip-off using PerlML.
47              
48             =cut
49              
50 2     2   191330 use 5.005;
  2         7  
  2         75  
51 2     2   10 use strict;
  2         3  
  2         64  
52 2     2   20 use Carp 'croak';
  2         4  
  2         108  
53 2     2   818 use Params::Util '_INSTANCE';
  2         2583  
  2         104  
54 2     2   642 use PPI::Util '_Document';
  2         728  
  2         82  
55 2     2   2630 use XML::SAX::Base ();
  2         39738  
  2         64  
56 2     2   977 eval "use prefork 'XML::SAX::Writer';";
  0         0  
  0         0  
57              
58 2     2   24 use vars qw{$VERSION @ISA};
  2         4  
  2         151  
59             BEGIN {
60 2     2   4 $VERSION = '0.08';
61 2         74 @ISA = 'XML::SAX::Base';
62             }
63              
64             # While in development, use a version-specific namespace.
65             # In theory, this ensures documents are only truly valid with the
66             # version they were created with.
67 2     2   10 use constant XMLNS => 'http://ali.as/xml/schema/perlml/$VERSION';
  2         3  
  2         2520  
68              
69              
70              
71              
72              
73             #####################################################################
74             # Constructor and Accessors
75              
76             =pod
77              
78             =head1 METHODS
79              
80             =head2 new [ Handler => $Handler | Output => $WriterConsumer ]
81              
82             The C constructor creates a new Perl SAX Driver instance.
83              
84             If passed no arguments, it creates a new default L object,
85             which by default will write the resulting PerlML file to STDOUT.
86              
87             If passed an C $Consumer> argument, this value will be passed
88             along to the L constructor. Any value that is legal for
89             the Output parameter to L is also legal here.
90              
91             If passed a C $Handler> argument, C<$Handler> will be used
92             as the SAX Handler directly. Any value provided via Output in this case will
93             be ignored.
94              
95             Returns a new C object, or C if you pass an illegal Output
96             value, and the L cannot be created.
97              
98             =cut
99              
100             sub new {
101 1     1 1 21 my $class = shift;
102 1         2 my %param = @_;
103              
104             # Create the empty object
105 1         5 my $self = bless {
106             NamespaceURI => '',
107             Prefix => '',
108             Handler => undef,
109             }, $class;
110              
111             # Have we been passed a custom handler?
112 1 50       5 if ( $param{Handler} ) {
113             ### It appears there is no way to test the validity of a SAX handler
114 0         0 $self->{Handler} = $param{Handler};
115             } else {
116             # Default to an XML::Writer.
117             # Have we been passed in Consumer for it?
118 1 50       3 if ( $param{Output} ) {
119 0         0 $self->{Output} = $param{Output};
120             } else {
121 1         3 my $Output = '';
122 1         11 $self->{Output} = \$Output;
123             }
124              
125             # Add the handler for the Output
126 1         968 require XML::SAX::Writer;
127 1 50       21658 $self->{Handler} = XML::SAX::Writer->new(
128             Output => $self->{Output},
129             ) or return undef;
130             }
131              
132             # Generate NamespaceURI information?
133 1 50       238 if ( $param{NamespaceURI} ) {
134 0 0       0 if ( length $param{NamespaceURI} > 1 ) {
135             # Custom namespace
136 0         0 $self->{NamespaceURI} = $param{NamespaceURI};
137             } else {
138             # Default namespace
139 0         0 $self->{NamespaceURI} = XMLNS;
140             }
141             }
142              
143             # Use a prefix?
144 1 50       9 if ( $param{Prefix} ) {
145 0         0 $self->{Prefix} = $param{Prefix};
146             }
147              
148 1         6 $self;
149             }
150              
151 0     0 0 0 sub NamespaceURI { $_[0]->{NamespaceURI} }
152 0     0 0 0 sub Prefix { $_[0]->{Prefix} }
153 0     0 1 0 sub Handler { $_[0]->{Handler} }
154 0     0 1 0 sub Output { $_[0]->{Output} }
155              
156              
157              
158              
159              
160             #####################################################################
161             # Main Methods
162              
163             # Prevent use as a SAX Filter.
164             # We only generate SAX events, we don't consume them.
165             sub start_document {
166 0   0 0 1 0 my $class = ref $_[0] || $_[0];
167 0         0 croak "$class can only be used as a SAX Driver";
168             }
169              
170             sub parse {
171 1     1 1 5181 my $self = shift;
172 1 50       14 my $Document = _INSTANCE(shift, 'PPI::Document') or return undef;
173              
174             # Generate the SAX2 events
175 1         21 $self->SUPER::start_document( {} );
176 1 50       411 $self->_parse_document( $Document ) or return undef;
177 1         9 $self->SUPER::end_document( {} );
178              
179 1         96 1;
180             }
181              
182             sub _parse {
183 21     21   53 my $self = shift;
184 21 50       163 my $Element = _INSTANCE(shift, 'PPI::Element') or return undef;
185              
186             # Split to the various generic handlers
187 21 0       94 $Element->isa('PPI::Token') ? $self->_parse_token( $Element )
    50          
    100          
188             : $Element->isa('PPI::Statement') ? $self->_parse_statement( $Element )
189             : $Element->isa('PPI::Structure') ? $self->_parse_structure( $Element )
190             : undef;
191             }
192              
193             sub _parse_document {
194 1     1   3 my $self = shift;
195 1 50       15 my $Document = _INSTANCE(shift, 'PPI::Document') or return undef;
196              
197             # Generate the SAX2 events
198 1 50       5 my $Element = $self->_element( $Document ) or return undef;
199 1         8 $self->start_element( $Element );
200 1         134 foreach my $Child ( $Document->elements ) {
201 7 50       23 $self->_parse( $Child ) or return undef;
202             }
203 1         4 $self->end_element( $Element );
204              
205 1         46 1;
206             }
207              
208             sub _parse_token {
209 19     19   21 my $self = shift;
210 19 50       120 my $Token = _INSTANCE(shift, 'PPI::Token') or return undef;
211              
212             # Support custom handlers
213 19         36 my $method = $self->_tag_method( $Token );
214 19 50       109 return $self->$method( $Token ) if $self->can($method);
215              
216             # Generate the SAX2 events
217 19 50       40 my $Element = $self->_element( $Token ) or return undef;
218 19         56 $self->start_element( $Element );
219 19         1137 $self->characters( {
220             Data => $Token->content,
221             } );
222 19         220 $self->end_element( $Element );
223              
224 19         2101 1;
225             }
226              
227             sub _parse_statement {
228 2     2   4 my $self = shift;
229 2 50       15 my $Statement = _INSTANCE(shift, 'PPI::Statement') or return undef;
230              
231             # Support custom handlers
232 2         6 my $method = $self->_tag_method( $Statement );
233 2 50 66     21 if ( $method ne '_parse_statement' and $self->can($method) ) {
234 0         0 return $self->$method( $Statement );
235             }
236              
237             # Generate the SAX2 events
238 2 50       6 my $Element = $self->_element( $Statement ) or return undef;
239 2         8 $self->start_element( $Element );
240 2         129 foreach my $Child ( $Statement->elements ) {
241 14 50       42 $self->_parse( $Child ) or return undef;
242             }
243 2         5 $self->end_element( $Element );
244              
245 2         118 1;
246             }
247              
248             sub _parse_structure {
249 0     0   0 my $self = shift;
250 0 0       0 my $Structure = _INSTANCE(shift, 'PPI::Structure') or return undef;
251              
252             # Support custom handlers
253 0         0 my $method = $self->_tag_method( $Structure );
254 0 0 0     0 if ( $self->can($method) and $method ne '_parse_structure' ) {
255 0         0 return $self->$method( $Structure );
256             }
257              
258             # Generate the SAX2 events
259 0 0       0 my $Element = $self->_element( $Structure ) or return undef;
260 0         0 $self->start_element( $Element );
261 0         0 foreach my $Child ( $Structure->elements ) {
262 0 0       0 $self->_parse( $Child ) or return undef;
263             }
264 0         0 $self->end_element( $Element );
265              
266 0         0 1;
267             }
268              
269              
270              
271              
272              
273             #####################################################################
274             # Support Methods
275              
276             # Strip out the Attributes for the end element
277             sub end_element {
278 22     22 1 43 delete $_[1]->{Attributes};
279 22         67 shift->SUPER::end_element(@_);
280             }
281              
282             # Auto-preparation of the text
283             sub characters {
284 19     19 1 99 my $self = shift;
285 19 50       84 (ref $_[0])
286             ? $self->SUPER::characters(shift)
287             : $self->SUPER::characters( {
288             Data => $self->_escape(shift),
289             } );
290             }
291              
292             sub _tag_method {
293 21     21   49 my $tag = lc ref $_[1];
294 21         64 $tag =~ s/::/_/g;
295 21         63 '_parse_' . substr $tag, 4;
296             }
297              
298             sub _element {
299 22     22   28 my $self = shift;
300 22 0       226 my ($LocalName, $attr) = _INSTANCE($_[0], 'PPI::Element')
    50          
301             ? ($_[0]->_xml_name, $_[0]->_xml_attr)
302             : ($_[0], (ref $_[1] eq 'HASH')
303             ? $_[1]
304             : {} );
305              
306             # Localise some variables for speed
307 22         299 my $NamespaceURI = $self->{NamespaceURI};
308 22 50       43 my $Prefix = $self->{Prefix}
309             ? "$self->{Prefix}:"
310             : '';
311              
312             # Convert the attributes to the full version
313 22         31 my %Attributes = ();
314 22         70 foreach my $key ( keys %$attr ) {
315 0         0 $Attributes{"{$NamespaceURI}$key"} = {
316             Name => $Prefix . $key,
317             NamespaceURI => $NamespaceURI,
318             Prefix => $Prefix,
319             LocalName => $key,
320             Value => $attr->{$key},
321             };
322             }
323              
324             # Create the main element
325             return {
326 22         183 Name => $Prefix . $LocalName,
327             NamespaceURI => $NamespaceURI,
328             Prefix => $Prefix,
329             LocalName => $LocalName,
330             Attributes => \%Attributes,
331             };
332             }
333              
334             ### Not sure if we escape here.
335             ### Just pass through for now.
336 0     0     sub _escape { $_[1] }
337              
338             1;
339              
340             =pod
341              
342             =head1 TO DO
343              
344             Design and create the PerlML Schema
345              
346             Make any changes needed to conform to it
347              
348             Write a bunch of tests
349              
350             =head1 SUPPORT
351              
352             Because the development of the PerlML Schema (and thus this module) has not
353             been completed yet, please do not report bugs B those that
354             are installation-related.
355              
356             Bugs should be reported via the CPAN bug tracker at
357              
358             L
359              
360             For other issues, or commercial enhancement or support, contact the author.
361              
362             =head1 AUTHOR
363              
364             Adam Kennedy Eadamk@cpan.orgE
365              
366             =head1 SEE ALSO
367              
368             L, L, L
369              
370             =head1 COPYRIGHT
371              
372             Thank you to Phase N (L) for permitting
373             the Open Sourcing and release of this distribution.
374              
375             Copyright 2004 - 2008 Adam Kennedy.
376              
377             This program is free software; you can redistribute
378             it and/or modify it under the same terms as Perl itself.
379              
380             The full text of the license can be found in the
381             LICENSE file included with this module.
382              
383             =cut