File Coverage

blib/lib/HTML/Differences.pm
Criterion Covered Total %
statement 75 78 96.1
branch 14 16 87.5
condition 4 7 57.1
subroutine 15 16 93.7
pod 2 2 100.0
total 110 119 92.4


line stmt bran cond sub pod time code
1             package HTML::Differences;
2             # git description: c74b954
3              
4             $HTML::Differences::VERSION = '0.01';
5 2     2   30441 use strict;
  2         3  
  2         63  
6 2     2   8 use warnings;
  2         3  
  2         98  
7              
8 2     2   9 use Exporter qw( import );
  2         4  
  2         63  
9 2     2   900 use HTML::TokeParser;
  2         17340  
  2         58  
10 2     2   1624 use Text::Diff qw( diff );
  2         13436  
  2         599  
11              
12             our @EXPORT_OK = qw( html_text_diff diffable_html );
13              
14             sub html_text_diff {
15 5     5 1 21134 my $html1 = shift;
16 5         9 my $html2 = shift;
17 5         9 my %p = @_;
18              
19 5   50     16 return diff(
      50        
20             diffable_html( $html1, %p ),
21             diffable_html( $html2, %p ),
22             {
23             CONTEXT => ( $p{context} || 2**31 ),
24             STYLE => $p{style} || 'Table',
25             },
26             );
27             }
28              
29             {
30             my %dispatch = (
31             D => 'declaration',
32             S => 'start_tag',
33             E => 'end_tag',
34             T => 'text',
35             C => 'comment',
36             PI => 'processing_instruction',
37             );
38              
39             sub diffable_html {
40 17     17 1 4127 my $html = shift;
41 17         30 my %p = @_;
42              
43 17         60 my $accumulator = _HTMLAccumulator->new( $p{ignore_comments} );
44              
45 17 50       81 my $parser = HTML::TokeParser->new( ref $html ? $html : \$html );
46 17         1873 while ( my $token = $parser->get_token() ) {
47 287         1817 my $type = shift @{$token};
  287         289  
48 287 50       432 my $method = $dispatch{$type}
49             or die "Unknown token type: $type";
50              
51 287         198 $accumulator->$method( @{$token} );
  287         433  
52             }
53              
54 17         111 return $accumulator->html_as_arrayref();
55             }
56             }
57              
58             package # hide from PAUSE
59             _HTMLAccumulator;
60              
61 2     2   16 use HTML::Entities qw( encode_entities );
  2         2  
  2         875  
62              
63             sub new {
64 17     17   19 my $class = shift;
65 17         21 my $ignore_comments = shift;
66              
67 17         67 return bless {
68             ignore_comments => $ignore_comments,
69             html => [],
70             in_pre => 0,
71             }, $class;
72             }
73              
74 17     17   170 sub html_as_arrayref { $_[0]->{html} }
75              
76             sub declaration {
77 11     11   12 push @{ $_[0]->{html} }, $_[1];
  11         42  
78             }
79              
80             sub start_tag {
81 64     64   43 my $self = shift;
82 64         57 my $tag = shift;
83 64         55 my $attr = shift;
84              
85             # Things like
give us "hr/" as the value of $tag.
86 64         66 $tag =~ s{\s*/$}{};
87              
88             # And
gives us "/" as an attribute.
89 64         45 delete $attr->{'/'};
90              
91 64 100       103 if ( $tag eq 'pre' ) {
92 1         2 $self->{in_pre} = 1;
93             }
94              
95 64         63 my $text = '<' . $tag;
96 64 100 66     123 if ( $attr && %{$attr} ) {
  64         347  
97 1         2 my @attrs;
98 1         2 for my $key ( sort keys %{$attr} ) {
  1         4  
99 1         6 push @attrs,
100             $key . '='
101             . q{"}
102             . encode_entities( $attr->{$key} )
103             . q{"};
104             }
105 1         28 $text .= q{ } . join q{ }, @attrs;
106             }
107 64         59 $text .= '>';
108              
109 64         41 push @{ $self->{html} }, $text;
  64         210  
110             }
111              
112             sub end_tag {
113 62     62   62 my $self = shift;
114 62         56 my $tag = shift;
115              
116 62 100       87 if ( $tag eq 'pre' ) {
117 1         3 $self->{in_pre} = 0;
118             }
119              
120 62         44 push @{ $self->{html} }, '';
  62         180  
121             }
122              
123             sub text {
124 146     146   133 my $self = shift;
125 146         106 my $text = shift;
126              
127 146 100       215 unless ( $self->{in_pre} ) {
128 145 100       457 return unless $text =~ /\S/;
129 29         94 $text =~ s/^\s+|\s+$//g;
130 29         32 $text =~ s/\s+/ /s;
131             }
132              
133 30         24 push @{ $self->{html} }, $text;
  30         87  
134             }
135              
136             sub comment {
137 4     4   6 my $self = shift;
138              
139 4 100       13 return if $self->{ignore_comments};
140              
141 2         2 push @{ $self->{html} }, $_[0];
  2         8  
142             }
143              
144             sub processing_instruction {
145 0     0     my $self = shift;
146 0           push @{ $self->{html} }, $_[0];
  0            
147             }
148              
149             1;
150              
151             # ABSTRACT: Reasonable sane HTML diffing
152              
153             __END__