Skip to content

Commit c124838

Browse files
committed
Merge pull request #36 from caxy/feature/img-diffing
Add support for diffing img elements
2 parents 65b1794 + bd61c85 commit c124838

File tree

8 files changed

+89
-36
lines changed

8 files changed

+89
-36
lines changed

demo/codes.css

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,30 @@
1-
/*
2-
Document : codes
3-
Created on : Sep 23, 2013, 4:41:58 PM
4-
Author : mgersten
5-
Description: CSS related to I-code specific display
6-
*/
1+
del.diffimg.diffsrc {
2+
display: inline-block;
3+
position: relative;
4+
}
5+
6+
del.diffimg.diffsrc:before {
7+
position: absolute;
8+
content: "";
9+
left: 0;
10+
top: 0;
11+
width: 100%;
12+
height: 100%;
13+
background: repeating-linear-gradient(
14+
to left top,
15+
rgba(255, 0, 0, 0),
16+
rgba(255, 0, 0, 0) 49.5%,
17+
rgba(255, 0, 0, 1) 49.5%,
18+
rgba(255, 0, 0, 1) 50.5%
19+
), repeating-linear-gradient(
20+
to left bottom,
21+
rgba(255, 0, 0, 0),
22+
rgba(255, 0, 0, 0) 49.5%,
23+
rgba(255, 0, 0, 1) 49.5%,
24+
rgba(255, 0, 0, 1) 50.5%
25+
);
26+
}
27+
728
.diff-list > li.normal,
829
.diff-list > li.removed,
930
.diff-list > li.replacement{

demo/demos.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

lib/Caxy/HtmlDiff/HtmlDiff.php

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -142,28 +142,39 @@ protected function replaceIsolatedDiffTags()
142142
protected function createIsolatedDiffTagPlaceholders(&$words)
143143
{
144144
$openIsolatedDiffTags = 0;
145-
$isolatedDiffTagIndicies = array();
145+
$isolatedDiffTagIndices = array();
146146
$isolatedDiffTagStart = 0;
147147
$currentIsolatedDiffTag = null;
148148
foreach ($words as $index => $word) {
149149
$openIsolatedDiffTag = $this->isOpeningIsolatedDiffTag($word, $currentIsolatedDiffTag);
150150
if ($openIsolatedDiffTag) {
151-
if ($openIsolatedDiffTags === 0) {
152-
$isolatedDiffTagStart = $index;
151+
if ($this->isSelfClosingTag($word) || stripos($word, '<img') !== false) {
152+
if ($openIsolatedDiffTags === 0) {
153+
$isolatedDiffTagIndices[] = array(
154+
'start' => $index,
155+
'length' => 1,
156+
'tagType' => $openIsolatedDiffTag,
157+
);
158+
$currentIsolatedDiffTag = null;
159+
}
160+
} else {
161+
if ($openIsolatedDiffTags === 0) {
162+
$isolatedDiffTagStart = $index;
163+
}
164+
$openIsolatedDiffTags++;
165+
$currentIsolatedDiffTag = $openIsolatedDiffTag;
153166
}
154-
$openIsolatedDiffTags++;
155-
$currentIsolatedDiffTag = $openIsolatedDiffTag;
156167
} elseif ($openIsolatedDiffTags > 0 && $this->isClosingIsolatedDiffTag($word, $currentIsolatedDiffTag)) {
157168
$openIsolatedDiffTags--;
158169
if ($openIsolatedDiffTags == 0) {
159-
$isolatedDiffTagIndicies[] = array ('start' => $isolatedDiffTagStart, 'length' => $index - $isolatedDiffTagStart + 1, 'tagType' => $currentIsolatedDiffTag);
170+
$isolatedDiffTagIndices[] = array ('start' => $isolatedDiffTagStart, 'length' => $index - $isolatedDiffTagStart + 1, 'tagType' => $currentIsolatedDiffTag);
160171
$currentIsolatedDiffTag = null;
161172
}
162173
}
163174
}
164175
$isolatedDiffTagScript = array();
165176
$offset = 0;
166-
foreach ($isolatedDiffTagIndicies as $isolatedDiffTagIndex) {
177+
foreach ($isolatedDiffTagIndices as $isolatedDiffTagIndex) {
167178
$start = $isolatedDiffTagIndex['start'] - $offset;
168179
$placeholderString = $this->config->getIsolatedDiffTagPlaceholder($isolatedDiffTagIndex['tagType']);
169180
$isolatedDiffTagScript[$start] = array_splice($words, $start, $isolatedDiffTagIndex['length'], $placeholderString);
@@ -185,15 +196,21 @@ protected function isOpeningIsolatedDiffTag($item, $currentIsolatedDiffTag = nul
185196
$tagsToMatch = $currentIsolatedDiffTag !== null
186197
? array($currentIsolatedDiffTag => $this->config->getIsolatedDiffTagPlaceholder($currentIsolatedDiffTag))
187198
: $this->config->getIsolatedDiffTags();
199+
$pattern = '#<%s(\s+[^>]*)?>#iU';
188200
foreach ($tagsToMatch as $key => $value) {
189-
if (preg_match("#<".$key."[^>]*>\\s*#iU", $item)) {
201+
if (preg_match(sprintf($pattern, $key), $item)) {
190202
return $key;
191203
}
192204
}
193205

194206
return false;
195207
}
196208

209+
protected function isSelfClosingTag($text)
210+
{
211+
return (bool) preg_match('/<[^>]+\/\s*>/', $text);
212+
}
213+
197214
/**
198215
* @param string $item
199216
* @param null|string $currentIsolatedDiffTag
@@ -205,8 +222,9 @@ protected function isClosingIsolatedDiffTag($item, $currentIsolatedDiffTag = nul
205222
$tagsToMatch = $currentIsolatedDiffTag !== null
206223
? array($currentIsolatedDiffTag => $this->config->getIsolatedDiffTagPlaceholder($currentIsolatedDiffTag))
207224
: $this->config->getIsolatedDiffTags();
225+
$pattern = '#</%s(\s+[^>]*)?>#iU';
208226
foreach ($tagsToMatch as $key => $value) {
209-
if (preg_match("#</".$key."[^>]*>\\s*#iU", $item)) {
227+
if (preg_match(sprintf($pattern, $key), $item)) {
210228
return $key;
211229
}
212230
}
@@ -306,7 +324,9 @@ protected function diffIsolatedPlaceholder($operation, $pos, $placeholder, $stri
306324
} elseif ($this->config->isUseTableDiffing() && $this->isTablePlaceholder($placeholder)) {
307325
return $this->diffTables($oldText, $newText);
308326
} elseif ($this->isLinkPlaceholder($placeholder)) {
309-
return $this->diffLinks($oldText, $newText);
327+
return $this->diffElementsByAttribute($oldText, $newText, 'href', 'a');
328+
} elseif ($this->isImagePlaceholder($placeholder)) {
329+
return $this->diffElementsByAttribute($oldText, $newText, 'src', 'img');
310330
}
311331

312332
return $this->diffElements($oldText, $newText, $stripWrappingTags);
@@ -367,22 +387,18 @@ protected function diffTables($oldText, $newText)
367387
return $diff->build();
368388
}
369389

370-
/**
371-
* @param string $oldText
372-
* @param string $newText
373-
*
374-
* @return string
375-
*/
376-
protected function diffLinks($oldText, $newText)
390+
protected function diffElementsByAttribute($oldText, $newText, $attribute, $element)
377391
{
378-
$oldHref = $this->getAttributeFromTag($oldText, 'href');
379-
$newHref = $this->getAttributeFromTag($newText, 'href');
392+
$oldAttribute = $this->getAttributeFromTag($oldText, $attribute);
393+
$newAttribute = $this->getAttributeFromTag($newText, $attribute);
394+
395+
if ($oldAttribute !== $newAttribute) {
396+
$diffClass = sprintf('diffmod diff%s diff%s', $element, $attribute);
380397

381-
if ($oldHref != $newHref) {
382398
return sprintf(
383399
'%s%s',
384-
$this->wrapText($oldText, 'del', 'diffmod diff-href'),
385-
$this->wrapText($newText, 'ins', 'diffmod diff-href')
400+
$this->wrapText($oldText, 'del', $diffClass),
401+
$this->wrapText($newText, 'ins', $diffClass)
386402
);
387403
}
388404

@@ -418,8 +434,8 @@ protected function processEqualOperation($operation)
418434
protected function getAttributeFromTag($text, $attribute)
419435
{
420436
$matches = array();
421-
if (preg_match(sprintf('/<a\s+[^>]*%s=([\'"])(.*)\1[^>]*>/i', $attribute), $text, $matches)) {
422-
return $matches[2];
437+
if (preg_match(sprintf('/<[^>]*\b%s\s*=\s*([\'"])(.*)\1[^>]*>/i', $attribute), $text, $matches)) {
438+
return htmlspecialchars_decode($matches[2]);
423439
}
424440

425441
return null;
@@ -445,6 +461,16 @@ public function isLinkPlaceholder($text)
445461
return $this->isPlaceholderType($text, 'a');
446462
}
447463

464+
/**
465+
* @param string $text
466+
*
467+
* @return bool
468+
*/
469+
public function isImagePlaceholder($text)
470+
{
471+
return $this->isPlaceholderType($text, 'img');
472+
}
473+
448474
/**
449475
* @param string $text
450476
* @param array|string $types
@@ -549,7 +575,12 @@ protected function insertTag($tag, $cssClass, &$words)
549575
$workTag[ 0 ] = str_replace( ">", ' class="diffmod">', $workTag[ 0 ] );
550576
}
551577
}
552-
$this->content .= implode( "", $workTag ) . $specialCaseTagInjection;
578+
579+
$appendContent = implode( "", $workTag ) . $specialCaseTagInjection;
580+
if (isset($workTag[0]) && false !== stripos($workTag[0], '<img')) {
581+
$appendContent = $this->wrapText($appendContent, $tag, $cssClass);
582+
}
583+
$this->content .= $appendContent;
553584
}
554585
}
555586
}

lib/Caxy/HtmlDiff/HtmlDiffConfig.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class HtmlDiffConfig
4848
'em' => '[[REPLACE_EM]]',
4949
'i' => '[[REPLACE_I]]',
5050
'a' => '[[REPLACE_A]]',
51+
'img' => '[[REPLACE_IMG]]',
5152
);
5253

5354
/**

tests/fixtures/HtmlDiff/issue-28-link-changes.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</newText>
1010

1111
<expected>
12-
Testing <del class="diffmod diff-href"><a href="http://google.com">Link Changes</a></del><ins class="diffmod diff-href"><a href="http://caxy.com">Link Changes</a></ins>
12+
Testing <del class="diffmod diffa diffhref"><a href="http://google.com">Link Changes</a></del><ins class="diffmod diffa diffhref"><a href="http://caxy.com">Link Changes</a></ins>
1313
And when the link <a href="http://samelink.com">stays the same</a>
1414
</expected>
1515

tests/fixtures/HtmlDiff/new-paragraph-and-list.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@
1616
</newText>
1717

1818
<expected>
19-
<i class="diffmod"><del class="diffmod">Corridors</del></i><em class="diffmod"><ins class="diffmod">Corridors</ins></em> shall be fire-resistance rated in accordance with Table 1020.1. The <i class="diffmod"><del class="diffmod">corridor</del></i><em class="diffmod"><ins class="diffmod">corridor</ins></em> walls required to be fire-resistance rated shall comply with Section 708 for <i class="diffmod"><del class="diffmod">fire partitions</del></i><em class="diffmod"><ins class="diffmod">fire partitions</ins></em>.<ul class="diffmod exception"><li><b><del class="diffmod">Exceptions:</del></b><ol><li><del class="diffmod">A </del><i class="diffmod"><del class="diffmod">fire-resistance rating</del></i><del class="diffmod"> is not required for </del><i class="diffmod"><del class="diffmod">corridors</del></i><del class="diffmod"> in an occupancy in Group E where each room that is used for instruction has not less than one door opening directly to the exterior and rooms for assembly purposes have not less than one-half of the required </del><i class="diffmod"><del class="diffmod">means of egress</del></i><del class="diffmod"> doors opening directly to the exterior. Exterior doors specified in this exception are required to be at ground level.</del></li><li><del class="diffmod">A </del><i class="diffmod"><del class="diffmod">fire-resistance rating</del></i><del class="diffmod"> is not required for </del><i class="diffmod"><del class="diffmod">corridors</del></i><del class="diffmod"> contained within a </del><i class="diffmod"><del class="diffmod">dwelling unit</del></i><del class="diffmod"> or </del><i class="diffmod"><del class="diffmod">sleeping unit</del></i><del class="diffmod"> in an occupancy in Groups I-1 and R.</del></li><li><del class="diffmod">A </del><i class="diffmod"><del class="diffmod">fire-resistance rating</del></i><del class="diffmod"> is not required for </del><i class="diffmod"><del class="diffmod">corridors</del></i><del class="diffmod"> in </del><i class="diffmod"><del class="diffmod">open parking garages</del></i><del class="diffmod">.</del></li><li><del class="diffmod">A </del><i class="diffmod"><del class="diffmod">fire-resistance rating</del></i><del class="diffmod"> is not required for </del><i class="diffmod"><del class="diffmod">corridors</del></i><del class="diffmod"> in an occupancy in Group B that is a space requiring only a single </del><i class="diffmod"><del class="diffmod">means of egress</del></i><del class="diffmod"> complying with Section 1006.2.</del></li><li><i><del class="diffmod">Corridors</del></i><del class="diffmod"> adjacent to the </del><i class="diffmod"><del class="diffmod">exterior walls</del></i><del class="diffmod"> of buildings shall be permitted to have unprotected openings on unrated </del><i class="diffmod"><del class="diffmod">exterior walls</del></i><del class="diffmod"> where unrated walls are permitted by Table 602 and unprotected openings are permitted by Table 705.8.</del></li></ol></li></ul><br / class="diffmod"><br /><ins class="diffmod">In addition, corridors in buildings of Types IIB, IIIB, and VB construction and assigned Risk Categories III and IV in Table 1604.5, other than Group I, shall have a fire resistance rating of not less than 1 hour where such buildings are any of the following:</ins><br / class="diffmod"><br /><ol><li><ins class="diffmod">Assigned a Seismic Design Category C or D in Table 1613.3.5(1).</ins></li><ins class="diffmod"> </ins><li class="diffmod"><ins class="diffmod">Located in a flood hazard area established in accordance with Section 1612.3.</ins></li><ins class="diffmod"> </ins><li class="diffmod"><ins class="diffmod">Located in a </ins><em class="diffmod"><ins class="diffmod">hurricane-prone regions</ins></em><ins class="diffmod">.</ins></li><ins class="diffmod"> </ins></ol><ul class="exception"><li><strong><ins class="diffmod">Exceptions:</ins></strong><ol><li><ins class="diffmod">A </ins><em class="diffmod"><ins class="diffmod">fire-resistance rating</ins></em><ins class="diffmod"> is not required for </ins><em class="diffmod"><ins class="diffmod">corridors</ins></em><ins class="diffmod"> in an occupancy in Group E where each room that is used for instruction has not less than one door opening directly to the exterior and rooms for assembly purposes have not less than one-half of the required </ins><em class="diffmod"><ins class="diffmod">means of egress</ins></em><ins class="diffmod"> doors opening directly to the exterior. Exterior doors specified in this exception are required to be at ground level.</ins></li><ins class="diffmod"> </ins><li class="diffmod"><ins class="diffmod">A </ins><em class="diffmod"><ins class="diffmod">fire-resistance rating</ins></em><ins class="diffmod"> is not required for </ins><em class="diffmod"><ins class="diffmod">corridors</ins></em><ins class="diffmod"> contained within a </ins><em class="diffmod"><ins class="diffmod">dwelling unit</ins></em><ins class="diffmod"> or </ins><em class="diffmod"><ins class="diffmod">sleeping unit</ins></em><ins class="diffmod"> in an occupancy in Groups I-1 and R.</ins></li><ins class="diffmod"> </ins><li class="diffmod"><ins class="diffmod">A </ins><em class="diffmod"><ins class="diffmod">fire-resistance rating</ins></em><ins class="diffmod"> is not required for </ins><em class="diffmod"><ins class="diffmod">corridors</ins></em><ins class="diffmod"> in </ins><em class="diffmod"><ins class="diffmod">open parking garages</ins></em><ins class="diffmod">.</ins></li><ins class="diffmod"> </ins><li class="diffmod"><ins class="diffmod">A </ins><em class="diffmod"><ins class="diffmod">fire-resistance rating</ins></em><ins class="diffmod"> is not required for </ins><em class="diffmod"><ins class="diffmod">corridors</ins></em><ins class="diffmod"> in an occupancy in Group B that is a space requiring only a single </ins><em class="diffmod"><ins class="diffmod">means of egress</ins></em><ins class="diffmod"> complying with Section 1006.2.</ins></li><ins class="diffmod"> </ins><li class="diffmod"><em><ins class="diffmod">Corridors</ins></em><ins class="diffmod"> adjacent to the </ins><em class="diffmod"><ins class="diffmod">exterior walls</ins></em><ins class="diffmod"> of buildings shall be permitted to have unprotected openings on unrated </ins><em class="diffmod"><ins class="diffmod">exterior walls</ins></em><ins class="diffmod"> where unrated walls are permitted by Table 602 and unprotected openings are permitted by Table 705.8.</ins></li><ins class="diffmod"> </ins></ol></li><ins class="diffmod"> </ins></ul>
19+
<i class="diffmod"><del class="diffmod">Corridors</del></i><em class="diffmod"><ins class="diffmod">Corridors</ins></em> shall be fire-resistance rated in accordance with Table 1020.1. The <i class="diffmod"><del class="diffmod">corridor</del></i><em class="diffmod"><ins class="diffmod">corridor</ins></em> walls required to be fire-resistance rated shall comply with Section 708 for <i class="diffmod"><del class="diffmod">fire partitions</del></i><em class="diffmod"><ins class="diffmod">fire partitions</ins></em>.<br / class="diffmod"><br /><ins class="diffins">In addition, corridors in buildings of Types IIB, IIIB, and VB construction and assigned Risk Categories III and IV in Table 1604.5, other than Group I, shall have a fire resistance rating of not less than 1 hour where such buildings are any of the following:</ins><br / class="diffmod"><br /><ol><li><ins class="diffins">Assigned a Seismic Design Category C or D in Table 1613.3.5(1).</ins></li><ins class="diffins"> </ins><li class="diffmod"><ins class="diffins">Located in a flood hazard area established in accordance with Section 1612.3.</ins></li><ins class="diffins"> </ins><li class="diffmod"><ins class="diffins">Located in a </ins><em class="diffmod"><ins class="diffins">hurricane-prone regions</ins></em><ins class="diffins">.</ins></li><ins class="diffins"> </ins></ol><ul class="exception" class="diff-list"><li class="normal"><b class="diffmod"><del class="diffmod">Exceptions:</del></b><strong class="diffmod"><ins class="diffmod">Exceptions:</ins></strong><ol class="diff-list"><li class="normal">A <i class="diffmod"><del class="diffmod">fire-resistance rating</del></i><em class="diffmod"><ins class="diffmod">fire-resistance rating</ins></em> is not required for <i class="diffmod"><del class="diffmod">corridors</del></i><em class="diffmod"><ins class="diffmod">corridors</ins></em> in an occupancy in Group E where each room that is used for instruction has not less than one door opening directly to the exterior and rooms for assembly purposes have not less than one-half of the required <i class="diffmod"><del class="diffmod">means of egress</del></i><em class="diffmod"><ins class="diffmod">means of egress</ins></em> doors opening directly to the exterior. Exterior doors specified in this exception are required to be at ground level.</li><li class="normal">A <i class="diffmod"><del class="diffmod">fire-resistance rating</del></i><em class="diffmod"><ins class="diffmod">fire-resistance rating</ins></em> is not required for <i class="diffmod"><del class="diffmod">corridors</del></i><em class="diffmod"><ins class="diffmod">corridors</ins></em> contained within a <i class="diffmod"><del class="diffmod">dwelling unit</del></i><em class="diffmod"><ins class="diffmod">dwelling unit</ins></em> or <i class="diffmod"><del class="diffmod">sleeping unit</del></i><em class="diffmod"><ins class="diffmod">sleeping unit</ins></em> in an occupancy in Groups I-1 and R.</li><li class="normal">A <i class="diffmod"><del class="diffmod">fire-resistance rating</del></i><em class="diffmod"><ins class="diffmod">fire-resistance rating</ins></em> is not required for <i class="diffmod"><del class="diffmod">corridors</del></i><em class="diffmod"><ins class="diffmod">corridors</ins></em> in <i class="diffmod"><del class="diffmod">open parking garages</del></i><em class="diffmod"><ins class="diffmod">open parking garages</ins></em>.</li><li class="normal">A <i class="diffmod"><del class="diffmod">fire-resistance rating</del></i><em class="diffmod"><ins class="diffmod">fire-resistance rating</ins></em> is not required for <i class="diffmod"><del class="diffmod">corridors</del></i><em class="diffmod"><ins class="diffmod">corridors</ins></em> in an occupancy in Group B that is a space requiring only a single <i class="diffmod"><del class="diffmod">means of egress</del></i><em class="diffmod"><ins class="diffmod">means of egress</ins></em> complying with Section 1006.2.</li><li class="normal"><i class="diffmod"><del class="diffmod">Corridors</del></i><em class="diffmod"><ins class="diffmod">Corridors</ins></em> adjacent to the <i class="diffmod"><del class="diffmod">exterior walls</del></i><em class="diffmod"><ins class="diffmod">exterior walls</ins></em> of buildings shall be permitted to have unprotected openings on unrated <i class="diffmod"><del class="diffmod">exterior walls</del></i><em class="diffmod"><ins class="diffmod">exterior walls</ins></em> where unrated walls are permitted by Table 602 and unprotected openings are permitted by Table 705.8.</li></ol></li></ul>
2020
</expected>

0 commit comments

Comments
 (0)