Reputation: 14982
How to get relative filename from absolute one and some given path?
For example:
foo('/a/b/c/1.txt', '/a/d/e'); // '../../b/c/1.txt'
foo('/a/b/../d/1.txt', '/a/d/e'); // '../c/1.txt'
Is there some native function for this?
My thoughts, if there is not:
realpath
replacement, because files can to not exist. exampleManual way looks too heavy for that common task..
Upvotes: 0
Views: 83
Reputation: 14982
For now, I assume there is not any native implementation and want to share my implementation of this.
/**
* realpath analog without filesystem check
*/
function str_normalize_path(string $path, string $sep = DIRECTORY_SEPARATOR): string {
$parts = array_filter(explode($sep, $path));
$stack = [];
foreach ($parts as $part) {
switch($part) {
case '.': break;
case '..': array_pop($stack); break; // excess '..' just ignored by array_pop([]) silency
default: array_push($stack, $part);
}
}
return implode($sep, $stack);
}
function str_relative_path(string $absolute, string $base, string $sep = DIRECTORY_SEPARATOR) {
$absolute = str_normalize_path($absolute);
$base = str_normalize_path($base);
// find common prefix
$prefix_len = 0;
for ($i = 0; $i < min(mb_strlen($absolute), mb_strlen($base)); ++$i) {
if ($absolute[$i] !== $base[$i]) break;
$prefix_len++;
}
// cut common prefix
if ($prefix_len > 0) {
$absolute = mb_substr($absolute, $prefix_len);
$base = mb_substr($base, $prefix_len);
}
// put '..'s for exit to closest common path
$base_length = count(explode($sep, $base));
$relative_parts = explode($sep, $absolute);
while($base_length-->0) array_unshift($relative_parts, '..');
return implode($sep, $relative_parts);
}
$abs = '/a/b/../fk1/fk2/../.././d//proj42/1.txt';
$base = '/a/d/fk/../e/f';
echo str_relative_path($abs, $base); // ../../proj42/1.txt
Upvotes: 1