/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "stdafx.h" #include "PathHelpers.h" #include "StringHelpers.h" #include "Util.h" #include #include #include #include #include // Returns position of last extension in last name (string::npos if not found) // note: returns string::npos for names starting from '.' and having no // '.' later (for example 'aaa/.ccc', 'a:.abc', '.rc') template static inline size_t findExtensionPosition_Tpl(const TS& path) { const size_t dotPos = path.rfind('.'); if (dotPos == TS::npos) { return TS::npos; } static const typename TS::value_type separators[] = { '\\', '/', ':', 0 }; const size_t separatorPos = path.find_last_of(separators); if (separatorPos != TS::npos) { if (separatorPos + 1 >= dotPos) { return TS::npos; } } else if (dotPos == 0) { return TS::npos; } return dotPos + 1; } static size_t findExtensionPosition(const string& path) { return findExtensionPosition_Tpl(path); } static size_t findExtensionPosition(const wstring& path) { return findExtensionPosition_Tpl(path); } string PathHelpers::FindExtension(const string& path) { const size_t extPos = findExtensionPosition(path); return (extPos == string::npos) ? string() : path.substr(extPos, string::npos); } wstring PathHelpers::FindExtension(const wstring& path) { const size_t extPos = findExtensionPosition(path); return (extPos == wstring::npos) ? wstring() : path.substr(extPos, wstring::npos); } template static inline TS ReplaceExtension_Tpl(const TS& path, const TS& newExtension) { if (path.empty()) { return TS(); } if (newExtension.empty()) { return PathHelpers::RemoveExtension(path); } const typename TS::value_type last = path[path.length() - 1]; if ((last == '\\') || (last == '/') || (last == ':') || (last == '.')) { return path; } const size_t extPos = findExtensionPosition(path); static const typename TS::value_type dot[] = { '.', 0 }; return ((extPos == TS::npos) ? path + dot : path.substr(0, extPos)) + newExtension; } string PathHelpers::ReplaceExtension(const string& path, const string& newExtension) { return ReplaceExtension_Tpl(path, newExtension); } wstring PathHelpers::ReplaceExtension(const wstring& path, const wstring& newExtension) { return ReplaceExtension_Tpl(path, newExtension); } string PathHelpers::RemoveExtension(const string& path) { const size_t extPos = findExtensionPosition(path); return (extPos == string::npos) ? path : path.substr(0, extPos - 1); } wstring PathHelpers::RemoveExtension(const wstring& path) { const size_t extPos = findExtensionPosition(path); return (extPos == wstring::npos) ? path : path.substr(0, extPos - 1); } template static inline TS GetDirectory_Tpl(const TS& path) { static const typename TS::value_type separators[] = { '/', '\\', ':', 0 }; const size_t pos = path.find_last_of(separators); if (pos == TS::npos) { return TS(); } if (path[pos] == ':' || pos == 0 || path[pos - 1] == ':') { return path.substr(0, pos + 1); } // Handle paths like "\\machine" if (pos == 1 && (path[0] == '/' || path[0] == '\\')) { return path; } return path.substr(0, pos); } string PathHelpers::GetDirectory(const string& path) { return GetDirectory_Tpl(path); } wstring PathHelpers::GetDirectory(const wstring& path) { return GetDirectory_Tpl(path); } template static inline TS GetFilename_Tpl(const TS& path) { static const typename TS::value_type separators[] = { '/', '\\', ':', 0 }; const size_t pos = path.find_last_of(separators); if (pos == TS::npos) { return path; } // Handle paths like "\\machine" if (pos == 1 && (path[0] == '/' || path[0] == '\\')) { return TS(); } return path.substr(pos + 1, TS::npos); } string PathHelpers::GetFilename(const string& path) { return GetFilename_Tpl(path); } wstring PathHelpers::GetFilename(const wstring& path) { return GetFilename_Tpl(path); } template static inline TS AddSeparator_Tpl(const TS& path) { if (path.empty()) { return TS(); } const typename TS::value_type last = path[path.length() - 1]; if (last == '/' || last == '\\' || last == ':') { return path; } #if defined(AZ_PLATFORM_WINDOWS) static const typename TS::value_type separator[] = { '\\', 0 }; #else static const typename TS::value_type separator[] = { '/', 0 }; #endif return path + separator; } string PathHelpers::AddSeparator(const string& path) { return AddSeparator_Tpl(path); } wstring PathHelpers::AddSeparator(const wstring& path) { return AddSeparator_Tpl(path); } template static inline TS RemoveSeparator_Tpl(const TS& path) { if (path.empty()) { return TS(); } const typename TS::value_type last = path[path.length() - 1]; if ((last == '/' || last == '\\') && path.length() > 1 && path[path.length() - 2] != ':') { return path.substr(0, path.length() - 1); } return path; } string PathHelpers::RemoveSeparator(const string& path) { return RemoveSeparator_Tpl(path); } wstring PathHelpers::RemoveSeparator(const wstring& path) { return RemoveSeparator_Tpl(path); } template static inline TS RemoveDuplicateSeparators_Tpl(const TS& path) { if (path.length() <= 1) { return path; } TS ret; ret.reserve(path.length()); const typename TS::value_type* p = path.c_str(); // We start from the second char just to avoid damaging UNC paths with double backslash at the beginning (e.g. "\\Server04\file.txt") ret += *p++; while (*p) { ret += *p++; if (p[-1] == '\\' || p[-1] == '/') { while (*p == '\\' || *p == '/') { ++p; } } } return ret; } string PathHelpers::RemoveDuplicateSeparators(const string& path) { return RemoveDuplicateSeparators_Tpl(path); } wstring PathHelpers::RemoveDuplicateSeparators(const wstring& path) { return RemoveDuplicateSeparators_Tpl(path); } template static inline TS Join_Tpl(const TS& path1, const TS& path2) { if (path1.empty()) { return path2; } if (path2.empty()) { return path1; } if (!PathHelpers::IsRelative(path2)) { assert(0 && "Join(): path2 is not relative"); return TS(); } const typename TS::value_type last = path1[path1.length() - 1]; if (last == '/' || last == '\\' || last == ':') { return path1 + path2; } #if defined(AZ_PLATFORM_WINDOWS) static const typename TS::value_type separator[] = { '\\', 0 }; #else static const typename TS::value_type separator[] = { '/', 0 }; #endif return path1 + separator + path2; } string PathHelpers::Join(const string& path1, const string& path2) { return Join_Tpl(path1, path2); } wstring PathHelpers::Join(const wstring& path1, const wstring& path2) { return Join_Tpl(path1, path2); } template static inline bool IsRelative_Tpl(const TS& path) { if (path.empty()) { return true; } return path[0] != '/' && path[0] != '\\' && path.find(':') == TS::npos; } bool PathHelpers::IsRelative(const string& path) { return IsRelative_Tpl(path); } bool PathHelpers::IsRelative(const wstring& path) { return IsRelative_Tpl(path); } string PathHelpers::ToUnixPath(const string& path) { return StringHelpers::Replace(path, '\\', '/'); } wstring PathHelpers::ToUnixPath(const wstring& path) { wstring s(path); std::replace(s.begin(), s.end(), L'\\', L'/'); return s; } string PathHelpers::ToDosPath(const string& path) { return StringHelpers::Replace(path, '/', '\\'); } wstring PathHelpers::ToDosPath(const wstring& path) { wstring s(path); std::replace(s.begin(), s.end(), L'/', L'\\'); return s; } string PathHelpers::ToPlatformPath(const string& path) { #if defined(AZ_PLATFORM_WINDOWS) return ToDosPath(path); #else return ToUnixPath(path); #endif } wstring PathHelpers::ToPlatformPath(const wstring& path) { #if defined(AZ_PLATFORM_WINDOWS) return ToDosPath(path); #else return ToUnixPath(path); #endif } string PathHelpers::GetAsciiPath(const char* pPath) { AZStd::wstring wstr; AZStd::to_wstring(wstr, pPath); return GetAsciiPath(wstr.c_str()); } string PathHelpers::GetAsciiPath(const wchar_t* pPath) { if (!pPath[0]) { return string(); } wstring w = ToPlatformPath(RemoveSeparator(wstring(pPath))); if (StringHelpers::Utf16ContainsAsciiOnly(w.c_str())) { return StringHelpers::ConvertAsciiUtf16ToAscii(w.c_str()); } // The path is non-ASCII, so let's resort to using short // filenames where needed (short names are always ASCII-only) // Long names components std::vector p0; StringHelpers::Split(w, wstring(L"\\"), true, p0); // find last component that is not in ASCII char set int lastNonAscii; for (lastNonAscii = (int)p0.size() - 1; lastNonAscii >= 0; --lastNonAscii) { if (!StringHelpers::Utf16ContainsAsciiOnly(p0[lastNonAscii].c_str())) { break; } } assert(lastNonAscii >= 0); string res; res.reserve(w.length()); w.clear(); for (int i = 0; i <= lastNonAscii; ++i) { w.append(p0[i]); if (i < lastNonAscii) { w.push_back('\\'); } } enum { kBufferLen = AZ_MAX_PATH_LEN }; wchar_t bufferWchars[kBufferLen]; #if defined(AZ_PLATFORM_WINDOWS) const int charCount = GetShortPathNameW(w.c_str(), bufferWchars, kBufferLen); #else const int charCount = w.length(); wcsncpy(bufferWchars, w.c_str(), kBufferLen); #endif if (charCount <= 0 || charCount >= kBufferLen) { return string(); } #if defined(AZ_PLATFORM_WINDOWS) // Paranoid if (!StringHelpers::Utf16ContainsAsciiOnly(bufferWchars)) { assert(0); return string(); } #endif // Short names components std::vector p1; StringHelpers::Split(wstring(bufferWchars), wstring(L"\\"), true, p1); for (size_t i = 0; i < (int)p0.size(); ++i) { if (!p0[i].empty()) { const wstring& p = (i > lastNonAscii || StringHelpers::Utf16ContainsAsciiOnly(p0[i].c_str())) ? p0[i] : p1[i]; res.append(StringHelpers::ConvertAsciiUtf16ToAscii(p.c_str())); } if (i + 1 < (int)p0.size()) { res.push_back('\\'); } } return res; } string PathHelpers::GetAbsoluteAsciiPath(const char* pPath) { char fullPath[AZ_MAX_PATH_LEN]; AZ::IO::LocalFileIO localFileIO; AZStd::string normalizedPath(pPath); AzFramework::StringFunc::Path::Normalize(normalizedPath); localFileIO.ConvertToAbsolutePath(normalizedPath.c_str(), fullPath, AZ_MAX_PATH_LEN); fullPath[sizeof(fullPath) - 1] = '\0'; AZStd::wstring wstr; AZStd::to_wstring(wstr, fullPath); return GetAsciiPath(wstr.c_str()); } string PathHelpers::GetAbsoluteAsciiPath(const wchar_t* pPath) { AZStd::string str; AZStd::to_string(str, pPath); AzFramework::StringFunc::Path::Normalize(str); char fullPath[AZ_MAX_PATH_LEN]; AZ::IO::LocalFileIO localFileIO; localFileIO.ConvertToAbsolutePath(str.c_str(), fullPath, AZ_MAX_PATH_LEN); fullPath[sizeof(fullPath) - 1] = '\0'; AZStd::wstring wstr; AZStd::to_wstring(wstr, fullPath); return GetAsciiPath(wstr.c_str()); } string PathHelpers::GetShortestRelativeAsciiPath(const string& baseFolder, const string& dependentPath) { const string d = GetAbsoluteAsciiPath(dependentPath.c_str()); if (d.empty()) { return PathHelpers::CanonicalizePath(dependentPath); } const string b = GetAbsoluteAsciiPath(baseFolder.c_str()); if (b.empty()) { return PathHelpers::CanonicalizePath(dependentPath); } const string b2 = AddSeparator(b); if (StringHelpers::StartsWithIgnoreCase(d, b2)) { const size_t len = d.length() - b2.length(); // note: len == 0 is possible in case of "C:\" and "C:\". return (len == 0) ? string(".") : d.substr(b2.length(), len); } std::vector p0; StringHelpers::Split(b2, string("\\"), true, p0); std::vector p1; StringHelpers::Split(d, string("\\"), true, p1); if (!StringHelpers::EqualsIgnoreCase(p0[0], p1[0])) { // got different drive letters return PathHelpers::CanonicalizePath(dependentPath); } if (StringHelpers::EqualsIgnoreCase(d, b)) { // exactly same path return string("."); } // Search for first non-matching component for (int i = 1; i < (int)p0.size(); ++i) { if (StringHelpers::EqualsIgnoreCase(p0[i], p1[i])) { continue; } string s; s.reserve(Util::getMax(d.length(), b.length())); for (int j = i; j < (int)p0.size(); ++j) { if (!p0[j].empty()) { s.append("..\\"); } } for (int j = i; j < (int)p1.size(); ++j) { s.append(p1[j]); if (j + 1 < (int)p1.size()) { s.push_back('\\'); } } return s; } assert(0); return string(); } string PathHelpers::CanonicalizePath(const string& path) { string result = RemoveSeparator(path); // remove .\ or ./ at the path beginning. if (result.length() > 2) { if (result[0] == '.' && (result[1] == '\\' || result[1] == '/')) { result = result.substr(2); } } return result; }