forked from mrclay/minify
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request mrclay#11 from SimonSimCity/master
Updated ImportProcessor to write relative for imported stuff like images
- Loading branch information
Showing
9 changed files
with
320 additions
and
266 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,157 +1,216 @@ | ||
<?php | ||
/** | ||
* Class Minify_ImportProcessor | ||
* @package Minify | ||
*/ | ||
|
||
/** | ||
* Linearize a CSS/JS file by including content specified by CSS import | ||
* declarations. In CSS files, relative URIs are fixed. | ||
* | ||
* @imports will be processed regardless of where they appear in the source | ||
* files; i.e. @imports commented out or in string content will still be | ||
* processed! | ||
* | ||
* This has a unit test but should be considered "experimental". | ||
* | ||
* @package Minify | ||
* @author Stephen Clay <steve@mrclay.org> | ||
*/ | ||
class Minify_ImportProcessor { | ||
|
||
public static $filesIncluded = array(); | ||
|
||
public static function process($file) | ||
{ | ||
self::$filesIncluded = array(); | ||
self::$_isCss = (strtolower(substr($file, -4)) === '.css'); | ||
$obj = new Minify_ImportProcessor(dirname($file)); | ||
return $obj->_getContent($file); | ||
} | ||
|
||
// allows callback funcs to know the current directory | ||
private $_currentDir = null; | ||
|
||
// allows _importCB to write the fetched content back to the obj | ||
private $_importedContent = ''; | ||
|
||
private static $_isCss = null; | ||
|
||
private function __construct($currentDir) | ||
{ | ||
$this->_currentDir = $currentDir; | ||
} | ||
|
||
private function _getContent($file) | ||
{ | ||
$file = realpath($file); | ||
if (! $file | ||
|| in_array($file, self::$filesIncluded) | ||
|| false === ($content = @file_get_contents($file)) | ||
) { | ||
// file missing, already included, or failed read | ||
return ''; | ||
} | ||
self::$filesIncluded[] = realpath($file); | ||
$this->_currentDir = dirname($file); | ||
|
||
// remove UTF-8 BOM if present | ||
if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) { | ||
$content = substr($content, 3); | ||
} | ||
// ensure uniform EOLs | ||
$content = str_replace("\r\n", "\n", $content); | ||
|
||
// process @imports | ||
$content = preg_replace_callback( | ||
'/ | ||
@import\\s+ | ||
(?:url\\(\\s*)? # maybe url( | ||
[\'"]? # maybe quote | ||
(.*?) # 1 = URI | ||
[\'"]? # maybe end quote | ||
(?:\\s*\\))? # maybe ) | ||
([a-zA-Z,\\s]*)? # 2 = media list | ||
; # end token | ||
/x' | ||
,array($this, '_importCB') | ||
,$content | ||
); | ||
|
||
if (self::$_isCss) { | ||
// rewrite remaining relative URIs | ||
$content = preg_replace_callback( | ||
'/url\\(\\s*([^\\)\\s]+)\\s*\\)/' | ||
,array($this, '_urlCB') | ||
,$content | ||
); | ||
} | ||
|
||
return $this->_importedContent . $content; | ||
} | ||
|
||
private function _importCB($m) | ||
{ | ||
$url = $m[1]; | ||
$mediaList = preg_replace('/\\s+/', '', $m[2]); | ||
|
||
if (strpos($url, '://') > 0) { | ||
// protocol, leave in place for CSS, comment for JS | ||
return self::$_isCss | ||
? $m[0] | ||
: "/* Minify_ImportProcessor will not include remote content */"; | ||
} | ||
if ('/' === $url[0]) { | ||
// protocol-relative or root path | ||
$url = ltrim($url, '/'); | ||
$file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR | ||
. strtr($url, '/', DIRECTORY_SEPARATOR); | ||
} else { | ||
// relative to current path | ||
$file = $this->_currentDir . DIRECTORY_SEPARATOR | ||
. strtr($url, '/', DIRECTORY_SEPARATOR); | ||
} | ||
$obj = new Minify_ImportProcessor(dirname($file)); | ||
$content = $obj->_getContent($file); | ||
if ('' === $content) { | ||
// failed. leave in place for CSS, comment for JS | ||
return self::$_isCss | ||
? $m[0] | ||
: "/* Minify_ImportProcessor could not fetch '{$file}' */";; | ||
} | ||
return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList)) | ||
? $content | ||
: "@media {$mediaList} {\n{$content}\n}\n"; | ||
} | ||
|
||
private function _urlCB($m) | ||
{ | ||
// $m[1] is either quoted or not | ||
$quote = ($m[1][0] === "'" || $m[1][0] === '"') | ||
? $m[1][0] | ||
: ''; | ||
$url = ($quote === '') | ||
? $m[1] | ||
: substr($m[1], 1, strlen($m[1]) - 2); | ||
if ('/' !== $url[0]) { | ||
if (strpos($url, '//') > 0) { | ||
// probably starts with protocol, do not alter | ||
} else { | ||
// prepend path with current dir separator (OS-independent) | ||
$path = $this->_currentDir | ||
. DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR); | ||
// strip doc root | ||
$path = substr($path, strlen(realpath($_SERVER['DOCUMENT_ROOT']))); | ||
// fix to absolute URL | ||
$url = strtr($path, '/\\', '//'); | ||
// remove /./ and /../ where possible | ||
$url = str_replace('/./', '/', $url); | ||
// inspired by patch from Oleg Cherniy | ||
do { | ||
$url = preg_replace('@/(?!\\.\\.?)[^/]+/\\.\\.@', '/', $url, 1, $changed); | ||
} while ($changed); | ||
} | ||
} | ||
return "url({$quote}{$url}{$quote})"; | ||
} | ||
} | ||
<?php | ||
/** | ||
* Class Minify_ImportProcessor | ||
* @package Minify | ||
*/ | ||
|
||
/** | ||
* Linearize a CSS/JS file by including content specified by CSS import | ||
* declarations. In CSS files, relative URIs are fixed. | ||
* | ||
* @imports will be processed regardless of where they appear in the source | ||
* files; i.e. @imports commented out or in string content will still be | ||
* processed! | ||
* | ||
* This has a unit test but should be considered "experimental". | ||
* | ||
* @package Minify | ||
* @author Stephen Clay <steve@mrclay.org> | ||
* @author Simon Schick <simonsimcity@gmail.com> | ||
*/ | ||
class Minify_ImportProcessor { | ||
|
||
public static $filesIncluded = array(); | ||
|
||
public static function process($file) | ||
{ | ||
self::$filesIncluded = array(); | ||
self::$_isCss = (strtolower(substr($file, -4)) === '.css'); | ||
$obj = new Minify_ImportProcessor(dirname($file)); | ||
return $obj->_getContent($file); | ||
} | ||
|
||
// allows callback funcs to know the current directory | ||
private $_currentDir = null; | ||
|
||
// allows callback funcs to know the directory of the file that inherits this one | ||
private $_previewsDir = null; | ||
|
||
// allows _importCB to write the fetched content back to the obj | ||
private $_importedContent = ''; | ||
|
||
private static $_isCss = null; | ||
|
||
/** | ||
* @param String $currentDir | ||
* @param String $previewsDir Is only used internally | ||
*/ | ||
private function __construct($currentDir, $previewsDir = "") | ||
{ | ||
$this->_currentDir = $currentDir; | ||
$this->_previewsDir = $previewsDir; | ||
} | ||
|
||
private function _getContent($file, $is_imported = false) | ||
{ | ||
$file = realpath($file); | ||
if (! $file | ||
|| in_array($file, self::$filesIncluded) | ||
|| false === ($content = @file_get_contents($file)) | ||
) { | ||
// file missing, already included, or failed read | ||
return ''; | ||
} | ||
self::$filesIncluded[] = realpath($file); | ||
$this->_currentDir = dirname($file); | ||
|
||
// remove UTF-8 BOM if present | ||
if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) { | ||
$content = substr($content, 3); | ||
} | ||
// ensure uniform EOLs | ||
$content = str_replace("\r\n", "\n", $content); | ||
|
||
// process @imports | ||
$content = preg_replace_callback( | ||
'/ | ||
@import\\s+ | ||
(?:url\\(\\s*)? # maybe url( | ||
[\'"]? # maybe quote | ||
(.*?) # 1 = URI | ||
[\'"]? # maybe end quote | ||
(?:\\s*\\))? # maybe ) | ||
([a-zA-Z,\\s]*)? # 2 = media list | ||
; # end token | ||
/x' | ||
,array($this, '_importCB') | ||
,$content | ||
); | ||
|
||
// You only need to rework the import-path if the script is imported | ||
if (self::$_isCss && $is_imported) { | ||
// rewrite remaining relative URIs | ||
$content = preg_replace_callback( | ||
'/url\\(\\s*([^\\)\\s]+)\\s*\\)/' | ||
,array($this, '_urlCB') | ||
,$content | ||
); | ||
} | ||
|
||
return $this->_importedContent . $content; | ||
} | ||
|
||
private function _importCB($m) | ||
{ | ||
$url = $m[1]; | ||
$mediaList = preg_replace('/\\s+/', '', $m[2]); | ||
|
||
if (strpos($url, '://') > 0) { | ||
// protocol, leave in place for CSS, comment for JS | ||
return self::$_isCss | ||
? $m[0] | ||
: "/* Minify_ImportProcessor will not include remote content */"; | ||
} | ||
if ('/' === $url[0]) { | ||
// protocol-relative or root path | ||
$url = ltrim($url, '/'); | ||
$file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR | ||
. strtr($url, '/', DIRECTORY_SEPARATOR); | ||
} else { | ||
// relative to current path | ||
$file = $this->_currentDir . DIRECTORY_SEPARATOR | ||
. strtr($url, '/', DIRECTORY_SEPARATOR); | ||
} | ||
$obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir); | ||
$content = $obj->_getContent($file, true); | ||
if ('' === $content) { | ||
// failed. leave in place for CSS, comment for JS | ||
return self::$_isCss | ||
? $m[0] | ||
: "/* Minify_ImportProcessor could not fetch '{$file}' */"; | ||
} | ||
return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList)) | ||
? $content | ||
: "@media {$mediaList} {\n{$content}\n}\n"; | ||
} | ||
|
||
private function _urlCB($m) | ||
{ | ||
// $m[1] is either quoted or not | ||
$quote = ($m[1][0] === "'" || $m[1][0] === '"') | ||
? $m[1][0] | ||
: ''; | ||
$url = ($quote === '') | ||
? $m[1] | ||
: substr($m[1], 1, strlen($m[1]) - 2); | ||
if ('/' !== $url[0]) { | ||
if (strpos($url, '//') > 0) { | ||
// probably starts with protocol, do not alter | ||
} else { | ||
// prepend path with current dir separator (OS-independent) | ||
$path = $this->_currentDir | ||
. DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR); | ||
// update the relative path by the directory of the file that imported this one | ||
$url = self::getPathDiff(realpath($this->_previewsDir), $path); | ||
} | ||
} | ||
return "url({$quote}{$url}{$quote})"; | ||
} | ||
|
||
/** | ||
* @param string $from | ||
* @param string $to | ||
* @param string $ps | ||
* @return string | ||
*/ | ||
private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR) | ||
{ | ||
$realFrom = $this->truepath($from); | ||
$realTo = $this->truepath($to); | ||
|
||
$arFrom = explode($ps, rtrim($realFrom, $ps)); | ||
$arTo = explode($ps, rtrim($realTo, $ps)); | ||
while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) | ||
{ | ||
array_shift($arFrom); | ||
array_shift($arTo); | ||
} | ||
return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo); | ||
} | ||
|
||
/** | ||
* This function is to replace PHP's extremely buggy realpath(). | ||
* @param string $path The original path, can be relative etc. | ||
* @return string The resolved path, it might not exist. | ||
* @see http://stackoverflow.com/questions/4049856/replace-phps-realpath | ||
*/ | ||
function truepath($path) | ||
{ | ||
// whether $path is unix or not | ||
$unipath = strlen($path) == 0 || $path{0} != '/'; | ||
// attempts to detect if path is relative in which case, add cwd | ||
if (strpos($path, ':') === false && $unipath) | ||
$path = $this->_currentDir . DIRECTORY_SEPARATOR . $path; | ||
|
||
// resolve path parts (single dot, double dot and double delimiters) | ||
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path); | ||
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); | ||
$absolutes = array(); | ||
foreach ($parts as $part) { | ||
if ('.' == $part) | ||
continue; | ||
if ('..' == $part) { | ||
array_pop($absolutes); | ||
} else { | ||
$absolutes[] = $part; | ||
} | ||
} | ||
$path = implode(DIRECTORY_SEPARATOR, $absolutes); | ||
// resolve any symlinks | ||
if (file_exists($path) && linkinfo($path) > 0) | ||
$path = readlink($path); | ||
// put initial separator that could have been lost | ||
$path = !$unipath ? '/' . $path : $path; | ||
return $path; | ||
} | ||
} |
4 changes: 2 additions & 2 deletions
4
...test_files/importProcessor/1/adjacent.css → ..._files/importProcessor/css/1/adjacent.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
/* @import url('bad.css' ) bad; */ | ||
adjacent2 foo { background: red url(/red.gif); } | ||
/* @import url('bad.css' ) bad; */ | ||
adjacent2 foo { background: red url(/red.gif); } | ||
adjacent2 bar { background: url('../green.gif') } |
6 changes: 3 additions & 3 deletions
6
...ests/_test_files/importProcessor/1/tv.css → .../_test_files/importProcessor/css/1/tv.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
@import url( adjacent.css ) all; | ||
@import '../input.css'; | ||
tv foo { background: red url(/red.gif); } | ||
@import url( adjacent.css ) all; | ||
@import '../input.css'; | ||
tv foo { background: red url(/red.gif); } | ||
tv bar { background: url('../green.gif') } |
6 changes: 3 additions & 3 deletions
6
.../_test_files/importProcessor/adjacent.css → ...st_files/importProcessor/css/adjacent.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
@import url(../css/styles.css); | ||
@import url(http://example.com/hello.css); | ||
adjacent foo { background: red url(/red.gif); } | ||
@import url(../../css/styles.css); | ||
@import url(http://example.com/hello.css); | ||
adjacent foo { background: red url(/red.gif); } | ||
adjacent bar { background: url('../green.gif') } |
Oops, something went wrong.