Skip to content

Commit

Permalink
Merge pull request caxy#36 from caxy/feature/img-diffing
Browse files Browse the repository at this point in the history
Add support for diffing img elements
  • Loading branch information
jschroed91 committed Mar 17, 2016
2 parents 65b1794 + bd61c85 commit c124838
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 36 deletions.
33 changes: 27 additions & 6 deletions demo/codes.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
/*
Document : codes
Created on : Sep 23, 2013, 4:41:58 PM
Author : mgersten
Description: CSS related to I-code specific display
*/
del.diffimg.diffsrc {
display: inline-block;
position: relative;
}

del.diffimg.diffsrc:before {
position: absolute;
content: "";
left: 0;
top: 0;
width: 100%;
height: 100%;
background: repeating-linear-gradient(
to left top,
rgba(255, 0, 0, 0),
rgba(255, 0, 0, 0) 49.5%,
rgba(255, 0, 0, 1) 49.5%,
rgba(255, 0, 0, 1) 50.5%
), repeating-linear-gradient(
to left bottom,
rgba(255, 0, 0, 0),
rgba(255, 0, 0, 0) 49.5%,
rgba(255, 0, 0, 1) 49.5%,
rgba(255, 0, 0, 1) 50.5%
);
}

.diff-list > li.normal,
.diff-list > li.removed,
.diff-list > li.replacement{
Expand Down
2 changes: 1 addition & 1 deletion demo/demos.json

Large diffs are not rendered by default.

81 changes: 56 additions & 25 deletions lib/Caxy/HtmlDiff/HtmlDiff.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,28 +142,39 @@ protected function replaceIsolatedDiffTags()
protected function createIsolatedDiffTagPlaceholders(&$words)
{
$openIsolatedDiffTags = 0;
$isolatedDiffTagIndicies = array();
$isolatedDiffTagIndices = array();
$isolatedDiffTagStart = 0;
$currentIsolatedDiffTag = null;
foreach ($words as $index => $word) {
$openIsolatedDiffTag = $this->isOpeningIsolatedDiffTag($word, $currentIsolatedDiffTag);
if ($openIsolatedDiffTag) {
if ($openIsolatedDiffTags === 0) {
$isolatedDiffTagStart = $index;
if ($this->isSelfClosingTag($word) || stripos($word, '<img') !== false) {
if ($openIsolatedDiffTags === 0) {
$isolatedDiffTagIndices[] = array(
'start' => $index,
'length' => 1,
'tagType' => $openIsolatedDiffTag,
);
$currentIsolatedDiffTag = null;
}
} else {
if ($openIsolatedDiffTags === 0) {
$isolatedDiffTagStart = $index;
}
$openIsolatedDiffTags++;
$currentIsolatedDiffTag = $openIsolatedDiffTag;
}
$openIsolatedDiffTags++;
$currentIsolatedDiffTag = $openIsolatedDiffTag;
} elseif ($openIsolatedDiffTags > 0 && $this->isClosingIsolatedDiffTag($word, $currentIsolatedDiffTag)) {
$openIsolatedDiffTags--;
if ($openIsolatedDiffTags == 0) {
$isolatedDiffTagIndicies[] = array ('start' => $isolatedDiffTagStart, 'length' => $index - $isolatedDiffTagStart + 1, 'tagType' => $currentIsolatedDiffTag);
$isolatedDiffTagIndices[] = array ('start' => $isolatedDiffTagStart, 'length' => $index - $isolatedDiffTagStart + 1, 'tagType' => $currentIsolatedDiffTag);
$currentIsolatedDiffTag = null;
}
}
}
$isolatedDiffTagScript = array();
$offset = 0;
foreach ($isolatedDiffTagIndicies as $isolatedDiffTagIndex) {
foreach ($isolatedDiffTagIndices as $isolatedDiffTagIndex) {
$start = $isolatedDiffTagIndex['start'] - $offset;
$placeholderString = $this->config->getIsolatedDiffTagPlaceholder($isolatedDiffTagIndex['tagType']);
$isolatedDiffTagScript[$start] = array_splice($words, $start, $isolatedDiffTagIndex['length'], $placeholderString);
Expand All @@ -185,15 +196,21 @@ protected function isOpeningIsolatedDiffTag($item, $currentIsolatedDiffTag = nul
$tagsToMatch = $currentIsolatedDiffTag !== null
? array($currentIsolatedDiffTag => $this->config->getIsolatedDiffTagPlaceholder($currentIsolatedDiffTag))
: $this->config->getIsolatedDiffTags();
$pattern = '#<%s(\s+[^>]*)?>#iU';
foreach ($tagsToMatch as $key => $value) {
if (preg_match("#<".$key."[^>]*>\\s*#iU", $item)) {
if (preg_match(sprintf($pattern, $key), $item)) {
return $key;
}
}

return false;
}

protected function isSelfClosingTag($text)
{
return (bool) preg_match('/<[^>]+\/\s*>/', $text);
}

/**
* @param string $item
* @param null|string $currentIsolatedDiffTag
Expand All @@ -205,8 +222,9 @@ protected function isClosingIsolatedDiffTag($item, $currentIsolatedDiffTag = nul
$tagsToMatch = $currentIsolatedDiffTag !== null
? array($currentIsolatedDiffTag => $this->config->getIsolatedDiffTagPlaceholder($currentIsolatedDiffTag))
: $this->config->getIsolatedDiffTags();
$pattern = '#</%s(\s+[^>]*)?>#iU';
foreach ($tagsToMatch as $key => $value) {
if (preg_match("#</".$key."[^>]*>\\s*#iU", $item)) {
if (preg_match(sprintf($pattern, $key), $item)) {
return $key;
}
}
Expand Down Expand Up @@ -306,7 +324,9 @@ protected function diffIsolatedPlaceholder($operation, $pos, $placeholder, $stri
} elseif ($this->config->isUseTableDiffing() && $this->isTablePlaceholder($placeholder)) {
return $this->diffTables($oldText, $newText);
} elseif ($this->isLinkPlaceholder($placeholder)) {
return $this->diffLinks($oldText, $newText);
return $this->diffElementsByAttribute($oldText, $newText, 'href', 'a');
} elseif ($this->isImagePlaceholder($placeholder)) {
return $this->diffElementsByAttribute($oldText, $newText, 'src', 'img');
}

return $this->diffElements($oldText, $newText, $stripWrappingTags);
Expand Down Expand Up @@ -367,22 +387,18 @@ protected function diffTables($oldText, $newText)
return $diff->build();
}

/**
* @param string $oldText
* @param string $newText
*
* @return string
*/
protected function diffLinks($oldText, $newText)
protected function diffElementsByAttribute($oldText, $newText, $attribute, $element)
{
$oldHref = $this->getAttributeFromTag($oldText, 'href');
$newHref = $this->getAttributeFromTag($newText, 'href');
$oldAttribute = $this->getAttributeFromTag($oldText, $attribute);
$newAttribute = $this->getAttributeFromTag($newText, $attribute);

if ($oldAttribute !== $newAttribute) {
$diffClass = sprintf('diffmod diff%s diff%s', $element, $attribute);

if ($oldHref != $newHref) {
return sprintf(
'%s%s',
$this->wrapText($oldText, 'del', 'diffmod diff-href'),
$this->wrapText($newText, 'ins', 'diffmod diff-href')
$this->wrapText($oldText, 'del', $diffClass),
$this->wrapText($newText, 'ins', $diffClass)
);
}

Expand Down Expand Up @@ -418,8 +434,8 @@ protected function processEqualOperation($operation)
protected function getAttributeFromTag($text, $attribute)
{
$matches = array();
if (preg_match(sprintf('/<a\s+[^>]*%s=([\'"])(.*)\1[^>]*>/i', $attribute), $text, $matches)) {
return $matches[2];
if (preg_match(sprintf('/<[^>]*\b%s\s*=\s*([\'"])(.*)\1[^>]*>/i', $attribute), $text, $matches)) {
return htmlspecialchars_decode($matches[2]);
}

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

/**
* @param string $text
*
* @return bool
*/
public function isImagePlaceholder($text)
{
return $this->isPlaceholderType($text, 'img');
}

/**
* @param string $text
* @param array|string $types
Expand Down Expand Up @@ -549,7 +575,12 @@ protected function insertTag($tag, $cssClass, &$words)
$workTag[ 0 ] = str_replace( ">", ' class="diffmod">', $workTag[ 0 ] );
}
}
$this->content .= implode( "", $workTag ) . $specialCaseTagInjection;

$appendContent = implode( "", $workTag ) . $specialCaseTagInjection;
if (isset($workTag[0]) && false !== stripos($workTag[0], '<img')) {
$appendContent = $this->wrapText($appendContent, $tag, $cssClass);
}
$this->content .= $appendContent;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions lib/Caxy/HtmlDiff/HtmlDiffConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class HtmlDiffConfig
'em' => '[[REPLACE_EM]]',
'i' => '[[REPLACE_I]]',
'a' => '[[REPLACE_A]]',
'img' => '[[REPLACE_IMG]]',
);

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/HtmlDiff/issue-28-link-changes.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</newText>

<expected>
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>
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>
And when the link <a href="http://samelink.com">stays the same</a>
</expected>

2 changes: 1 addition & 1 deletion tests/fixtures/HtmlDiff/new-paragraph-and-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
</newText>

<expected>
<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>
<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>
</expected>
Loading

0 comments on commit c124838

Please sign in to comment.