/** * Calculates the difference between two objects or arrays and renders them to a html * based page in human-readable format. Currently only works reliably on arrays. * * @version 1.0 * @since 1.0 * @author http://ca2.php.net/manual/en/function.var-dump.php#92594 * * @param array/object &$from | From object * @param array/object &$to | To object */ public static function diff(&$from_ref, &$to_ref, $granularity = 2) { $from = self::string_dump($from_ref, '$'); $to = self::string_dump($to_ref, '$'); $pre = "<pre style='margin: 0px 0px 10px 0px; display: block; background: white; color: black; font-family:"; $pre .= " Verdana; border: 1px solid #cccccc; padding: 5px; font-size: 10px; line-height: 13px; white-space:pre;'>"; echo $pre; $start_time = gettimeofday(true); $granularityStacks = array(FOX_fineDiff::$paragraphGranularity, FOX_fineDiff::$sentenceGranularity, FOX_fineDiff::$wordGranularity, FOX_fineDiff::$characterGranularity); $diff = new FOX_fineDiff($from, $to, $granularityStacks[$granularity]); $rendered_diff = $diff->renderDiffToHTML(); ?> <div class="panecontainer" style="white-space:nowrap;"> <div id="htmldiff"> <div class="pane" style="white-space:nowrap;"><?php echo $rendered_diff; ?> </div> </div> </div> <?php }
/** * This is the core algorithm which actually perform the diff itself, * fragmenting the strings as per specified delimiters. * * This function is naturally recursive, however for performance purpose * a local job queue is used instead of outright recursivity. */ private static function doFragmentDiff($from_text, $to_text, $delimiters) { // Empty delimiter means character-level diffing. // In such case, use code path optimized for character-level diffing. if (empty($delimiters)) { return FOX_fineDiff::doCharDiff($from_text, $to_text); } $result = array(); // fragment-level diffing $from_text_len = strlen($from_text); $to_text_len = strlen($to_text); $from_fragments = FOX_fineDiff::extractFragments($from_text, $delimiters); $to_fragments = FOX_fineDiff::extractFragments($to_text, $delimiters); $jobs = array(array(0, $from_text_len, 0, $to_text_len)); $cached_array_keys = array(); while ($job = array_pop($jobs)) { // get the segments which must be diff'ed list($from_segment_start, $from_segment_end, $to_segment_start, $to_segment_end) = $job; // catch easy cases first $from_segment_length = $from_segment_end - $from_segment_start; $to_segment_length = $to_segment_end - $to_segment_start; if (!$from_segment_length || !$to_segment_length) { if ($from_segment_length) { $result[$from_segment_start * 4] = new FOX_fineDiffDeleteOp($from_segment_length); } elseif ($to_segment_length) { $result[$from_segment_start * 4 + 1] = new FOX_fineDiffInsertOp(substr($to_text, $to_segment_start, $to_segment_length)); } continue; } // find longest copy operation for the current segments $best_copy_length = 0; $from_base_fragment_index = $from_segment_start; $cached_array_keys_for_current_segment = array(); while ($from_base_fragment_index < $from_segment_end) { $from_base_fragment = $from_fragments[$from_base_fragment_index]; $from_base_fragment_length = strlen($from_base_fragment); // performance boost: cache array keys if (!isset($cached_array_keys_for_current_segment[$from_base_fragment])) { if (!isset($cached_array_keys[$from_base_fragment])) { $to_all_fragment_indices = $cached_array_keys[$from_base_fragment] = array_keys($to_fragments, $from_base_fragment, true); } else { $to_all_fragment_indices = $cached_array_keys[$from_base_fragment]; } // get only indices which falls within current segment if ($to_segment_start > 0 || $to_segment_end < $to_text_len) { $to_fragment_indices = array(); foreach ($to_all_fragment_indices as $to_fragment_index) { if ($to_fragment_index < $to_segment_start) { continue; } if ($to_fragment_index >= $to_segment_end) { break; } $to_fragment_indices[] = $to_fragment_index; } $cached_array_keys_for_current_segment[$from_base_fragment] = $to_fragment_indices; } else { $to_fragment_indices = $to_all_fragment_indices; } } else { $to_fragment_indices = $cached_array_keys_for_current_segment[$from_base_fragment]; } // iterate through collected indices foreach ($to_fragment_indices as $to_base_fragment_index) { $fragment_index_offset = $from_base_fragment_length; // iterate until no more match for (;;) { $fragment_from_index = $from_base_fragment_index + $fragment_index_offset; if ($fragment_from_index >= $from_segment_end) { break; } $fragment_to_index = $to_base_fragment_index + $fragment_index_offset; if ($fragment_to_index >= $to_segment_end) { break; } if ($from_fragments[$fragment_from_index] !== $to_fragments[$fragment_to_index]) { break; } $fragment_length = strlen($from_fragments[$fragment_from_index]); $fragment_index_offset += $fragment_length; } if ($fragment_index_offset > $best_copy_length) { $best_copy_length = $fragment_index_offset; $best_from_start = $from_base_fragment_index; $best_to_start = $to_base_fragment_index; } } $from_base_fragment_index += strlen($from_base_fragment); // If match is larger than half segment size, no point trying to find better // TODO: Really? if ($best_copy_length >= $from_segment_length / 2) { break; } // no point to keep looking if what is left is less than // current best match if ($from_base_fragment_index + $best_copy_length >= $from_segment_end) { break; } } if ($best_copy_length) { $jobs[] = array($from_segment_start, $best_from_start, $to_segment_start, $best_to_start); $result[$best_from_start * 4 + 2] = new FOX_fineDiffCopyOp($best_copy_length); $jobs[] = array($best_from_start + $best_copy_length, $from_segment_end, $best_to_start + $best_copy_length, $to_segment_end); } else { $result[$from_segment_start * 4] = new FOX_fineDiffReplaceOp($from_segment_length, substr($to_text, $to_segment_start, $to_segment_length)); } } ksort($result, SORT_NUMERIC); return array_values($result); }