Skip to content

Commit f249285

Browse files
committed
Update table diff with initial diffing logic
1 parent 385a3df commit f249285

File tree

5 files changed

+232
-16
lines changed

5 files changed

+232
-16
lines changed

demo/index.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010

1111
$input = file_get_contents('php://input');
1212

13+
ini_set('display_errors', 1);
14+
error_reporting(E_ERROR);
15+
1316
if ($input) {
1417
$data = json_decode($input, true);
15-
$diff = new HtmlDiff($data['oldText'], $data['newText']);
18+
$diff = new HtmlDiff($data['oldText'], $data['newText'], 'UTF-8', array());
1619
$diff->build();
1720

1821
header('Content-Type: application/json');

lib/Caxy/HtmlDiff/HtmlDiff.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
namespace Caxy\HtmlDiff;
44

55
class HtmlDiff extends AbstractDiff
6-
{
7-
protected $oldWords = array();
8-
protected $newWords = array();
6+
{
97
protected $wordIndices;
108
protected $oldTables;
119
protected $newTables;
@@ -205,6 +203,13 @@ protected function processEqualOperation($operation)
205203
$this->content .= implode( "", $result );
206204
}
207205

206+
protected function findMatchingTableInOld($operation, $posInNew)
207+
{
208+
$offset = $posInNew - $operation->startInNew;
209+
210+
return $this->oldTables[$operation->startInOld + $offset];
211+
}
212+
208213
protected function insertTag($tag, $cssClass, &$words)
209214
{
210215
while (true) {

lib/Caxy/HtmlDiff/TableDiff.php

Lines changed: 186 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
namespace Caxy\HtmlDiff;
44

5+
/**
6+
* @todo clean up way to iterate between new and old cells
7+
* @todo Make sure diffed table keeps <tbody> or other table structure elements
8+
* @todo find matches of row/cells in order to handle row/cell additions/deletions
9+
*/
510
class TableDiff extends AbstractDiff
611
{
712
protected $oldRows = array();
@@ -10,41 +15,185 @@ class TableDiff extends AbstractDiff
1015
protected $oldTable = null;
1116
protected $newTable = null;
1217
protected $diffTable = null;
18+
protected $diffDom = null;
19+
20+
protected $newRowOffsets = 0;
21+
protected $oldRowOffsets = 0;
1322

1423
public function build()
1524
{
1625
$this->splitInputsToWords();
1726
$this->buildTableDoms();
1827

28+
$this->diffDom = new \DOMDocument();
29+
30+
$this->normalizeFormat();
31+
32+
//print_r($this->newTable);die();
33+
1934
$this->diffTableContent();
2035

2136
return $this->content;
2237
}
2338

39+
protected function normalizeFormat()
40+
{
41+
$oldRows = $this->oldTable['children'];
42+
$newRows = $this->newTable['children'];
43+
44+
foreach ($newRows as $rowIndex => $newRow) {
45+
$oldRow = isset($oldRows[$rowIndex]) ? $oldRows[$rowIndex] : null;
46+
47+
if (!$oldRow) {
48+
continue;
49+
}
50+
51+
$newRowOffset = 0;
52+
$oldRowOffset = 0;
53+
54+
foreach ($newRow['children'] as $cellIndex => $newCell) {
55+
$oldCell = isset($oldRow['children'][$cellIndex]) ? $oldRow['children'][$cellIndex] : null;
56+
57+
if ($oldCell) {
58+
$oldRowspan = $oldCell->getAttribute('rowspan') ?: 1;
59+
$oldColspan = $oldCell->getAttribute('colspan') ?: 1;
60+
$newRowspan = $newCell->getAttribute('rowspan') ?: 1;
61+
$newColspan = $newCell->getAttribute('colspan') ?: 1;
62+
63+
if ($oldRowspan > $newRowspan) {
64+
// add placeholders in next row of new rows
65+
$offset = $oldRowspan - $newRowspan;
66+
if ($offset > $newRowOffset) {
67+
$newRowOffset = $offset;
68+
}
69+
} elseif ($newRowspan > $oldRowspan) {
70+
$offset = $newRowspan - $oldRowspan;
71+
if ($offset > $oldRowOffset) {
72+
$oldRowOffset = $offset;
73+
}
74+
}
75+
}
76+
}
77+
78+
if ($oldRowOffset > 0 && isset($newRows[$rowIndex + 1])) {
79+
$blankRow = $this->diffDom->createElement('tr');
80+
81+
$insertArray = array();
82+
for ($i = 0; $i < $oldRowOffset; $i++) {
83+
$insertArray[] = array('dom' => $blankRow, 'children' => array());
84+
}
85+
86+
array_splice($this->oldTable['children'], $rowIndex + 1, 0, $insertArray);
87+
} elseif ($newRowOffset > 0 && isset($newRows[$rowIndex + 1])) {
88+
$blankRow = $this->diffDom->createElement('tr');
89+
90+
$insertArray = array();
91+
for ($i = 0; $i < $newRowOffset; $i++) {
92+
$insertArray[] = array('dom' => $blankRow, 'children' => array());
93+
}
94+
array_splice($this->newTable['children'], $rowIndex + 1, 0, $insertArray);
95+
}
96+
}
97+
}
98+
2499
protected function diffTableContent()
25100
{
26-
$this->diffTable = new \DOMDocument();
101+
$this->diffDom = new \DOMDocument();
102+
$this->diffTable = $this->diffDom->importNode($this->newTable['dom']->cloneNode(false), false);
103+
$this->diffDom->appendChild($this->diffTable);
27104

28105
$oldRows = $this->oldTable['children'];
29106
$newRows = $this->newTable['children'];
30107

31108
foreach ($newRows as $index => $row) {
32-
if (isset($oldRows[$index])) {
33-
$this->diffRows($oldRows[$index], $row);
34-
} else {
35-
109+
$rowDom = $this->diffRows(
110+
isset($oldRows[$index]) ? $oldRows[$index] : null,
111+
$row
112+
);
113+
114+
$this->diffTable->appendChild($rowDom);
115+
}
116+
117+
if (count($oldRows) > count($newRows)) {
118+
foreach (array_slice($oldRows, count($newRows)) as $row) {
119+
$rowDom = $this->diffRows(
120+
$row,
121+
null
122+
);
123+
124+
$this->diffTable->appendChild($rowDom);
36125
}
37126
}
127+
128+
$this->content = $this->htmlFromNode($this->diffTable);
38129
}
39130

40131
protected function diffRows($oldRow, $newRow)
41132
{
42-
133+
// create tr dom element
134+
$rowToClone = $newRow ?: $oldRow;
135+
$diffRow = $this->diffDom->importNode($rowToClone['dom']->cloneNode(false), false);
136+
137+
$oldCells = $oldRow ? $oldRow['children'] : array();
138+
$newCells = $newRow ? $newRow['children'] : array();
139+
140+
foreach ($newCells as $index => $cell) {
141+
$diffCell = $this->diffCells(
142+
isset($oldCells[$index]) ? $oldCells[$index] : null,
143+
$cell
144+
);
145+
146+
$diffRow->appendChild($diffCell);
147+
}
148+
149+
if (count($oldCells) > count($newCells)) {
150+
foreach (array_slice($oldCells, count($newCells)) as $cell) {
151+
$diffCell = $this->diffCells(
152+
$cell,
153+
null
154+
);
155+
156+
$diffRow->appendChild($diffCell);
157+
}
158+
}
159+
160+
return $diffRow;
161+
}
162+
163+
protected function getNewCellNode(\DOMElement $oldCell = null, \DOMElement $newCell = null)
164+
{
165+
// If only one cell exists, use it
166+
if (!$oldCell || !$newCell) {
167+
$clone = $newCell ? $newCell->cloneNode(false) : $oldCell->cloneNode(false);
168+
} else {
169+
$clone = $newCell->cloneNode(false);
170+
171+
$oldRowspan = $oldCell->getAttribute('rowspan') ?: 1;
172+
$oldColspan = $oldCell->getAttribute('colspan') ?: 1;
173+
$newRowspan = $newCell->getAttribute('rowspan') ?: 1;
174+
$newColspan = $newCell->getAttribute('colspan') ?: 1;
175+
176+
$clone->setAttribute('rowspan', max($oldRowspan, $newRowspan));
177+
$clone->setAttribute('colspan', max($oldColspan, $newColspan));
178+
}
179+
180+
return $this->diffDom->importNode($clone);
43181
}
44182

45-
protected function insertRow($row)
183+
protected function diffCells($oldCell, $newCell)
46184
{
185+
$diffCell = $this->getNewCellNode($oldCell, $newCell);
47186

187+
$oldContent = $oldCell ? $this->getInnerHtml($oldCell) : '';
188+
$newContent = $newCell ? $this->getInnerHtml($newCell) : '';
189+
190+
$htmlDiff = new HtmlDiff($oldContent, $newContent, $this->encoding, $this->specialCaseChars, $this->groupDiffs);
191+
192+
$diff = $htmlDiff->build();
193+
194+
$this->setInnerHtml($diffCell, $diff);
195+
196+
return $diffCell;
48197
}
49198

50199
protected function buildTableDoms()
@@ -89,7 +238,7 @@ protected function parseTableRow(\DOMNode $node)
89238
{
90239
$row = array();
91240
foreach ($node->childNodes as $child) {
92-
if (in_array($child->name, array('td', 'th'))) {
241+
if (in_array($child->nodeName, array('td', 'th'))) {
93242
$row[] = $child;
94243
}
95244
}
@@ -99,9 +248,34 @@ protected function parseTableRow(\DOMNode $node)
99248

100249
protected function getInnerHtml($node)
101250
{
102-
$body = $node->ownerDocument->documentElement->firstChild->firstChild;
103-
$document = new \DOMDocument();
104-
$document->appendChild($document->importNode($body, true));
105-
return $document->saveHTML();
251+
$innerHtml = '';
252+
$children = $node->childNodes;
253+
254+
foreach ($children as $child) {
255+
$innerHtml .= $this->htmlFromNode($child);
256+
}
257+
258+
return $innerHtml;
259+
}
260+
261+
protected function htmlFromNode($node)
262+
{
263+
$domDocument = new \DOMDocument();
264+
$newNode = $domDocument->importNode($node, true);
265+
$domDocument->appendChild($newNode);
266+
return trim($domDocument->saveHTML());
267+
}
268+
269+
protected function setInnerHtml($node, $html)
270+
{
271+
$doc = new \DOMDocument();
272+
$doc->loadHTML($html);
273+
$fragment = $node->ownerDocument->createDocumentFragment();
274+
$root = $doc->getElementsByTagName('body')->item(0);
275+
foreach ($root->childNodes as $child) {
276+
$fragment->appendChild($node->ownerDocument->importNode($child, true));
277+
}
278+
279+
$node->appendChild($fragment);
106280
}
107281
}

lib/Caxy/HtmlDiff/TableMatch.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Caxy\HtmlDiff;
4+
5+
class TableMatch
6+
{
7+
public $startInOld;
8+
public $startInNew;
9+
public $endInOld;
10+
public $endInNew;
11+
12+
public function __construct($startInOld, $startInNew, $endInOld, $endInNew)
13+
{
14+
$this->startInOld = $startInOld;
15+
$this->startInNew = $startInNew;
16+
$this->endInOld = $endInOld;
17+
$this->endInNew = $endInNew;
18+
}
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Caxy\HtmlDiff;
4+
5+
class TablePosition
6+
{
7+
public $row;
8+
public $cell;
9+
10+
public function __construct($row, $cell)
11+
{
12+
$this->row = $row;
13+
$this->cell = $cell;
14+
}
15+
}

0 commit comments

Comments
 (0)