2つの絶対ファイルシステムパス(AとB)があり、「Bからの相対A」を表す3番目のファイルシステムパスを生成したいと思います。
使用事例:
boost::filesystem
にはrelative ~ relative => absolute
を解決するためのcomplete
があるようですが、これを逆に行うことはありません(absolute ~ absolute => relative
)。
Boostパスでやりたいです。
バージョンの時点で 1.60. boost.filesystemはこれをサポートしています。メンバー関数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;
}
絶対パスを相対パスに変換できるコードを書いたところです。私のすべてのユースケースで機能しますが、完璧であることを保証することはできません。
読みやすくするために、boost :: filesystemを「fs」に短縮しました。関数定義では、「relative_to」のデフォルト値としてfs :: path :: current_path()を使用できます。
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::filesystem
を使用することを考えていましたが、アプリケーションがQtライブラリとBoostライブラリの両方を使用するため、1つの簡単なメソッドでこのタスクを実行するQtを使用することにしました QString QDir :: relatedFilePath (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"
それは魅力のように機能し、私の人生の数時間を節約しました。
ブーストから進化したC++ 17とその std::filesystem::relative
を使用すると、これは簡単です。
#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.
}
出力(2番目のパラメーターはベースです)
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
に導入された)を使用して両方のパスを正規化することです。
ブーストファイルシステムの上に構築したライブラリでこれを行う方法は次のとおりです。
ステップ1:「最も深い共通ルート」を決定します。基本的に、これは2つのパスの最大公約数のようなものです。たとえば、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;
}
これはあなたが望むことをします-あなたはそれとBが両方の子孫である共通フォルダにぶつかるまでAから上に行き、そしてBに下がる。おそらく私が持っている「commonDirsInOrder」リターンは必要ないだろうが、「 routeFrom1To2 "return ISあなたが求めているもの。
実際に作業ディレクトリを「B」に変更する場合は、「routeFrom1To2」を直接使用できます。この関数は、すべての「..」部分にもかかわらず絶対パスを生成することに注意してください。ただし、これは問題にはなりません。
このトリックの簡単な解決策を1つ書き留めました。 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;
}
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;
}