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} }, '' . $tag . '>'; | ||||
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__ |