| 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__ |