从两个绝对路径获取相对路径

发布于 2024-11-03 10:01:09 字数 404 浏览 1 评论 0原文

我有两个绝对文件系统路径(A 和 B),并且我想生成第三个文件系统路径来表示“A 相对于 B”。

用例:

  • 管理播放列表的媒体播放器。
  • 用户将文件添加到播放列表。
  • 添加到播放列表的新文件路径相对于播放列表路径
  • 将来,整个音乐目录(包括播放列表)都会转移到其他地方。
  • 所有路径仍然有效,因为它们是相对于播放列表的。

boost::filesystem 似乎已完整 来解析relative ~relative =>绝对,但没有任何相反的操作(绝对〜绝对=>相对)。

我想用Boost路径来做到这一点。

I have two absolute filesystem paths (A and B), and I want to generate a third filesystem path that represents "A relative from B".

Use case:

  • Media player managing a playlist.
  • User adds file to playlist.
  • New file path added to playlist relative to playlist path.
  • In the future, entire music directory (including playlist) moved elsewhere.
  • All paths still valid because they are relative to the playlist.

boost::filesystem appears to have complete to resolve relative ~ relative => absolute, but nothing to do this in reverse (absolute ~ absolute => relative).

I want to do it with Boost paths.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

小梨窩很甜 2024-11-10 10:01:09

使用 C++17 及其 std::filesystem::relative ,从boost演变而来,这是一个显而易见的:

#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main()
{
    const fs::path base("/is/the/speed/of/light/absolute");
    const fs::path p("/is/the/speed/of/light/absolute/or/is/it/relative/to/the/observer");
    const fs::path p2("/little/light/races/in/orbit/of/a/rogue/planet");
    std::cout << "Base is base: " << fs::relative(p, base).generic_string() << '\n'
              << "Base is deeper: " << fs::relative(base, p).generic_string() << '\n'
              << "Base is orthogonal: " << fs::relative(p2, base).generic_string();
    // Omitting exception handling/error code usage for simplicity.
}

输出(第二个参数是base)

Base is base: or/is/it/relative/to/the/observer
Base is deeper: ../../../../../../..
Base is orthogonal: ../../../../../../little/light/races/in/orbit/of/a/rogue/planet

它使用std::filesystem::path::lexically_relative 进行比较。
与纯词法函数的区别在于,std::filesystem::relative 解析符号链接并使用
std::filesystem::weakly_canonical (这是在比较之前引入relative)。

With C++17 and its std::filesystem::relative, which evolved from boost, this is a no-brainer:

#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main()
{
    const fs::path base("/is/the/speed/of/light/absolute");
    const fs::path p("/is/the/speed/of/light/absolute/or/is/it/relative/to/the/observer");
    const fs::path p2("/little/light/races/in/orbit/of/a/rogue/planet");
    std::cout << "Base is base: " << fs::relative(p, base).generic_string() << '\n'
              << "Base is deeper: " << fs::relative(base, p).generic_string() << '\n'
              << "Base is orthogonal: " << fs::relative(p2, base).generic_string();
    // Omitting exception handling/error code usage for simplicity.
}

Output (second parameter is base)

Base is base: or/is/it/relative/to/the/observer
Base is deeper: ../../../../../../..
Base is orthogonal: ../../../../../../little/light/races/in/orbit/of/a/rogue/planet

It uses std::filesystem::path::lexically_relative for comparison.
The difference to the pure lexical function is, that std::filesystem::relative resolves symlinks and normalizes both paths using
std::filesystem::weakly_canonical (which was introduced for relative) before comparison.

小耗子 2024-11-10 10:01:09

自版本 1.60.0 boost 起。文件系统确实支持这一点。您正在寻找成员函数path lexically_relative(const path& p) const

1.60.0 之前的原始答案如下。


Boost不支持这个;这是一个悬而未决的问题 - #1976(完整的逆函数) - 尽管如此似乎没有得到太多关注。

这是一个看似幼稚的解决方法,似乎可以解决问题(不确定是否可以改进):

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
#include <stdexcept>

/**
 * https://svn.boost.org/trac/boost/ticket/1976#comment:2
 * 
 * "The idea: uncomplete(/foo/new, /foo/bar) => ../new
 *  The use case for this is any time you get a full path (from an open dialog, perhaps)
 *  and want to store a relative path so that the group of files can be moved to a different
 *  directory without breaking the paths. An IDE would be a simple example, so that the
 *  project file could be safely checked out of subversion."
 * 
 * ALGORITHM:
 *  iterate path and base
 * compare all elements so far of path and base
 * whilst they are the same, no write to output
 * when they change, or one runs out:
 *   write to output, ../ times the number of remaining elements in base
 *   write to output, the remaining elements in path
 */
boost::filesystem::path
naive_uncomplete(boost::filesystem::path const p, boost::filesystem::path const base) {

    using boost::filesystem::path;
    using boost::filesystem::dot;
    using boost::filesystem::slash;

    if (p == base)
        return "./";
        /*!! this breaks stuff if path is a filename rather than a directory,
             which it most likely is... but then base shouldn't be a filename so... */

    boost::filesystem::path from_path, from_base, output;

    boost::filesystem::path::iterator path_it = p.begin(),    path_end = p.end();
    boost::filesystem::path::iterator base_it = base.begin(), base_end = base.end();

    // check for emptiness
    if ((path_it == path_end) || (base_it == base_end))
        throw std::runtime_error("path or base was empty; couldn't generate relative path");

#ifdef WIN32
    // drive letters are different; don't generate a relative path
    if (*path_it != *base_it)
        return p;

    // now advance past drive letters; relative paths should only go up
    // to the root of the drive and not past it
    ++path_it, ++base_it;
#endif

    // Cache system-dependent dot, double-dot and slash strings
    const std::string _dot  = std::string(1, dot<path>::value);
    const std::string _dots = std::string(2, dot<path>::value);
    const std::string _sep = std::string(1, slash<path>::value);

    // iterate over path and base
    while (true) {

        // compare all elements so far of path and base to find greatest common root;
        // when elements of path and base differ, or run out:
        if ((path_it == path_end) || (base_it == base_end) || (*path_it != *base_it)) {

            // write to output, ../ times the number of remaining elements in base;
            // this is how far we've had to come down the tree from base to get to the common root
            for (; base_it != base_end; ++base_it) {
                if (*base_it == _dot)
                    continue;
                else if (*base_it == _sep)
                    continue;

                output /= "../";
            }

            // write to output, the remaining elements in path;
            // this is the path relative from the common root
            boost::filesystem::path::iterator path_it_start = path_it;
            for (; path_it != path_end; ++path_it) {

                if (path_it != path_it_start)
                    output /= "/";

                if (*path_it == _dot)
                    continue;
                if (*path_it == _sep)
                    continue;

                output /= *path_it;
            }

            break;
        }

        // add directory level to both paths and continue iteration
        from_path /= path(*path_it);
        from_base /= path(*base_it);

        ++path_it, ++base_it;
    }

    return output;
}

As of version 1.60.0 boost.filesystem does support this. You're looking for the member function path lexically_relative(const path& p) const.

Original, pre-1.60.0 answer below.


Boost doesn't support this; it's an open issue — #1976 (Inverse function for complete) — that nevertheless doesn't seem to be getting much traction.

Here's a vaguely naive workaround that seems to do the trick (not sure whether it can be improved):

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
#include <stdexcept>

/**
 * https://svn.boost.org/trac/boost/ticket/1976#comment:2
 * 
 * "The idea: uncomplete(/foo/new, /foo/bar) => ../new
 *  The use case for this is any time you get a full path (from an open dialog, perhaps)
 *  and want to store a relative path so that the group of files can be moved to a different
 *  directory without breaking the paths. An IDE would be a simple example, so that the
 *  project file could be safely checked out of subversion."
 * 
 * ALGORITHM:
 *  iterate path and base
 * compare all elements so far of path and base
 * whilst they are the same, no write to output
 * when they change, or one runs out:
 *   write to output, ../ times the number of remaining elements in base
 *   write to output, the remaining elements in path
 */
boost::filesystem::path
naive_uncomplete(boost::filesystem::path const p, boost::filesystem::path const base) {

    using boost::filesystem::path;
    using boost::filesystem::dot;
    using boost::filesystem::slash;

    if (p == base)
        return "./";
        /*!! this breaks stuff if path is a filename rather than a directory,
             which it most likely is... but then base shouldn't be a filename so... */

    boost::filesystem::path from_path, from_base, output;

    boost::filesystem::path::iterator path_it = p.begin(),    path_end = p.end();
    boost::filesystem::path::iterator base_it = base.begin(), base_end = base.end();

    // check for emptiness
    if ((path_it == path_end) || (base_it == base_end))
        throw std::runtime_error("path or base was empty; couldn't generate relative path");

#ifdef WIN32
    // drive letters are different; don't generate a relative path
    if (*path_it != *base_it)
        return p;

    // now advance past drive letters; relative paths should only go up
    // to the root of the drive and not past it
    ++path_it, ++base_it;
#endif

    // Cache system-dependent dot, double-dot and slash strings
    const std::string _dot  = std::string(1, dot<path>::value);
    const std::string _dots = std::string(2, dot<path>::value);
    const std::string _sep = std::string(1, slash<path>::value);

    // iterate over path and base
    while (true) {

        // compare all elements so far of path and base to find greatest common root;
        // when elements of path and base differ, or run out:
        if ((path_it == path_end) || (base_it == base_end) || (*path_it != *base_it)) {

            // write to output, ../ times the number of remaining elements in base;
            // this is how far we've had to come down the tree from base to get to the common root
            for (; base_it != base_end; ++base_it) {
                if (*base_it == _dot)
                    continue;
                else if (*base_it == _sep)
                    continue;

                output /= "../";
            }

            // write to output, the remaining elements in path;
            // this is the path relative from the common root
            boost::filesystem::path::iterator path_it_start = path_it;
            for (; path_it != path_end; ++path_it) {

                if (path_it != path_it_start)
                    output /= "/";

                if (*path_it == _dot)
                    continue;
                if (*path_it == _sep)
                    continue;

                output /= *path_it;
            }

            break;
        }

        // add directory level to both paths and continue iteration
        from_path /= path(*path_it);
        from_base /= path(*base_it);

        ++path_it, ++base_it;
    }

    return output;
}
远山浅 2024-11-10 10:01:09

我刚刚编写了可以将绝对路径转换为相对路径的代码。它适用于我的所有用例,但我不能保证它是完美的。

为了便于阅读,我将 boost::filesystem 缩写为“fs”。在函数定义中,您可以使用 fs::path::current_path() 作为“relative_to”的默认值。

fs::path relativePath( const fs::path &path, const fs::path &relative_to )
{
    // create absolute paths
    fs::path p = fs::absolute(path);
    fs::path r = fs::absolute(relative_to);

    // if root paths are different, return absolute path
    if( p.root_path() != r.root_path() )
        return p;

    // initialize relative path
    fs::path result;

    // find out where the two paths diverge
    fs::path::const_iterator itr_path = p.begin();
    fs::path::const_iterator itr_relative_to = r.begin();
    while( itr_path != p.end() && itr_relative_to != r.end() && *itr_path == *itr_relative_to ) {
        ++itr_path;
        ++itr_relative_to;
    }

    // add "../" for each remaining token in relative_to
    if( itr_relative_to != r.end() ) {
        ++itr_relative_to;
        while( itr_relative_to != r.end() ) {
            result /= "..";
            ++itr_relative_to;
        }
    }

    // add remaining path
    while( itr_path != p.end() ) {
        result /= *itr_path;
        ++itr_path;
    }

    return result;
}

I just wrote code that can translate an absolute path to a relative path. It works in all my use cases, but I can not guarantee it is flawless.

I have abreviated boost::filesystem to 'fs' for readability. In the function definition, you can use fs::path::current_path() as a default value for 'relative_to'.

fs::path relativePath( const fs::path &path, const fs::path &relative_to )
{
    // create absolute paths
    fs::path p = fs::absolute(path);
    fs::path r = fs::absolute(relative_to);

    // if root paths are different, return absolute path
    if( p.root_path() != r.root_path() )
        return p;

    // initialize relative path
    fs::path result;

    // find out where the two paths diverge
    fs::path::const_iterator itr_path = p.begin();
    fs::path::const_iterator itr_relative_to = r.begin();
    while( itr_path != p.end() && itr_relative_to != r.end() && *itr_path == *itr_relative_to ) {
        ++itr_path;
        ++itr_relative_to;
    }

    // add "../" for each remaining token in relative_to
    if( itr_relative_to != r.end() ) {
        ++itr_relative_to;
        while( itr_relative_to != r.end() ) {
            result /= "..";
            ++itr_relative_to;
        }
    }

    // add remaining path
    while( itr_path != p.end() ) {
        result /= *itr_path;
        ++itr_path;
    }

    return result;
}
心不设防 2024-11-10 10:01:09

我只是考虑使用 boost::filesystem 来完成相同的任务,但是 - 由于我的应用程序同时使用 Qt 和 Boost 库,我决定使用 Qt,它通过一种简单的方法来完成此任务 QString QDir::relativeFilePath( const QString & fileName )

QDir dir("/home/bob");
QString s;

s = dir.relativeFilePath("images/file.jpg");     // s is "images/file.jpg"
s = dir.relativeFilePath("/home/mary/file.txt"); // s is "../mary/file.txt"

它的工作原理就像魅力让我节省了几个小时的时间。

I was just thinking about using boost::filesystem for the same task, but - since my application uses both Qt and Boost libraries, I decided to use Qt which does this task with one simple method QString QDir::relativeFilePath( const QString & fileName ):

QDir dir("/home/bob");
QString s;

s = dir.relativeFilePath("images/file.jpg");     // s is "images/file.jpg"
s = dir.relativeFilePath("/home/mary/file.txt"); // s is "../mary/file.txt"

It works like a charm and saved me a few hours of my life.

凤舞天涯 2024-11-10 10:01:09

以下是我在基于 boost 文件系统构建的库中执行此操作的方法:

步骤 1:确定“最深的公共根”。基本上,它就像两条路径的最大公约数。例如,如果您的 2 个路径是“C:\a\b\c\d”和“C:\a\b\c\l.txt”,那么它们共享的公共根是“C:\a \公元前\”。

要实现这一点,请将两个路径转换为绝对形式(而不是规范形式)(您将希望能够对推测路径和符号链接执行此操作)。

步骤 2:要从 A 到 B,请在 A 后添加足够的“../”副本,以将目录树向上移动到公共根,然后添加 B 的字符串以沿着树向下移动到该目录。在 Windows 上,您可以有 2 条没有公共根的路径,因此从任何 A 到任何 B 并不总是可能的。

namespace fs = boost::filesystem;

bool GetCommonRoot(const fs::path& path1,
                       const fs::path& path2,
                       fs::path& routeFrom1To2,
                       std::vector<fs::path>& commonDirsInOrder)
{
   fs::path pathA( fs::absolute( path1));
   fs::path pathB( fs::absolute( path2));

   // Parse both paths into vectors of tokens. I call them "dir" because they'll
   // be the common directories unless both paths are the exact same file.
   // I also Remove the "." and ".." paths as part of the loops

   fs::path::iterator    iter;
   std::vector<fs::path> dirsA;
   std::vector<fs::path> dirsB;
   for(iter = pathA.begin(); iter != pathA.end(); ++iter) {
       std::string token = (*iter).string();
       if(token.compare("..") == 0) {      // Go up 1 level => Pop vector
          dirsA.pop_back();
       }
       else if(token.compare(".") != 0) {  // "." means "this dir" => ignore it
          dirsA.push_back( *iter);
       }
   }
   for(iter = pathB.begin(); iter != pathB.end(); ++iter) {
       std::string token = (*iter).string();
       if(token.compare("..") == 0) {      // Go up 1 level => Pop vector
          dirsB.pop_back();
       }
       else if(token.compare(".") != 0) {  // "." means "this dir" => ignore it
          dirsB.push_back( *iter);
       }
   }

   // Determine how far to check in each directory set
   size_t commonDepth = std::min<int>( dirsA.size(), dirsB.size());
   if(!commonDepth) {
       // They don't even share a common root- no way from A to B
       return false;
   }

   // Match entries in the 2 vectors until we see a divergence
   commonDirsInOrder.clear();
   for(size_t i=0; i<commonDepth; ++i) {
      if(dirsA[i].string().compare( dirsB[i].string()) != 0) {   // Diverged
         break;
      }
      commonDirsInOrder.push_back( dirsA[i]);  // I could use dirsB too.
   }

   // Now determine route: start with A
   routeFrom1To2.clear();
   for(size_t i=0; i<commonDepth; ++i) {
       routeFrom1To2 /= dirsA[i];
   }
   size_t backupSteps = dirsA.size() - commonDepth; // # of "up dir" moves we need
   for(size_t i=0; i<backupSteps; ++i) {
       routeFrom1To2 /= "../";
   }

   // Append B's path to go down to it from the common root
   for(size_t i=commonDepth; i<dirsB.size(); ++i) {
       routeFrom1To2 /= dirsB[i];    // ensures absolutely correct subdirs
   }
   return true;

将执行您想要的操作 - 您从 A 上升,直到到达它和 B 都是其后代的公共文件夹,然后下降到 B。您可能不需要我拥有的“commonDirsInOrder”返回,但是“routeFrom1To2” return 就是您要的那个。

如果您打算实际将工作目录更改为“B”,您可以直接使用“routeFrom1To2”。请注意,尽管有所有“..”部分,此函数仍将生成绝对路径,但这应该不是问题。

Here's how I do it in the library I build on top of boost filesystem:

Step 1: Determine "deepest common root". Basically, its like the greatest common denominator for 2 paths. For example, if you're 2 paths are "C:\a\b\c\d" and "C:\a\b\c\l.txt" then the common root they both share is "C:\a\b\c\".

To get this, convert both paths into absolute- NOT canonical- form (you'll want to be able to do this for speculative paths & symlinks).

Step 2: To go from A to B, you suffix A with enough copies of "../" to shift up the directory tree to the common root, then add the string for B to travel down the tree to it. On windows you can have 2 paths with no common root, so going from any A to any B is not always possible.

namespace fs = boost::filesystem;

bool GetCommonRoot(const fs::path& path1,
                       const fs::path& path2,
                       fs::path& routeFrom1To2,
                       std::vector<fs::path>& commonDirsInOrder)
{
   fs::path pathA( fs::absolute( path1));
   fs::path pathB( fs::absolute( path2));

   // Parse both paths into vectors of tokens. I call them "dir" because they'll
   // be the common directories unless both paths are the exact same file.
   // I also Remove the "." and ".." paths as part of the loops

   fs::path::iterator    iter;
   std::vector<fs::path> dirsA;
   std::vector<fs::path> dirsB;
   for(iter = pathA.begin(); iter != pathA.end(); ++iter) {
       std::string token = (*iter).string();
       if(token.compare("..") == 0) {      // Go up 1 level => Pop vector
          dirsA.pop_back();
       }
       else if(token.compare(".") != 0) {  // "." means "this dir" => ignore it
          dirsA.push_back( *iter);
       }
   }
   for(iter = pathB.begin(); iter != pathB.end(); ++iter) {
       std::string token = (*iter).string();
       if(token.compare("..") == 0) {      // Go up 1 level => Pop vector
          dirsB.pop_back();
       }
       else if(token.compare(".") != 0) {  // "." means "this dir" => ignore it
          dirsB.push_back( *iter);
       }
   }

   // Determine how far to check in each directory set
   size_t commonDepth = std::min<int>( dirsA.size(), dirsB.size());
   if(!commonDepth) {
       // They don't even share a common root- no way from A to B
       return false;
   }

   // Match entries in the 2 vectors until we see a divergence
   commonDirsInOrder.clear();
   for(size_t i=0; i<commonDepth; ++i) {
      if(dirsA[i].string().compare( dirsB[i].string()) != 0) {   // Diverged
         break;
      }
      commonDirsInOrder.push_back( dirsA[i]);  // I could use dirsB too.
   }

   // Now determine route: start with A
   routeFrom1To2.clear();
   for(size_t i=0; i<commonDepth; ++i) {
       routeFrom1To2 /= dirsA[i];
   }
   size_t backupSteps = dirsA.size() - commonDepth; // # of "up dir" moves we need
   for(size_t i=0; i<backupSteps; ++i) {
       routeFrom1To2 /= "../";
   }

   // Append B's path to go down to it from the common root
   for(size_t i=commonDepth; i<dirsB.size(); ++i) {
       routeFrom1To2 /= dirsB[i];    // ensures absolutely correct subdirs
   }
   return true;

}

This will do what you want- you go up from A until you hit the common folder it and B are both descendants of, then go down to B. You probably don't need the "commonDirsInOrder" return that I have, but the "routeFrom1To2" return IS the one you're asking for.

If you plan to actually change the working directory to "B" you can use "routeFrom1To2" directly. Be aware that this function will produce an absolute path despite all the ".." parts, but that shouldn't be a problem.

才能让你更想念 2024-11-10 10:01:09

我为这个技巧写下了一个简单的解决方案。
boost库没有使用,只有STL的std::string,std::vector。

Win32平台已经过测试。

只需调用:

strAlgExeFile = helper.GetRelativePath(PathA, PathB);

并且,它将返回从 PathAPathB 的相对路径。

例子:

strAlgExeFile = helper.GetRelativePath((helper.GetCurrentDir()).c_str(), strAlgExeFile.c_str());

#ifdef _WIN32                                                                              
    #define STR_TOKEN "\\"                                                                 
    #define LAST_FOLDER "..\\"                                                             
    #define FOLDER_SEP "\\"                                                                
    #define LINE_BREAK "\r\n"                                                              
#else                                                                                      
    #define STR_TOKEN "/"                                                                  
    #define LAST_FOLDER "../"                                                              
    #define FOLDER_SEP "/"                                                                 
    #define LINE_BREAK "\n"                                                                
#endif // _WIN32                                                                           

void CHelper::SplitStr2Vec(const char* pszPath, vector<string>& vecString)                 
{                                                                                          
  char * pch;                                                                              

  pch = strtok (const_cast < char*> (pszPath), STR_TOKEN );                                
  while (pch != NULL)                                                                      
  {                                                                                        
    vecString.push_back( pch );                                                            
    pch = strtok (NULL, STR_TOKEN );                                                       
  }                                                                                        
}                                                                                          

string& CHelper::GetRelativePath(const char* pszPath1,const char* pszPath2)                
{                                                                                          
    vector<string> vecPath1, vecPath2;                                                     
    vecPath1.clear();                                                                      
    vecPath2.clear();                                                                      
    SplitStr2Vec(pszPath1, vecPath1);                                                      
    SplitStr2Vec(pszPath2, vecPath2);                                                      
    size_t iSize = ( vecPath1.size() < vecPath2.size() )? vecPath1.size(): vecPath2.size();
    unsigned int iSameSize(0);                                                             
    for (unsigned int i=0; i<iSize; ++i)                                                   
    {                                                                                      
        if ( vecPath1[i] != vecPath2[i])                                                   
        {                                                                                  
            iSameSize = i;                                                                 
            break;                                                                         
        }                                                                                  
    }                                                                                      

    m_strRelativePath = "";                                                                
    for (unsigned int i=0 ; i< (vecPath1.size()-iSameSize) ; ++i)                          
        m_strRelativePath += const_cast<char *> (LAST_FOLDER);                             

    for (unsigned int i=iSameSize ; i<vecPath2.size() ; ++i)                               
    {                                                                                      
        m_strRelativePath += vecPath2[i];                                                  
        if( i < (vecPath2.size()-1) )                                                      
            m_strRelativePath += const_cast<char *> (FOLDER_SEP);                          
    }                                                                                      

    return m_strRelativePath;                                                              
}

I have write down one simple solution for this trick.
There's no usage on boost libraries, only STL's std::string, std::vector.

The Win32 platform has been tested.

Just calling:

strAlgExeFile = helper.GetRelativePath(PathA, PathB);

And, it would return relative path from PathA to PathB.

Example:

strAlgExeFile = helper.GetRelativePath((helper.GetCurrentDir()).c_str(), strAlgExeFile.c_str());

#ifdef _WIN32                                                                              
    #define STR_TOKEN "\\"                                                                 
    #define LAST_FOLDER "..\\"                                                             
    #define FOLDER_SEP "\\"                                                                
    #define LINE_BREAK "\r\n"                                                              
#else                                                                                      
    #define STR_TOKEN "/"                                                                  
    #define LAST_FOLDER "../"                                                              
    #define FOLDER_SEP "/"                                                                 
    #define LINE_BREAK "\n"                                                                
#endif // _WIN32                                                                           

void CHelper::SplitStr2Vec(const char* pszPath, vector<string>& vecString)                 
{                                                                                          
  char * pch;                                                                              

  pch = strtok (const_cast < char*> (pszPath), STR_TOKEN );                                
  while (pch != NULL)                                                                      
  {                                                                                        
    vecString.push_back( pch );                                                            
    pch = strtok (NULL, STR_TOKEN );                                                       
  }                                                                                        
}                                                                                          

string& CHelper::GetRelativePath(const char* pszPath1,const char* pszPath2)                
{                                                                                          
    vector<string> vecPath1, vecPath2;                                                     
    vecPath1.clear();                                                                      
    vecPath2.clear();                                                                      
    SplitStr2Vec(pszPath1, vecPath1);                                                      
    SplitStr2Vec(pszPath2, vecPath2);                                                      
    size_t iSize = ( vecPath1.size() < vecPath2.size() )? vecPath1.size(): vecPath2.size();
    unsigned int iSameSize(0);                                                             
    for (unsigned int i=0; i<iSize; ++i)                                                   
    {                                                                                      
        if ( vecPath1[i] != vecPath2[i])                                                   
        {                                                                                  
            iSameSize = i;                                                                 
            break;                                                                         
        }                                                                                  
    }                                                                                      

    m_strRelativePath = "";                                                                
    for (unsigned int i=0 ; i< (vecPath1.size()-iSameSize) ; ++i)                          
        m_strRelativePath += const_cast<char *> (LAST_FOLDER);                             

    for (unsigned int i=iSameSize ; i<vecPath2.size() ; ++i)                               
    {                                                                                      
        m_strRelativePath += vecPath2[i];                                                  
        if( i < (vecPath2.size()-1) )                                                      
            m_strRelativePath += const_cast<char *> (FOLDER_SEP);                          
    }                                                                                      

    return m_strRelativePath;                                                              
}
放手` 2024-11-10 10:01:09

我需要在没有 Boost 的情况下执行此操作,而其他基于 std 的解决方案无法为我执行此操作,因此我重新实现了它。当我正在研究这个时,我意识到我以前也做过......

无论如何,它不像其他一些那样完整,但可能对人们有用。它是 Windows 特定的;使其 POSIX 的更改涉及目录分隔符和字符串比较中的区分大小写。

在我实现并工作后不久,我必须将周围的功能转移到Python,所以所有这些都归结为os.path.relpath(to, from)

static inline bool StringsEqual_i(const std::string& lhs, const std::string& rhs)
{
    return _stricmp(lhs.c_str(), rhs.c_str()) == 0;
}

static void SplitPath(const std::string& in_path, std::vector<std::string>& split_path)
{
    size_t start = 0;
    size_t dirsep;
    do
    {
        dirsep = in_path.find_first_of("\\/", start);
        if (dirsep == std::string::npos)
            split_path.push_back(std::string(&in_path[start]));
        else
            split_path.push_back(std::string(&in_path[start], &in_path[dirsep]));
        start = dirsep + 1;
    } while (dirsep != std::string::npos);
}

/**
 * Get the relative path from a base location to a target location.
 *
 * \param to The target location.
 * \param from The base location. Must be a directory.
 * \returns The resulting relative path.
 */
static std::string GetRelativePath(const std::string& to, const std::string& from)
{
    std::vector<std::string> to_dirs;
    std::vector<std::string> from_dirs;

    SplitPath(to, to_dirs);
    SplitPath(from, from_dirs);

    std::string output;
    output.reserve(to.size());

    std::vector<std::string>::const_iterator to_it = to_dirs.begin(),
                                             to_end = to_dirs.end(),
                                             from_it = from_dirs.begin(),
                                             from_end = from_dirs.end();

    while ((to_it != to_end) && (from_it != from_end) && StringsEqual_i(*to_it, *from_it))
    {
         ++to_it;
         ++from_it;
    }

    while (from_it != from_end)
    {
        output += "..\\";
        ++from_it;
    }

    while (to_it != to_end)
    {
        output += *to_it;
        ++to_it;

        if (to_it != to_end)
            output += "\\";
    }

    return output;
}

I needed to do this without Boost and the other std based solution didn't do it for me so I reimplemented it. As I was working on this I realized that I'd done it before too...

Anyway, it's not as complete as some of the others but might be useful to people. It's Windows-specific; changes to make it POSIX involve directory separator and case sensitivity in the string compare.

Shortly after I got this implemented and working I had to transfer the surrounding functionality to Python so all of this just boiled down to os.path.relpath(to, from).

static inline bool StringsEqual_i(const std::string& lhs, const std::string& rhs)
{
    return _stricmp(lhs.c_str(), rhs.c_str()) == 0;
}

static void SplitPath(const std::string& in_path, std::vector<std::string>& split_path)
{
    size_t start = 0;
    size_t dirsep;
    do
    {
        dirsep = in_path.find_first_of("\\/", start);
        if (dirsep == std::string::npos)
            split_path.push_back(std::string(&in_path[start]));
        else
            split_path.push_back(std::string(&in_path[start], &in_path[dirsep]));
        start = dirsep + 1;
    } while (dirsep != std::string::npos);
}

/**
 * Get the relative path from a base location to a target location.
 *
 * \param to The target location.
 * \param from The base location. Must be a directory.
 * \returns The resulting relative path.
 */
static std::string GetRelativePath(const std::string& to, const std::string& from)
{
    std::vector<std::string> to_dirs;
    std::vector<std::string> from_dirs;

    SplitPath(to, to_dirs);
    SplitPath(from, from_dirs);

    std::string output;
    output.reserve(to.size());

    std::vector<std::string>::const_iterator to_it = to_dirs.begin(),
                                             to_end = to_dirs.end(),
                                             from_it = from_dirs.begin(),
                                             from_end = from_dirs.end();

    while ((to_it != to_end) && (from_it != from_end) && StringsEqual_i(*to_it, *from_it))
    {
         ++to_it;
         ++from_it;
    }

    while (from_it != from_end)
    {
        output += "..\\";
        ++from_it;
    }

    while (to_it != to_end)
    {
        output += *to_it;
        ++to_it;

        if (to_it != to_end)
            output += "\\";
    }

    return output;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文