File Coverage

blib/lib/Devel/TraceSAX.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 Devel::TraceSAX;
2              
3             =head1 NAME
4              
5             Devel::TraceSAX - Trace SAX events
6              
7             =head1 SYNOPSIS
8              
9             ## From the command line:
10             perl -d:TraceSAX script.pl
11             perl -d:TraceSAX=-dump_all script.pl
12              
13             ## procedural:
14             use Devel::TraceSAX;
15              
16             trace_SAX $obj1;
17              
18             ## Emitting additional messages
19             use Devel::TraceSAX qw( emit_trace_SAX_message );
20              
21             emit_trace_SAX_message "this is a test";
22              
23             =head1 DESCRIPTION
24              
25             B: alpha code alert!!! This module and its API subject to change,
26             possibly radically :).
27              
28             Traces SAX events in a program. Works by applying Devel::TraceCalls to
29             a tracer on the desired classes for all known SAX event types (according to
30             XML::SAX::EventMethodMaker and XML::SAX::Machines).
31              
32             =head2 Emitting messages if and only if Devel::TraceCalls is loaded
33              
34             use constant _tracing => defined $Devel::TraceSAX::VERSION;
35              
36             BEGIN {
37             eval "use Devel::TraceCalls qw( emit_trace_SAX_message )"
38             if _tracing;
39             }
40              
41             emit_trace_SAX_message( "hi!" ) if _tracing;
42              
43             Using the constant C<_tracing> allows expressions like
44              
45             emit_trace_SAX_message(...) if _tracing;
46              
47             to be optimized away at compile time, resulting in little or no
48             performance penalty.
49              
50             =cut
51              
52             $VERSION=0.021;
53              
54             @EXPORT = qw( trace_SAX emit_trace_SAX_message );
55             %EXPORT_TAGS = ( all => \@EXPORT_OK );
56              
57             ## TODO: Can't recall why this class isn't an exporter, need to try that.
58             @ISA = qw( Devel::TraceCalls );
59              
60 1     1   6205 use strict;
  1         3  
  1         37  
61 1     1   2302 use Devel::TraceCalls qw( trace_calls );
  1         30182  
  1         9  
62 1     1   625 use XML::SAX::EventMethodMaker qw( sax_event_names );
  0            
  0            
63             use UNIVERSAL;
64             use Exporter;
65              
66             use vars qw( @methods );
67              
68             sub empty($) { ! defined $_[0] || ! length $_[0] }
69              
70             ## When outputting strings, we usually use this to make invisible
71             ## characters visible and to keep trace messages all on the same line.
72             ## This does not put the quotation marks on the string because lots of
73             ## things like PIs and comments don't use them. This will yield some
74             ## non-XMLish looking strings, but that's ok, we're going for
75             ## readability for a perl programmer, not w3c compliance.
76             sub _esc {
77             ## Some of these should never occur in XML. But this isn't
78             ## XML, it's SAX events and anything can happen (sometimes event
79             ## legitamately, esp. with non XML data sources).
80             local $_ = $_[0];
81             s/\\/\\\\/g;
82             s/\n/\\n/g;
83             s/"/"/g;
84             s/([\000-\037])/sprintf "&#%02x;", $1/ge;
85             return $_;
86             }
87              
88              
89             sub _dqify {
90             local $_ = $_[0];
91             s/\\/\\\\/g;
92             $_ = _esc $_;
93             return qq{"$_"};
94             }
95              
96              
97             @methods = (
98             qw(
99             new
100             set_handler
101             set_handlers
102             set_aggregator
103             start_manifold_document
104             end_manifold_document
105             ),
106             sax_event_names "Handler", "ParseMethods"
107             );
108              
109             ##
110             ## WARNING: UGLY CODE AHEAD. I'm still debugging this.
111             ##
112              
113             ## Note that we ignore some common words in methods.
114             my @scan_methods = grep !/set_handler|warning|error|parse/, sort @methods;
115             my $methods = join "|", map quotemeta, @scan_methods;
116             $methods = qr/^(?:$methods)(?!\n)$/;
117              
118             ##
119             ## -d:TraceSAX and -MDevel::TraceSAX support
120             ##
121             my $always_dump;
122              
123             sub import {
124             my $self = shift;
125              
126             if ( ! (caller(0))[2] ) {
127             require Devel::TraceSAX::DB;
128             for ( @_ ) {
129             if ( $_ eq "-dump_all" ) {
130             $always_dump = 1;
131             }
132             else {
133             warn "Devel::TraceSAX: unknown parameter '$_'\n";
134             }
135             }
136             return;
137             }
138              
139             my $meth = Exporter->can( "export_to_level" );
140             $meth->( __PACKAGE__, 1, @_ );
141             }
142              
143              
144             ## External API to add a SAX object instance
145             sub trace_SAX {
146             my ( $processor, $id ) = @_;
147             trace_calls {
148             Objects => [ $processor ],
149             ObjectId => $id,
150             Subs => \@methods,
151             LogFormatter => \&log_formatter,
152             };
153             }
154              
155              
156             ## External API to add a SAX object instance
157             sub emit_trace_SAX_message {
158             goto &Devel::TraceCalls::emit_trace_message;
159             }
160              
161              
162             sub log_formatter {
163             my ( $tp, $r, $params ) = @_;
164              
165             #warn Data::Dumper::Dumper( $tp, $r );
166              
167             my $short_sub_name = $r->{Name};
168             $short_sub_name =~ s/.*://;
169              
170             if ( ! $always_dump
171             && ( my $meth = __PACKAGE__->can( "format_$short_sub_name" ) )
172             ) {
173             return $meth->( @_ );
174             }
175             else {
176             return undef;
177             }
178              
179             return "FOO\n";
180             }
181              
182              
183             ##
184             ## Parser formatters
185             ##
186             my %builtin_types = map { ( $_ => undef ) } qw(
187             SCALAR
188             ARRAY
189             Regexp
190             REF
191             HASH
192             CODE
193             );
194              
195             sub _stringify_blessed_refs {
196             my $s = shift;
197             my $type = ref $s;
198              
199             return $s if ! $type || $type eq "Regexp" ;
200              
201             if ( $type eq "HASH" ) {
202             $s = {
203             map {
204             ( $_ => _stringify_blessed_refs( $s->{$_} ) );
205             } keys %$s
206             };
207             }
208             elsif ( $type eq "ARRAY" ) {
209             $s = [ map _stringify_blessed_refs( $_ ), @$s ];
210             }
211             elsif( $type eq "Regexp" ) {
212             $s = "$s";
213             }
214             elsif ( !exists $builtin_types{$type} ) {
215             ## A blessed ref...
216             $s = $type;
217             }
218              
219             return $s;
220             }
221              
222              
223             sub format_set_handler {
224             my ( $tp, $r, $params ) = @_;
225              
226             return {
227             Args => [
228             ],
229             };
230             }
231              
232              
233             sub format_start_element {
234             my ( $tp, $r, $params ) = @_;
235              
236             return undef if @$params != 2;
237             my $elt = $params->[1];
238             return undef if ! defined( $elt ) || ref $elt ne "HASH";
239              
240             for ( keys %$elt ) {
241             next if $_ eq "Name"
242             || $_ eq "LocalName"
243             || $_ eq "Prefix"
244             || $_ eq "Attributes";
245             return undef unless empty $elt->{$_};
246             }
247              
248             return {
249             Args => join( "",
250             ": <",
251             (
252             (
253             defined $elt
254             && ref $elt eq "HASH"
255             && exists $elt->{Name}
256             && defined $elt->{Name}
257             )
258             ? ( defined $elt->{Name} ? _esc $elt->{Name} : "???" )
259             : "???"
260             ),
261             exists $elt->{Attributes} && defined $elt->{Attributes}
262             ? map {
263             " " . _esc( $_->{Name} ) . "=" . _dqify $_->{Value} ;
264             } values %{$elt->{Attributes}}
265             : (),
266             ">"
267             ),
268             };
269             }
270              
271              
272             sub format_end_element {
273             my ( $tp, $r, $params ) = @_;
274              
275             return undef if @$params != 2;
276             my $elt = $params->[1];
277             return undef if ! defined( $elt ) || ref $elt ne "HASH";
278              
279             for ( keys %$elt ) {
280             next if $_ eq "Name"
281             || $_ eq "LocalName"
282             || $_ eq "Prefix"
283             || $_ eq "Attributes";
284             return undef unless empty $elt->{$_};
285             }
286              
287             return {
288             Args => join( "",
289             ":
290             (
291             (
292             defined $elt
293             && ref $elt eq "HASH"
294             && exists $elt->{Name}
295             && defined $elt->{Name}
296             )
297             ? ( defined $elt->{Name} ? _esc $elt->{Name} : "???" )
298             : "???"
299             ),
300             ">"
301             ),
302             };
303             }
304              
305             sub format_characters {
306             my ( $tp, $r, $params ) = @_;
307              
308             return undef if @$params != 2;
309             my $data = $params->[1];
310             return undef if ! defined( $data ) || ref $data ne "HASH";
311             return undef if ! exists $data->{Data} || ! defined $data->{Data};
312              
313              
314             for ( keys %$data ) {
315             next if $_ eq "Data";
316             return undef;
317             }
318              
319             return { Args => ": " . _dqify( $data->{Data} ) . "\n" };
320             }
321              
322              
323             sub format_comment {
324             my ( $tp, $r, $params ) = @_;
325              
326             return undef if @$params != 2;
327             my $data = $params->[1];
328             return undef if ! defined( $data ) || ref $data ne "HASH";
329             return undef if ! exists $data->{Data} || ! defined $data->{Data};
330              
331             for ( keys %$data ) {
332             next if $_ eq "Data";
333             return undef;
334             }
335              
336             return { Args => ": \n" };
337             }
338              
339              
340             sub format_processing_instruction {
341             my ( $tp, $r, $params ) = @_;
342              
343             return undef if @$params != 2;
344             my $data = $params->[1];
345             return undef if ! defined( $data ) || ref $data ne "HASH";
346             return undef if ! exists $data->{Target} || ! defined $data->{Target};
347              
348             for ( keys %$data ) {
349             next if $_ eq "Target";
350             next if $_ eq "Data";
351             return undef;
352             }
353              
354             my $pi = $data->{Target};
355             $pi .= " $data->{Data}"
356             if exists $data->{Data} && ! empty $data->{Data};
357              
358             return { Args => ": \n" };
359             }
360              
361              
362             sub format_parse {
363             my ( $tp, $r, $params ) = @_;
364              
365             return undef if @$params != 2 || ref $params->[1] ne "HASH" ;
366              
367             return {
368             Args => [
369             $params->[0],
370             _stringify_blessed_refs $params->[1],
371             ]
372             };
373             }
374              
375             =head1 TODO
376              
377             Add a lot more formatting clean-up.
378              
379             =head1 LIMITATIONS
380              
381             This module overloads CORE::GLOBAL::require when used from the command
382             line via -d: or -M. For some reason this causes spurious warnings like
383              
384             Unquoted string "fields" may clash with future reserved word at /usr/local/lib/perl5/5.6.1/base.pm line 87.
385              
386             That line looks like "require fields;", so it looks like the (*) prototype
387             on our CORE::GLOBAL::require = sub (*) {...} isn't having it's desired
388             effect. It would be nice to clean these up.
389              
390             =head1 AUTHOR
391              
392             Barrie Slaymaker
393              
394             =head1 LICENSE
395              
396             You may use this under the terms of either the Artistic License or any
397             version of the BSD or GPL licenses :).
398              
399             =cut
400              
401             1;