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

Reply via email to