Hi Michael and Robert,
Here is the modified submission. Thoughts?
Sukender
PVLE - Lightweight cross-platform game engine - http://pvle.sourceforge.net/
----- "Sukender" <[email protected]> a écrit :
> You're absolutely right, Michael, but the function is called only for
> complete paths. I'll make a tiny change in order to make it clearer
> (or make the function more general).
>
> Sukender
> PVLE - Lightweight cross-platform game engine -
> http://pvle.sourceforge.net/
>
> ----- "Michael Platings" <[email protected]> a écrit :
>
> > Hi Sukender,
> > I just had a quick look at this and if I'm reading it right, valid
> > paths such as "..\..\folder\thing.osg" would cause a warning in
> > cleanPath() and would return an incorrect path.
> >
> >
> > On 10 February 2011 16:05, Sukender < [email protected] > wrote:
> >
> >
> > Hi Robert,
> >
> > This is kind of tricky submission... I found that the current
> > getRealPath() implementation for Windows doesn't work well with UTF8
> > paths. So I tried to add the support using Unicode Windows API
> calls.
> > But unfortunately, the GetFullPathNameW() *corrupts the stack*!
> Yes...
> > I've done multiple tries and each with the same conclusion. I added
> a
> > detailed comment about this, and finally wrote my own implementation
> > of this Windows API function. So you'll find:
> > - getFullPathName(), a replacement for GetFullPathNameA() and
> > GetFullPathNameW()
> > - cleanPath(), which simply removes "." and ".." from a path
> > - and a modified getRealPath()
> >
> > This is a bit risky as this is quite low level. Moreover I cannot be
> > sure this submission 100% works in all cases, and I do not have
> access
> > to more robust impementations (ie. C++0x or boost::filesystem).
> > Please tell me if it seems okay for you.
> >
> > File modified: rev.12156.
> >
> > Cheers,
> >
> > Sukender
> > PVLE - Lightweight cross-platform game engine -
> > http://pvle.sourceforge.net/
> >
> > _______________________________________________
> > osg-submissions mailing list
> > [email protected]
> >
> http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org
> >
> >
> >
> > _______________________________________________
> > osg-submissions mailing list
> > [email protected]
> >
> http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org
> _______________________________________________
> osg-submissions mailing list
> [email protected]
> http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/ConvertUTF>
#ifdef WIN32
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <direct.h>
#endif
#if defined(__sgi)
#include <ctype.h>
#elif defined(__GNUC__) || !defined(WIN32) || defined(__MWERKS__)
#include <cctype>
using std::tolower;
#endif
using namespace std;
static const char * const PATH_SEPARATORS = "/\\";
static unsigned int PATH_SEPARATORS_LEN = 2;
std::string osgDB::getFilePath(const std::string& fileName)
{
std::string::size_type slash = fileName.find_last_of(PATH_SEPARATORS);
if (slash==std::string::npos) return std::string();
else return std::string(fileName, 0, slash);
}
std::string osgDB::getSimpleFileName(const std::string& fileName)
{
std::string::size_type slash = fileName.find_last_of(PATH_SEPARATORS);
if (slash==std::string::npos) return fileName;
else return std::string(fileName.begin()+slash+1,fileName.end());
}
std::string osgDB::getFileExtension(const std::string& fileName)
{
std::string::size_type dot = fileName.find_last_of('.');
std::string::size_type slash = fileName.find_last_of(PATH_SEPARATORS);
if (dot==std::string::npos || (slash!=std::string::npos && dot<slash))
return std::string("");
return std::string(fileName.begin()+dot+1,fileName.end());
}
std::string osgDB::getFileExtensionIncludingDot(const std::string& fileName)
{
std::string::size_type dot = fileName.find_last_of('.');
std::string::size_type slash = fileName.find_last_of(PATH_SEPARATORS);
if (dot==std::string::npos || (slash!=std::string::npos && dot<slash))
return std::string("");
return std::string(fileName.begin()+dot,fileName.end());
}
std::string osgDB::convertFileNameToWindowsStyle(const std::string& fileName)
{
std::string new_fileName(fileName);
std::string::size_type slash = 0;
while( (slash=new_fileName.find_first_of(UNIX_PATH_SEPARATOR,slash)) !=
std::string::npos)
{
new_fileName[slash]=WINDOWS_PATH_SEPARATOR;
}
return new_fileName;
}
std::string osgDB::convertFileNameToUnixStyle(const std::string& fileName)
{
std::string new_fileName(fileName);
std::string::size_type slash = 0;
while( (slash=new_fileName.find_first_of(WINDOWS_PATH_SEPARATOR,slash)) !=
std::string::npos)
{
new_fileName[slash]=UNIX_PATH_SEPARATOR;
}
return new_fileName;
}
char osgDB::getNativePathSeparator()
{
#if defined(WIN32) && !defined(__CYGWIN__)
return WINDOWS_PATH_SEPARATOR;
#else
return UNIX_PATH_SEPARATOR;
#endif
}
bool osgDB::isFileNameNativeStyle(const std::string& fileName)
{
#if defined(WIN32) && !defined(__CYGWIN__)
return fileName.find(UNIX_PATH_SEPARATOR) == std::string::npos; // return
true if no unix style slash exist
#else
return fileName.find(WINDOWS_PATH_SEPARATOR) == std::string::npos; //
return true if no windows style backslash exist
#endif
}
std::string osgDB::convertFileNameToNativeStyle(const std::string& fileName)
{
#if defined(WIN32) && !defined(__CYGWIN__)
return convertFileNameToWindowsStyle(fileName);
#else
return convertFileNameToUnixStyle(fileName);
#endif
}
std::string osgDB::getLowerCaseFileExtension(const std::string& filename)
{
return convertToLowerCase(osgDB::getFileExtension(filename));
}
std::string osgDB::convertToLowerCase(const std::string& str)
{
std::string lowcase_str(str);
for(std::string::iterator itr=lowcase_str.begin();
itr!=lowcase_str.end();
++itr)
{
*itr = tolower(*itr);
}
return lowcase_str;
}
// strip one level of extension from the filename.
std::string osgDB::getNameLessExtension(const std::string& fileName)
{
std::string::size_type dot = fileName.find_last_of('.');
std::string::size_type slash = fileName.find_last_of(PATH_SEPARATORS);
// Finds forward slash *or* back slash
if (dot==std::string::npos || (slash!=std::string::npos && dot<slash))
return fileName;
return std::string(fileName.begin(),fileName.begin()+dot);
}
// strip all extensions from the filename.
std::string osgDB::getNameLessAllExtensions(const std::string& fileName)
{
// Finds start serach position: from last slash, or the begining of the
string if none found
std::string::size_type startPos = fileName.find_last_of(PATH_SEPARATORS);
// Finds forward slash *or* back slash
if (startPos == std::string::npos) startPos = 0;
std::string::size_type dot = fileName.find_first_of('.', startPos);
// Finds *FIRST* dot from start pos
if (dot==std::string::npos) return fileName;
return std::string(fileName.begin(),fileName.begin()+dot);
}
std::string osgDB::getStrippedName(const std::string& fileName)
{
std::string simpleName = getSimpleFileName(fileName);
return getNameLessExtension( simpleName );
}
bool osgDB::equalCaseInsensitive(const std::string& lhs,const std::string& rhs)
{
if (lhs.size()!=rhs.size()) return false;
std::string::const_iterator litr = lhs.begin();
std::string::const_iterator ritr = rhs.begin();
while (litr!=lhs.end())
{
if (tolower(*litr)!=tolower(*ritr)) return false;
++litr;
++ritr;
}
return true;
}
bool osgDB::equalCaseInsensitive(const std::string& lhs,const char* rhs)
{
if (rhs==NULL || lhs.size()!=strlen(rhs)) return false;
std::string::const_iterator litr = lhs.begin();
const char* cptr = rhs;
while (litr!=lhs.end())
{
if (tolower(*litr)!=tolower(*cptr)) return false;
++litr;
++cptr;
}
return true;
}
bool osgDB::containsServerAddress(const std::string& filename)
{
// need to check for ://
std::string::size_type pos(filename.find("://"));
if (pos == std::string::npos)
return false;
std::string proto(filename.substr(0, pos));
return Registry::instance()->isProtocolRegistered(proto);
}
std::string osgDB::getServerProtocol(const std::string& filename)
{
std::string::size_type pos(filename.find("://"));
if (pos != std::string::npos)
return filename.substr(0,pos);
return "";
}
std::string osgDB::getServerAddress(const std::string& filename)
{
std::string::size_type pos(filename.find("://"));
if (pos != std::string::npos)
{
std::string::size_type pos_slash = filename.find_first_of('/',pos+3);
if (pos_slash!=std::string::npos)
{
return filename.substr(pos+3,pos_slash-pos-3);
}
else
{
return filename.substr(pos+3,std::string::npos);
}
}
return "";
}
std::string osgDB::getServerFileName(const std::string& filename)
{
std::string::size_type pos(filename.find("://"));
if (pos != std::string::npos)
{
std::string::size_type pos_slash = filename.find_first_of('/',pos+3);
if (pos_slash!=std::string::npos)
{
return filename.substr(pos_slash+1,std::string::npos);
}
else
{
return "";
}
}
return filename;
}
std::string osgDB::concatPaths(const std::string& left, const std::string&
right)
{
#if defined(WIN32) && !defined(__CYGWIN__)
const char delimiterNative = WINDOWS_PATH_SEPARATOR;
const char delimiterForeign = UNIX_PATH_SEPARATOR;
#else
const char delimiterNative = UNIX_PATH_SEPARATOR;
const char delimiterForeign = WINDOWS_PATH_SEPARATOR;
#endif
if(left.empty())
{
return(right);
}
char lastChar = left[left.size() - 1];
if(lastChar == delimiterNative)
{
return left + right;
}
else if(lastChar == delimiterForeign)
{
return left.substr(0, left.size() - 1) + delimiterNative + right;
}
else // lastChar != a delimiter
{
return left + delimiterNative + right;
}
}
/// Helper to iterate over elements of a path (including Windows' root, if any).
class PathIterator {
public:
PathIterator(const std::string & v) : end(v.end()), start(v.begin()),
stop(v.begin()) { operator++(); }
bool valid() const { return start!=end; }
PathIterator & operator++()
{
if (!valid()) return *this;
start = skipSeparators(stop);
if (start != end) stop = next(start);
return *this;
}
std::string operator*()
{
if (!valid()) return std::string();
return std::string(start, stop);
}
protected:
std::string::const_iterator end; ///< End of path string
std::string::const_iterator start; ///< Points to the first char of an
element, or ==end() if no more
std::string::const_iterator stop; ///< Points to the separator after
'start', or ==end()
/// Iterate until 'it' points to something different from a separator
std::string::const_iterator skipSeparators(std::string::const_iterator it)
{
for (; it!=end && std::find_first_of(it, it+1, PATH_SEPARATORS,
PATH_SEPARATORS+PATH_SEPARATORS_LEN) != it+1; ++it) {}
return it;
}
std::string::const_iterator next(std::string::const_iterator it)
{
return std::find_first_of(it, end, PATH_SEPARATORS,
PATH_SEPARATORS+PATH_SEPARATORS_LEN);
}
};
namespace osgDB {
/// Gets root part of a path, or an empty string if none found
///\return Either "", or "/" or "\", or a windows root (like "C:").
std::string getPathRoot(const std::string& path)
{
// Test for unix root
if (path.empty()) return "";
if (path[0] == '/' || path[0] == '\\') return std::string(1, path[0]);
// Now test for Windows root
if (path.length()<2) return "";
if (path[1] == ':') return path.substr(0, 2); // We should check
that path[0] is a letter, but as ':' is invalid in paths in other cases, that's
not a problem.
return "";
}
/// Utility for cleanCompletePath() and cleanRelativePath().
std::string composePath(const std::vector<std::string> & parts)
{
#if defined(WIN32) && !defined(__CYGWIN__)
const char delimiterNative = osgDB::WINDOWS_PATH_SEPARATOR;
#else
const char delimiterNative = osgDB::UNIX_PATH_SEPARATOR;
#endif
std::string res;
std::vector<std::string>::const_iterator it=parts.begin(),
itEnd=parts.end();
if (it!=itEnd) {
res += *it;
++it;
}
for(; it!=itEnd; ++it)
{
res += delimiterNative;
res += *it;
}
return res;
}
/// Simply removes "." and ".." in complete paths, with no further processing.
/// The function is supposed to be fed with correct paths, an will issue a
warning for paths such as "/a/../../b". However the function will not fail and
will return the most probable path (here "b" in the example).
std::string cleanCompletePath(const std::string& path)
{
std::vector<std::string> parts;
bool warnIssued(false);
for(PathIterator it(path); it.valid(); ++it)
{
if (*it == ".") continue;
if (*it == "..") {
if (parts.empty())
{
if (!warnIssued)
{
OSG_WARN << "cleanCompletePath() encountered ill-formatted
path: '" << path << "'" << std::endl;
warnIssued = true;
}
//return path; // Don't! Try to continue...
}
else parts.pop_back();
continue;
}
parts.push_back(*it);
}
const std::string root( getPathRoot(path) );
// Iterator doesn't process Unix-like root ('/'), so we must add it if
necessary.
return (root.length()<2 ? root : "") + composePath(parts);
}
/// Simply removes "." and ".." in relative paths, with no further processing.
/// The function will keep meaningful "..". For instance, "a/../../b" will
become "../b".
std::string cleanRelativePath(const std::string& path)
{
std::vector<std::string> parts;
for(PathIterator it(path); it.valid(); ++it)
{
if (*it == ".") continue;
if (*it == "..") {
if (parts.empty()) parts.push_back("..");
else parts.pop_back();
continue;
}
parts.push_back(*it);
}
return composePath(parts);
}
#if defined(WIN32) && !defined(__CYGWIN__)
// Sukender: I don't know why, but GetFullPathNameW seem to have a faulty
implementation which corrupts the stack, whereas the ANSI version
(GetFullPathNameA) does not when UNICODE is not defined. Maybe it's the
opposite when UNICODE is defined?
// Anyway, we cannot rely on GetFullPathName() because OSG_USE_UTF8_FILENAME
and UNICODE are absolutely not the same.
// However, we must have a wide char function (at least for
OSG_USE_UTF8_FILENAME) or else we'll loose characters which are not in the
system's codepage (Ex: Chinese on a Latin1 system).
// So here is a home-made implementation which does the same as the Windows
API, without access violation(!):
// "GetFullPathName merges the name of the current drive and directory with
a specified file name to determine the full path"
/// Replacement for Win32 API functions GetFullPathNameA() and
GetFullPathNameW(), which uses OSG-style filenames.
/// Merges the name of the current drive and directory with a specified file
name to determine the full path.
std::string getFullPathName(const std::string & path)
{
// 3 cases:
// 1. path is absolute ("C:/abc")
if (path.size()>=2 && path[1]==':')
{
return cleanCompletePath(path);
}
// 2. path is relative to drive ("/abc")
if (path.size()>=1 && (path[0]=='/' || path[0]=='\\'))
{
char currentDrive = 'A' + _getdrive()-1; // 1=A...
return cleanCompletePath(std::string(1, currentDrive) + ":" + path);
}
// 3. path is relative (or empty)
wchar_t * buffer = _wgetcwd(NULL, 0);
#ifdef OSG_USE_UTF8_FILENAME
std::string curPath(convertUTF16toUTF8(buffer));
#else
std::string
curPath(convertStringFromUTF8toCurrentCodePage(convertUTF16toUTF8(buffer)));
#endif
free(buffer);
return cleanCompletePath(concatPaths(curPath, path));
}
#endif
// Copied from FileUtils.cpp
// MAY NEED MOVING THESE DEFINITIONS IN A HEADER?
#ifdef OSG_USE_UTF8_FILENAME
#define OSGDB_STRING_TO_FILENAME(s) osgDB::convertUTF8toUTF16(s)
#define OSGDB_FILENAME_TO_STRING(s) osgDB::convertUTF16toUTF8(s)
#define OSGDB_FILENAME_TEXT(x) L ## x
#define OSGDB_WINDOWS_FUNCT(x) x ## W
#define OSGDB_WINDOWS_FUNCT_STRING(x) #x "W"
typedef wchar_t filenamechar;
typedef std::wstring filenamestring;
#else
#define OSGDB_STRING_TO_FILENAME(s) s
#define OSGDB_FILENAME_TO_STRING(s) s
#define OSGDB_FILENAME_TEXT(x) x
#define OSGDB_WINDOWS_FUNCT(x) x ## A
#define OSGDB_WINDOWS_FUNCT_STRING(x) #x "A"
typedef char filenamechar;
typedef std::string filenamestring;
#endif
} // namespace osgDB
std::string osgDB::getRealPath(const std::string& path)
{
#if defined(WIN32) && !defined(__CYGWIN__)
//filenamestring pathStr( OSGDB_STRING_TO_FILENAME(_path) );
//filenamechar retbuf[MAX_PATH + 1];
filenamechar tempbuf1[MAX_PATH + 1];
filenamechar tempbuf2[MAX_PATH + 1];
std::string competePath = osgDB::getFullPathName(path);
// Force drive letter to upper case
if (competePath.size()>=2 && competePath[1] == ':' &&
islower(competePath[0]))
competePath[0] = _toupper(competePath[0]);
if (fileExists(competePath))
{
// Canonicalise the full path
OSGDB_WINDOWS_FUNCT(GetShortPathName)(OSGDB_STRING_TO_FILENAME(competePath).c_str(),
tempbuf1, sizeof(tempbuf1));
OSGDB_WINDOWS_FUNCT(GetLongPathName)(tempbuf1, tempbuf2,
sizeof(tempbuf2));
return OSGDB_FILENAME_TO_STRING(tempbuf2);
}
else
{
// Canonicalise the directories
filenamestring filePathW(
OSGDB_STRING_TO_FILENAME(getFilePath(competePath)) );
if (0 == OSGDB_WINDOWS_FUNCT(GetShortPathName)(filePathW.c_str(),
tempbuf1, sizeof(tempbuf1)))
return competePath;
if (0 == OSGDB_WINDOWS_FUNCT(GetLongPathName)(tempbuf1, tempbuf2,
sizeof(tempbuf2)))
return competePath;
std::string filePath( OSGDB_FILENAME_TO_STRING(tempbuf2) );
filePath += WINDOWS_PATH_SEPARATOR;
filePath.append(getSimpleFileName(competePath));
return filePath;
}
#else
char resolved_path[PATH_MAX];
char* result = realpath(path.c_str(), resolved_path);
if (result) return std::string(resolved_path);
else return path;
#endif
}
std::string osgDB::getPathRelative(const std::string& from, const std::string&
to)
{
// This implementation is not 100% robust, and should be replaced with
C++0x "std::path" as soon as possible.
// Definition: an "element" is a part between slashes. Ex: "/a/b" has two
elements ("a" and "b").
// Algorithm:
// 1. If paths are neither both absolute nor both relative, then we cannot
do anything (we need to make them absolute, but need additionnal info on how to
make it). Return.
// 2. If both paths are absolute and root isn't the same (for Windows only,
as roots are of the type "C:", "D:"), then the operation is impossible. Return.
// 3. Iterate over two paths elements until elements are equal
// 4. For each remaining element in "from", add ".." to result
// 5. For each remaining element in "to", add this element to result
// 1 & 2
const std::string root = getPathRoot(from);
if (root != getPathRoot(to)) {
OSG_INFO << "Cannot relativise paths. From=" << from << ", To=" << to
<< ". Returning 'to' unchanged." << std::endl;
//return to;
return osgDB::getSimpleFileName(to);
}
// 3
PathIterator itFrom(from), itTo(to);
// Iterators may point to Windows roots. As we tested they are equal, there
is no need to ++itFrom and ++itTo.
// However, if we got an Unix root, we must add it to the result.
std::string res(root.length()<2 ? root : "");
for(; itFrom.valid() && itTo.valid() && *itFrom==*itTo; ++itFrom, ++itTo) {}
// 4
for(; itFrom.valid(); ++itFrom) res += "../";
// 5
for(; itTo.valid(); ++itTo) res += *itTo + "/";
// Remove trailing slash before returning
if (!res.empty() && std::find_first_of(res.rbegin(), res.rbegin()+1,
PATH_SEPARATORS, PATH_SEPARATORS+PATH_SEPARATORS_LEN) != res.rbegin()+1)
{
return res.substr(0, res.length()-1);
}
return res;
}
//using namespace osgDB;
//std::string testA = getPathRelative("C:\\a\\b", "C:\\a/b/d/f"); // d/f
//std::string testB = getPathRelative("C:\\a\\d", "C:\\a/b/d/f"); //
../b/d/f
//std::string testC = getPathRelative("C:\\ab", "C:\\a/b/d/f"); //
../a/b/d/f
//std::string testD = getPathRelative("a/d", "a/d"); // ""
//std::string testE = getPathRelative("a", "a/d"); // ../d
//std::string testF = getPathRelative("C:/a/b", "a/d"); //
Precondition fail. Returns d.
//std::string testG = getPathRelative("/a/b", "a/d"); //
Precondition fail. Returns d.
//std::string testH = getPathRelative("a/b", "/a/d"); //
Precondition fail. Returns d.
_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org