459 lines
12 KiB
C++
459 lines
12 KiB
C++
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Crytek Character Animation source code
|
|
//
|
|
// History:
|
|
// 10 Feb. 2003 :- Created by Sergiy Migdalskiy
|
|
//
|
|
// Contains:
|
|
// Generic inline implementations of miscellaneous string manipulation utilities,
|
|
// including path manipulation
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef _CRY_ENGINE_STRING_UTILS_HDR_
|
|
#define _CRY_ENGINE_STRING_UTILS_HDR_
|
|
|
|
#if defined(LINUX)
|
|
#include <ctype.h>
|
|
#endif
|
|
|
|
namespace CryStringUtils
|
|
{
|
|
|
|
// removes the extension from the file path
|
|
inline void StripFileExtension (string&strFileName)
|
|
{
|
|
for (const char* p = strFileName.c_str() + strFileName.length() - 1;
|
|
p >= strFileName.c_str();
|
|
--p)
|
|
{
|
|
switch (*p)
|
|
{
|
|
case ':':
|
|
case '/':
|
|
case '\\':
|
|
// we've reached a path separator - it means there's no extension in this name
|
|
return;
|
|
|
|
case '.':
|
|
// there's an extension in this file name
|
|
strFileName.resize (p - strFileName.c_str());
|
|
return;
|
|
}
|
|
};
|
|
// it seems the file name is a pure name, without path or extension
|
|
}
|
|
|
|
// replaces the given file extension with the new one
|
|
// NOTE: the new extension must NOTE be with the dot
|
|
inline void ReplaceExtension (string& strFileName, const char* szNewExtension)
|
|
{
|
|
StripFileExtension(strFileName);
|
|
strFileName += '.';
|
|
strFileName += szNewExtension;
|
|
}
|
|
|
|
|
|
// removes the extension from the file path
|
|
inline char* StripFileExtension (char * szFilePath)
|
|
{
|
|
for (char* p = szFilePath + (int)strlen(szFilePath)-1; p >= szFilePath; --p)
|
|
{
|
|
switch(*p)
|
|
{
|
|
case ':':
|
|
case '/':
|
|
case '\\':
|
|
// we've reached a path separator - it means there's no extension in this name
|
|
return NULL;
|
|
case '.':
|
|
// there's an extension in this file name
|
|
*p = '\0';
|
|
return p+1;
|
|
}
|
|
}
|
|
// it seems the file name is a pure name, without path or extension
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// returns the parent directory of the given file or directory.
|
|
// the returned path is WITHOUT the trailing slash
|
|
// if the input path has a trailing slash, it's ignored
|
|
// nGeneration - is the number of parents to scan up
|
|
template <class String>
|
|
String GetParentDirectory (const String& strFilePath, int nGeneration = 1)
|
|
{
|
|
for (const char* p = strFilePath.c_str() + strFilePath.length() - 2; // -2 is for the possible trailing slash: there always must be some trailing symbol which is the file/directory name for which we should get the parent
|
|
p >= strFilePath.c_str();
|
|
--p)
|
|
{
|
|
switch (*p)
|
|
{
|
|
case ':':
|
|
return String (strFilePath.c_str(), p);
|
|
break;
|
|
case '/':
|
|
case '\\':
|
|
// we've reached a path separator - return everything before it.
|
|
if (!--nGeneration)
|
|
return String(strFilePath.c_str(), p);
|
|
break;
|
|
}
|
|
};
|
|
// it seems the file name is a pure name, without path or extension
|
|
return String();
|
|
}
|
|
|
|
// converts all chars to lower case
|
|
inline string toLower (const string& str)
|
|
{
|
|
string strResult = str;
|
|
for (string::iterator it = strResult.begin(); it != strResult.end(); ++it)
|
|
*it = tolower (*it);
|
|
return strResult;
|
|
}
|
|
|
|
// searches and returns the pointer to the extension of the given file
|
|
inline const char* FindExtension (const char* szFileName)
|
|
{
|
|
const char* szEnd = szFileName + (int)strlen(szFileName);
|
|
for (const char* p = szEnd-1; p >= szFileName; --p)
|
|
if (*p == '.')
|
|
return p+1;
|
|
|
|
return szEnd;
|
|
}
|
|
|
|
// searches and returns the pointer to the file name in the given file path
|
|
inline const char* FindFileNameInPath (const char* szFilePath)
|
|
{
|
|
for (const char* p = szFilePath + (int)strlen(szFilePath)-1; p >= szFilePath; --p)
|
|
if (*p == '\\' || *p == '/')
|
|
return p+1;
|
|
return szFilePath;
|
|
}
|
|
|
|
// works like strstr, but is case-insensitive
|
|
inline const char* stristr(const char* szString, const char* szSubstring)
|
|
{
|
|
int nSuperstringLength = (int)strlen(szString);
|
|
int nSubstringLength = (int)strlen(szSubstring);
|
|
|
|
for (int nSubstringPos = 0; nSubstringPos <= nSuperstringLength - nSubstringLength; ++nSubstringPos)
|
|
{
|
|
if (strnicmp(szString+nSubstringPos, szSubstring, nSubstringLength) == 0)
|
|
return szString+nSubstringPos;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// replaces slashes
|
|
inline void UnifyFilePath (string& strPath)
|
|
{
|
|
// replace all forward slashes with backslashes
|
|
for (string::iterator itPath = strPath.begin(); itPath != strPath.end(); ++itPath)
|
|
if (*itPath == '/')
|
|
*itPath = '\\';
|
|
else
|
|
*itPath = tolower (*itPath);
|
|
}
|
|
|
|
// converts the number to a string
|
|
inline string toString(unsigned nNumber)
|
|
{
|
|
char szNumber[16];
|
|
sprintf (szNumber, "%u", nNumber);
|
|
return szNumber;
|
|
}
|
|
|
|
inline string toString(signed int nNumber)
|
|
{
|
|
char szNumber[16];
|
|
sprintf (szNumber, "%d", nNumber);
|
|
return szNumber;
|
|
}
|
|
|
|
#ifdef MATRIX_H
|
|
inline string toString(const Matrix44& m)
|
|
{
|
|
char szBuf[0x200];
|
|
sprintf (szBuf, "{%g,%g,%g,%g}{%g,%g,%g,%g}{%g,%g,%g,%g}{%g,%g,%g,%g}",
|
|
m(0,0),m(0,1),m(0,2),m(0,3),
|
|
m(1,0),m(1,1),m(1,2),m(1,3),
|
|
m(2,0),m(2,1),m(2,2),m(2,3),
|
|
m(3,0),m(3,1),m(3,2),m(3,3));
|
|
return szBuf;
|
|
}
|
|
#endif
|
|
|
|
#ifdef _CRYQUAT_H
|
|
inline string toString (const CryQuat& q)
|
|
{
|
|
char szBuf[0x100];
|
|
sprintf (szBuf, "{%g,{%g,%g,%g}}", q.w, q.v.x, q.v.y, q.v.z);
|
|
return szBuf;
|
|
}
|
|
#endif
|
|
|
|
#ifdef VECTOR_H
|
|
inline string toString (const Vec3& v)
|
|
{
|
|
char szBuf[0x80];
|
|
sprintf (szBuf, "{%g,%g,%g}", v.x, v.y, v.z);
|
|
return szBuf;
|
|
}
|
|
#endif
|
|
|
|
// does the same as strstr, but the szString is allowed to be no more than the specified size
|
|
inline const char* strnstr (const char* szString, const char* szSubstring, int nSuperstringLength)
|
|
{
|
|
int nSubstringLength = (int)strlen(szSubstring);
|
|
if (!nSubstringLength)
|
|
return szString;
|
|
|
|
for (int nSubstringPos = 0; szString[nSubstringPos] && nSubstringPos < nSuperstringLength - nSubstringLength; ++nSubstringPos)
|
|
{
|
|
if (strncmp(szString+nSubstringPos, szSubstring, nSubstringLength) == 0)
|
|
return szString+nSubstringPos;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// calculates the number of characters in the given string, limited by the end pointer
|
|
inline int strnlen (const char* szString, const char* szStringEnd)
|
|
{
|
|
const char* p;
|
|
for (p = szString; p < szStringEnd && *p; ++p)
|
|
continue;
|
|
|
|
return p - szString;
|
|
}
|
|
|
|
|
|
// finds the string in the array of strings
|
|
// returns its 0-based index or -1 if not found
|
|
// comparison is case-sensitive
|
|
// The string array end is demarked by the NULL value
|
|
inline int findString(const char* szString, const char* arrStringList[])
|
|
{
|
|
for (const char** p = arrStringList; *p; ++p)
|
|
{
|
|
if (0 == strcmp(*p, szString))
|
|
return p - arrStringList;
|
|
}
|
|
return -1; // string was not found
|
|
}
|
|
|
|
|
|
// This function is used for printing out sets of objects of string type.
|
|
// just forms the comma-delimited string where each string in the set is presented as a formatted substring
|
|
inline string toString (const std::set<string>& setStrings)
|
|
{
|
|
string strResult;
|
|
if (!setStrings.empty ())
|
|
{
|
|
strResult += "{";
|
|
for (std::set<string>::const_iterator it = setStrings.begin(); it != setStrings.end(); ++it)
|
|
{
|
|
if (it != setStrings.begin())
|
|
strResult += ", ";
|
|
strResult += "\"";
|
|
strResult += *it;
|
|
strResult += "\"";
|
|
}
|
|
strResult += "}";
|
|
}
|
|
return strResult ;
|
|
}
|
|
|
|
// cuts the string and adds leading ... if it's longer than specified maximum length
|
|
inline string cutString (const string& strPath, unsigned nMaxLength)
|
|
{
|
|
if (strPath.length() > nMaxLength && nMaxLength > 3)
|
|
return string("...") + string (strPath.c_str() + strPath.length() - (nMaxLength - 3));
|
|
else
|
|
return strPath;
|
|
}
|
|
|
|
|
|
/*
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// this class is used for Boyer/Moore/Gosper-assisted 'egrep' search
|
|
// First, upon construction, the delta table is compiled (you don't need to know what it is,
|
|
// just believe me - it's needed for fast substring search). Then you can pass the actual text
|
|
// buffer(s) to search the substring in.
|
|
class CBMGSubstring
|
|
{
|
|
public:
|
|
enum {g_nMaxPatternLength = 64};
|
|
// initializes the pattern substring; this must not be bigger than g_nMaxPatternLength
|
|
CBMGSubstring(const char* szPattern);
|
|
|
|
// searchees for the substring in the given text buffer
|
|
const char* findSubstringIn (const char* szBuffer, int nBufLength);
|
|
protected:
|
|
string m_strPattern;
|
|
unsigned char m_arrDelta[0x100];
|
|
};
|
|
|
|
CBMGSubstring::CBMGSubstring (const char* szPattern):
|
|
m_strPattern (szPattern)
|
|
{
|
|
int nPatternLength = m_strPattern.length(); // pattern length
|
|
if (nPatternLength > g_nMaxPatternLength)
|
|
m_strPattern.resize (nPatternLength = g_nMaxPatternLength);
|
|
|
|
memset(m_arrDelta, nPatternLength, sizeof(m_arrDelta));
|
|
|
|
for (int i = 0; i < nPatternLength; ++i)
|
|
m_arrDelta[((const unsigned char *)szPattern)[i]] = nPatternLength - i - 1;
|
|
}
|
|
|
|
// searchees for the substring in the given text buffer
|
|
const char* CBMGSubstring::findSubstringIn (const char* buffer, int buflen)
|
|
{
|
|
char *s; // temp ptr for comparisons
|
|
int inc, // position increment
|
|
k, // current buffer index
|
|
nhits, // match ctr
|
|
patlen; // pattern length
|
|
|
|
nhits = 0;
|
|
k = (patlen = m_strPattern.length()) - 1;
|
|
|
|
for (;;)
|
|
{
|
|
// the following (unsigned char *) type casts save a
|
|
// few clocks by freeing us from some XCHGs
|
|
while ((inc = m_arrDelta[((unsigned char *)buffer)[k]]) &&
|
|
((k += inc) < buflen))
|
|
;
|
|
if (k >= buflen)
|
|
return NULL; // we didn't find
|
|
|
|
s = buffer + (k++ - (patlen - 1));
|
|
if (!strncmp(s, m_strPattern.c_str(), patlen))
|
|
return s;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
*/
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Converts the given set of NUMBERS into the string
|
|
template <typename T>
|
|
string toString (const std::set<T>& setMtls, const char* szFormat, const char* szPostfix = "")
|
|
{
|
|
string strResult;
|
|
char szBuffer[64];
|
|
if (!setMtls.empty ())
|
|
{
|
|
strResult += strResult.empty()?"(":" (";
|
|
for (typename std::set<T>::const_iterator it = setMtls.begin(); it != setMtls.end(); )
|
|
{
|
|
if (it != setMtls.begin())
|
|
strResult += ", ";
|
|
sprintf (szBuffer, szFormat, *it);
|
|
strResult += szBuffer;
|
|
T nStart = *it;
|
|
|
|
++it;
|
|
|
|
if (it != setMtls.end() && *it == nStart + 1)
|
|
{
|
|
T nPrev = *it;
|
|
// we've got a region
|
|
while (++it !=setMtls.end() && *it == nPrev+1)
|
|
nPrev = *it;
|
|
if (nPrev == nStart + 1)
|
|
// special case - range of length 1
|
|
strResult += ",";
|
|
else
|
|
strResult += "..";
|
|
sprintf (szBuffer, szFormat, nPrev);
|
|
strResult += szBuffer;
|
|
}
|
|
}
|
|
strResult += ")";
|
|
}
|
|
return szPostfix[0]?strResult+szPostfix:strResult;
|
|
}
|
|
|
|
// returns true if the string matches the wildcard
|
|
inline bool MatchWildcard (const char* szString, const char* szWildcard)
|
|
{
|
|
const char* pString = szString, *pWildcard = szWildcard;
|
|
// skip the obviously the same starting substring
|
|
while (*pWildcard && *pWildcard != '*' && *pWildcard != '?')
|
|
if (*pString != *pWildcard)
|
|
return false; // must be exact match unless there's a wildcard character in the wildcard string
|
|
else
|
|
++pString, ++pWildcard;
|
|
|
|
if (!*pString)
|
|
{
|
|
// this will only match if there are no non-wild characters in the wildcard
|
|
for(; *pWildcard; ++pWildcard)
|
|
if (*pWildcard != '*' && *pWildcard != '?')
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
switch(*pWildcard)
|
|
{
|
|
case '\0':
|
|
return false; // the only way to match them after the leading non-wildcard characters is !*pString, which was already checked
|
|
|
|
// we have a wildcard with wild character at the start.
|
|
case '*':
|
|
{
|
|
// merge consecutive ? and *, since they are equivalent to a single *
|
|
while (*pWildcard == '*' || *pWildcard == '?')
|
|
++pWildcard;
|
|
|
|
if (!*pWildcard)
|
|
return true; // the rest of the string doesn't matter: the wildcard ends with *
|
|
|
|
for (; *pString; ++pString)
|
|
if (MatchWildcard(pString, pWildcard))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
case '?':
|
|
return MatchWildcard(pString+1, pWildcard + 1) || MatchWildcard(pString, pWildcard+1);
|
|
default:
|
|
assert (0);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// the type used to parse a yes/no string in the CAL file
|
|
enum YesNoType
|
|
{
|
|
nYNT_Yes,
|
|
nYNT_No,
|
|
nYNT_Invalid
|
|
};
|
|
|
|
// parse the yes/no string in the cal file
|
|
inline YesNoType toYesNoType(const char* szString)
|
|
{
|
|
if (!stricmp(szString, "yes")
|
|
|| !stricmp(szString, "enable")
|
|
|| !stricmp(szString, "1"))
|
|
return nYNT_Yes;
|
|
if (!stricmp(szString, "no")
|
|
|| !stricmp(szString, "disable")
|
|
|| !stricmp(szString, "0"))
|
|
return nYNT_No;
|
|
return nYNT_Invalid;
|
|
}
|
|
|
|
}
|
|
|
|
#endif |