/*********************************************************************** * fstring.cpp - Unicode string class with UTF-8 support * * * * This file is part of the FINAL CUT widget toolkit * * * * Copyright 2012-2021 Markus Gans * * * * FINAL CUT is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation; either version 3 of * * the License, or (at your option) any later version. * * * * FINAL CUT is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this program. If not, see * * . * ***********************************************************************/ #include #include #include #include #include "final/fapplication.h" #include "final/flog.h" #include "final/fstring.h" namespace finalcut { // static class attributes wchar_t FString::null_char{L'\0'}; const wchar_t FString::const_null_char{L'\0'}; //---------------------------------------------------------------------- // class FString //---------------------------------------------------------------------- // constructors and destructor //---------------------------------------------------------------------- FString::FString (int len) { if ( len > 0 ) string = std::wstring(std::size_t(len), L'\0'); } //---------------------------------------------------------------------- FString::FString (std::size_t len) : string{std::wstring(len, L'\0')} { } //---------------------------------------------------------------------- FString::FString (std::size_t len, wchar_t c) : string{std::wstring(len, c)} { } //---------------------------------------------------------------------- FString::FString (std::size_t len, const UniChar& c) : FString(len, wchar_t(c)) { } //---------------------------------------------------------------------- FString::FString (const FString& s) // copy constructor { if ( ! s.isEmpty() ) { std::wstring copy(s.string); _assign (copy); } } //---------------------------------------------------------------------- FString::FString (FString&& s) noexcept // move constructor : string{s.string} { s.string.clear(); } //---------------------------------------------------------------------- FString::FString (const std::wstring& s) { if ( ! s.empty() ) { std::wstring str(s); _assign (str); } } //---------------------------------------------------------------------- FString::FString (const wchar_t s[]) { if ( s ) { std::wstring str(s); _assign (str); } } //---------------------------------------------------------------------- FString::FString (const std::string& s) { if ( ! s.empty() ) { auto wide_string = _toWideString(s.c_str()); _assign(wide_string); } } //---------------------------------------------------------------------- FString::FString (const char s[]) { if ( s ) { auto wide_string = _toWideString(s); _assign(wide_string); } } //---------------------------------------------------------------------- FString::FString (const UniChar& c) { std::wstring str{ static_cast(c) }; _assign (str); } //---------------------------------------------------------------------- FString::FString (const wchar_t c) { if ( c ) { std::wstring str{ c }; _assign (str); } } //---------------------------------------------------------------------- FString::FString (const char c) { if ( c ) { std::wstring str{ wchar_t(c & 0xff) }; _assign (str); } } //---------------------------------------------------------------------- FString::~FString() = default; // destructor // FString operators //---------------------------------------------------------------------- FString& FString::operator = (const FString& s) { if ( &s != this ) { std::wstring str{s.string}; _assign (str); } return *this; } //---------------------------------------------------------------------- FString& FString::operator = (FString&& s) noexcept { if ( &s != this ) { string = s.string; s.string.clear(); } return *this; } //---------------------------------------------------------------------- const FString& FString::operator += (const FString& s) { string.append(s.string); return *this; } //---------------------------------------------------------------------- FString& FString::operator << (const FString& s) { string.append(s.string); return *this; } //---------------------------------------------------------------------- FString& FString::operator << (const UniChar& c) { FString s{static_cast(c)}; string.append(s.string); return *this; } //---------------------------------------------------------------------- FString& FString::operator << (const wchar_t c) { FString s{c}; string.append(s.string); return *this; } //---------------------------------------------------------------------- FString& FString::operator << (const char c) { FString s{c}; string.append(s.string); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (FString& s) const { s.string.append(string); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (std::wstring& s) const { s += string; return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (std::string& s) const { s.append(toString()); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (wchar_t& c) const { c = ( ! isEmpty() ) ? string[0] : L'\0'; return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (char& c) const { c = ( ! isEmpty() ) ? char(string[0] & 0xff) : '\0'; return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (sInt16& num) const { num = toShort(); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (uInt16& num) const { num = toUShort(); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (sInt32& num) const { num = toInt(); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (uInt32& num) const { num = toUInt(); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (sInt64& num) const { num = toLong(); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (uInt64& num) const { num = toULong(); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (double& num) const { num = toDouble(); return *this; } //---------------------------------------------------------------------- const FString& FString::operator >> (float& num) const { num = toFloat(); return *this; } //---------------------------------------------------------------------- FString::operator bool () const { return ! isEmpty(); } //---------------------------------------------------------------------- const FString& FString::operator () () const { return *this; } // public methods of FString //---------------------------------------------------------------------- FString FString::clear() { string.clear(); return *this; } //---------------------------------------------------------------------- const wchar_t* FString::wc_str() const { // Returns a constant wide character string return string.c_str(); } //---------------------------------------------------------------------- wchar_t* FString::wc_str() { // Returns a wide character string return const_cast(string.c_str()); } //---------------------------------------------------------------------- const char* FString::c_str() const { // Returns a constant c-string if ( ! isEmpty() ) { char_string = _toCharString(string); return char_string.c_str(); } else return ""; } //---------------------------------------------------------------------- char* FString::c_str() { // Returns a c-string if ( ! isEmpty() ) { char_string = _toCharString(string); return const_cast(char_string.c_str()); } else return const_cast(""); } //---------------------------------------------------------------------- std::string FString::toString() const { return c_str(); } //---------------------------------------------------------------------- FString FString::toLower() const { FString s{*this}; auto to_lower = [] (wchar_t& c) { c = wchar_t(std::towlower(std::wint_t(c))); }; std::for_each (s.begin(), s.end(), to_lower); return s; } //---------------------------------------------------------------------- FString FString::toUpper() const { FString s{*this}; auto to_upper = [] (wchar_t& c) { c = wchar_t(std::towupper(std::wint_t(c))); }; std::for_each (s.begin(), s.end(), to_upper); return s; } //---------------------------------------------------------------------- sInt16 FString::toShort() const { const long num = toLong(); if ( num > SHRT_MAX ) throw std::overflow_error ("overflow"); if ( num < SHRT_MIN ) throw std::underflow_error ("underflow"); return static_cast(num); } //---------------------------------------------------------------------- uInt16 FString::toUShort() const { const uLong num = toULong(); if ( num > USHRT_MAX ) throw std::overflow_error ("overflow"); return static_cast(num); } //---------------------------------------------------------------------- int FString::toInt() const { const long num = toLong(); if ( num > INT_MAX ) throw std::overflow_error ("overflow"); if ( num < INT_MIN ) throw std::underflow_error ("underflow"); return static_cast(num); } //---------------------------------------------------------------------- uInt FString::toUInt() const { const uLong num = toULong(); if ( num > UINT_MAX ) throw std::overflow_error ("overflow"); return static_cast(num); } //---------------------------------------------------------------------- long FString::toLong() const { bool neg{false}; long num{0}; long tenth_limit{LONG_MAX / 10}; long tenth_limit_digit{LONG_MAX % 10}; const FString s{trim()}; const wchar_t* p = s.string.c_str(); if ( s.isEmpty() ) throw std::invalid_argument ("empty value"); if ( *p == L'-' ) { p++; neg = true; tenth_limit = -(LONG_MIN / 10); tenth_limit_digit += 1; } else if ( *p == L'+' ) { p++; } while ( std::iswdigit(std::wint_t(*p)) ) { auto d = uChar(*p - L'0'); if ( num > tenth_limit || (num == tenth_limit && d > tenth_limit_digit) ) { if ( neg ) throw std::underflow_error ("underflow"); else throw std::overflow_error ("overflow"); } num = (num << 3) + (num << 1) + d; // (10 * num) + d p++; } if ( *p != L'\0' && ! std::iswdigit(std::wint_t(*p)) ) throw std::invalid_argument ("no valid number"); if ( neg ) num = (~num) + 1; return num; } //---------------------------------------------------------------------- uLong FString::toULong() const { uLong num{0}; const uLong tenth_limit{ULONG_MAX / 10}; const uLong tenth_limit_digit{ULONG_MAX % 10}; const FString s{trim()}; const wchar_t* p = s.string.c_str(); if ( s.isEmpty() ) throw std::invalid_argument ("empty value"); if ( *p == L'-' ) { throw std::underflow_error ("underflow"); } else if ( *p == L'+' ) { p++; } while ( std::iswdigit(std::wint_t(*p)) ) { const auto d = uChar(*p - L'0'); if ( num > tenth_limit || (num == tenth_limit && d > tenth_limit_digit) ) { throw std::overflow_error ("overflow"); } num = (num << 3) + (num << 1) + d; // (10 * num) + d p++; } if ( *p != L'\0' && ! std::iswdigit(std::wint_t(*p)) ) throw std::invalid_argument ("no valid number"); return num; } //---------------------------------------------------------------------- float FString::toFloat() const { const double num = toDouble(); if ( num > double(FLT_MAX) || num < double(-FLT_MAX) ) throw std::overflow_error ("overflow"); if ( std::fabs(num) < double(FLT_EPSILON) ) // num == 0.0f throw std::underflow_error ("underflow"); return static_cast(num); } //---------------------------------------------------------------------- double FString::toDouble() const { if ( isEmpty() ) throw std::invalid_argument ("null value"); wchar_t* p{}; const double ret = std::wcstod(string.c_str(), &p); if ( p != nullptr && *p != L'\0' ) throw std::invalid_argument ("no valid floating point value"); if ( errno == ERANGE ) { if ( ret >= HUGE_VAL || ret <= -HUGE_VAL ) throw std::overflow_error ("overflow"); if ( std::fabs(ret) < DBL_EPSILON ) // ret == 0.0l throw std::underflow_error ("underflow"); } return ret; } //---------------------------------------------------------------------- FString FString::ltrim() const { // Handle empty string if ( isEmpty() ) return *this; auto iter = string.begin(); const auto last = string.end(); while ( iter != last && std::iswspace(std::wint_t(*iter)) ) ++iter; if ( iter != last ) return std::wstring(iter, last); else return {}; } //---------------------------------------------------------------------- FString FString::rtrim() const { // Handle empty string if ( isEmpty() ) return *this; const auto r_end = string.rend(); auto r_iter = string.rbegin(); while ( r_iter != r_end && std::iswspace(std::wint_t(*r_iter)) ) ++r_iter; if ( r_iter != r_end ) return std::wstring( make_reverse_iterator(r_end) , make_reverse_iterator(r_iter) ); else return {}; } //---------------------------------------------------------------------- FString FString::trim() const { // Handle NULL and empty string if ( isEmpty() ) return *this; const FString s{ltrim()}; return s.rtrim(); } //---------------------------------------------------------------------- FString FString::left (std::size_t len) const { // Handle empty and too long strings if ( isEmpty() || len > getLength() ) return *this; return string.substr(0, len); } //---------------------------------------------------------------------- FString FString::right (std::size_t len) const { // Handle empty and too long strings if ( isEmpty() || len > getLength() ) return *this; return string.substr(string.size() - len, len); } //---------------------------------------------------------------------- FString FString::mid (std::size_t pos, std::size_t len) const { // Handle empty string if ( isEmpty() ) return *this; if ( pos == 0 ) pos = 1; auto length = string.length(); if ( pos <= length && pos + len > length ) len = length - pos + 1; if ( pos > length || pos + len - 1 > length || len == 0 ) return {}; auto first = string.begin() + static_cast(pos) - 1; auto last = first + static_cast(len); return std::wstring(first, last); } //---------------------------------------------------------------------- FStringList FString::split (const FString& delimiter) const { if ( isEmpty() ) return {}; const FString s{*this}; FStringList string_list{}; auto delimiter_length = delimiter.getLength(); std::wstring::size_type first = 0; std::wstring::size_type last; while ( (last = s.string.find(delimiter.string, first)) != std::wstring::npos ) { string_list.emplace_back(std::wstring(s.string, first, last - first)); first = last + delimiter_length; } string_list.emplace_back(std::wstring(s.string, first)); return string_list; } //---------------------------------------------------------------------- FString& FString::setString (const FString& s) { std::wstring str{s.string}; _assign (str); return *this; } //---------------------------------------------------------------------- FString& FString::setNumber (sInt64 num) { std::array buf{}; wchar_t* s = &buf[29]; // Pointer to the last character auto abs_num = static_cast(num); if ( num < 0 ) abs_num = static_cast(-num); *s = '\0'; do { *--s = L"0123456789"[abs_num % 10]; abs_num /= 10; } while ( abs_num ); if ( num < 0 ) *--s = '-'; std::wstring str{s}; _assign (str); return *this; } //---------------------------------------------------------------------- FString& FString::setNumber (uInt64 num) { wchar_t buf[30]{}; wchar_t* s = &buf[29]; // Pointer to the last character *s = '\0'; do { *--s = L"0123456789"[num % 10]; num /= 10; } while ( num ); std::wstring str{s}; _assign (str); return *this; } //---------------------------------------------------------------------- FString& FString::setNumber (lDouble f_num, int precision) { std::array format{}; // = "%.Lg" wchar_t* s = &format[0]; *s++ = L'%'; *s++ = L'.'; // The precision can not have more than 2 digits if ( precision > 99 ) precision = 99; if ( precision >= 10 ) { // The precision value is 2 digits long *s++ = precision / 10 + L'0'; *s++ = precision % 10 + L'0'; } else { // The precision value has only 1 digit *s++ = precision + L'0'; } *s++ = L'L'; *s++ = L'g'; *s = L'\0'; return sprintf(format.data(), f_num); } //---------------------------------------------------------------------- FString& FString::setFormatedNumber (sInt64 num, char separator) { int n{0}; std::array buf{}; wchar_t* s = &buf[29]; // Pointer to the last character auto abs_num = static_cast(num); if ( separator == 0 ) separator = ' '; if ( num < 0 ) abs_num = static_cast(-num); *s = L'\0'; do { *--s = L"0123456789"[abs_num % 10]; abs_num /= 10; n++; if ( abs_num && n % 3 == 0 ) *--s = separator; } while ( abs_num ); if ( num < 0 ) *--s = '-'; std::wstring str{s}; _assign (str); return *this; } //---------------------------------------------------------------------- FString& FString::setFormatedNumber (uInt64 num, char separator) { int n{0}; wchar_t buf[30]{}; wchar_t* s = &buf[29]; // Pointer to the last character *s = L'\0'; if ( separator == 0 ) separator = ' '; do { *--s = L"0123456789"[num % 10]; num /= 10; n++; if ( num && n % 3 == 0 ) *--s = separator; } while ( num ); std::wstring str{s}; _assign (str); return *this; } // FString operators //---------------------------------------------------------------------- bool FString::operator < (const FString& s) const { return string < s.string; } //---------------------------------------------------------------------- bool FString::operator <= (const FString& s) const { return string <= s.string; } //---------------------------------------------------------------------- bool FString::operator == (const FString& s) const { return string == s.string; } //---------------------------------------------------------------------- bool FString::operator != (const FString& s) const { return ! ( *this == s ); } //---------------------------------------------------------------------- bool FString::operator >= (const FString& s) const { return string >= s.string; } //---------------------------------------------------------------------- bool FString::operator > (const FString& s) const { return string > s.string; } //---------------------------------------------------------------------- const FString& FString::insert (const FString& s, int pos) { if ( isNegative(pos) || uInt(pos) > string.length() ) throw std::out_of_range(""); string.insert(uInt(pos), s.string, 0 , s.getLength()); return *this; } //---------------------------------------------------------------------- const FString& FString::insert (const FString& s, std::size_t pos) { if ( pos > string.length() ) throw std::out_of_range(""); string.insert(uInt(pos), s.string, 0 , s.getLength()); return *this; } //---------------------------------------------------------------------- FString FString::replace (const FString& from, const FString& to) const { // Handle empty strings if ( isEmpty() || from.isEmpty() ) return *this; FString s{*this}; std::wstring::size_type pos{}; while ( (pos = s.string.find(from.string, pos)) != std::wstring::npos ) { s.string.replace (pos, from.getLength(), to.string); pos += to.getLength(); } return s; } //---------------------------------------------------------------------- FString FString::replaceControlCodes() const { FString s{*this}; for (auto&& c : s) { if ( c <= L'\x1f' ) { c += L'\x2400'; } else if ( c == L'\x7f' ) { c = L'\x2421'; } else if ( c >= L'\x80' && c <= L'\x9f' ) { c = L' '; } else if ( ! std::iswprint(std::wint_t(c)) ) c = L' '; } return s; } //---------------------------------------------------------------------- FString FString::expandTabs (int tabstop) const { if ( tabstop <= 0 ) return *this; FString outstr{}; const auto tab_split = split(L"\t"); const auto last = tab_split.end() - 1; auto iter = tab_split.begin(); while ( iter != tab_split.end() ) { if ( iter != last ) { const auto len = iter->getLength(); const auto tab_len = std::size_t(tabstop); outstr += *iter + std::wstring(tab_len - (len % tab_len), L' '); } else outstr += *iter; ++iter; } return outstr; } //---------------------------------------------------------------------- FString FString::removeDel() const { FString s{*this}; std::size_t i{0}; std::size_t count{0}; for (auto&& c : s) { if ( c == 0x7f ) { count++; } else if ( count > 0 ) { count--; } else // count == 0 { s.string[i] = c; i++; } } s.string.erase(i); return s; } //---------------------------------------------------------------------- FString FString::removeBackspaces() const { FString s{*this}; std::size_t i{0}; for (auto&& c : s) { if ( c != L'\b' ) { s.string[i] = c; i++; } else if ( i > 0 ) { i--; } } s.string.erase(i); return s; } //---------------------------------------------------------------------- const FString& FString::overwrite (const FString& s, int pos) { if ( pos < 0 ) return overwrite (s, 0); return overwrite (s, std::size_t(pos)); } //---------------------------------------------------------------------- const FString& FString::overwrite (const FString& s, std::size_t pos) { if ( pos > string.length() ) pos = string.length(); string.replace(pos, s.getLength(), s.string); return *this; } //---------------------------------------------------------------------- const FString& FString::remove (std::size_t pos, std::size_t len) { auto length = string.length(); if ( pos > length ) return *this; if ( pos + len > length ) len = length - pos; string.erase (pos, len); return *this; } //---------------------------------------------------------------------- bool FString::includes (const FString& s) const { if ( s.isEmpty() ) return false; return string.find(s.string) != std::wstring::npos; } // private methods of FString //---------------------------------------------------------------------- inline void FString::_assign (std::wstring& s) { if ( string == s ) return; s.swap(string); } //---------------------------------------------------------------------- inline std::string FString::_toCharString (const std::wstring& s) const { if ( s.empty() ) return {}; const wchar_t* src = s.c_str(); auto state = std::mbstate_t(); auto size = std::wcsrtombs(nullptr, &src, 0, &state) + 1; std::vector dest(size); const auto mblength = std::wcsrtombs (dest.data(), &src, size, &state); if ( mblength == static_cast(-1) && errno != EILSEQ ) { return {}; } return dest.data(); } //---------------------------------------------------------------------- inline std::wstring FString::_toWideString (const std::string& s) const { if ( s.empty() ) return {}; const char* src = s.c_str(); auto state = std::mbstate_t(); auto size = std::mbsrtowcs(nullptr, &src, 0, &state) + 1; if ( size == 0 ) // ...malformed UTF-8 string return {}; std::vector dest(size); const auto wide_length = std::mbsrtowcs (dest.data(), &src, size, &state); if ( wide_length == static_cast(-1) ) { if ( src != s.c_str() ) return dest.data(); else { return {}; } } if ( wide_length == size ) dest[size - 1] = '\0'; if ( wide_length != 0 ) return dest.data(); else return {}; } // FString non-member operators //---------------------------------------------------------------------- FString operator + (const FString& s1, const FString& s2) { auto tmp = s1.string + s2.string; return tmp; } //---------------------------------------------------------------------- std::ostream& operator << (std::ostream& outstr, const FString& s) { const auto width = std::size_t(outstr.width()); if ( s.string.length() > 0 ) { outstr << s._toCharString(s.string).c_str(); } else if ( width > 0 ) { const FString fill_str{width, wchar_t(outstr.fill())}; outstr << s._toCharString(fill_str.string).c_str(); } return outstr; } //---------------------------------------------------------------------- std::istream& operator >> (std::istream& instr, FString& s) { std::array buf{}; instr.getline (buf.data(), FString::INPBUFFER); auto wide_string = s._toWideString(buf.data()); if ( ! wide_string.empty() ) { s._assign (wide_string); } return instr; } //---------------------------------------------------------------------- std::wostream& operator << (std::wostream& outstr, const FString& s) { const auto width = std::size_t(outstr.width()); if ( s.string.length() > 0 ) { outstr << s.string; } else if ( width > 0 ) { const FString fill_str{width, outstr.fill()}; outstr << fill_str.string; } return outstr; } //---------------------------------------------------------------------- std::wistream& operator >> (std::wistream& instr, FString& s) { std::array buf{}; instr.getline (buf.data(), FString::INPBUFFER); std::wstring str(buf.data()); s._assign (str); return instr; } } // namespace finalcut