File Coverage

blib/lib/XML/DocStats.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::DocStats;
2              
3             # this module produces a simple format of an XML document with statics
4             #
5             # Copyright (c) 2001-2002 Alan Dickey
6             # All rights reserved.
7             #
8             # This program is free software; you can redistribute it and/or
9             # modify it under the same terms as Perl itself.
10              
11 3     3   23404 use strict;
  3         6  
  3         99  
12              
13 3     3   1041 use Object::_Initializer;
  3         34722  
  3         83  
14 3     3   2045 use XML::Parser::PerlSAX;
  0            
  0            
15              
16             use vars qw($VERSION @ISA);
17              
18             $VERSION = '0.01';
19              
20             @ISA = qw(Object::_Initializer);
21              
22             # Preloaded methods.
23             # new and _init inherited from Object::_Initializer
24              
25             sub _defaults { # called by Object::_Initializer::new
26             my ($self) = @_;
27             my %defaults = qw(
28             format html
29             output print
30             print_htmlpage yes
31             print_element yes
32             print_text yes
33             print_entity yes
34             print_doctype yes
35             print_xmldcl yes
36             print_comment yes
37             print_pi yes
38             );
39             $defaults{xmlsource} = {ByteStream => \*STDIN};
40             $self->_init(%defaults);
41             }
42              
43             sub analyze {
44             my ($self, @params) = @_;
45              
46             my %params = (%{$self}, @params,);
47              
48             my $handler = MySaxHandler->new(%params);
49             my $parser = XML::Parser::PerlSAX->new(Handler => $handler);
50              
51             my %parser_args = (Source => $self->xmlsource, UseAttributeOrder => 1);
52              
53             eval {$parser->parse(%parser_args)};
54              
55             if ($@) { # xml not well formed, get error message from XML::Parser
56             require XML::Parser;
57             my $xml = $self->xmlsource->{ByteStream};
58             $xml = $self->xmlsource->{String} unless $xml;
59             $xml = $self->xmlsource->{SystemId} unless $xml;
60             my $p1 = new XML::Parser(ErrorContext => 3);
61             eval{$p1->parse($xml)};
62             $handler->fatal_error($@);
63             }
64             return $handler->_output_buffer unless $params{output} eq 'print';
65             }
66              
67             package MySaxHandler;
68              
69             use vars qw(@ISA);
70              
71             @ISA = qw(Object::_Initializer);
72              
73             sub ok_print {
74             my ($self,$item) = @_;
75             $self->{"print_$item"} eq 'yes';
76             }
77              
78             sub prnt {
79             my ($self,@message) = @_;
80             if ($self->output eq 'print') {print @message;}
81             else {$self->{_output_buffer} .= join'',@message;}
82             }
83              
84             sub fatal_error {
85             my ($self,$message) = @_;
86             $message =~ s{\<}{\<\;}g if $self->format eq 'html';
87             $message =~ s{\>}{\>\;}g if $self->format eq 'html';
88             $self->prnt($self->color('ERROR',$message));
89             $self->end_document;
90             }
91              
92             sub xml_decl {
93             my ($self,$option) = @_;
94             my @options = qw(Version Encoding Standalone);
95             my @attrs;
96             for my $opt (@options) {
97             push @attrs,"$opt='".$option->{$opt}."'" if exists $option->{$opt};
98             }
99             $self->print($self->color('XML','XML-DCL: ').$self->color('ATTR'," @attrs\n")) if $self->ok_print('xmldcl');
100             $self->stats('!XML-DCL');
101             }
102              
103             sub doctype_decl {
104             my ($self,$option) = @_;
105             my @options = qw(Name SystemId PublicId Internal);
106             my @attrs;
107             for my $opt (@options) {
108             push @attrs,"$opt='".$option->{$opt}."'" if $option->{$opt};
109             }
110             $self->print($self->color('DTD','DOCTYPE: ').$self->color('ATTR'," @attrs\n")) if $self->ok_print('doctype');
111             $self->stats('!DOCTYPE');
112             }
113              
114             sub start_document {
115             my ($self) = @_;
116             $self->_init(level=>0,chars=>{},element=>'',elestack=>[],STATS=>{});
117             $self->_init(_output_buffer=>'') unless $self->output eq 'print';
118             $self->stats('!BYTES',$self->{BYTES}) if exists $self->{BYTES};
119             my $title = "Start parse of XML on ${\$self->_timeformat}";
120             $self->prnt(<ok_print('htmlpage') and ($self->format eq 'html');
121            
122             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
123            
124            
125             $title
126            
127            
128            
129             HTML
130             $self->prnt('
') if $self->format eq 'html'; 
131             $self->prnt($self->color('STATS',"$title\n"));
132             }
133              
134             sub end_document {
135             my ($self) = @_;
136             $self->printstats;
137             $self->prnt($self->color('STATS',"Finish parse of XML on ${\$self->_timeformat}"));
138             $self->prnt('') if $self->format eq 'html';
139             $self->prnt(<ok_print('htmlpage') and ($self->format eq 'html');
140            
141            
142             HTML
143             }
144              
145             sub print {
146             my ($self, @items) = @_;
147             my $indent = ' ' x $self->level;
148             $self->prnt($indent,@items);
149             }
150              
151             sub trim {
152             my ($self, $text) = @_;
153             $text =~ s/^\s*//;
154             $text =~ s/\s*$//;
155             return $text;
156             }
157              
158             sub color {
159             my ($self, $tag, $text) = @_;
160             my %color = qw(element purple PI maroon TEXT blue COMMENT green ATTR olive XML teal DTD navy ERROR red STATS fuchsia ROOT fuchsia ENTITY fuchsia);
161             return "$text" if $self->format eq 'html';
162             return $text;
163             }
164              
165             sub escape {
166             my ($self, $text) = @_;
167             return $text unless $self->format eq 'html';
168             $text =~ s{\<}{\<\;}g;
169             $text =~ s{\>}{\>\;}g;
170             $text =~ s{\n}{\ \;}g;
171             return $text;
172             }
173              
174             sub start_element {
175             my ($self, $element) = @_;
176            
177             push @{$self->elestack},$self->element if $self->element;
178             $self->prnt($self->color('ROOT',"ROOT: ${\$element->{Name}}\n")) unless $self->element;
179             $self->element($element->{Name});
180             $self->print($self->color('element',$self->element)) if $self->ok_print('element');
181             my $lev = $self->level;
182             $self->level(++$lev);
183             $self->chars->{$lev.$self->element}=undef;
184             my @attrs;
185             for my $attr (@{$element->{AttributeOrder}}) {
186             $self->stats('@'.$attr);
187             $self->stats('^'.$element->{Attributes}->{$attr});
188             push @attrs,"$attr='".$element->{Attributes}->{$attr}."'";
189             }
190             $self->prnt($self->color('ATTR'," @attrs")) if @attrs and $self->ok_print('element');
191             $self->stats('!ATTRIBUTE',scalar(@attrs)) if @attrs;
192             $self->prnt("\n") if $self->ok_print('element');
193             $self->stats($self->element);
194             $self->stats('!ELEMENT');
195             }
196              
197             sub entity_reference {
198             my ($self, $entity) = @_;
199             $self->stats('!ENTITY');
200             $self->print($self->color('ENTITY','ENTITY: ')."'${\$entity->{Name}}'\n") if $self->ok_print('entity');
201             $self->stats('&'.$entity->{Name});
202             }
203              
204              
205             sub characters {
206             my ($self, $characters) = @_;
207             my $text = $self->trim($characters->{Data});
208             $self->chars->{$self->level.$self->element} .= $text;
209             $text = $self->escape($text);
210             $self->print($self->color('TEXT','TEXT: ').$self->color('element',$self->element)." '$text'\n") if $text and $self->ok_print('text');
211             $self->stats('!TEXT') if $text;
212             }
213              
214             sub end_element {
215             my ($self, $element) = @_;
216             my $lev = $self->level;
217             $self->chars->{$lev.$self->element} = undef;
218             $self->level(--$lev);
219             $self->element(pop @{$self->elestack});
220             }
221              
222             sub processing_instruction {
223             my ($self, $pi) = @_;
224             my $target = $pi->{Target};
225             (my $data = $pi->{Data}) =~ s/\n//g;
226             $data =~ s/\s+/ /g;
227             my @attrs = ("Target='$target'","Data='$data'");
228             $self->print($self->color('PI','PI: ').$self->color('element',$self->element).$self->color('ATTR'," @attrs\n")) if $self->ok_print('pi');
229             $self->stats('!PI');
230             }
231              
232             sub comment {
233             my ($self, $comment) = @_;
234             my $text = $self->trim($comment->{Data});
235             $text = $self->escape($text);
236             $self->print($self->color('COMMENT','COMMENT: ').$self->color('element',$self->element)." '$text'\n") if $self->ok_print('comment');
237             $self->stats('!COMMENT');
238             }
239              
240             sub stats {
241             my ($self, $stat, $amount) = @_;
242             # $stat = "!$stat"; # invalid element name
243             $amount = 1 unless $amount;
244             $self->STATS->{$stat} = exists $self->STATS->{$stat}?
245             $amount+($self->STATS->{$stat}):
246             $amount;
247             }
248              
249             sub printstat {
250             my ($self,$label,$quote,@keys) = @_;
251             my @attrs;
252             for my $attr (@keys) {
253             (my $name = $attr) =~ s/^[!@^&]//;
254             $name =~ s{\&}{&}g;
255             push @attrs,$self->STATS->{$attr}." $quote$name$quote";
256             }
257             $self->prnt($self->color('STATS',$label).$self->color('ATTR',join(', ',@attrs))) if @attrs;
258             $self->prnt("\n");
259             }
260              
261             sub printstats {
262             my ($self) = @_;
263             $self->prnt("\n");
264             my @keys = sort keys %{$self->STATS};
265             $self->printstat('TOTALS: ','',grep {m/^!/} @keys);
266             $self->printstat('ELEMENTS: ','',grep {not m/^[!@^&]/} @keys);
267             $self->printstat('ATTRIBUTES: ','',grep {m/^@/} @keys);
268             $self->printstat('ATTRVALUES: ',"'",grep {m/^\^/} @keys);
269             $self->printstat('ENTITIES: ','',grep {m/^&/} @keys);
270             }
271              
272             sub start_cdata {
273             my ($self, $element) = @_;
274             $self->stats('!CDATA');
275             }
276              
277             1;
278             __END__