home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Blogs / wordpress2.6.exe / wordpress2.6 / wp-includes / wp-diff.php < prev   
Encoding:
PHP Script  |  2008-08-08  |  9.9 KB  |  319 lines

  1. <?php
  2.  
  3. if ( !class_exists( 'Text_Diff' ) ) {
  4.     require( dirname(__FILE__).'/Text/Diff.php' );
  5.     require( dirname(__FILE__).'/Text/Diff/Renderer.php' );
  6.     require( dirname(__FILE__).'/Text/Diff/Renderer/inline.php' );
  7. }
  8.  
  9.  
  10. /* Descendent of a bastard child of piece of an old MediaWiki Diff Formatter
  11.  *
  12.  * Basically all that remains is the table structure and some method names.
  13.  */
  14.  
  15. class WP_Text_Diff_Renderer_Table extends Text_Diff_Renderer {
  16.     var $_leading_context_lines  = 10000;
  17.     var $_trailing_context_lines = 10000;
  18.     var $_diff_threshold = 0.6;
  19.  
  20.     var $inline_diff_renderer = 'WP_Text_Diff_Renderer_inline';
  21.  
  22.     function Text_Diff_Renderer_Table( $params = array() ) {
  23.         $parent = get_parent_class($this);
  24.         $this->$parent( $params );
  25.     }
  26.  
  27.     function _startBlock( $header ) {
  28.         return '';
  29.     }
  30.  
  31.     function _lines( $lines, $prefix=' ' ) {
  32.     }
  33.  
  34.     // HTML-escape parameter before calling this
  35.     function addedLine( $line ) {
  36.         return "<td>+</td><td class='diff-addedline'>{$line}</td>";
  37.     }
  38.  
  39.     // HTML-escape parameter before calling this
  40.     function deletedLine( $line ) {
  41.         return "<td>-</td><td class='diff-deletedline'>{$line}</td>";
  42.     }
  43.  
  44.     // HTML-escape parameter before calling this
  45.     function contextLine( $line ) {
  46.         return "<td> </td><td class='diff-context'>{$line}</td>";
  47.     }
  48.  
  49.     function emptyLine() {
  50.         return '<td colspan="2"> </td>';
  51.     }
  52.  
  53.     function _added( $lines, $encode = true ) {
  54.         $r = '';
  55.         foreach ($lines as $line) {
  56.             if ( $encode )
  57.                 $line = htmlspecialchars( $line );
  58.             $r .= '<tr>' . $this->emptyLine() . $this->addedLine( $line ) . "</tr>\n";
  59.         }
  60.         return $r;
  61.     }
  62.  
  63.     function _deleted( $lines, $encode = true ) {
  64.         $r = '';
  65.         foreach ($lines as $line) {
  66.             if ( $encode )
  67.                 $line = htmlspecialchars( $line );
  68.             $r .= '<tr>' . $this->deletedLine( $line ) . $this->emptyLine() . "</tr>\n";
  69.         }
  70.         return $r;
  71.     }
  72.  
  73.     function _context( $lines, $encode = true ) {
  74.         $r = '';
  75.         foreach ($lines as $line) {
  76.             if ( $encode )
  77.                 $line = htmlspecialchars( $line );
  78.             $r .= '<tr>' .
  79.                 $this->contextLine( $line ) . $this->contextLine( $line ) . "</tr>\n";
  80.         }
  81.         return $r;
  82.     }
  83.  
  84.     // Process changed lines to do word-by-word diffs for extra highlighting (TRAC style)
  85.     // sometimes these lines can actually be deleted or added rows - we do additional processing
  86.     // to figure that out
  87.     function _changed( $orig, $final ) {
  88.         $r = '';
  89.  
  90.         // Does the aforementioned additional processing
  91.         // *_matches tell what rows are "the same" in orig and final.  Those pairs will be diffed to get word changes
  92.         //    match is numeric: an index in other column
  93.         //    match is 'X': no match.  It is a new row
  94.         // *_rows are column vectors for the orig column and the final column.
  95.         //    row >= 0: an indix of the $orig or $final array
  96.         //    row  < 0: a blank row for that column
  97.         list($orig_matches, $final_matches, $orig_rows, $final_rows) = $this->interleave_changed_lines( $orig, $final );
  98.  
  99.  
  100.         // These will hold the word changes as determined by an inline diff
  101.         $orig_diffs  = array();
  102.         $final_diffs = array();
  103.  
  104.         // Compute word diffs for each matched pair using the inline diff
  105.         foreach ( $orig_matches as $o => $f ) {
  106.             if ( is_numeric($o) && is_numeric($f) ) {
  107.                 $text_diff = new Text_Diff( 'auto', array( array($orig[$o]), array($final[$f]) ) );
  108.                 $renderer = new $this->inline_diff_renderer;
  109.                 $diff = $renderer->render( $text_diff );
  110.  
  111.                 // If they're too different, don't include any <ins> or <dels>
  112.                 if ( $diff_count = preg_match_all( '!(<ins>.*?</ins>|<del>.*?</del>)!', $diff, $diff_matches ) ) {
  113.                     // length of all text between <ins> or <del>
  114.                     $stripped_matches = strlen(strip_tags( join(' ', $diff_matches[0]) ));
  115.                     // since we count lengith of text between <ins> or <del> (instead of picking just one),
  116.                     //    we double the length of chars not in those tags.
  117.                     $stripped_diff = strlen(strip_tags( $diff )) * 2 - $stripped_matches;
  118.                     $diff_ratio = $stripped_matches / $stripped_diff;
  119.                     if ( $diff_ratio > $this->_diff_threshold )
  120.                         continue; // Too different.  Don't save diffs.
  121.                 }
  122.  
  123.                 // Un-inline the diffs by removing del or ins
  124.                 $orig_diffs[$o]  = preg_replace( '|<ins>.*?</ins>|', '', $diff );
  125.                 $final_diffs[$f] = preg_replace( '|<del>.*?</del>|', '', $diff );
  126.             }
  127.         }
  128.  
  129.         foreach ( array_keys($orig_rows) as $row ) {
  130.             // Both columns have blanks.  Ignore them.
  131.             if ( $orig_rows[$row] < 0 && $final_rows[$row] < 0 )
  132.                 continue;
  133.  
  134.             // If we have a word based diff, use it.  Otherwise, use the normal line.
  135.             $orig_line  = isset($orig_diffs[$orig_rows[$row]])
  136.                 ? $orig_diffs[$orig_rows[$row]]
  137.                 : htmlspecialchars($orig[$orig_rows[$row]]);
  138.             $final_line = isset($final_diffs[$final_rows[$row]])
  139.                 ? $final_diffs[$final_rows[$row]]
  140.                 : htmlspecialchars($final[$final_rows[$row]]);
  141.  
  142.             if ( $orig_rows[$row] < 0 ) { // Orig is blank.  This is really an added row.
  143.                 $r .= $this->_added( array($final_line), false );
  144.             } elseif ( $final_rows[$row] < 0 ) { // Final is blank.  This is really a deleted row.
  145.                 $r .= $this->_deleted( array($orig_line), false );
  146.             } else { // A true changed row.
  147.                 $r .= '<tr>' . $this->deletedLine( $orig_line ) . $this->addedLine( $final_line ) . "</tr>\n";
  148.             }
  149.         }
  150.  
  151.         return $r;
  152.     }
  153.  
  154.     // Takes changed blocks and matches which rows in orig turned into which rows in final.
  155.     // Returns
  156.     //    *_matches ( which rows match with which )
  157.     //    *_rows ( order of rows in each column interleaved with blank rows as necessary )
  158.     function interleave_changed_lines( $orig, $final ) {
  159.  
  160.         // Contains all pairwise string comparisons.  Keys are such that this need only be a one dimensional array.
  161.         $matches = array();
  162.         foreach ( array_keys($orig) as $o ) {
  163.             foreach ( array_keys($final) as $f ) {
  164.                 $matches["$o,$f"] = $this->compute_string_distance( $orig[$o], $final[$f] );
  165.             }
  166.         }
  167.         asort($matches); // Order by string distance.
  168.  
  169.         $orig_matches  = array();
  170.         $final_matches = array();
  171.  
  172.         foreach ( $matches as $keys => $difference ) {
  173.             list($o, $f) = explode(',', $keys);
  174.             $o = (int) $o;
  175.             $f = (int) $f;
  176.  
  177.             // Already have better matches for these guys
  178.             if ( isset($orig_matches[$o]) && isset($final_matches[$f]) )
  179.                 continue;
  180.  
  181.             // First match for these guys.  Must be best match
  182.             if ( !isset($orig_matches[$o]) && !isset($final_matches[$f]) ) {
  183.                 $orig_matches[$o] = $f;
  184.                 $final_matches[$f] = $o;
  185.                 continue;
  186.             }
  187.  
  188.             // Best match of this final is already taken?  Must mean this final is a new row.
  189.             if ( isset($orig_matches[$o]) )
  190.                 $final_matches[$f] = 'x';
  191.  
  192.             // Best match of this orig is already taken?  Must mean this orig is a deleted row.
  193.             elseif ( isset($final_matches[$f]) )
  194.                 $orig_matches[$o] = 'x';
  195.         }
  196.  
  197.         // We read the text in this order
  198.         ksort($orig_matches);
  199.         ksort($final_matches);
  200.  
  201.  
  202.         // Stores rows and blanks for each column.
  203.         $orig_rows = $orig_rows_copy = array_keys($orig_matches);
  204.         $final_rows = array_keys($final_matches);
  205.  
  206.         // Interleaves rows with blanks to keep matches aligned.
  207.         // We may end up with some extraneous blank rows, but we'll just ignore them later.
  208.         foreach ( $orig_rows_copy as $orig_row ) {
  209.             $final_pos = array_search($orig_matches[$orig_row], $final_rows, true);
  210.             $orig_pos = (int) array_search($orig_row, $orig_rows, true);
  211.  
  212.             if ( false === $final_pos ) { // This orig is paired with a blank final.
  213.                 array_splice( $final_rows, $orig_pos, 0, -1 );
  214.             } elseif ( $final_pos < $orig_pos ) { // This orig's match is up a ways.  Pad final with blank rows.
  215.                 $diff_pos = $final_pos - $orig_pos;
  216.                 while ( $diff_pos < 0 )
  217.                     array_splice( $final_rows, $orig_pos, 0, $diff_pos++ );
  218.             } elseif ( $final_pos > $orig_pos ) { // This orig's match is down a ways.  Pad orig with blank rows.
  219.                 $diff_pos = $orig_pos - $final_pos;
  220.                 while ( $diff_pos < 0 )
  221.                     array_splice( $orig_rows, $orig_pos, 0, $diff_pos++ );
  222.             }
  223.         }
  224.  
  225.  
  226.         // Pad the ends with blank rows if the columns aren't the same length
  227.         $diff_count = count($orig_rows) - count($final_rows);
  228.         if ( $diff_count < 0 ) {
  229.             while ( $diff_count < 0 )
  230.                 array_push($orig_rows, $diff_count++);
  231.         } elseif ( $diff_count > 0 ) {
  232.             $diff_count = -1 * $diff_count;
  233.             while ( $diff_count < 0 )
  234.                 array_push($final_rows, $diff_count++);
  235.         }
  236.  
  237.         return array($orig_matches, $final_matches, $orig_rows, $final_rows);
  238.  
  239. /*
  240.         // Debug
  241.         echo "\n\n\n\n\n";
  242.  
  243.         echo "-- DEBUG Matches: Orig -> Final --";
  244.  
  245.         foreach ( $orig_matches as $o => $f ) {
  246.             echo "\n\n\n\n\n";
  247.             echo "ORIG: $o, FINAL: $f\n";
  248.             var_dump($orig[$o],$final[$f]);
  249.         }
  250.         echo "\n\n\n\n\n";
  251.  
  252.         echo "-- DEBUG Matches: Final -> Orig --";
  253.  
  254.         foreach ( $final_matches as $f => $o ) {
  255.             echo "\n\n\n\n\n";
  256.             echo "FINAL: $f, ORIG: $o\n";
  257.             var_dump($final[$f],$orig[$o]);
  258.         }
  259.         echo "\n\n\n\n\n";
  260.  
  261.         echo "-- DEBUG Rows: Orig -- Final --";
  262.  
  263.         echo "\n\n\n\n\n";
  264.         foreach ( $orig_rows as $row => $o ) {
  265.             if ( $o < 0 )
  266.                 $o = 'X';
  267.             $f = $final_rows[$row];
  268.             if ( $f < 0 )
  269.                 $f = 'X';
  270.             echo "$o -- $f\n";
  271.         }
  272.         echo "\n\n\n\n\n";
  273.  
  274.         echo "-- END DEBUG --";
  275.  
  276.         echo "\n\n\n\n\n";
  277.  
  278.         return array($orig_matches, $final_matches, $orig_rows, $final_rows);
  279. */
  280.     }
  281.  
  282.  
  283.     // Computes a number that is intended to reflect the "distance" between two strings.
  284.     function compute_string_distance( $string1, $string2 ) {
  285.         // Vectors containing character frequency for all chars in each string
  286.         $chars1 = count_chars($string1);
  287.         $chars2 = count_chars($string2);
  288.  
  289.         // L1-norm of difference vector.
  290.         $difference = array_sum( array_map( array(&$this, 'difference'), $chars1, $chars2 ) );
  291.  
  292.         // $string1 has zero length? Odd.  Give huge penalty by not dividing.
  293.         if ( !$string1 )
  294.             return $difference;
  295.  
  296.         // Return distance per charcter (of string1)
  297.         return $difference / strlen($string1);
  298.     }
  299.  
  300.     function difference( $a, $b ) {
  301.         return abs( $a - $b );
  302.     }
  303.  
  304. }
  305.  
  306. // Better word splitting than the PEAR package provides
  307. class WP_Text_Diff_Renderer_inline extends Text_Diff_Renderer_inline {
  308.  
  309.     function _splitOnWords($string, $newlineEscape = "\n") {
  310.         $string = str_replace("\0", '', $string);
  311.         $words  = preg_split( '/([^\w])/u', $string, -1, PREG_SPLIT_DELIM_CAPTURE );
  312.         $words  = str_replace( "\n", $newlineEscape, $words );
  313.         return $words;
  314.     }
  315.  
  316. }
  317.  
  318. ?>
  319.