1Text::WordDiff(3)     User Contributed Perl Documentation    Text::WordDiff(3)
2
3
4

Name

6       Text::WordDiff - Track changes between documents
7

Synopsis

9           use Text::WordDiff;
10
11           my $diff = word_diff 'file1.txt', 'file2.txt', { STYLE => 'HTML' };
12           my $diff = word_diff \$string1,   \$string2,   { STYLE => 'ANSIColor' };
13           my $diff = word_diff \*FH1,       \*FH2;       \%options;
14           my $diff = word_diff \&reader1,   \&reader2;
15           my $diff = word_diff \@records1,  \@records2;
16
17           # May also mix input types:
18           my $diff = word_diff \@records1,  'file_B.txt';
19

Description

21       This module is a variation on the lovely Text::Diff module.  Rather
22       than generating traditional line-oriented diffs, however, it generates
23       word-oriented diffs. This can be useful for tracking changes in
24       narrative documents or documents with very long lines. To diff source
25       code, one is still best off using Text::Diff. But if you want to see
26       how a short story changed from one version to the next, this module
27       will do the job very nicely.
28
29   What is a Word?
30       I'm glad you asked! Well, sort of. It's a really hard question to
31       answer. I consulted a number of sources, but really just did my best to
32       punt on the question by reformulating it as, "How do I split text up
33       into individual words?" The short answer is to split on word
34       boundaries. However, every word has two boundaries, one at the
35       beginning and one at the end. So splitting on "/\b/" didn't work so
36       well. What I really wanted to do was to split on the beginning of every
37       word. Fortunately, _Mastering Regular Expressions_ has a recipe for
38       that: "/(?<!\w)(?=\w)/". I've borrowed this regular expression for use
39       in Perls before 5.6.x, but go for the Unicode variant in 5.6.0 and
40       newer: "/(?<!\p{IsWord})(?=\p{IsWord})/". With either of these regular
41       expressions, this sentence, for example, would be split up into the
42       following tokens:
43
44         my @words = (
45             'With ',
46             'either ',
47             'of ',
48             'these ',
49             'regular ',
50             "expressions,\n",
51             'this ',
52             'sentence, ',
53             'for ',
54             'example, ',
55             'would ',
56             'be ',
57             'split ',
58             'up ',
59             'into ',
60             'the ',
61             'following ',
62             'tokens:'
63         );
64
65       Note that this allows the tokens to include any spacing or punctuation
66       after each word. So it's not just comparing words, but word-like
67       tokens. This makes sense to me, at least, as the diff is between these
68       tokens, and thus leads to a nice word-and-space-and-punctation type
69       diff. It's not unlike what a word processor might do (although a lot of
70       them are character-based, but that seemed a bit extreme--feel free to
71       dupe this module into Text::CharDiff!).
72
73       Now, I acknowledge that there are localization issues with this
74       approach. In particular, it will fail with Chinese, Japanese, and
75       Korean text, as these languages don't put non-word characters between
76       words. Ideally, Test::WordDiff would then split on every charaters
77       (since a single character often equals a word), but such is not the
78       case when the "utf8" flag is set on a string.  For example, This simple
79       script:
80
81         use strict;
82         use utf8;
83         use Data::Dumper;
84         my $string = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
85         my @tokens = split /(?<!\p{IsWord})(?=\p{IsWord})/msx, $string;
86         print Dumper \@tokens;
87
88       Outputs:
89
90         $VAR1 = [
91                   "\x{bf08}\x{bf09}\x{bf18}\x{bf19}\x{bf1b}\x{bf1c}\x{bf1d}\x{bf40}\x{bf41}\x{bf44}\x{bf48}\x{bf50}\x{bf51}\x{bf55}\x{bf94}\x{bfb0}\x{bfc5}\x{bfcc}\x{bfcd}\x{bfd0}\x{bfd4}\x{bfdc}\x{bfdf}\x{bfe1}\x{c03c}\x{c051}\x{c058}\x{c05c}\x{c060}\x{c068}\x{c069}\x{c090}"
92                 ];
93
94       Not so useful. It seems to be less of a problem if the "use utf8;" line
95       is commented out, in which case we get:
96
97         $VAR1 = [
98                   'X',
99                   'X',
100                   'X',
101                   'X',
102                   'X',
103                   'X',
104                   'X',
105                   'X',
106                   'X',
107                   'X',
108                   'X',
109                   'X',
110                   'X',
111                   'X',
112                   'X',
113                   'X',
114                   'X',
115                   'X',
116                   'X',
117                   'X',
118                   'X',
119                   'X',
120                   'X',
121                   'X',
122                   '?',
123                   '?X',
124                   'X',
125                   'X',
126                   'X',
127                   'X',
128                   'X',
129                   'X'
130                 ];
131
132       Someone whose more familiar with non-space-using languages will have to
133       explain to me how I might be able to duplicate this pattern within the
134       scope of "use utf8;", seing as it may very well be important to have it
135       on in order to ensure proper character semantics.
136
137       However, if my word tokenization approach is just too naive, and you
138       decide that you need to take a different approach (maybe use
139       Lingua::ZH::Toke or similar module), you can still use this module;
140       you'll just have to tokenize your strings into words yourself, and pass
141       them to word_diff() as array references:
142
143         word_diff \@my_words1, \@my_words2;
144

Options

146       word_diff() takes two arguments from which to draw input and an
147       optional hash reference of options to control its output. The first two
148       arguments contain the data to be diffed, and each may be in the form of
149       any of the following (that is, they can be in two different formats):
150
151       ·   String
152
153           A bare scalar will be assumed to be a file name. The file will be
154           opened and split up into words. word_diff() will also "stat" the
155           file to get the last modified time for use in the header, unless
156           the relevant option ("MTIME_A" or "MTIME_B") has been specified
157           explicitly.
158
159       ·   Scalar Reference
160
161           A scalar reference will be assumed to refer to a string. That
162           string will be split up into words.
163
164       ·   Array Reference
165
166           An array reference will be assumed to be a list of words.
167
168       ·   File Handle
169
170           A glob or IO::Handle-derived object will be read from and split up
171           into its constituent words.
172
173       The optional hash reference may contain the following options.
174       Additional options may be specified by the formattting class; see the
175       specific class for details.
176
177       ·   STYLE
178
179           "ANSIColor", "HTML" or an object or class name for a class
180           providing "file_header()", "hunk_header()", "same_items()",
181           "delete_items()", "insert_items()", "hunk_footer()" and
182           "file_footer()" methods. Defaults to "ANSIColor" for nice display
183           of diffs in an ANSI Color-supporting terminal.
184
185           If the package indicated by the "STYLE" has no "new()" method,
186           "word_diff()" will load it automatically (lazy loading). It will
187           then instantiate an object of that class, passing in the options
188           hash reference with which the formatting class can initialize the
189           object.
190
191           Styles may be specified as class names ("STYLE => "My::Foo""), in
192           which case they will be instantiated by calling the "new()"
193           construcctor and passing in the options hash reference, or as
194           objects ("STYLE => My::Foo->new").
195
196           The simplest way to implement your own formatting style is to
197           create a new class that inherits from Text::WordDiff::Base, wherein
198           the "new()" method is already provided, and the "file_header()"
199           returns a Unified diff-style header. All of the other formatting
200           methods simply return empty strings, and are therefore ripe for
201           overriding.
202
203       ·   FILENAME_A, MTIME_A, FILENAME_B, MTIME_B
204
205           The name of the file and the modification time "files" in epoch
206           seconds.  Unless a defined value is specified for these options,
207           they will be filled in for each file when word_diff() is passed a
208           filename. If a filename is not passed in and "FILENAME_A" and
209           "FILENAME_B" are not defined, the header will not be printed by the
210           base formatting base class.
211
212       ·   OUTPUT
213
214           The method by which diff output should be, well, output. Examples
215           and their equivalent subroutines:
216
217               OUTPUT => \*FOOHANDLE,   # like: sub { print FOOHANDLE shift() }
218               OUTPUT => \$output,      # like: sub { $output .= shift }
219               OUTPUT => \@output,      # like: sub { push @output, shift }
220               OUTPUT => sub { $output .= shift },
221
222           If "OUTPUT" is not defined, word_diff() will simply return the diff
223           as a string. If "OUTPUT" is a code reference, it will be called
224           once with the file header, once for each hunk body, and once for
225           each piece of content. If "OUTPUT" is an IO::Handle-derived object,
226           output will be sent to that handle.
227
228       ·   FILENAME_PREFIX_A, FILENAME_PREFIX_B
229
230           The string to print before the filename in the header. Defaults are
231           "---", "+++".
232
233       ·   DIFF_OPTS
234
235           A hash reference to be passed as the options to
236           "Algorithm::Diff->new".  See Algorithm::Diff for details on
237           available options.
238

Formatting Classes

240       Text::WordDiff comes with two formatting classes:
241
242       Text::WordDiff::ANSIColor
243           This is the default formatting class. It emits a header and then
244           the diff content, with deleted text in bodfaced red and inserted
245           text in boldfaced green.
246
247       Text::WordDiff::HTML
248           Specify "STYLE => 'HTML'" to take advantage of this formatting
249           class. It outputs the diff content as XHTML, with deleted text in
250           "<del>" elements and inserted text in "<ins>" elements.
251
252       To implement your own formatting class, simply inherit from
253       Text::WordDiff::Base and override its methods as necssary. By default,
254       only the "file_header()" formatting method returns a value. All others
255       simply return empty strings, and are therefore ripe for overriding:
256
257         package My::WordDiff::Format;
258         use base 'Text::WordDiff::Base';
259
260         sub file_footer { return "End of diff\n"; }
261
262       The methods supplied by the base class are:
263
264       "new()"
265           Constructs and returns a new formatting object. It takes a single
266           hash reference as its argument, and uses it to construct the
267           object. The nice thing about this is that if you want to support
268           other options in your formatting class, you can just use them in
269           the formatting object constructed by the Text::WordDiff::Base class
270           and document that they can be passed as part of the options hash
271           refernce to word_diff().
272
273       "file_header()"
274           Called once for a single call to "word_diff()", this method outputs
275           the header for the whole diff. This is the only formatting method
276           in the base class that returns anything other than an empty string.
277           It collects the filenames from "filname_a()" and "filename_b()"
278           and, if they're defined, uses the relevant prefixes and
279           modification times to return a unified diff-style header.
280
281       "hunk_header()"
282           This method is called for each diff hunk. It should output any
283           necessary header for the hunk.
284
285       "same_items()"
286           This method is called for items that have not changed between the
287           two sequnces being compared. The unchanged items will be passed as
288           a list to the method.
289
290       "delete_items"
291           This method is called for items in the first sequence that are not
292           present in the second sequcne. The deleted items will be passed as
293           a list to the method.
294
295       "insert_items"
296           This method is called for items in the second sequence that are not
297           present in the first sequcne. The inserted items will be passed as
298           a list to the method.
299
300       "hunk_footer"
301           This method is called at the end of a hunk. It should output any
302           necessary content to close out the hunk.
303
304       "file_footer()"
305           This method is called once when the whole diff has been procssed.
306           It should output any necessary content to close out the diff file.
307
308       "filename_a"
309           This accessor returns the value specified for the "FILENAME_A"
310           option to word_diff().
311
312       "filename_b"
313           This accessor returns the value specified for the "FILENAME_B"
314           option to word_diff().
315
316       "mtime_a"
317           This accessor returns the value specified for the "MTIME_A" option
318           to word_diff().
319
320       "mtime_b"
321           This accessor returns the value specified for the "MTIME_B" option
322           to word_diff().
323
324       "filename_prefix_a"
325           This accessor returns the value specified for the
326           "FILENAME_PREFIX_A" option to word_diff().
327
328       "filename_prefix_b"
329           This accessor returns the value specified for the
330           "FILENAME_PREFIX_B" option to word_diff().
331

See Also

333       Text::Diff
334           Inspired the interface and implementation of this module. Thanks
335           Barry!
336
337       Text::ParagraphDiff
338           A module that attempts to diff paragraphs and the words in them.
339
340       Algorithm::Diff
341           The module that makes this all possible.
342

Support

344       This module is stored in an open repository at the following address:
345
346       https://svn.kineticode.com/Text-WordDiff/trunk/
347       <https://svn.kineticode.com/Text-WordDiff/trunk/>
348
349       Patches against Text::WordDiff are welcome. Please send bug reports to
350       <bug-text-worddiff@rt.cpan.org>.
351

Author

353       David Wheeler <david@kineticode.com>
354
356       Copyright (c) 2005-2008 David Wheeler. Some Rights Reserved.
357
358       This module is free software; you can redistribute it and/or modify it
359       under the same terms as Perl itself.
360
361
362
363perl v5.12.0                      2010-05-07                 Text::WordDiff(3)
Impressum