* * This file is part of Gombe-MI. * * Gombe-MI is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Gombe-MI. If not, see . * * Web interface for (w)diff * * Bugs: * Does not detect bad return codes from cmp. * * Karl O. Pinc */ // Includes require('constants.php.inc'); require('lib.php.inc'); // Constants // dtype values -- what sort of diff to do. define('D_TABLE', 'table'); define('D_WORD', 'word'); define('D_LINE', 'line'); define('D_FILE', 'file'); define('DTYPE_DEFAULT', D_TABLE); // default $dtype value // Constants for converting wdiff markup to html define('DELETED_MARKUP', ''); define('ADDED_MARKUP', ''); define('CLOSE_MARKUP', ''); define('WD_DELETED_PATTERN', '^\[-.*-]$'); define('WD_ADDED_PATTERN', '^\{\+.*\+\}$'); define('WD_MARKUP_LEN', '4'); // Total length of wdiff markup (e.g. {++}) define('WD_DELETED_START', '[-'); define('WD_DELETED_END', '-]'); define('WD_ADDED_START', '{+'); define('WD_ADDED_END', '+}'); define('WD_DELETED_START_LEN', '2'); define('WD_DELETED_END_LEN', '2'); define('WD_ADDED_START_LEN', '2'); define('WD_ADDED_END_LEN', '2'); $oldmarks = array(WD_DELETED_START , WD_DELETED_END , WD_ADDED_START , WD_ADDED_END); $newmarks = array( DELETED_MARKUP , CLOSE_MARKUP , ADDED_MARKUP , CLOSE_MARKUP); // Some constants for table diffs define('DF_SAME', 's'); define('DF_DELETED', 'd'); define('DF_ADDED', 'a'); // // Functions. // function move_diff_file($dtype, $from_path, $to_path) { /* * Move a file we will diff, converting eol sequences if necessary. * * Input: * $dtype Type of diff we will do * $from_path Pathname to move. * $to_path Place to move to. * * Returns: True on success, false otherwise. * * Side Effects: moves file, fiddles eol * * Bugs: */ if ($dtype == D_FILE) { return move_uploaded_file($from_path, $to_path); } else { if (!is_uploaded_file($from_path)) { return FALSE; } return move_and_fix_eol($from_path, $to_path) == 0; } } function textual($f, $dtype, $letter) { /* * See if a file is ASCII text. * * Input: * $f Path to file. * $dtype Type of comparsion being done. * $letter Letter identifying file A or B. * * Returns: True when file is text. * * Side Effects: Outputs html when non-textual. * * Bugs: It's probably a bad idea to have side effects. */ exec("file -b $f", $result, $err); reset($result); $line = current($result); if (preg_match('/( |^)[Tt]ext(,?|$)/', $line) != 1) { print '

File ' . $letter . ' cannot be compared by '; switch ($dtype) { case D_TABLE: case D_WORD: print 'word'; break; case D_LINE: print 'line'; break; }; print " because it is not text. File $letter is: "; print htmlentities($line, ENT_QUOTES); print '

'; return FALSE; }; return TRUE; }; function compare_files($dtype) { /* * Compare two files (if any have been uploaded). * * Input: * $dtype The type of comparison to do. * * Returns: TRUE when there is HTML output, FALSE otherwise. * * Side Effects: Outputs html. * * Bugs: */ global $newmarks, $oldmarks; // // Get the rest of the form data. // // Filea if (array_key_exists('filea', $_FILES)) { $filea = htmlentities($_FILES['filea']['name'], ENT_QUOTES); $filea_tmp = $_FILES['filea']['tmp_name']; $filea_err = $_FILES['filea']['error']; } else { $filea = ''; $filea_tmp = ''; } // Fileb if (array_key_exists('fileb', $_FILES)) { $fileb = htmlentities($_FILES['fileb']['name'], ENT_QUOTES); $fileb_tmp = $_FILES['fileb']['tmp_name']; $fileb_err = $_FILES['fileb']['error']; } else { $fileb = ''; $fileb_tmp = ''; } // // Set about comparing the files. // if ($filea_tmp == '' && $fileb_tmp == '') { // We've not yet gotten uploads from the user so we // don't need to compare anything. return FALSE; } if ($filea_err != UPLOAD_ERR_OK || $fileb_err != UPLOAD_ERR_OK) { // Some sort of error uploading. print explain_error("File A [Old File] ($filea)", $filea_err); print explain_error("File B [New File] ($fileb)", $fileb_err); return TRUE; } // // Process input values // switch ($dtype) { case D_TABLE: $command = 'wdiff -n'; break; case D_WORD: $command = 'wdiff -n'; break; case D_LINE: $command = 'diff -u'; break; case D_FILE: $command = 'cmp'; break; } // Compare the two files. $tmpa = '/var/tmp/FileA.' . getmypid(); $tmpb = '/var/tmp/FileB.' . getmypid(); if (!move_diff_file($dtype, $filea_tmp, $tmpa) || !move_diff_file($dtype, $fileb_tmp, $tmpb)) { print '

Unexpected failure trying to move uploaded files.

'; return TRUE; } print '

'; print "File A [Old File]: " . $filea . "
"; print "File B [New File]: " . $fileb; print '

'; if ($dtype == D_TABLE || $dtype == D_WORD || $dtype == D_LINE) { // Make sure the files are text and can be compared. $aistext = textual($tmpa, $dtype, 'A'); $bistext = textual($tmpb, $dtype, 'B'); if (!($aistext && $bistext)) { return TRUE; }; }; $command = "$command $tmpa $tmpb"; if ($dtype == D_FILE) { $command .= ' 2>&1'; } exec($command, $result, $err); unlink($tmpa); unlink($tmpb); if ($err == 0) { // The files are identical. if (trim($filea) == trim($fileb)) { // File names are identical too! print '

Uh, the same file was uploaded twice....
'; print '(Or you uploaded 2 files having identical names and content.)

'; print '
'; } else { print '

The files are identical!'; print '(You should be so proud!)

'; } return TRUE; }; // The files are not identical. // // Display the result // switch ($dtype) { case D_FILE: reset($result); $line = substr(current($result), 5); // Skip over "cmp: " print '

'; print htmlentities($line); print '

'; break; case D_TABLE: // Tabular by word output /* Debug print '
'; print '
';
        foreach ($result as $line) {
          print "$line\n";
        }
        print '
'; reset($result); */ // Debug print '
'; // Go through the result and display it line by line // in a fixed-width font, and colorize it. print "\n"; $lineno = 1; foreach ($result as $line) { // Start a new row in the table. print '"; if ($line != '') { $td_close = ''; $words = preg_split('/[[:space:]]/' , htmlentities($line, ENT_QUOTES)); // Go through the words of the line and pair // the changes. $paired_words = array(); $next_pwi = 0; // pwi is "paired_words index" $max_pwi = $next_pwi; $wtype = DF_SAME; $next_wtype = $wtype; foreach ($words as $word) { // Look at the word and see if it begins // an add or delete sequence. // Strip off beginning of sequence marks. $prefix = substr($word, 0, WD_DELETED_START_LEN); if ($prefix == WD_DELETED_START) { // Start of delete sequence. $next_wtype = DF_DELETED; $word = substr($word, WD_DELETED_START_LEN); $pwi_saved = $next_pwi; } elseif ($prefix == WD_ADDED_START) { // Start of add sequence. if ($wtype == DF_DELETED) { // Add sequence follows delete sequence. // Rewind to pair adds with deletes. $next_pwi = $pwi_saved; } $word = substr($word, WD_ADDED_START_LEN); $next_wtype = DF_ADDED; } // Remember characteristics of current word and // prep for next iteration. // We do this so early because when the word // ends an add or delete sequence that affects // both the markup of of the next word and it's // column. $pwi = $next_pwi; $wtype = $next_wtype; $next_pwi++; if ($next_pwi > $max_pwi) { // Remember how far we got before the adds // "rewound" back over deletes. $max_pwi = $next_pwi; } // See if the word ends an add or delete sequence. // Remember seqeuence for future. // Strip off end of sequence marks. $suffix = substr($word, strlen($word) - WD_DELETED_END_LEN); if ($suffix == WD_DELETED_END) { // Finish delete sequence. $word = substr($word, 0, strlen($word) - WD_DELETED_END_LEN); $next_wtype = DF_SAME; } elseif ($suffix == WD_ADDED_END) { // Finish add sequence. $word = substr($word , 0 , strlen($word) - WD_ADDED_END_LEN); $next_wtype = DF_SAME; // Make sure that ending an add resynronizes // and regular data is not paired with deletes. $next_pwi = $max_pwi; } // Put html markup on the word. if ($wtype == DF_DELETED) { // In a delete sequence. $word = DELETED_MARKUP . $word . CLOSE_MARKUP; } elseif ($wtype == DF_ADDED) { // In a add sequence. $word = ADDED_MARKUP . $word . CLOSE_MARKUP; } // Save the word. if (array_key_exists($pwi, $paired_words)) { // There's already something in the pair. $paired_words[$pwi] .= " $word"; } else { $paired_words[$pwi] = $word; } } // Output the line. foreach ($paired_words as $td) { print "\n"; } } print "\n"; $lineno++; } print "
Line' . str_repeat(' ', 5 - strlen($lineno)) . "$lineno$td
\n"; break; case D_WORD: print '
'; print '

'; $lineno = 1; foreach ($result as $line) { print str_repeat(' ', 5 - strlen($lineno)) . $lineno . ': ' . str_replace($oldmarks , $newmarks , htmlentities($line, ENT_QUOTES)) . '
'; print "\n"; $lineno++; } print '

'; break; case D_LINE: print '
'; print '
';
        $colored = FALSE;

        foreach ($result as $line) {
          $c = substr($line, 0, 1);
          switch ($c) {
            case '+':
              print '
';
              $colored = TRUE;
              break;
            case '-':
              print '
';
              $colored = TRUE;
              break;
            default:
              if ($colored) {
                print '
';
                $colored = FALSE;
              }
              break;
          }
          print htmlentities($line, ENT_QUOTES);
          print "\n";
        }
        print '
'; break; } if ($dtype != D_FILE) { ?>

Color Key:
In File A [Old File] but not in File B.
In File B [New File] but not in File A.

Unexpected failure in wdiff command. Error: ' // . $err // . '

'; // // print '

'; return TRUE; }; // // Body of code // ?> wwwdiff -- A web based diff program

Compare Two Files

'; }; // // Ouput a form where the user can upload files to compare. ?>

/> 
/> 
/> 
/>