22
33namespace 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+ */
510class 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}
0 commit comments