OSDN Git Service

Fix untranslated strings
[winmerge-jp/winmerge-jp.git] / Src / paths.cpp
1 /** 
2  * @file  paths.cpp
3  *
4  * @brief Path handling routines
5  */
6
7 #include "paths.h"
8 #include <windows.h>
9 #include <cassert>
10 #include <cstring>
11 #include <direct.h>
12 #include <mbctype.h> // MBCS (multibyte codepage stuff)
13 #include <shlobj.h>
14 #include <shlwapi.h>
15 #include "PathContext.h"
16 #include "coretools.h"
17
18 namespace paths
19 {
20
21 static bool IsSlash(const String& pszStart, size_t nPos);
22 static bool GetDirName(const String& sDir, String& sName);
23
24 /** 
25  * @brief Checks if char in string is slash.
26  * @param [in] pszStart String to check.
27  * @param [in] nPos of char in string to check (0-based index).
28  * @return true if char is slash.
29  */
30 static bool IsSlash(const String& pszStart, size_t nPos)
31 {
32         return pszStart[nPos]=='/' || 
33 #ifdef _UNICODE
34                pszStart[nPos]=='\\';
35 #else
36                 // Avoid 0x5C (ASCII backslash) byte occurring as trail byte in MBCS
37                (pszStart[nPos]=='\\' && !_ismbstrail((unsigned char *)pszStart.c_str(), (unsigned char *)pszStart.c_str() + nPos));
38 #endif
39 }
40
41 /** 
42  * @brief Checks if string ends with slash.
43  * This function checks if given string ends with slash. In many places,
44  * especially in GUI, we assume folder paths end with slash.
45  * @param [in] s String to check.
46  * @return true if last char in string is slash.
47  */
48 bool EndsWithSlash(const String& s)
49 {
50         if (size_t len = s.length())
51                 return IsSlash(s, (int)len - 1);
52         return false;
53 }
54
55 /** 
56  * @brief Checks if path exists and if it points to folder or file.
57  * This function first checks if path exists. If path exists
58  * then function checks if path points to folder or file.
59  * @param [in] szPath Path to check.
60  * @return One of:
61  * - DOES_NOT_EXIST : path does not exists
62  * - IS_EXISTING_DIR : path points to existing folder
63  * - IS_EXISTING_FILE : path points to existing file
64  */
65 PATH_EXISTENCE DoesPathExist(const String& szPath, bool (*IsArchiveFile)(const String&))
66 {
67         if (szPath.empty())
68                 return DOES_NOT_EXIST;
69
70         // Expand environment variables:
71         // Convert "%userprofile%\My Documents" to "C:\Documents and Settings\username\My Documents"
72         const TCHAR *lpcszPath = szPath.c_str();
73         TCHAR expandedPath[_MAX_PATH];
74
75         if (_tcschr(lpcszPath, '%'))
76         {
77                 DWORD dwLen = ExpandEnvironmentStrings(lpcszPath, expandedPath, _MAX_PATH);
78                 if (dwLen > 0 && dwLen < _MAX_PATH)
79                         lpcszPath = expandedPath;
80         }
81
82         DWORD attr = GetFileAttributes(lpcszPath);
83
84         if (attr == ((DWORD) -1))
85         {
86                 if (IsArchiveFile && IsArchiveFile(szPath))
87                         return IS_EXISTING_DIR;
88                 return DOES_NOT_EXIST;
89         }
90         else if (attr & FILE_ATTRIBUTE_DIRECTORY)
91                 return IS_EXISTING_DIR;
92         else
93         {
94                 if (IsArchiveFile && IsArchiveFile(szPath))
95                         return IS_EXISTING_DIR;
96                 return IS_EXISTING_FILE;
97         }
98 }
99
100 /**
101  * @brief Like shlwapi's PathFindFileName(), but works with both \ and /.
102  * @param [in] Path
103  * @return Filename
104  */
105 String FindFileName(const String& path)
106 {
107         const TCHAR *filename = path.c_str();
108         while (const TCHAR *slash = _tcspbrk(filename, _T("\\/")))
109         {
110                 if (*(slash + 1) == '\0')
111                         break;
112                 filename = slash + 1;
113         }
114         return filename;
115 }
116
117 /**
118  * @brief Like shlwapi's PathFindFileName(), but works with both \ and /.
119  * @param [in] Path
120  * @return Filename
121  */
122 String FindExtension(const String& path)
123 {
124         return ::PathFindExtension(path.c_str());
125 }
126
127 /** 
128  * @brief Strip trailing slas.
129  * This function strips trailing slas from given path. Root paths are special
130  * case and they are left intact. Since C:\ is a valid path but C: is not.
131  * @param [in,out] sPath Path to strip.
132  */
133 void normalize(String & sPath)
134 {
135         size_t len = sPath.length();
136         if (!len)
137                 return;
138
139         // prefix root with current drive
140         sPath = GetLongPath(sPath);
141
142         // Do not remove trailing slash from root directories
143         if (len == 3 && sPath[1] == ':')
144                 return;
145
146         // remove any trailing slash
147         if (EndsWithSlash(sPath))
148                 sPath.resize(sPath.length() - 1);
149 }
150
151 /**
152  * @brief Get canonical name of folder.
153  * @param [in] sDir Folder to handle.
154  * @param [out] sName Canonicalized folder name.
155  * @return true if canonical name exists.
156  * @todo Should we return empty string as sName when returning false?
157  */
158 static bool GetDirName(const String& sDir, String& sName)
159 {
160         // FindFirstFile doesn't work for root:
161         // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/findfirstfile.asp
162         // You cannot use root directories as the lpFileName input string for FindFirstFile\97with or without a trailing backslash.
163         if (sDir[0] && sDir[1] == ':' && sDir[2] == '\0')
164         {
165                 // I don't know if this work for empty root directories
166                 // because my first return value is not a dot directory, as I would have expected
167                 WIN32_FIND_DATA ffd;
168                 TCHAR sPath[8];
169                 StringCchPrintf(sPath, sizeof(sPath)/sizeof(sPath[0]), _T("%s\\*"), sDir.c_str());
170                 HANDLE h = FindFirstFile(sPath, &ffd);
171                 if (h == INVALID_HANDLE_VALUE)
172                         return false;
173                 FindClose(h);
174                 sName = sDir;
175                 return true;
176         }
177         // (Couldn't get info for just the directory from CFindFile)
178         WIN32_FIND_DATA ffd;
179         HANDLE h = FindFirstFile(sDir.c_str(), &ffd);
180         if (h == INVALID_HANDLE_VALUE)
181                 return false;
182         sName = ffd.cFileName;
183         FindClose(h);
184         return true;
185 }
186
187 /**
188  * Convert path to canonical long path.
189  * This function converts given path to canonical long form. For example
190  * foldenames with ~ short names are expanded. Also environment strings are
191  * expanded if @p bExpandEnvs is true. If path does not exist, make canonical
192  * the part that does exist, and leave the rest as is. Result, if a directory,
193  * usually does not have a trailing backslash.
194  * @param [in] sPath Path to convert.
195  * @param [in] bExpandEnvs If true environment variables are expanded.
196  * @return Converted path.
197  */
198 String GetLongPath(const String& szPath, bool bExpandEnvs)
199 {
200         String sPath = szPath;
201         size_t len = sPath.length();
202         if (len < 1)
203                 return sPath;
204
205         TCHAR fullPath[_MAX_PATH] = {0};
206         TCHAR *lpPart;
207
208         //                                         GetFullPathName  GetLongPathName
209         // Convert to fully qualified form              Yes               No
210         //    (Including .)
211         // Convert /, //, \/, ... to \                  Yes               No
212         // Handle ., .., ..\..\..                       Yes               No
213         // Convert 8.3 names to long names              No                Yes
214         // Fail when file/directory does not exist      No                Yes
215         //
216         // Fully qualify/normalize name using GetFullPathName.
217
218         // Expand environment variables:
219         // Convert "%userprofile%\My Documents" to "C:\Documents and Settings\username\My Documents"
220         TCHAR expandedPath[_MAX_PATH];
221         const TCHAR *lpcszPath = sPath.c_str();
222         if (bExpandEnvs && _tcschr(lpcszPath, '%'))
223         {
224                 DWORD dwLen = ExpandEnvironmentStrings(lpcszPath, expandedPath, _MAX_PATH);
225                 if (dwLen > 0 && dwLen < _MAX_PATH)
226                         lpcszPath = expandedPath;
227         }
228
229         DWORD dwLen = GetFullPathName(lpcszPath, _MAX_PATH, fullPath, &lpPart);
230         if (dwLen == 0 || dwLen >= _MAX_PATH)
231                 _tcscpy_safe(fullPath, lpcszPath);
232
233         // We are done if this is not a short name.
234         if (_tcschr(fullPath, _T('~')) == NULL)
235                 return fullPath;
236
237         // We have to do it the hard way because GetLongPathName is not
238         // available on Win9x and some WinNT 4
239
240         // The file/directory does not exist, use as much long name as we can
241         // and leave the invalid stuff at the end.
242         String sLong;
243         TCHAR *ptr = fullPath;
244         TCHAR *end = NULL;
245
246         // Skip to \ position     d:\abcd or \\host\share\abcd
247         // indicated by ^           ^                    ^
248         if (_tcslen(ptr) > 2)
249                 end = _tcschr(fullPath+2, _T('\\'));
250         if (end && !_tcsncmp(fullPath, _T("\\\\"),2))
251                 end = _tcschr(end+1, _T('\\'));
252
253         if (!end)
254                 return fullPath;
255
256         *end = 0;
257         sLong += ptr;
258         ptr = &end[1];
259
260         // now walk down each directory and do short to long name conversion
261         while (ptr)
262         {
263                 end = _tcschr(ptr, '\\');
264                 // zero-terminate current component
265                 // (if we're at end, its already zero-terminated)
266                 if (end)
267                         *end = 0;
268
269                 String sTemp(sLong);
270                 sTemp += '\\';
271                 sTemp += ptr;
272
273                 // advance to next component (or set ptr==0 to flag end)
274                 ptr = (end ? end+1 : 0);
275
276                 // (Couldn't get info for just the directory from CFindFile)
277                 WIN32_FIND_DATA ffd;
278                 HANDLE h = FindFirstFile(sTemp.c_str(), &ffd);
279                 if (h == INVALID_HANDLE_VALUE)
280                 {
281                         sLong = sTemp;
282                         if (ptr)
283                         {
284                                 sLong += '\\';
285                                 sLong += ptr;
286                         }
287                         return sLong;
288                 }
289                 sLong += '\\';
290                 sLong += ffd.cFileName;
291                 FindClose(h);
292         }
293         return sLong;
294 }
295
296 /**
297  * @brief Check if the path exist and create the folder if needed.
298  * This function checks if path exists. If path does not yet exist
299  * function created needed folder structure. So this function is the
300  * easy way to create a needed folder structure. Environment strings are
301  * expanded when handling paths.
302  * @param [in] sPath Path to check/create.
303  * @return true if path exists or if we successfully created it.
304  */
305 bool CreateIfNeeded(const String& szPath)
306 {
307         if (szPath.empty())
308                 return false;
309
310         String sTemp;
311         if (GetDirName(szPath, sTemp))
312                 return true;
313
314         if (szPath.length() >= _MAX_PATH)
315                 return false;
316
317         // Expand environment variables:
318         // Convert "%userprofile%\My Documents" to "C:\Documents and Settings\username\My Documents"
319         TCHAR fullPath[_MAX_PATH];
320         if (_tcschr(szPath.c_str(), '%'))
321         {
322                 DWORD dwLen = ExpandEnvironmentStrings(szPath.c_str(), fullPath, _MAX_PATH);
323                 if (dwLen == 0 || dwLen >= _MAX_PATH)
324                         _tcscpy_safe(fullPath, szPath.c_str());
325         }
326         else
327                 _tcscpy_safe(fullPath, szPath.c_str());
328         // Now fullPath holds our desired path
329
330         TCHAR *ptr = fullPath;
331         TCHAR *end = NULL;
332
333         // Skip to \ position     d:\abcd or \\host\share\abcd
334         // indicated by ^           ^                    ^
335         if (_tcslen(ptr) > 2)
336                 end = _tcschr(fullPath+2, _T('\\'));
337         if (end && !_tcsncmp(fullPath, _T("\\\\"),2))
338                 end = _tcschr(end+1, _T('\\'));
339
340         if (!end) return false;
341
342         // check that first component exists
343         *end = 0;
344         if (!GetDirName(fullPath, sTemp))
345                 return false;
346         *end = '\\';
347
348         ptr = end+1;
349
350         while (ptr)
351         {
352                 end = _tcschr(ptr, '\\');
353                 // zero-terminate current component
354                 // (if we're at end, its already zero-terminated)
355                 if (end)
356                         *end = 0;
357
358                 // advance to next component (or set ptr==0 to flag end)
359                 ptr = (end ? end+1 : 0);
360
361                 String sNextName;
362                 if (!GetDirName(fullPath, sNextName))
363                 {
364                         // try to create directory, and then double-check its existence
365                         if (!CreateDirectory(fullPath, 0) ||
366                                 !GetDirName(fullPath, sNextName))
367                         {
368                                 return false;
369                         }
370                 }
371                 // if not finished, restore directory string we're working in
372                 if (ptr)
373                         *end = '\\';
374         }
375         return true;
376 }
377
378 /** 
379  * @brief Check if paths are both folders or files.
380  * This function checks if paths are "compatible" as in many places we need
381  * to have two folders or two files.
382  * @param [in] paths Left and right paths.
383  * @return One of:
384  *  - IS_EXISTING_DIR : both are directories & exist
385  *  - IS_EXISTING_FILE : both are files & exist
386  *  - DOES_NOT_EXIST : in all other cases
387 */
388 PATH_EXISTENCE GetPairComparability(const PathContext & paths, bool (*IsArchiveFile)(const String&))
389 {
390         // fail if not both specified
391         if (paths.GetSize() < 2 || paths[0].empty() || paths[1].empty())
392                 return DOES_NOT_EXIST;
393         PATH_EXISTENCE p1 = DoesPathExist(paths[0], IsArchiveFile);
394         // short circuit testing right if left doesn't exist
395         if (p1 == DOES_NOT_EXIST)
396                 return DOES_NOT_EXIST;
397         PATH_EXISTENCE p2 = DoesPathExist(paths[1], IsArchiveFile);
398         if (p1 != p2)
399         {
400                 p1 = DoesPathExist(paths[0]);
401                 p2 = DoesPathExist(paths[1]);
402                 if (p1 != p2)
403                         return DOES_NOT_EXIST;
404         }
405         if (paths.GetSize() < 3) return p1; 
406         PATH_EXISTENCE p3 = DoesPathExist(paths[2], IsArchiveFile);
407         if (p2 != p3)
408         {
409                 p1 = DoesPathExist(paths[0]);
410                 p2 = DoesPathExist(paths[1]);
411                 p3 = DoesPathExist(paths[2]);
412                 if (p1 != p2 || p2 != p3)
413                         return DOES_NOT_EXIST;
414         }
415         return p1;
416 }
417
418 /**
419  * @brief Check if the given path points to shotcut.
420  * Windows considers paths having a filename with extension ".lnk" as
421  * shortcuts. This function checks if the given path is shortcut.
422  * We usually want to expand shortcuts with ExpandShortcut().
423  * @param [in] inPath Path to check;
424  * @return true if the path points to shortcut, false otherwise.
425  */
426 bool IsShortcut(const String& inPath)
427 {
428         const TCHAR ShortcutExt[] = _T(".lnk");
429         TCHAR ext[_MAX_EXT] = {0};
430         _tsplitpath(inPath.c_str(), NULL, NULL, NULL, ext);
431         if (_tcsicmp(ext, ShortcutExt) == 0)
432                 return true;
433         else
434                 return false;
435 }
436
437 bool IsDirectory(const String &path)
438 {
439         return !!PathIsDirectory(path.c_str());
440 }
441
442 //////////////////////////////////////////////////////////////////
443 //      use IShellLink to expand the shortcut
444 //      returns the expanded file, or "" on error
445 //
446 //      original code was part of CShortcut 
447 //      1996 by Rob Warner
448 //      rhwarner@southeast.net
449 //      http://users.southeast.net/~rhwarner
450
451 /** 
452  * @brief Expand given shortcut to full path.
453  * @param [in] inFile Shortcut to expand.
454  * @return Full path or empty string if error happened.
455  */
456 String ExpandShortcut(const String &inFile)
457 {
458         assert(inFile != _T(""));
459
460         // No path, nothing to return
461         if (inFile == _T(""))
462                 return _T("");
463
464         String outFile;
465         IShellLink* psl;
466         HRESULT hres;
467
468         // Create instance for shell link
469         hres = ::CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
470                 IID_IShellLink, (LPVOID*) &psl);
471         if (SUCCEEDED(hres))
472         {
473                 // Get a pointer to the persist file interface
474                 IPersistFile* ppf;
475                 hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*) &ppf);
476                 if (SUCCEEDED(hres))
477                 {
478                         WCHAR wsz[MAX_PATH];
479 #ifdef _UNICODE
480                         _tcscpy_safe(wsz, inFile.c_str());
481 #else
482                         ::MultiByteToWideChar(CP_ACP, 0, inFile.c_str(), -1, wsz, MAX_PATH);
483 #endif
484
485                         // Load shortcut
486                         hres = ppf->Load(wsz, STGM_READ);
487
488                         if (SUCCEEDED(hres))
489                         {
490                                 // find the path from that
491                                 TCHAR buf[MAX_PATH] = {0};
492                                 psl->GetPath(buf, MAX_PATH, NULL, SLGP_UNCPRIORITY);
493                                 outFile = buf;
494                         }
495                         ppf->Release();
496                 }
497                 psl->Release();
498         }
499
500         // if this fails, outFile == ""
501         return outFile;
502 }
503
504 /** 
505  * @brief Append subpath to path.
506  * This function appends subpath to given path. Function ensures there
507  * is only one backslash between path parts.
508  * @param [in] path "Base" path where other part is appended.
509  * @param [in] subpath Path part to append to base part.
510  * @return Formatted path. If one of arguments is empty then returns
511  * non-empty argument. If both argumets are empty empty string is returned.
512  */
513 String ConcatPath(const String & path, const String & subpath)
514 {
515         if (path.empty())
516                 return subpath;
517         if (subpath.empty())
518                 return path;
519         if (EndsWithSlash(path))
520         {
521                 return String(path).append(subpath.c_str() + (IsSlash(subpath, 0) ? 1 : 0));
522         }
523         else
524         {
525                 if (IsSlash(subpath, 0))
526                 {
527                         return path + subpath;
528                 }
529                 else
530                 {
531                         return path + _T("\\") + subpath;
532                 }
533         }
534 }
535
536 /** 
537  * @brief Get parent path.
538  * This function returns parent path for given path. For example for
539  * path "c:\folder\subfolder" we return "c:\folder".
540  * @param [in] path Path to get parent path for.
541  * @return Parent path.
542  */
543 String GetParentPath(const String& path)
544 {
545         String parentPath(path);
546         size_t len = parentPath.length();
547
548         // Remove last '\' from paths
549         if (parentPath[len - 1] == '\\')
550         {
551                 parentPath.resize(len - 1);
552                 --len;
553         }
554
555         // Remove last part of path
556         size_t pos = parentPath.rfind('\\');
557
558         if (pos != parentPath.npos)
559         {
560                 // Do not remove trailing slash from root directories
561                 parentPath.resize(pos == 2 ? pos + 1 : pos);
562         }
563         return parentPath;
564 }
565
566 /** 
567  * @brief Get last subdirectory of path.
568  *
569  * Returns last subdirectory name (if one exists) from given path.
570  * For example:
571  * - C:\work\myproject returns \myproject
572  * @param [in] path Original path.
573  * @return Last subdirectory in path.
574  */
575 String GetLastSubdir(const String & path)
576 {
577         String parentPath(path);
578         size_t len = parentPath.length();
579
580         // Remove last '\' from paths
581         if (parentPath[len - 1] == '\\')
582         {
583                 parentPath.erase(len - 1, 1);
584                 --len;
585         }
586
587         // Find last part of path
588         size_t pos = parentPath.find_last_of('\\');
589         if (pos >= 2 && pos != String::npos)
590                 parentPath.erase(0, pos);
591         return parentPath;
592 }
593
594 /** 
595  * @brief Checks if path is an absolute path.
596  * @param [in] path Path to check.
597  * @return true if given path is absolute path.
598  */
599 bool IsPathAbsolute(const String &path)
600 {
601         if (path.length() < 3)
602                 return false;
603         
604         size_t pos = path.find_last_of('\\');
605
606         // Absolute path must have "\" and cannot start with it.
607         // Also "\\blahblah" is invalid.
608         if (pos < 2 || pos == String::npos)
609                 return false;
610
611         // Maybe "X:\blahblah"?
612         if (path[1] == ':' && path[2] == '\\')
613                 return true;
614
615         // So "\\blahblah\"?
616         if (path[0] == '\\' && path[1] == '\\' && pos > 2)
617                 return true;
618         else
619                 return false;
620 }
621
622 /**
623  * @brief Checks if folder exists and creates it if needed.
624  * This function checks if folder exists and creates it if not.
625  * @param [in] sPath
626  * @return Path if it exists or were created successfully. If
627  * path points to file or folder failed to create returns empty
628  * string.
629  */
630 String EnsurePathExist(const String & sPath)
631 {
632         int rtn = DoesPathExist(sPath);
633         if (rtn == IS_EXISTING_DIR)
634                 return sPath;
635         if (rtn == IS_EXISTING_FILE)
636                 return _T("");
637         if (!CreateIfNeeded(sPath))
638                 return _T("");
639         // Check creating folder succeeded
640         if (DoesPathExist(sPath) == IS_EXISTING_DIR)
641                 return sPath;
642         else
643                 return _T("");
644 }
645
646 /**
647  * @brief Return true if *pszChar is a slash (either direction) or a colon
648  *
649  * begin points to start of string, in case multibyte trail test is needed
650  */
651 bool IsSlashOrColon(const TCHAR *pszChar, const TCHAR *begin)
652 {
653 #ifdef _UNICODE
654                 return (*pszChar == '/' || *pszChar == ':' || *pszChar == '\\');
655 #else
656                 // Avoid 0x5C (ASCII backslash) byte occurring as trail byte in MBCS
657                 return (*pszChar == '/' || *pszChar == ':' 
658                         || (*pszChar == '\\' && !_ismbstrail((unsigned char *)begin, (unsigned char *)pszChar)));
659 #endif
660 }
661
662 /**
663  * @brief Extract path name components from given full path.
664  * @param [in] pathLeft Original path.
665  * @param [out] pPath Folder name component of full path, excluding
666    trailing slash.
667  * @param [out] pFile File name part, excluding extension.
668  * @param [out] pExt Filename extension part, excluding leading dot.
669  */
670 void SplitFilename(const String& pathLeft, String* pPath, String* pFile, String* pExt)
671 {
672         const TCHAR *pszChar = pathLeft.c_str() + pathLeft.length();
673         const TCHAR *pend = pszChar;
674         const TCHAR *extptr = 0;
675         bool ext = false;
676
677         while (pathLeft.c_str() < --pszChar)
678         {
679                 if (*pszChar == '.')
680                 {
681                         if (!ext)
682                         {
683                                 if (pExt)
684                                 {
685                                         (*pExt) = pszChar + 1;
686                                 }
687                                 ext = true; // extension is only after last period
688                                 extptr = pszChar;
689                         }
690                 }
691                 else if (IsSlashOrColon(pszChar, pathLeft.c_str()))
692                 {
693                         // Ok, found last slash, so we collect any info desired
694                         // and we're done
695
696                         if (pPath)
697                         {
698                                 // Grab directory (omit trailing slash)
699                                 size_t len = pszChar - pathLeft.c_str();
700                                 if (*pszChar == ':')
701                                         ++len; // Keep trailing colon ( eg, C:filename.txt)
702                                 *pPath = pathLeft;
703                                 pPath->erase(len); // Cut rest of path
704                         }
705
706                         if (pFile)
707                         {
708                                 // Grab file
709                                 *pFile = pszChar + 1;
710                         }
711
712                         goto endSplit;
713                 }
714         }
715
716         // Never found a delimiter
717         if (pFile)
718         {
719                 *pFile = pathLeft;
720         }
721
722 endSplit:
723         // if both filename & extension requested, remove extension from filename
724
725         if (pFile && pExt && extptr)
726         {
727                 size_t extlen = pend - extptr;
728                 pFile->erase(pFile->length() - extlen);
729         }
730 }
731
732 // Split Rational ClearCase view name (file_name@@file_version).
733 void SplitViewName(const TCHAR *s, String * path, String * name, String * ext)
734 {
735         String sViewName(s);
736         size_t nOffset = sViewName.find(_T("@@"));
737         if (nOffset != String::npos)
738         {
739                 sViewName.erase(nOffset);
740                 SplitFilename(sViewName, path, name, ext);
741         }
742 }
743
744 /**
745  * @brief Return path component from full path.
746  * @param [in] fullpath Full path to split.
747  * @return Path without filename.
748  */
749 String GetPathOnly(const String& fullpath)
750 {
751         if (fullpath.empty()) return _T("");
752         String spath;
753         SplitFilename(fullpath, &spath, 0, 0);
754         return spath;
755 }
756
757 bool IsURLorCLSID(const String& path)
758 {
759         return (path.find(_T("://")) != String::npos || path.find(_T("::{")) != String::npos);
760 }
761
762 bool IsDecendant(const String& path, const String& ancestor)
763 {
764         return path.length() > ancestor.length() && 
765                    strutils::compare_nocase(String(path.c_str(), path.c_str() + ancestor.length()), ancestor) == 0;
766 }
767
768 static void replace_char(TCHAR *s, int target, int repl)
769 {
770         TCHAR *p;
771         for (p=s; *p != _T('\0'); p = _tcsinc(p))
772                 if (*p == target)
773                         *p = (TCHAR)repl;
774 }
775
776 String ToWindowsPath(const String& path)
777 {
778         String winpath = path;
779         replace_char(&*winpath.begin(), '/', '\\');
780         return winpath;
781 }
782
783 String ToUnixPath(const String& path)
784 {
785         String unixpath = path;
786         replace_char(&*unixpath.begin(), '\\', '/');
787         return unixpath;
788 }
789
790 }