2 /* Copyright (C) 2012, 2018 The Meme Factory, Inc. http://www.meme.com/
3 * Copyright (C) 2006, 2007, 2008, 2011, Karl O. Pinc <kop@meme.com>
5 * This file is part of Gombe-MI.
7 * Gombe-MI is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Gombe-MI. If not, see <http://www.gnu.org/licenses/>.
20 * Web interface for (w)diff
23 * Does not detect bad return codes from cmp.
25 * Karl O. Pinc <kop@meme.com>
29 require('constants.php.inc');
30 require('lib.php.inc');
34 // dtype values -- what sort of diff to do.
35 define('D_TABLE', 'table');
36 define('D_WORD', 'word');
37 define('D_LINE', 'line');
38 define('D_FILE', 'file');
40 define('DTYPE_DEFAULT', D_TABLE); // default $dtype value
42 // Constants for converting wdiff markup to html
43 define('DELETED_MARKUP', '<em class="deleted">');
44 define('ADDED_MARKUP', '<em class="added">');
45 define('CLOSE_MARKUP', '</em>');
46 define('WD_DELETED_PATTERN', '^\[-.*-]$');
47 define('WD_ADDED_PATTERN', '^\{\+.*\+\}$');
48 define('WD_MARKUP_LEN', '4'); // Total length of wdiff markup (e.g. {++})
49 define('WD_DELETED_START', '[-');
50 define('WD_DELETED_END', '-]');
51 define('WD_ADDED_START', '{+');
52 define('WD_ADDED_END', '+}');
53 define('WD_DELETED_START_LEN', '2');
54 define('WD_DELETED_END_LEN', '2');
55 define('WD_ADDED_START_LEN', '2');
56 define('WD_ADDED_END_LEN', '2');
57 $oldmarks = array(WD_DELETED_START
61 $newmarks = array( DELETED_MARKUP
66 // Some constants for table diffs
67 define('DF_SAME', 's');
68 define('DF_DELETED', 'd');
69 define('DF_ADDED', 'a');
75 function move_diff_file($dtype, $from_path, $to_path) {
77 * Move a file we will diff, converting eol sequences if necessary.
80 * $dtype Type of diff we will do
81 * $from_path Pathname to move.
82 * $to_path Place to move to.
84 * Returns: True on success, false otherwise.
86 * Side Effects: moves file, fiddles eol
91 if ($dtype == D_FILE) {
92 return move_uploaded_file($from_path, $to_path);
94 if (!is_uploaded_file($from_path)) {
97 return move_and_fix_eol($from_path, $to_path) == 0;
101 function textual($f, $dtype, $letter) {
103 * See if a file is ASCII text.
107 * $dtype Type of comparsion being done.
108 * $letter Letter identifying file A or B.
110 * Returns: True when file is text.
112 * Side Effects: Outputs html when non-textual.
114 * Bugs: It's probably a bad idea to have side effects.
117 exec("file -b $f", $result, $err);
119 $line = current($result);
121 if (preg_match('/( |^)[Tt]ext(,?|$)/', $line) != 1) {
122 print '<p class="error"> File ' . $letter . ' cannot be compared by ';
133 print " because it is not text. File $letter is: ";
134 print htmlentities($line, ENT_QUOTES);
143 function compare_files($dtype) {
145 * Compare two files (if any have been uploaded).
148 * $dtype The type of comparison to do.
150 * Returns: TRUE when there is HTML output, FALSE otherwise.
152 * Side Effects: Outputs html.
157 global $newmarks, $oldmarks;
160 // Get the rest of the form data.
164 if (array_key_exists('filea', $_FILES)) {
165 $filea = htmlentities($_FILES['filea']['name'], ENT_QUOTES);
166 $filea_tmp = $_FILES['filea']['tmp_name'];
167 $filea_err = $_FILES['filea']['error'];
174 if (array_key_exists('fileb', $_FILES)) {
175 $fileb = htmlentities($_FILES['fileb']['name'], ENT_QUOTES);
176 $fileb_tmp = $_FILES['fileb']['tmp_name'];
177 $fileb_err = $_FILES['fileb']['error'];
184 // Set about comparing the files.
187 if ($filea_tmp == '' && $fileb_tmp == '') {
188 // We've not yet gotten uploads from the user so we
189 // don't need to compare anything.
193 if ($filea_err != UPLOAD_ERR_OK
194 || $fileb_err != UPLOAD_ERR_OK) {
195 // Some sort of error uploading.
196 print explain_error("File A [Old File] ($filea)", $filea_err);
197 print explain_error("File B [New File] ($fileb)", $fileb_err);
202 // Process input values
206 $command = 'wdiff -n';
210 $command = 'wdiff -n';
214 $command = 'diff -u';
222 // Compare the two files.
223 $tmpa = '/var/tmp/FileA.' . getmypid();
224 $tmpb = '/var/tmp/FileB.' . getmypid();
225 if (!move_diff_file($dtype, $filea_tmp, $tmpa)
226 || !move_diff_file($dtype, $fileb_tmp, $tmpb)) {
227 print '<p class="error">Unexpected failure trying to move uploaded files.</p>';
232 print "File A [Old File]: " . $filea . "<br />";
233 print "File B [New File]: " . $fileb;
236 if ($dtype == D_TABLE || $dtype == D_WORD || $dtype == D_LINE) {
237 // Make sure the files are text and can be compared.
238 $aistext = textual($tmpa, $dtype, 'A');
239 $bistext = textual($tmpb, $dtype, 'B');
240 if (!($aistext && $bistext)) {
245 $command = "$command $tmpa $tmpb";
246 if ($dtype == D_FILE) {
249 exec($command, $result, $err);
255 // The files are identical.
257 if (trim($filea) == trim($fileb)) {
258 // File names are identical too!
259 print '<p><em class="error">Uh, the same file was uploaded twice....</em><br />';
260 print '(Or you uploaded 2 files having identical names <em>and</em> content.)</p>';
263 print '<p class="success">The files are identical!';
264 print '<em class="remark">(You should be so proud!)</em></p>';
270 // The files are not identical.
273 // Display the result
278 $line = substr(current($result), 5); // Skip over "cmp: "
279 print '<p class="error">';
280 print htmlentities($line);
286 // Tabular by word output
291 foreach ($result as $line) {
300 // Go through the result and display it line by line
301 // in a fixed-width font, and colorize it.
302 print "<table class=\"comparison\">\n";
305 foreach ($result as $line) {
306 // Start a new row in the table.
308 . str_repeat(' ', 5 - strlen($lineno))
313 $words = preg_split('/[[:space:]]/'
314 , htmlentities($line, ENT_QUOTES));
316 // Go through the words of the line and pair
318 $paired_words = array();
319 $next_pwi = 0; // pwi is "paired_words index"
320 $max_pwi = $next_pwi;
322 $next_wtype = $wtype;
323 foreach ($words as $word) {
324 // Look at the word and see if it begins
325 // an add or delete sequence.
326 // Strip off beginning of sequence marks.
327 $prefix = substr($word, 0, WD_DELETED_START_LEN);
328 if ($prefix == WD_DELETED_START) {
329 // Start of delete sequence.
330 $next_wtype = DF_DELETED;
331 $word = substr($word, WD_DELETED_START_LEN);
332 $pwi_saved = $next_pwi;
333 } elseif ($prefix == WD_ADDED_START) {
334 // Start of add sequence.
335 if ($wtype == DF_DELETED) {
336 // Add sequence follows delete sequence.
337 // Rewind to pair adds with deletes.
338 $next_pwi = $pwi_saved;
340 $word = substr($word, WD_ADDED_START_LEN);
341 $next_wtype = DF_ADDED;
344 // Remember characteristics of current word and
345 // prep for next iteration.
346 // We do this so early because when the word
347 // ends an add or delete sequence that affects
348 // both the markup of of the next word and it's
351 $wtype = $next_wtype;
354 if ($next_pwi > $max_pwi) {
355 // Remember how far we got before the adds
356 // "rewound" back over deletes.
357 $max_pwi = $next_pwi;
360 // See if the word ends an add or delete sequence.
361 // Remember seqeuence for future.
362 // Strip off end of sequence marks.
363 $suffix = substr($word, strlen($word)
364 - WD_DELETED_END_LEN);
365 if ($suffix == WD_DELETED_END) {
366 // Finish delete sequence.
367 $word = substr($word, 0, strlen($word)
368 - WD_DELETED_END_LEN);
369 $next_wtype = DF_SAME;
370 } elseif ($suffix == WD_ADDED_END) {
371 // Finish add sequence.
376 $next_wtype = DF_SAME;
377 // Make sure that ending an add resynronizes
378 // and regular data is not paired with deletes.
379 $next_pwi = $max_pwi;
382 // Put html markup on the word.
383 if ($wtype == DF_DELETED) {
384 // In a delete sequence.
385 $word = DELETED_MARKUP . $word . CLOSE_MARKUP;
386 } elseif ($wtype == DF_ADDED) {
387 // In a add sequence.
388 $word = ADDED_MARKUP . $word . CLOSE_MARKUP;
392 if (array_key_exists($pwi, $paired_words)) {
393 // There's already something in the pair.
394 $paired_words[$pwi] .= " $word";
396 $paired_words[$pwi] = $word;
401 foreach ($paired_words as $td) {
402 print "<td class=\"comparison\">$td</td>\n";
416 print '<p class="comparison">';
419 foreach ($result as $line) {
420 print str_repeat(' ', 5 - strlen($lineno))
423 . str_replace($oldmarks
425 , htmlentities($line, ENT_QUOTES))
441 foreach ($result as $line) {
442 $c = substr($line, 0, 1);
445 print '</pre><pre class="added">';
449 print '</pre><pre class="deleted">';
459 print htmlentities($line, ENT_QUOTES);
467 if ($dtype != D_FILE) {
470 <em class="deleted">In File A [Old File] but not in File B.</em><br />
471 <em class="added">In File B [New File] but not in File A.</em><br />
479 // Some error running wdiff.
480 // print '<p class="error">Unexpected failure in wdiff command. Error: '
493 ?><?xml version="1.0" encoding="UTF-8" standalone="no"?>
494 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
495 <html xmlns="http://www.w3.org/1999/xhtml">
497 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
498 <title>wwwdiff -- A web based diff program</title>
499 <link rel="stylesheet" href="/gombemi.css" type="text/css" />
504 <a href="<?php print HOME_TARGET; ?>">HOME</a>
507 <h1>Compare Two Files</h1>
511 // Get some of the form data.
514 if (array_key_exists('dtype', $_REQUEST)) {
515 $dtype = $_REQUEST['dtype'];
517 $dtype = DTYPE_DEFAULT;
520 // Compare the files (assuming anything was uploaded).
521 if (compare_files($dtype)) {
526 // Ouput a form where the user can upload files to compare.
529 <form action="" enctype="multipart/form-data" method="post">
532 <label for="filea_id">File A [Old File]</label>
541 <label for="fileb_id">File B [New File]</label>
550 // The kind of diff to do.
558 value="<?php print D_TABLE; ?>"
559 <?php if ($dtype == D_TABLE) {
560 print 'checked="checked"'; } ?>
561 /> <label for="dtype-table">Tabular by Word</label><br />
567 value="<?php print D_WORD; ?>"
568 <?php if ($dtype == D_WORD) {
569 print 'checked="checked"'; } ?>
570 /> <label for="dtype-word">By Word</label><br />
576 value="<?php print D_LINE; ?>"
577 <?php if ($dtype == D_LINE) {
578 print 'checked="checked"'; } ?>
579 /> <label for="dtype-line">By Line</label><br />
585 value="<?php print D_FILE; ?>"
586 <?php if ($dtype == D_FILE) {
587 print 'checked="checked"'; } ?>
588 /> <label for="dtype-file">By File</label>
592 <input value="Diff" tabindex="20" type="submit" />
599 <a href="<?php print HOME_TARGET; ?>">HOME</a>