diff options
author | chai <chaifix@163.com> | 2019-08-14 22:50:43 +0800 |
---|---|---|
committer | chai <chaifix@163.com> | 2019-08-14 22:50:43 +0800 |
commit | 15740faf9fe9fe4be08965098bbf2947e096aeeb (patch) | |
tree | a730ec236656cc8cab5b13f088adfaed6bb218fb /Runtime/Utilities/PathNameUtility.cpp |
Diffstat (limited to 'Runtime/Utilities/PathNameUtility.cpp')
-rw-r--r-- | Runtime/Utilities/PathNameUtility.cpp | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/Runtime/Utilities/PathNameUtility.cpp b/Runtime/Utilities/PathNameUtility.cpp new file mode 100644 index 0000000..d168c61 --- /dev/null +++ b/Runtime/Utilities/PathNameUtility.cpp @@ -0,0 +1,601 @@ +#include "UnityPrefix.h" +#include "PathNameUtility.h" +#include "Word.h" + +#include <algorithm> + +using namespace std; + +static void AppendPathNameImpl( const string& pathName, const string& append, char separator, std::string& res ) +{ + res.reserve (pathName.size () + append.size () + 1); + if (!pathName.empty () && !append.empty ()) + { + if (pathName[pathName.size () - 1] == separator) + { + if (append[0] == separator) + { + res += pathName; + res.append (append.begin () + 1, append.end ()); + return; + } + else + { + res += pathName; + res += append; + return; + } + } + else + { + if (append[0] == separator) + { + res += pathName; + res += append; + return; + } + else + { + res += pathName; + res += separator; + res += append; + return; + } + } + } + else if (pathName.empty ()) + res = append; + else + res = pathName; +} + +string AppendPathName (const string& pathName, const string& append) +{ + string res; + AppendPathNameImpl( pathName, append, kPathNameSeparator, res ); + return res; +} + +string PlatformAppendPathName (const string& pathName, const string& append) +{ + string res; + AppendPathNameImpl( pathName, append, kPlatformPathNameSeparator, res ); + return res; +} + + +string AppendPathNameExtension (const string& pathName, const string& extension) +{ + if (extension.empty ()) + return pathName; + + string newPathName; + newPathName.reserve (pathName.size () + 1 + extension.size ()); + newPathName.append (pathName); + newPathName.append ("."); + newPathName.append (extension); + return newPathName; +} + +string GetPathNameExtension (const string& pathName) +{ + return GetPathNameExtension (pathName.c_str(), pathName.size()); +} + +const char* GetPathNameExtension (const char* path, size_t length) +{ + for (size_t i=0;i<length;i++) + { + if (path[length - i - 1] == kPathNameSeparator) + return ""; + if (path[length - i - 1] == '.') + return path + length - i; + } + return ""; +} + +string DeletePathNameExtension (const string& pathName) +{ + string::size_type slash = pathName.rfind (kPathNameSeparator); + string::size_type dot = pathName.rfind ('.'); + + if (dot != string::npos) + { + if (slash == string::npos || dot > slash) + return string (&pathName[0], dot); + } + return pathName; +} + +vector<string> FindSeparatedPathComponents (char const* constPathName, size_t size, char separator) +{ + char const* current = constPathName, *end = constPathName + size; + vector<string> components; + + while (current != end) + { + char const* pos = std::find (current, end, separator); + + if (pos != current) + components.push_back (std::string (current, pos)); + + if (pos == end) + break; + + current = pos + 1; + } + + return components; +} + +string DeleteLastPathNameComponent (const string& pathName) +{ + string::size_type p = pathName.rfind (kPathNameSeparator); + if (p == string::npos) + return string (); + else + return string (&pathName[0], p); +} + +string PlatformDeleteLastPathNameComponent (const string& pathName) +{ + string::size_type p = pathName.rfind (kPlatformPathNameSeparator); + if (p == string::npos) + return string (); + else + return string (&pathName[0], p); +} + +string GetLastPathNameComponent (const string& pathName) +{ + return GetLastPathNameComponent(pathName.c_str(), pathName.size()); +} + +const char* GetLastPathNameComponent (const char* path, size_t length) +{ + for (size_t i=0;i<length;i++) + { + if (path[length - i - 1] == kPathNameSeparator) + return path + length - i; + } + return path; +} + +string PlatformGetLastPathNameComponent(const string& pathName) +{ + string::size_type p = pathName.rfind (kPlatformPathNameSeparator); + if (p == string::npos) + return pathName; + else + return string (&pathName[1 + p], pathName.size () - 1 - p); +} + +string DeleteFirstPathNameComponent (const string& pathName) +{ + string::size_type p = pathName.find (kPathNameSeparator); + if (p == string::npos) + return string (); + else + return string (&pathName[1 + p], pathName.size () - 1 - p); +} + +string StandardizePathName (const string& pathName) +{ + if (pathName.empty ()) + return pathName; + + // Remove initial / if not a // in the beginning + if (pathName[0] == kPathNameSeparator && pathName.size () > 1 && pathName[1] != kPathNameSeparator) + return string (&pathName[1], pathName.size () - 1); + else + return pathName; +} + +bool IsPathNameVisible (const string& path) +{ + if (path.empty()) + return false; + + string::size_type p = 0; + while (p < path.size ()) + { + if (path[p] == '.') + return false; + p = path.find (kPathNameSeparator, p); + if (p == string::npos) + return true; + p++; + } + return true; +} + + +void ConvertSeparatorsToUnity( char* pathName ) +{ + while( *pathName != '\0' ) { + if( *pathName == '\\' ) + *pathName = kPathNameSeparator; + ++pathName; + } +} + +void ConvertSeparatorsToPlatform( char* pathName ) +{ + if (kPathNameSeparator != kPlatformPathNameSeparator) { + while( *pathName != '\0' ) { + if( *pathName == kPathNameSeparator ) + *pathName = kPlatformPathNameSeparator; + ++pathName; + } + } +} + +void ConvertSeparatorsToPlatform( std::string& pathName ) +{ + if (kPathNameSeparator != kPlatformPathNameSeparator) { + std::string::iterator it = pathName.begin(), itEnd = pathName.end(); + while( it != itEnd ) { + if( *it == kPathNameSeparator ) + *it = kPlatformPathNameSeparator; + ++it; + } + } +} + +#if UNITY_EDITOR +const char* invalidFilenameChars = "/?<>\\:*|\""; + +const char* GetInvalidFilenameChars () +{ + return invalidFilenameChars; +} + +bool IsValidFileNameSymbol (const char c) +{ + return std::string(invalidFilenameChars).find(c) == string::npos; +} + +bool CheckValidFileName (const std::string& name) +{ + return CheckValidFileNameDetail(name) == kFileNameValid; +} + + +std::string MakeFileNameValid (const std::string& fileName) +{ + string output = fileName; + + // Disallow space at the beginning + while (!output.empty() && output[0] == ' ') + output.erase(0, 1); + + if (output.empty()) + return output; + + // Disallow space at the end + if (output[output.size()-1] == ' ' || output[output.size()-1] == '.') + output[output.size()-1] = '_'; + + for (int i=0;i<output.size();i++) + { + if (!IsValidFileNameSymbol(output[i])) + output[i] = '_'; + } + + + if (CheckValidFileName(output)) + return output; + else + return string(); +} + + +FileNameValid CheckValidFileNameDetail (const std::string& name) +{ + // none of these can be part of file/folder name + if( name.find_first_of("/\\") != string::npos ) + return kFileNameInvalid; + + // anything up to first dot can't be: com1..com9, lpt1..lpt9, con, nul, prn + size_t dotIndex = name.find('.'); + if( dotIndex == 4 ) + { + if( name[0]=='c' && name[1]=='o' && name[2]=='m' && name[3]>='0' && name[3]<='9' ) + return kFileNameInvalid; + if( name[0]=='l' && name[1]=='p' && name[2]=='t' && name[3]>='0' && name[3]<='9' ) + return kFileNameInvalid; + } + if( dotIndex == 3 ) + { + if( name[0]=='c' && name[1]=='o' && name[2]=='n' ) + return kFileNameInvalid; + if( name[0]=='n' && name[1]=='u' && name[2]=='l' ) + return kFileNameInvalid; + if( name[0]=='p' && name[1]=='r' && name[2]=='n' ) + return kFileNameInvalid; + } + + // . on OS X means invisible file + if( dotIndex == 0 ) + return kFileNameInvalid; + + // file/folder name can't end with a dot or a space + if( !name.empty() ) + { + // http://support.microsoft.com/kb/115827 + char lastChar = name[name.size()-1]; + if( lastChar == ' ' || lastChar == '.' ) + return kFileNameInvalid; + + // File names starting with a space will not get preserved correctly when zipping + // on windows. So don't allow that. + if (name[0] == ' ') + return kFileNameNotRecommended; + } + + if( name.find_first_of(invalidFilenameChars) != string::npos ) + return kFileNameNotRecommended; + + return kFileNameValid; +} + +std::string TrimSlash(const std::string& path) +{ + if (!path.empty()) + { + const char lastChar = path[path.size() - 1]; + if (lastChar == '/' || lastChar == '\\') + return path.substr(0, path.size() - 1); + } + + return path; +} + +string NormalizeUnicode (const string& utf8, bool decompose) +{ +#if UNITY_OSX + // OS X stores Unicode file names in decomposed form. Ie † (U-Umlaut), becomes represented as two characters + // -U and ¬ (Unicode U+0308), instead of a single † (Unicode U+00DC). For proper display and consistency of + // names, they need to be converted into precomposed form. + CFStringRef cfLowercased = StringToCFString(utf8); + if (!cfLowercased) + ErrorStringMsg("Failed to convert string '%s' to CFString", utf8.c_str()); + CFMutableStringRef cf = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, cfLowercased); + CFStringNormalize(cf, decompose?kCFStringNormalizationFormD:kCFStringNormalizationFormC); + string result = CFStringToString(cf); + if (cf != NULL) + CFRelease(cf); + if (cfLowercased != NULL) + CFRelease(cfLowercased); + return result; +#else + return utf8; +#endif +} + +// Serialization does weird shit without case mangling - even if filesystem paths shouldn't +// be mangled on Linux, we apparently need them mangled in some serialization stuff anyway... +string& GetGoodPathNameForGUID (const string& pathname) +{ + static string lowercased; + + lowercased = GetGoodPathName(pathname); + +#if UNITY_LINUX + ToLowerInplace(lowercased); +#endif + + return lowercased; +} + +string& GetGoodPathName (const string& pathname) +{ + static string lowercased; + if (lowercased.capacity () < 4096) + lowercased.reserve (4096); + + if (!pathname.empty () && pathname[0] == kPathNameSeparator) + { + lowercased.clear(); + return lowercased; + } + + lowercased = pathname; + +#if UNITY_WIN + ConvertSeparatorsToUnity( lowercased ); +#endif + +#if UNITY_OSX + if (RequiresNormalization(lowercased)) + lowercased = NormalizeUnicode (lowercased, false); +#endif + +#if !UNITY_LINUX + ToLowerInplace(lowercased); +#endif + + return lowercased; +} + +#endif + + +#if !UNITY_PLUGIN +bool StartsWithPath (const std::string &s, const std::string& beginsWith) +{ + size_t beginsWithSize = beginsWith.size (); + if (s.size () < beginsWithSize) + return false; + + for (size_t i=0;i<beginsWithSize;i++) + { + if (ToLower(s[i]) != ToLower(beginsWith[i])) + return false; + } + + // Exact match or empty beginsWith + if (s.size () == beginsWithSize || beginsWith.empty()) + return true; + // Next unmatched character or last matched character is + // path separator. + else if (s[beginsWithSize] == kPathNameSeparator || + beginsWith[beginsWithSize - 1] == kPathNameSeparator) + return true; + else + return false; +} + +// Converts a hex digit to the actual value. Invalid hex characters are treated as '0' +static inline unsigned char Unhex(char c) { + if( c >= '0' && c <= '9') + return c-'0'; + if( c >= 'a' && c <= 'f') + return c-'a'+10; + if( c >= 'A' && c <= 'F') + return c-'A'+10; + return 0; +} + +// Takes a string containing path characters and URL escapes and returns a string that can be used as a file name +string EncodePath(const string& s) +{ + string result; + size_t i=0; + while( i < s.size() ) + { + size_t j=s.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ", i); + if (j == string::npos ) + { + result += ToLower(s.substr(i)); + break; + } + + if( j>i ) result += ToLower(s.substr(i,j-i)); + + if ( s[j] == '%' && j+2 < s.size() ) + { + // decode %xx URL escapes + unsigned char decoded = ToLower((char)(Unhex(s[j+1]) << 4 | Unhex(s[j+2]))); + if( (decoded >= 'a' && decoded <= 'z') || (decoded >= '0' && decoded <= '9') || decoded == ' ' ) + result += decoded; + else if (decoded == '/') + result += '-'; + else + result += Format("_%02x",(int)decoded); + i=j+3; + + } + else if (s[j] == '?') + { + // Don't include URL parameters + break; + } + else if (s[j] == '/') + { + // Convert slashes to dashes + while ( j+1 < s.size() && s[j+1] == '/' ) j++; // Collapse multiple /es into one + result += '-'; + i=j+1; + } + else + { + // Encode anything "dangerous" as _XX hex encoding + result += Format("_%02x",(int)s[j]); + i=j+1; + } + } + + return result; +} + +string DecodePath(const string& s) +{ + string result; + size_t i=0; + while( i < s.size() ) + { + size_t j=s.find("_", i); + if (j == string::npos ) + { + result += s.substr(i); + break; + } + + if( j>i ) result += s.substr (i,j-i); + + string escape = s.substr (j, 3); + unsigned int ch = ' '; + sscanf(escape.c_str(), "_%x", &ch); + + result += (unsigned char)ch; + i = j+3; + } + + return result; +} + +#endif + + + +// -------------------------------------------------------------------------- + +#if ENABLE_UNIT_TESTS + +#include "External/UnitTest++/src/UnitTest++.h" + +SUITE (PathNameUtilityTests) +{ + #if UNITY_EDITOR + TEST(RequiresNormalization) + { + CHECK(!RequiresNormalization("")); + CHECK(!RequiresNormalization("!aA09~|")); + CHECK(RequiresNormalization("\n")); + CHECK(RequiresNormalization("\t")); + CHECK(RequiresNormalization("\x80")); + CHECK(RequiresNormalization("\xCC")); + } + + TEST(CheckValidFileName) + { + CHECK_EQUAL(kFileNameNotRecommended, CheckValidFileNameDetail(" temp")); + CHECK_EQUAL(kFileNameValid, CheckValidFileNameDetail("temp")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("temp.")); + CHECK_EQUAL(kFileNameValid, CheckValidFileNameDetail("tet.png.bytes")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("test/")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("test\\")); + CHECK_EQUAL(kFileNameNotRecommended, CheckValidFileNameDetail("te:st")); + + CHECK_EQUAL(kFileNameValid, CheckValidFileNameDetail("test...test.png")); + CHECK_EQUAL(kFileNameValid, CheckValidFileNameDetail("test..test.png")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("..")); + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail("...")); + + CHECK_EQUAL(kFileNameInvalid, CheckValidFileNameDetail(GetLastPathNameComponent("Assets/\\bad.tmp"))); + + CHECK_EQUAL("test", MakeFileNameValid("test")); + CHECK_EQUAL("", MakeFileNameValid(" ")); + CHECK_EQUAL("", MakeFileNameValid(" ")); + CHECK_EQUAL("", MakeFileNameValid("..test")); + CHECK_EQUAL("test", MakeFileNameValid(" test")); + CHECK_EQUAL("test_", MakeFileNameValid(" test ")); + CHECK_EQUAL("test_", MakeFileNameValid("test.")); + CHECK_EQUAL("____", MakeFileNameValid(" /// ")); + } + #endif + + TEST(GetPathNameExtension) + { + CHECK(strcmp(GetPathNameExtension(".dll").c_str(), "dll") == 0); + CHECK(strcmp(GetPathNameExtension(".dll/boing").c_str(), "") == 0); + CHECK(strcmp(GetPathNameExtension("hello/.dlL").c_str(), "dlL") == 0); + CHECK(strcmp(GetPathNameExtension("hello/blah.grr.dll").c_str(), "dll") == 0); + CHECK(strcmp(GetPathNameExtension("hello/boing.dll").c_str(), "dll") == 0); + } + + +} // SUITE + +#endif // ENABLE_UNIT_TESTS |