1

I'm looking for an efficient way to convert absolute filepath to a path relative to a specific directory.

Let's say we have to following structure:

D:\main\test1\blah.txt  
D:\test2\foo.txt

With "D:\main" being the reference directory, then result would be:

  • blah.txt => "\test1\blah.txt"
  • foo.txt => "..\test2\foo.txt"

Any clue ?


Notes for the record:

It seems that:

Community
  • 1
  • 1
Cédric Françoys
  • 870
  • 1
  • 11
  • 22
  • Many fileSystems provide API calls for such purposes. Anyway, why do you need this retrograde step? – Martin James Mar 23 '16 at 09:06
  • This is for storing metadata into a sub-dir of the parent directory. Storing absolute path is not safe since it might change depending on the way current drive is mounted. – Cédric Françoys Mar 23 '16 at 09:15
  • 1
    Check if your reference folder `"D:\main\"` matches the first part of your absolute filepath. If it does, keep the rest of the filepath. If it does not, replace the drive spec with the appropriate number of `"..\"` – Weather Vane Mar 23 '16 at 09:38
  • It seems a good start. I'll try to come up with something in that sense. – Cédric Françoys Mar 23 '16 at 10:36

3 Answers3

2

You are giving windows paths in your example. So, if it is acceptable for you to use the WinAPI functions, you can use PathRelativePathTo.

jdarthenay
  • 3,062
  • 1
  • 15
  • 20
0

Here is the shortest solution I could figure out.

Algorithm is actually quite simple:

Given 1) a reference path (path to which result path will be relative to); and 2) an absolute path (full path of a file) :

  • while path parts are equals : skip them
  • when we come across a difference

    1. add a ".." for each remaining part of reference path
    2. add remaining parts from absolute path

The only limitation under windows is in case of distinct volume (drive letters differ), in which situation we have no choice but to return the original absolute path.

Cross-platform C source :

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
    const char* path_separator = "\\";
#else
    const char* path_separator = "/";
#endif  

#define FILENAME_MAX 1024


char* get_relative_path(char* reference_path, char* absolute_path) {
    static char relative_path[FILENAME_MAX];
    // init result string
    relative_path[0] = '\0';    
    // check first char (under windows, if differs, we return absolute path)
    if(absolute_path[0] != reference_path[0]) {
        return absolute_path;
    }    
    // make copies to prevent altering original strings
    char* path_a = strdup(absolute_path);
    char* path_r = strdup(reference_path);

    int inc;
    int size_a = strlen(path_a)+1;
    int size_r = strlen(path_r)+1;

    for(inc = 0; inc < size_a && inc < size_r; inc += strlen(path_a+inc)+1) {
        char* token_a = strchr(path_a+inc, path_separator[0]);
        char* token_r = strchr(path_r+inc, path_separator[0]);        
        if(token_a) token_a[0] = '\0';
        if(token_r) token_r[0] = '\0';        
        if(strcmp(path_a+inc, path_r+inc) != 0) break;
    }

    for(int inc_r = inc; inc_r < size_r; inc_r += strlen(path_r+inc_r)+1) {
        strcat(relative_path, "..");
        strcat(relative_path, path_separator);        
        if( !strchr(reference_path+inc_r, path_separator[0]) ) break;
    }

    if(inc < size_a) strcat(relative_path, absolute_path+inc);

    return relative_path;
}
Cédric Françoys
  • 870
  • 1
  • 11
  • 22
-1

First thing you need to identify the file path separator as stated here.

const char kPathSeparator =
#ifdef _WIN32
                            '\\';
#else
                            '/';
#endif

Then, you need to write a function to compute a canonical absolute file path. You will have to use #ifdef _WIN32 again because there are specific windows treatment required (add current disk at the begining of path if none is present). After that, remove all . in the path, and remove all .. with their previous directory.

Once this function is written, you need to use it twice to get your origin and target canonical absolute paths, and then as explains @Weather Vane you need to identify the common part in the two paths and to add the number of .. concatenated to the end of the target canonical path.

Community
  • 1
  • 1
jdarthenay
  • 3,062
  • 1
  • 15
  • 20
  • Please note that MSVC / Windows does not care whether you use the forward or the backward slash separator, to open a file. It would make life easier just to replace any backward slashes. – Weather Vane Mar 23 '16 at 11:02
  • @Weather Vane Maybe replacing all backslashes in path by slashes for Windows then. But UNIX files and directories may contain a backslash, so this substitution cannot be done for UNIX. – jdarthenay Mar 23 '16 at 11:11
  • Actually my concern about the code being cross-platform is more related to the functions involved rather than dealing with path separator. – Cédric Françoys Mar 23 '16 at 16:25