// Copyright (C) 2002-2005 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine" and the "irrXML" project. // For conditions of distribution and use, see copyright notice in irrlicht.h and irrXML.h #ifndef __IRR_STRING_H_INCLUDED__ #define __IRR_STRING_H_INCLUDED__ #include "irrTypes.h" namespace irr { namespace core { //! Very simple string class with some useful features. /** string<c8> and string<wchar_t> work both with unicode AND ascii, so you can assign unicode to string<c8> and ascii to string<wchar_t> (and the other way round) if your ever would want to. Note that the conversation between both is not done using an encoding. Known bugs: Special characters like 'Ä', 'Ü' and 'Ö' are ignored in the methods make_upper, make_lower and equals_ignore_case. */ template <class T> class string { public: //! Default constructor string() : array(0), allocated(1), used(1) { array = new T[1]; array[0] = 0x0; } //! Constructor string(const string<T>& other) : array(0), allocated(0), used(0) { *this = other; } //! Constructs a string from an int string(int number) : array(0), allocated(0), used(0) { // store if negative and make positive bool negative = false; if (number < 0) { number *= -1; negative = true; } // temporary buffer for 16 numbers c8 tmpbuf[16]; tmpbuf[15] = 0; s32 idx = 15; // special case '0' if (!number) { tmpbuf[14] = '0'; *this = &tmpbuf[14]; return; } // add numbers while(number && idx) { idx--; tmpbuf[idx] = (c8)('0' + (number % 10)); number = number / 10; } // add sign if (negative) { idx--; tmpbuf[idx] = '-'; } *this = &tmpbuf[idx]; } //! Constructor for copying a string from a pointer with a given lenght template <class B> string(const B* c, s32 lenght) : array(0), allocated(0), used(0) { if (!c) return; allocated = used = lenght+1; array = new T[used]; for (s32 l = 0; l<lenght; ++l) array[l] = (T)c[l]; array[lenght] = 0; } //! Constructor for unicode and ascii strings template <class B> string(const B* c) : array(0),allocated(0), used(0) { *this = c; } //! destructor ~string() { delete [] array; } //! Assignment operator string<T>& operator=(const string<T>& other) { if (this == &other) return *this; delete [] array; allocated = used = other.size()+1; array = new T[used]; const T* p = other.c_str(); for (s32 i=0; i<used; ++i, ++p) array[i] = *p; return *this; } //! Assignment operator for strings, ascii and unicode template <class B> string<T>& operator=(const B* c) { if (!c) { if (!array) { array = new T[1]; allocated = 1; used = 1; } array[0] = 0x0; return *this; } if ((void*)c == (void*)array) return *this; s32 len = 0; const B* p = c; while(*p) { ++len; ++p; } // we'll take the old string for a while, because the new string could be // a part of the current string. T* oldArray = array; allocated = used = len+1; array = new T[used]; for (s32 l = 0; l<len+1; ++l) array[l] = (T)c[l]; delete [] oldArray; return *this; } //! Add operator for other strings string<T> operator+(const string<T>& other) { string<T> str(*this); str.append(other); return str; } //! Add operator for strings, ascii and unicode template <class B> string<T> operator+(const B* c) { string<T> str(*this); str.append(c); return str; } //! Direct access operator T& operator [](const s32 index) const { _IRR_DEBUG_BREAK_IF(index>=used) // bad index return array[index]; } //! Comparison operator bool operator ==(const T* str) const { int i; for(i=0; array[i] && str[i]; ++i) if (array[i] != str[i]) return false; return !array[i] && !str[i]; } //! Comparison operator bool operator ==(const string<T>& other) const { for(s32 i=0; array[i] && other.array[i]; ++i) if (array[i] != other.array[i]) return false; return used == other.used; } //! Is smaller operator bool operator <(const string<T>& other) const { for(s32 i=0; array[i] && other.array[i]; ++i) if (array[i] != other.array[i]) return (array[i] < other.array[i]); return used < other.used; } //! Equals not operator bool operator !=(const string<T>& other) const { return !(*this == other); } //! Returns length of string /** \return Returns length of the string in characters. */ s32 size() const { return used-1; } //! Returns character string /** \return Returns pointer to C-style zero terminated string. */ const T* c_str() const { return array; } //! Makes the string lower case. void make_lower() { const T A = (T)'A'; const T Z = (T)'Z'; const T diff = (T)'a' - A; for (s32 i=0; i<used; ++i) { if (array[i]>=A && array[i]<=Z) array[i] += diff; } } //! Makes the string upper case. void make_upper() { const T a = (T)'a'; const T z = (T)'z'; const T diff = (T)'A' - a; for (s32 i=0; i<used; ++i) { if (array[i]>=a && array[i]<=z) array[i] += diff; } } //! Compares the string ignoring case. /** \param other: Other string to compare. \return Returns true if the string are equal ignoring case. */ bool equals_ignore_case(const string<T>& other) const { for(s32 i=0; array[i] && other[i]; ++i) if (toLower(array[i]) != toLower(other[i])) return false; return used == other.used; } //! compares the first n characters of the strings bool equalsn(const string<T>& other, int len) { int i; for(i=0; array[i] && other[i] && i < len; ++i) if (array[i] != other[i]) return false; // if one (or both) of the strings was smaller then they // are only equal if they have the same lenght return (i == len) || (used == other.used); } //! compares the first n characters of the strings bool equalsn(const T* str, int len) { int i; for(i=0; array[i] && str[i] && i < len; ++i) if (array[i] != str[i]) return false; // if one (or both) of the strings was smaller then they // are only equal if they have the same lenght return (i == len) || (array[i] == 0 && str[i] == 0); } //! Appends a character to this string /** \param character: Character to append. */ void append(T character) { if (used + 1 > allocated) reallocate((s32)used + 1); used += 1; array[used-2] = character; array[used-1] = 0; } //! Appends a string to this string /** \param other: String to append. */ void append(const string<T>& other) { --used; s32 len = other.size(); if (used + len + 1 > allocated) reallocate((s32)used + (s32)len + 1); for (s32 l=0; l<len+1; ++l) array[l+used] = other[l]; used = used + len + 1; } //! Appends a string of the length l to this string. /** \param other: other String to append to this string. \param length: How much characters of the other string to add to this one. */ void append(const string<T>& other, s32 length) { s32 len = other.size(); if (len < length) { append(other); return; } len = length; --used; if (used + len > allocated) reallocate((s32)used + (s32)len); for (s32 l=0; l<len; ++l) array[l+used] = other[l]; used = used + len; } //! Reserves some memory. /** \param count: Amount of characters to reserve. */ void reserve(s32 count) { if (count < allocated) return; reallocate(count); } //! finds first occurrence of character in string /** \param c: Character to search for. \return Returns position where the character has been found, or -1 if not found. */ s32 findFirst(T c) const { for (s32 i=0; i<used; ++i) if (array[i] == c) return i; return -1; } //! finds first occurrence of a character of a list in string /** \param c: List of strings to find. For example if the method should find the first occurance of 'a' or 'b', this parameter should be "ab". \param count: Amount of characters in the list. Ususally, this should be strlen(ofParameter1) \return Returns position where one of the character has been found, or -1 if not found. */ s32 findFirstChar(T* c, int count) const { for (s32 i=0; i<used; ++i) for (int j=0; j<count; ++j) if (array[i] == c[j]) return i; return -1; } //! Finds first position of a character not in a given list. /** \param c: List of characters not to find. For example if the method should find the first occurance of a character not 'a' or 'b', this parameter should be "ab". \param count: Amount of characters in the list. Ususally, this should be strlen(ofParameter1) \return Returns position where the character has been found, or -1 if not found. */ template <class B> s32 findFirstCharNotInList(B* c, int count) const { for (int i=0; i<used; ++i) { int j; for (j=0; j<count; ++j) if (array[i] == c[j]) break; if (j==count) return i; } return -1; } //! Finds last position of a character not in a given list. /** \param c: List of characters not to find. For example if the method should find the first occurance of a character not 'a' or 'b', this parameter should be "ab". \param count: Amount of characters in the list. Ususally, this should be strlen(ofParameter1) \return Returns position where the character has been found, or -1 if not found. */ template <class B> s32 findLastCharNotInList(B* c, int count) const { for (int i=used-2; i>=0; --i) { int j; for (j=0; j<count; ++j) if (array[i] == c[j]) break; if (j==count) return i; } return -1; } //! finds next occurrence of character in string /** \param c: Character to search for. \param startPos: Position in string to start searching. \return Returns position where the character has been found, or -1 if not found. */ s32 findNext(T c, s32 startPos) const { for (s32 i=startPos; i<used; ++i) if (array[i] == c) return i; return -1; } //! finds last occurrence of character in string //! \param c: Character to search for. //! \return Returns position where the character has been found, //! or -1 if not found. s32 findLast(T c) const { for (s32 i=used-1; i>=0; --i) if (array[i] == c) return i; return -1; } //! Returns a substring //! \param begin: Start of substring. //! \param length: Length of substring. string<T> subString(s32 begin, s32 length) { if (length <= 0) return string<T>(""); string<T> o; o.reserve(length+1); for (s32 i=0; i<length; ++i) o.array[i] = array[i+begin]; o.array[length] = 0; o.used = o.allocated; return o; } void operator += (T c) { append(c); } void operator += (const string<T>& other) { append(other); } void operator += (int i) { append(string<T>(i)); } //! replaces all characters of a special type with another one void replace(T toReplace, T replaceWith) { for (s32 i=0; i<used; ++i) if (array[i] == toReplace) array[i] = replaceWith; } //! trims the string. /** Removes whitespace from begin and end of the string. */ void trim() { const char whitespace[] = " \t\n"; const int whitespacecount = 3; // find start and end of real string without whitespace int begin = findFirstCharNotInList(whitespace, whitespacecount); if (begin == -1) return; int end = findLastCharNotInList(whitespace, whitespacecount); if (end == -1) return; *this = subString(begin, (end +1) - begin); } //! Erases a character from the string. May be slow, because all elements //! following after the erased element have to be copied. //! \param index: Index of element to be erased. void erase(int index) { _IRR_DEBUG_BREAK_IF(index>=used || index<0) // access violation for (int i=index+1; i<used; ++i) array[i-1] = array[i]; --used; } private: //! Returns a character converted to lower case T toLower(const T& t) const { if (t>=(T)'A' && t<=(T)'Z') return t + ((T)'a' - (T)'A'); else return t; } //! Reallocate the array, make it bigger or smaler void reallocate(s32 new_size) { T* old_array = array; array = new T[new_size]; allocated = new_size; s32 amount = used < new_size ? used : new_size; for (s32 i=0; i<amount; ++i) array[i] = old_array[i]; if (allocated < used) used = allocated; delete [] old_array; } //--- member variables T* array; s32 allocated; s32 used; }; //! Typedef for character strings typedef string<irr::c8> stringc; //! Typedef for wide character strings typedef string<wchar_t> stringw; } // end namespace core } // end namespace irr #endif