una rutas ruta relativos relativas relativa qué ejercicios archivo acceso absolutas absoluta c++ boost boost-filesystem

c++ - relativos - rutas relativas arcgis



Obtener ruta relativa de dos rutas absolutas (7)

A partir de la versión 1.60.0 boost.filesystem es compatible con esto. Está buscando la path lexically_relative(const path& p) const función miembro path lexically_relative(const path& p) const .

Original, pre-1.60.0 respuesta a continuación.

Boost no es compatible con esto; es un tema abierto - # 1976 (función inversa para completar) - que sin embargo no parece estar recibiendo mucha tracción.

Aquí hay una solución vagamente ingenua que parece hacer el truco (no estoy seguro si se puede mejorar):

#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; }

Tengo dos rutas absolutas del sistema de archivos (A y B), y quiero generar una tercera ruta del sistema de archivos que representa "Un pariente de B".

Caso de uso:

  • Reproductor de medios administrando una lista de reproducción.
  • El usuario agrega el archivo a la lista de reproducción.
  • Nueva ruta de archivo agregada a la lista de reproducción relativa a la ruta de la lista de reproducción .
  • En el futuro, todo el directorio de música (incluida la lista de reproducción) se movió a otra parte.
  • Todas las rutas siguen siendo válidas porque son relativas a la lista de reproducción.

boost::filesystem parece haber complete para resolver relative ~ relative => absolute , pero nada para hacer esto en reversa ( absolute ~ absolute => relative ).

Quiero hacerlo con las rutas de Boost.


Acabo de escribir un código que puede traducir una ruta absoluta a una ruta relativa. Funciona en todos mis casos de uso, pero no puedo garantizar que sea perfecto.

He abreviado boost :: filesystem a ''fs'' para la legibilidad. En la definición de función, puede usar fs :: path :: current_path () como valor predeterminado para ''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 == *itr_relative_to && itr_path != p.end() && itr_relative_to != r.end() ) { ++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; }


Así es como lo hago en la biblioteca que construyo sobre el sistema de archivos boost:

Paso 1: determina la "raíz común más profunda". Básicamente, es como el mayor denominador común para 2 rutas. Por ejemplo, si sus 2 rutas son "C: / a / b / c / d" y "C: / a / b / c / l.txt", entonces la raíz común que ambas comparten es "C: / a /antes de Cristo/".

Para lograr esto, convierta ambas rutas en forma absoluta, NO canónica (querrá hacer esto para rutas especulativas y enlaces simbólicos).

Paso 2: para pasar de A a B, sufra un sufijo A con suficientes copias de "../" para desplazar el árbol de directorios a la raíz común, luego agregue la cadena para que B se desplace hacia él. En Windows, puede tener 2 rutas sin raíz común, por lo que no siempre es posible pasar de A 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;

}

Esto hará lo que quieras: pasarás de A hasta que toques la carpeta común de la que B es descendiente y luego descenderás a B. Probablemente no necesites el retorno "commonDirsInOrder" que tengo, pero el " routeFrom1To2 "return ES el que está solicitando.

Si planea realmente cambiar el directorio de trabajo a "B", puede usar "routeFrom1To2" directamente. Tenga en cuenta que esta función producirá una ruta absoluta a pesar de todas las partes "..", pero eso no debería ser un problema.


Con C ++ 17 y su std::filesystem::relative , que evolucionó a partir de boost, esta es una obviedad:

#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. }

Salida (el segundo parámetro es la 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

Utiliza std::filesystem::path::lexically_relative para comparar. La diferencia con la función léxica pura es que std::filesystem::relative resuelve enlaces simbólicos y normaliza ambas rutas usando std::filesystem::weakly_canonical (que se introdujo por relative ) antes de la comparación.


Estaba pensando en usar boost::filesystem para la misma tarea, pero como mi aplicación usa las bibliotecas Qt y Boost, decidí usar Qt, que hace esta tarea con un método simple 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"

Funciona como un amuleto y me salvó algunas horas de mi vida.


He escrito una solución simple para este truco. No hay uso en las bibliotecas de impulso, solo std::string , std::vector STL.

La plataforma Win32 ha sido probada.

Solo llamando:

strAlgExeFile = helper.GetRelativePath(PathA, PathB);

Y, devolvería la ruta relativa de PathA a PathB .

Ejemplo:

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; }


Necesitaba hacer esto sin Boost y la otra solución basada en std no lo hizo por mí, así que lo reimplementé. Mientras estaba trabajando en esto, me di cuenta de que ya lo había hecho antes ...

De todos modos, no es tan completo como algunos de los otros, pero podría ser útil para las personas. Es específico de Windows; los cambios para hacer que POSIX involucren el separador de directorio y la distinción entre mayúsculas y minúsculas en la comparación de cadenas.

Poco después de que esto se implementó y funcionaba, tuve que transferir la funcionalidad circundante a Python para que todo esto simplemente se os.path.relpath(to, from) a 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; }