/**
  * @group performance
  */
 public function testParagraphPerformance()
 {
     $fixturesPath = __DIR__ . '/../../../../fixtures/Performance/';
     $expected = file_get_contents($fixturesPath . 'paragraphs_expected.html');
     $diff = new HtmlDiff(file_get_contents($fixturesPath . 'paragraphs.html'), file_get_contents($fixturesPath . 'paragraphs_changed.html'), 'UTF-8', array());
     $output = $diff->build();
     $this->assertSame($this->stripExtraWhitespaceAndNewLines($output), $this->stripExtraWhitespaceAndNewLines($expected));
 }
 public function testHtmlDiffConfigStatic()
 {
     $oldText = '<b>text</b>';
     $newText = '<b>t3xt</b>';
     $config = new HtmlDiffConfig();
     $config->setPurifierCacheLocation('/tmp');
     $diff = HtmlDiff::create($oldText, $newText, $config);
     $diff->setHTMLPurifierConfig($this->config);
     $diff->build();
 }
Exemple #3
0
 public static function diff($old, $new)
 {
     $htmlDiff = new HtmlDiff($old, $new);
     return $htmlDiff->build();
 }
Exemple #4
0
 protected function diffLists(DiffList $oldList, DiffList $newList)
 {
     $oldMatchData = array();
     $newMatchData = array();
     $oldListIndices = array();
     $newListIndices = array();
     $oldListItems = array();
     $newListItems = array();
     foreach ($oldList->getListItems() as $oldIndex => $oldListItem) {
         if ($oldListItem instanceof DiffListItem) {
             $oldListItems[$oldIndex] = $oldListItem;
             $oldListIndices[] = $oldIndex;
             $oldMatchData[$oldIndex] = array();
             // Get match percentages
             foreach ($newList->getListItems() as $newIndex => $newListItem) {
                 if ($newListItem instanceof DiffListItem) {
                     if (!in_array($newListItem, $newListItems)) {
                         $newListItems[$newIndex] = $newListItem;
                     }
                     if (!in_array($newIndex, $newListIndices)) {
                         $newListIndices[] = $newIndex;
                     }
                     if (!array_key_exists($newIndex, $newMatchData)) {
                         $newMatchData[$newIndex] = array();
                     }
                     $oldText = implode('', $oldListItem->getText());
                     $newText = implode('', $newListItem->getText());
                     // similar_text
                     $percentage = null;
                     similar_text($oldText, $newText, $percentage);
                     $oldMatchData[$oldIndex][$newIndex] = $percentage;
                     $newMatchData[$newIndex][$oldIndex] = $percentage;
                 }
             }
         }
     }
     $currentIndexInOld = 0;
     $currentIndexInNew = 0;
     $oldCount = count($oldListIndices);
     $newCount = count($newListIndices);
     $difference = max($oldCount, $newCount) - min($oldCount, $newCount);
     $diffOutput = '';
     foreach ($newList->getListItems() as $newIndex => $newListItem) {
         if ($newListItem instanceof DiffListItem) {
             $operation = null;
             $oldListIndex = array_key_exists($currentIndexInOld, $oldListIndices) ? $oldListIndices[$currentIndexInOld] : null;
             $class = 'normal';
             if (null !== $oldListIndex && array_key_exists($oldListIndex, $oldMatchData)) {
                 // Check percentage matches of upcoming list items in old.
                 $matchPercentage = $oldMatchData[$oldListIndex][$newIndex];
                 // does the old list item match better?
                 $otherMatchBetter = false;
                 foreach ($oldMatchData[$oldListIndex] as $index => $percentage) {
                     if ($index > $newIndex && $percentage > $matchPercentage) {
                         $otherMatchBetter = $index;
                     }
                 }
                 if (false !== $otherMatchBetter && $newCount > $oldCount && $difference > 0) {
                     $diffOutput .= sprintf('%s', $newListItem->getHtml('normal new', 'ins'));
                     ++$currentIndexInNew;
                     --$difference;
                     continue;
                 }
                 $replacement = false;
                 // is there a better old list item match coming up?
                 if ($oldCount > $newCount) {
                     while ($difference > 0 && $this->hasBetterMatch($newMatchData[$newIndex], $oldListIndex)) {
                         $diffOutput .= sprintf('%s', $oldListItems[$oldListIndex]->getHtml('removed', 'del'));
                         ++$currentIndexInOld;
                         --$difference;
                         $oldListIndex = array_key_exists($currentIndexInOld, $oldListIndices) ? $oldListIndices[$currentIndexInOld] : null;
                         $matchPercentage = $oldMatchData[$oldListIndex][$newIndex];
                         $replacement = true;
                     }
                 }
                 $nextOldListIndex = array_key_exists($currentIndexInOld + 1, $oldListIndices) ? $oldListIndices[$currentIndexInOld + 1] : null;
                 if ($nextOldListIndex !== null && $oldMatchData[$nextOldListIndex][$newIndex] > $matchPercentage && $oldMatchData[$nextOldListIndex][$newIndex] > $this->config->getMatchThreshold()) {
                     // Following list item in old is better match, use that.
                     $diffOutput .= sprintf('%s', $oldListItems[$oldListIndex]->getHtml('removed', 'del'));
                     ++$currentIndexInOld;
                     $oldListIndex = $nextOldListIndex;
                     $matchPercentage = $oldMatchData[$oldListIndex][$newIndex];
                     $replacement = true;
                 }
                 if ($matchPercentage > $this->config->getMatchThreshold() || $currentIndexInNew === $currentIndexInOld) {
                     // Diff the two lists.
                     $htmlDiff = HtmlDiff::create($oldListItems[$oldListIndex]->getInnerHtml(), $newListItem->getInnerHtml(), $this->config);
                     $diffContent = $htmlDiff->build();
                     $diffOutput .= sprintf('%s%s%s', $newListItem->getStartTagWithDiffClass($replacement ? 'replacement' : 'normal'), $diffContent, $newListItem->getEndTag());
                 } else {
                     $diffOutput .= sprintf('%s', $oldListItems[$oldListIndex]->getHtml('removed', 'del'));
                     $diffOutput .= sprintf('%s', $newListItem->getHtml('replacement', 'ins'));
                 }
                 ++$currentIndexInOld;
             } else {
                 $diffOutput .= sprintf('%s', $newListItem->getHtml('normal new', 'ins'));
             }
             ++$currentIndexInNew;
         }
     }
     // Output any additional list items
     while (array_key_exists($currentIndexInOld, $oldListIndices)) {
         $oldListIndex = $oldListIndices[$currentIndexInOld];
         $diffOutput .= sprintf('%s', $oldListItems[$oldListIndex]->getHtml('removed', 'del'));
         ++$currentIndexInOld;
     }
     return sprintf('%s%s%s', $newList->getStartTagWithDiffClass(), $diffOutput, $newList->getEndTag());
 }
 /**
  * @dataProvider diffContentProvider
  *
  * @param $oldText
  * @param $newText
  * @param $expected
  */
 public function testHtmlDiff($oldText, $newText, $expected)
 {
     $diff = new HtmlDiff(trim($oldText), trim($newText), 'UTF-8', array());
     $output = $diff->build();
     static::assertEquals($this->stripExtraWhitespaceAndNewLines($expected), $this->stripExtraWhitespaceAndNewLines($output));
 }
Exemple #6
0
 /**
  * @param TableCell|null $oldCell
  * @param TableCell|null $newCell
  * @param bool           $usingExtraRow
  *
  * @return \DOMElement
  */
 protected function diffCells($oldCell, $newCell, $usingExtraRow = false)
 {
     $diffCell = $this->getNewCellNode($oldCell, $newCell);
     $oldContent = $oldCell ? $this->getInnerHtml($oldCell->getDomNode()) : '';
     $newContent = $newCell ? $this->getInnerHtml($newCell->getDomNode()) : '';
     $htmlDiff = HtmlDiff::create(mb_convert_encoding($oldContent, 'UTF-8', 'HTML-ENTITIES'), mb_convert_encoding($newContent, 'UTF-8', 'HTML-ENTITIES'), $this->config);
     $diff = $htmlDiff->build();
     $this->setInnerHtml($diffCell, $diff);
     if (null === $newCell) {
         $diffCell->setAttribute('class', trim($diffCell->getAttribute('class') . ' del'));
     }
     if (null === $oldCell) {
         $diffCell->setAttribute('class', trim($diffCell->getAttribute('class') . ' ins'));
     }
     if ($usingExtraRow) {
         $diffCell->setAttribute('class', trim($diffCell->getAttribute('class') . ' extra-row'));
     }
     return $diffCell;
 }
Exemple #7
0
 /**
  * @param Operation[]|array     $operations
  * @param \simple_html_dom_node $oldListNode
  * @param \simple_html_dom_node $newListNode
  *
  * @return string
  */
 protected function processOperations($operations, $oldListNode, $newListNode)
 {
     $output = '';
     $indexInOld = 0;
     $indexInNew = 0;
     $lastOperation = null;
     foreach ($operations as $operation) {
         $replaced = false;
         while ($operation->startInOld > ($operation->action === Operation::ADDED ? $indexInOld : $indexInOld + 1)) {
             $li = $oldListNode->children($indexInOld);
             $matchingLi = null;
             if ($operation->startInNew > ($operation->action === Operation::DELETED ? $indexInNew : $indexInNew + 1)) {
                 $matchingLi = $newListNode->children($indexInNew);
             }
             if (null !== $matchingLi) {
                 $htmlDiff = HtmlDiff::create($li->innertext, $matchingLi->innertext, $this->config);
                 $li->innertext = $htmlDiff->build();
                 $indexInNew++;
             }
             $class = self::CLASS_LIST_ITEM_NONE;
             if ($lastOperation === Operation::DELETED && !$replaced) {
                 $class = self::CLASS_LIST_ITEM_CHANGED;
                 $replaced = true;
             }
             $li->setAttribute('class', trim($li->getAttribute('class') . ' ' . $class));
             $output .= $li->outertext;
             $indexInOld++;
         }
         switch ($operation->action) {
             case Operation::ADDED:
                 for ($i = $operation->startInNew; $i <= $operation->endInNew; $i++) {
                     $output .= $this->addListItem($newListNode->children($i - 1));
                 }
                 $indexInNew = $operation->endInNew;
                 break;
             case Operation::DELETED:
                 for ($i = $operation->startInOld; $i <= $operation->endInOld; $i++) {
                     $output .= $this->deleteListItem($oldListNode->children($i - 1));
                 }
                 $indexInOld = $operation->endInOld;
                 break;
             case Operation::CHANGED:
                 $changeDelta = 0;
                 for ($i = $operation->startInOld; $i <= $operation->endInOld; $i++) {
                     $output .= $this->deleteListItem($oldListNode->children($i - 1));
                     $changeDelta--;
                 }
                 for ($i = $operation->startInNew; $i <= $operation->endInNew; $i++) {
                     $output .= $this->addListItem($newListNode->children($i - 1), $changeDelta < 0);
                     $changeDelta++;
                 }
                 $indexInOld = $operation->endInOld;
                 $indexInNew = $operation->endInNew;
                 break;
         }
         $lastOperation = $operation->action;
     }
     $oldCount = count($oldListNode->children());
     $newCount = count($newListNode->children());
     while ($indexInOld < $oldCount) {
         $li = $oldListNode->children($indexInOld);
         $matchingLi = null;
         if ($indexInNew < $newCount) {
             $matchingLi = $newListNode->children($indexInNew);
         }
         if (null !== $matchingLi) {
             $htmlDiff = HtmlDiff::create($li->innertext(), $matchingLi->innertext(), $this->config);
             $li->innertext = $htmlDiff->build();
             $indexInNew++;
         }
         $class = self::CLASS_LIST_ITEM_NONE;
         if ($lastOperation === Operation::DELETED) {
             $class = self::CLASS_LIST_ITEM_CHANGED;
         }
         $li->setAttribute('class', trim($li->getAttribute('class') . ' ' . $class));
         $output .= $li->outertext;
         $indexInOld++;
     }
     $newListNode->innertext = $output;
     $newListNode->setAttribute('class', trim($newListNode->getAttribute('class') . ' diff-list'));
     return $newListNode->outertext;
 }
Exemple #8
0
    if (!is_string($value)) {
        $value = var_export($value, true);
    }
    if (!array_key_exists($key, $debugOutput)) {
        $debugOutput[$key] = array();
    }
    $debugOutput[$key][] = $value;
}
$input = file_get_contents('php://input');
if ($input) {
    header('Content-Type: application/json');
    $data = json_decode($input, true);
    $oldText = $data['oldText'];
    $newText = $data['newText'];
    $useTableDiffing = isset($data['tableDiffing']) ? $data['tableDiffing'] : true;
    $diff = new HtmlDiff($oldText, $newText, 'UTF-8', array());
    if (array_key_exists('matchThreshold', $data)) {
        $diff->setMatchThreshold($data['matchThreshold']);
    }
    $diff->setUseTableDiffing($useTableDiffing);
    $diffOutput = $diff->build();
    $diffOutput = iconv('UTF-8', 'UTF-8//IGNORE', $diffOutput);
    $jsonOutput = json_encode(array('diff' => $diffOutput, 'debug' => $debugOutput));
    if (false === $jsonOutput) {
        throw new \Exception('Failed to encode JSON: ' . json_last_error_msg());
    }
    echo $jsonOutput;
} else {
    header('Content-Type: text/html');
    echo file_get_contents('demo.html');
}
Exemple #9
0
<?php

use Caxy\HtmlDiff\HtmlDiff;
require __DIR__ . '/../lib/Caxy/HtmlDiff/HtmlDiff.php';
require __DIR__ . '/../lib/Caxy/HtmlDiff/Match.php';
require __DIR__ . '/../lib/Caxy/HtmlDiff/Operation.php';
$input = file_get_contents('php://input');
if ($input) {
    $data = json_decode($input, true);
    $diff = new HtmlDiff($data['oldText'], $data['newText']);
    $diff->build();
    header('Content-Type: application/json');
    echo json_encode(array('diff' => $diff->getDifference()));
} else {
    header('Content-Type: text/html');
    echo file_get_contents('demo.html');
}