C++ 从两个绝对路径中获取相对路径
我有两个绝对文件系统路径(A和B),我想生成第三个文件系统路径,表示“B的相对路径” 用例:C++ 从两个绝对路径中获取相对路径,c++,boost,boost-filesystem,C++,Boost,Boost Filesystem,我有两个绝对文件系统路径(A和B),我想生成第三个文件系统路径,表示“B的相对路径” 用例: 管理播放列表的媒体播放器 用户将文件添加到播放列表中 相对于播放列表路径添加到播放列表的新文件路径 将来,整个音乐目录(包括播放列表)都会移到别处 所有路径仍然有效,因为它们是相对于播放列表的 boost::filesystem似乎有complete来解析relative~relative=>absolute,但没有相反的操作(absolute~absolute=>relative) 我想用Boos
- 管理播放列表的媒体播放器
- 用户将文件添加到播放列表中
- 相对于播放列表路径添加到播放列表的新文件路径
- 将来,整个音乐目录(包括播放列表)都会移到别处
- 所有路径仍然有效,因为它们是相对于播放列表的
boost::filesystem
似乎有complete
来解析relative~relative=>absolute
,但没有相反的操作(absolute~absolute=>relative
)
我想用Boost路径来实现这一点。从版本Boost开始。文件系统确实支持这一点。您正在查找成员函数path词汇相对(constpath&p)const
以下是1.60.0之前的原始答案
Boost不支持这一点;这是一个悬而未决的问题——尽管如此,似乎并没有得到多少关注 这里有一个看似幼稚的解决方法,似乎可以解决问题(不确定是否可以改进):
#包括
#包括
#包括
#包括
/**
* https://svn.boost.org/trac/boost/ticket/1976#comment:2
*
*想法:未完成(/foo/new,/foo/bar)=>../new
*这种情况的用例是在任何时候获得完整路径(可能是从打开的对话框)
*并希望存储相对路径,以便将文件组移动到其他位置
*IDE就是一个简单的例子,因此
*项目文件可以安全地签出subversion。“
*
*算法:
*迭代路径和基
*比较迄今为止路径和基础的所有元素
*虽然它们是相同的,但没有写入到输出
*当它们发生变化或一个用完时:
*写入到输出,../乘以基中剩余的元素数
*写入到输出,路径中的剩余元素
*/
boost::filesystem::path
未完成(boost::filesystem::path const p,boost::filesystem::path const base){
使用boost::filesystem::path;
使用boost::filesystem::dot;
使用boost::filesystem::slash;
如果(p==基准)
返回“/”;
/*!!如果路径是文件名而不是目录,则会中断内容,
最有可能是…但是base不应该是文件名所以*/
boost::filesystem::path from_path,from_base,output;
boost::filesystem::path::迭代器path_it=p.begin(),path_end=p.end();
boost::filesystem::path::迭代器base_it=base.begin(),base_end=base.end();
//检查是否有空
if((路径_it==路径_end)| |(基本_it==基本_end))
throw std::runtime_错误(“路径或基为空;无法生成相对路径”);
#ifdef WIN32
//驱动器号不同;不生成相对路径
if(*path\u it!=*base\u it)
返回p;
//现在前进超过驱动器号;相对路径应该只上升
//到驱动器的根,而不是经过它
++路径_it,++base_it;
#恩迪夫
//缓存系统相关的点、双点和斜线字符串
常量std::string _dot=std::string(1,dot::value);
常量std::string _dots=std::string(2,dot::value);
常量std::string _sep=std::string(1,斜杠::值);
//在路径和基上迭代
while(true){
//比较迄今为止路径和基的所有元素,以找到最大的公共根;
//当path和base的元素不同或耗尽时:
如果((路径_it==路径_end)| |(基础_it==基础_end)| |(*路径_it!=*基础_it)){
//写入到输出,../乘以基中剩余的元素数;
//这就是我们必须从树的底部往下走多远才能找到共同的根
对于(;base_it!=base_end;++base_it){
if(*基本点=U点)
继续;
else if(*base\u it==\u sep)
继续;
输出/=“./”;
}
//写入到输出,路径中的剩余元素;
//这是相对于公共根的路径
boost::filesystem::path::iterator path\u it\u start=path\u it;
for(;path_it!=path_end;++path_it){
如果(路径它!=路径它开始)
输出/=“/”;
如果(*路径=点)
继续;
if(*path\U it==\U sep)
继续;
输出/=*路径_it;
}
打破
}
//将目录级别添加到两个路径并继续迭代
from_path/=path(*path_it);
from_base/=路径(*base_it);
++路径_it,++base_it;
}
返回输出;
}
我正在考虑为同一任务使用boost::filesystem
,但是-由于我的应用程序同时使用Qt和boost库,我决定使用Qt,它通过一种简单的方法完成此任务:
它就像一个符咒,为我节省了几个小时的生命。我刚刚编写了可以将绝对路径转换为相对路径的代码。它适用于我所有的用例,但我不能保证它是完美的 为了可读性,我将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;
}
我已经为这个技巧写下了一个简单的解决方案。 boost库上没有使用,只有STL的
std::string
,std::vector
Win32平台已经过测试
只是打电话:
strAlgExeFile = helper.GetRelativePath(PathA, PathB);
并且,它将返回从PathA
到PathB
的相对路径
例如:
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;
}
strAlgExeFile=helper.GetRelativePath((helper.GetCurrentDir()).c_str(),strAlgExeFile.c_str());
#ifdef_WIN32
#定义STR\u标记“\\”
strAlgExeFile = helper.GetRelativePath(PathA, PathB);
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;
}
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;
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;
}
#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 is base: or/is/it/relative/to/the/observer
Base is deeper: ../../../../../../..
Base is orthogonal: ../../../../../../little/light/races/in/orbit/of/a/rogue/planet