File Coverage

blib/lib/Text/Diff/FormattedHTML.pm
Criterion Covered Total %
statement 21 87 24.1
branch 0 30 0.0
condition n/a
subroutine 7 20 35.0
pod 3 3 100.0
total 31 140 22.1


\n", @_); \n", \n", \n",
line stmt bran cond sub pod time code
1             package Text::Diff::FormattedHTML;
2              
3 1     1   24204 use 5.006;
  1         5  
  1         43  
4 1     1   6 use strict;
  1         2  
  1         34  
5 1     1   5 use warnings;
  1         7  
  1         31  
6              
7 1     1   1016 use File::Slurp;
  1         19518  
  1         86  
8 1     1   1212 use Algorithm::Diff 'traverse_balanced';
  1         5811  
  1         65  
9 1     1   917 use String::Diff 'diff';
  1         2778  
  1         59  
10              
11 1     1   5 use base 'Exporter';
  1         3  
  1         1160  
12              
13             our @EXPORT = (qw'diff_files diff_strings diff_css');
14              
15             =head1 NAME
16              
17             Text::Diff::FormattedHTML - Generate a colorful HTML diff of strings/files.
18              
19             =head1 VERSION
20              
21             Version 0.07
22              
23             =cut
24              
25             our $VERSION = '0.07';
26              
27              
28             =head1 SYNOPSIS
29              
30             use Text::Diff::FormattedHTML;
31              
32             my $output = diff_files($file1, $file2);
33              
34             # for strings
35              
36             my $output = diff_strings( { vertical => 1 }, $file1, $file2);
37              
38              
39             # as you might want some CSS:
40             open OUT, ">diff.html";
41             print OUT "\n";
42             print OUT diff_files('fileA', 'fileB');
43             close OUT;
44              
45              
46             =head1 DESCRIPTION
47              
48             Presents in a (nice?) HTML table the difference between two files or strings.
49             Inspired on GitHub diff view.
50              
51             =head1 SUBROUTINES
52              
53             =head2 diff_files
54              
55             my $html = diff_files("filename1", "filename2");
56              
57             C and C support a first optional argument
58             (an hash reference) where options can be set. At the moment the only
59             valid option is C that can be set to a true value, for a
60             more compact table.
61              
62             =cut
63              
64             sub diff_files {
65 0     0 1   my $settings = {};
66 0 0         $settings = shift if ref($_[0]) eq "HASH";
67              
68 0           my ($f1, $f2) = @_;
69              
70 0 0         die "$f1 not available" unless -f $f1;
71 0 0         die "$f2 not available" unless -f $f2;
72              
73 0           my @f1 = read_file $f1;
74 0           my @f2 = read_file $f2;
75              
76 0           _internal_diff($settings, \@f1, \@f2);
77             }
78              
79             =head2 diff_strings
80              
81             my $html = diff_strings("string1", "string2");
82              
83             Compare strings. First split by newline, and then treat them as file
84             content (see function above).
85              
86             =cut
87              
88             sub diff_strings {
89 0     0 1   my $settings = {};
90 0 0         $settings = shift if ref($_[0]) eq "HASH";
91              
92 0           my ($s1, $s2) = @_;
93 0           my @f1 = split /\n/, $s1;
94 0           my @f2 = split /\n/, $s2;
95 0           _internal_diff($settings, \@f1, \@f2);
96             }
97              
98             =head2 diff_css
99              
100             my $css = diff_css;
101              
102             Return the default css. You are invited to override it.
103              
104             =cut
105              
106             sub diff_css {
107 0     0 1   return <<'EOCSS';
108             table.diff {
109             border-collapse: collapse;
110             border-top: solid 1px #999999;
111             border-left: solid 1px #999999;
112             }
113              
114             table.diff td {
115             padding: 2px;
116             padding-left: 5px;
117             padding-right: 5px;
118             border-right: solid 1px #999999;
119             border-bottom: solid 1px #999999;
120             }
121              
122             table.diff td:nth-child(1),
123             table.diff td:nth-child(2) {
124             background-color: #deedff;
125             }
126              
127             table.diff tr.change,
128             table.diff tr.disc_a,
129             table.diff tr.disc_b {
130             background-color: #ffffdd;
131             }
132              
133             table.diff tr.del {
134             background-color: #ffeeee;
135             }
136              
137             table.diff tr.ins {
138             background-color: #eeffee;
139             }
140              
141              
142             table.diff td:nth-child(3),
143             table.diff td:nth-child(4) {
144             font-family: monospace;
145             white-space: pre;
146             }
147              
148             table.diff td ins {
149             padding: 2px;
150             color: #009900;
151             background-color: #ccffcc;
152             text-decoration: none;
153             font-weight: bold;
154             }
155              
156             table.diff td del {
157             padding: 2px;
158             color: #990000;
159             background-color: #ffcccc;
160             text-decoration: none;
161             font-weight: bold;
162             }
163              
164             EOCSS
165             }
166              
167             sub _protect {
168 0     0     my $x = shift;
169 0 0         if ($x) {
170 0           $x =~ s/&/&/g;
171 0           $x =~ s/
172 0           $x =~ s/>/>/g;
173             }
174 0           return $x;
175             }
176              
177             sub _internal_diff {
178 0     0     my ($settings, $sq1, $sq2) = @_;
179              
180             my $get = sub {
181 0     0     my ($l, $r) = @_;
182 0           $l = $sq1->[$l];
183 0           $r = $sq2->[$r];
184 0 0         chomp($l) if $l;
185 0 0         chomp($r) if $r;
186 0           return ($l,$r);
187 0           };
188              
189 0           my ($ll, $rl);
190              
191             my $line = sub {
192 0     0     sprintf("
%s%s%s%s
193 0           };
194              
195 0 0         if ($settings->{vertical}) {
196             $line = sub {
197 0     0     my $out = "";
198 0           my ($class, $ln, $rn, $l, $r) = @_;
199 0 0         if ($l eq $r) {
200 0           $out .= sprintf("
%s%s%s
201             $class, $ln, $rn, $l);
202             } else {
203 0 0         $class eq "disc_a" && ($class = "disc_a del");
204 0 0         $class eq "disc_b" && ($class = "disc_b ins");
205              
206 0 0         $class eq "change" && ($class = "change del");
207 0 0         $l and $out .= sprintf("
%s%s
208             $class, $ln, $l);
209 0 0         $class eq "change del" && ($class = "change ins");
210 0 0         $r and $out .= sprintf("
%s%s
211             $class, $rn, $r);
212             }
213 0           $out
214             }
215 0           }
216              
217 0           my $out = "\n";
218              
219             traverse_balanced $sq1, $sq2,
220             {
221             MATCH => sub {
222 0     0     my ($l, $r) = $get->(@_);
223 0           ++$ll; ++$rl;
  0            
224 0           $out .= $line->('match', $ll, $rl, _protect($l), _protect($r));
225             },
226             DISCARD_A => sub {
227 0     0     my ($l, $r) = $get->(@_);
228 0           ++$ll;
229 0           $out .= $line->('disc_a', $ll, '', _protect($l), '');
230             },
231             DISCARD_B => sub {
232 0     0     my ($l, $r) = $get->(@_);
233 0           ++$rl;
234 0           $out .= $line->('disc_b', '', $rl, '', _protect($r));
235             },
236             CHANGE => sub {
237 0     0     my ($l, $r) = $get->(@_);
238 0           my $diff = diff($l, $r,
239             remove_open => '#del#',
240             remove_close => '#/del#',
241             append_open => '#ins#',
242             append_close => '#/ins#',
243             );
244 0           ++$ll; ++$rl;
  0            
245 0           $out .= $line->('change', $ll, $rl,
246             _retag(_protect($diff->[0])), _retag(_protect($diff->[1])));
247             },
248 0           };
249 0           $out .= "
\n";
250             }
251              
252             sub _retag {
253 0     0     my $x = shift;
254 0           $x =~ s/#(.?(?:del|ins))#/<$1>/g;
255 0           return $x;
256             }
257              
258             =head1 AUTHOR
259              
260             Alberto Simoes, C<< >>
261              
262             =head1 BUGS
263              
264             Please report any bugs or feature requests to
265             C, or through the web
266             interface at
267             L.
268             I will be notified, and then you'll automatically be notified of
269             progress on your bug as I make changes.
270              
271              
272              
273              
274             =head1 SUPPORT
275              
276             You can find documentation for this module with the perldoc command.
277              
278             perldoc Text::Diff::FormattedHTML
279              
280              
281             You can also look for information at:
282              
283             =over 4
284              
285             =item * RT: CPAN's request tracker (report bugs here)
286              
287             L
288              
289             =item * AnnoCPAN: Annotated CPAN documentation
290              
291             L
292              
293             =item * CPAN Ratings
294              
295             L
296              
297             =item * Search CPAN
298              
299             L
300              
301             =back
302              
303              
304             =head1 ACKNOWLEDGEMENTS
305              
306              
307             =head1 LICENSE AND COPYRIGHT
308              
309             Copyright 2011 Alberto Simoes.
310              
311             This program is free software; you can redistribute it and/or modify it
312             under the terms of either: the GNU General Public License as published
313             by the Free Software Foundation; or the Artistic License.
314              
315             See http://dev.perl.org/licenses/ for more information.
316              
317              
318             =cut
319              
320             1; # End of Text::Diff::FormattedHTML