/* PIP - Platform Independent Primitives String Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru This program 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. This program 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 "piincludes_p.h" #include "pistring.h" #include "pistringlist.h" #ifdef PIP_ICU # define U_NOEXCEPT # include "unicode/ucnv.h" #endif #ifdef WINDOWS # include #endif #include //! \addtogroup Core //! \{ //! \class PIString pistring.h //! \brief //! \~english String class //! \~russian Класс строки //! //! \~\details //! \~english \section PIString_sec0 Synopsis //! \~russian \section PIString_sec0 Краткий обзор //! //! \~english //! String is a sequence of \a PIChar. Real memory size of string is symbols count * 2. //! String can be constucted from many types of data and can be converted //! to many types. There are many operators and handly functions to use //! string as you wish. //! //! \~russian //! Строка состоит из последовательности \a PIChar. Реальный объем памяти, //! занимаемый строкой, равен количеству символов * 2. Строка может быть //! создана из множества типов и преобразована в несколько типов. //! Имеет множество методов для манипуляций. //! //! \} const char PIString::toBaseN[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^'}; const int PIString::fromBaseN[] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; const float PIString::ElideLeft = 0.f; const float PIString::ElideCenter = .5f; const float PIString::ElideRight = 1.f; #ifndef CC_VC # define pisprintf(f, v) char ch[256]; memset(ch, 0, 256); sprintf(ch, f, v); return PIString(ch); #else # define pisprintf(f, v) char ch[256]; memset(ch, 0, 256); sprintf_s(ch, 256, f, v); return PIString(ch); #endif PIString PIString::itos(const int num) {pisprintf("%d", num);} PIString PIString::ltos(const long num) {pisprintf("%ld", num);} PIString PIString::lltos(const llong num) {pisprintf("%lld", num);} PIString PIString::uitos(const uint num) {pisprintf("%u", num);} PIString PIString::ultos(const ulong num) {pisprintf("%lu", num);} PIString PIString::ulltos(const ullong num) {pisprintf("%llu", num);} PIString PIString::ftos(const float num, char format, int precision) { char f[8] = "%."; int wr = sprintf(&(f[2]), "%d", precision); f[2 + wr] = format; f[3 + wr] = 0; pisprintf(f, num); } PIString PIString::dtos(const double num, char format, int precision) { char f[8] = "%."; int wr = sprintf(&(f[2]), "%d", precision); f[2 + wr] = format; f[3 + wr] = 0; pisprintf(f, num); } #undef pisprintf PIString PIString::fromNumberBaseS(const llong value, int base, bool * ok) { if (value == 0LL) return PIString('0'); if (base < 2 || base > 40) { if (ok != 0) *ok = false; return PIString(); } if (ok != 0) *ok = true; if (base == 10) return lltos(value); PIString ret; llong v = value < 0 ? -value : value, cn; int b = base; while (v >= llong(base)) { cn = v % b; v /= b; //cout << int(cn) << ", " << int(v) << endl; ret.push_front(PIChar(toBaseN[cn])); } if (v > 0) ret.push_front(PIChar(toBaseN[v])); if (value < 0) ret.push_front('-'); return ret; } PIString PIString::fromNumberBaseU(const ullong value, int base, bool * ok) { if (value == 0ULL) return PIString('0'); if (base < 2 || base > 40) { if (ok != 0) *ok = false; return PIString(); } if (ok != 0) *ok = true; if (base == 10) return ulltos(value); PIString ret; ullong v = value, cn; int b = base; while (v >= ullong(base)) { cn = v % b; v /= b; //cout << int(cn) << ", " << int(v) << endl; ret.push_front(PIChar(toBaseN[cn])); } if (v > 0) ret.push_front(PIChar(toBaseN[v])); return ret; } llong PIString::toNumberBase(const PIString & value, int base, bool * ok) { static const PIString s_0x = PIStringAscii("0x"); PIString v = value.trimmed(); if (base < 0) { int ind = v.find(s_0x); if (ind == 0 || ind == 1) { v.remove(ind, 2); base = 16; } else { base = 10; } } else if (base < 2 || base > 40) { if (ok != 0) *ok = false; return 0; } if (ok) *ok = true; PIVector digits; llong ret = 0, m = 1; bool neg = false; int cs; for (int i = 0; i < v.size_s(); ++i) { if (v[i] == PIChar('-')) { neg = !neg; continue; } cs = fromBaseN[int(v[i].toAscii())]; if (cs < 0 || cs >= base) { if (ok) *ok = false; break; } digits << cs; } for (int i = digits.size_s() - 1; i >= 0; --i) { ret += digits[i] * m; m *= base; } if (neg) ret = -ret; return ret; } void PIString::appendFromChars(const char * c, int s, const char * codepage) { if (s == 0) return; int old_sz = size_s(); if (s == -1) s = strlen(c); #ifdef PIP_ICU UErrorCode e((UErrorCode)0); UConverter * cc = ucnv_open(codepage, &e); if (cc) { enlarge(s); // UChar * ucs = new UChar[s]; // memset(ucs, 0, s * sizeof(UChar)); e = (UErrorCode)0; int sz = ucnv_toUChars(cc, (UChar*)(PIDeque::data(old_sz)), s, c, s, &e); //printf("appendFromChars %d -> %d\n", s, sz); //printf("PIString %d -> %d\n", c[0], ucs[0]); resize(old_sz+sz); // for (int i = 0; i < sz; ++i) { // push_back(PIChar((ushort)ucs[i])); // } // delete[] ucs; ucnv_close(cc); return; } #else # ifdef WINDOWS int sz = MultiByteToWideChar((uint)(uintptr_t)codepage, MB_ERR_INVALID_CHARS, c, s, 0, 0); if (sz <= 0) return; enlarge(sz); MultiByteToWideChar((uint)(uintptr_t)codepage, MB_ERR_INVALID_CHARS, c, s, (LPWSTR)PIDeque::data(old_sz), sz); return; //printf("request %d\n", sz); # else std::mbstate_t state{}; PIChar c16; int sz = 0; while(std::size_t rc = std::mbrtoc16((char16_t*)&c16, c+sz, s - sz, &state)) { if(rc == (std::size_t)-3) append(c16); else if(rc == (std::size_t)-2) break; else if(rc == (std::size_t)-1) break; else { sz += rc; append(c16); } } // const char ** pc; // char * c_ = nullptr; // if (s > 0) { // c_ = (char*)malloc(s+1); // memcpy(c_, c, s); // c_[s] = '\0'; // pc = (const char **)&c_; // } else { // pc = &c; // } // size_t len = mbsrtowcs(NULL, pc, 0, &state); // wchar_t wstr[len+1]; // mbsrtowcs(&wstr[0], pc, len+1, &state); // if (c_) free(c_); // d.enlarge(len); // for (size_t i=0; i '\0') ret.appendFromChars(s, -1 #ifdef PIP_ICU , c #else # ifdef WINDOWS , __utf8name__ # endif #endif ); return ret; } //! \~\details //! \~english //! Example: //! \~russian //! Пример: //! \~\code //! piCout << PIString::readableSize(512); // 512 B //! piCout << PIString::readableSize(5120); // 5.0 kB //! piCout << PIString::readableSize(512000); // 500.0 kB //! piCout << PIString::readableSize(5120000); // 4.8 MB //! piCout << PIString::readableSize(512000000); // 488.2 MB //! piCout << PIString::readableSize(51200000000); // 47.6 GB //! \endcode PIString PIString::readableSize(llong bytes) { PIString s; s.setReadableSize(bytes); return s; } void PIString::buildData(const char * cp) const { //data_.clear(); deleteData(); int sz = 0; #ifdef PIP_ICU UErrorCode e((UErrorCode)0); UConverter * cc = ucnv_open(cp, &e); if (cc) { const size_t len = MB_CUR_MAX*size()+1; data_ = (char *)malloc(len); sz = ucnv_fromUChars(cc, data_, len, (const UChar*)(PIDeque::data()), size_s(), &e); // char uc[8]; // data_.reserve(size_s()); // for (int i = 0; i < size_s(); ++i) { // if (at(i).isAscii()) { // data_.push_back(uchar(at(i).unicode16Code())); // } else { // e = (UErrorCode)0; // for (int j = 0; j < sz; ++j) { // data_.push_back(uc[j]); // } // } // } ucnv_close(cc); data_[sz] = '\0'; // data_.push_back('\0'); return; } #else # ifdef WINDOWS sz = WideCharToMultiByte((uint)(uintptr_t)cp, 0, (LPCWCH)PIDeque::data(), PIDeque::size_s(), 0, 0, NULL, NULL); //printf("WideCharToMultiByte %d %d\n", (uint)(uintptr_t)cp, sz); if (sz <= 0) { //printf("WideCharToMultiByte erro %d\n", GetLastError()); data_ = (char *)malloc(1); data_[0] = '\0'; return; } data_ = (char *)malloc(sz+1); //data_.resize(sz); WideCharToMultiByte((uint)(uintptr_t)cp, 0, (LPCWCH)PIDeque::data(), PIDeque::size_s(), (LPSTR)data_, sz, NULL, NULL); data_[sz] = '\0'; return; # else wchar_t wc; //char tc[MB_CUR_MAX]; mbstate_t state; memset(&state, 0, sizeof(state)); data_ = (char *)malloc(MB_CUR_MAX*size()+1); char *p = data_; for (int i = 0; i < size_s(); ++i) { // if (at(i).isAscii()) { // data_.push_back(uchar(at(i).toAscii())); // continue; // } wc = at(i).toWChar(); sz = wcrtomb(p, wc, &state); if (sz < 0) break; p += sz; } p[0] = '\0'; # endif #endif } void PIString::deleteData() const { if (!data_) return; free(data_); data_ = nullptr; } void PIString::trimsubstr(int &st, int &fn) const { for (int i = 0; i < length(); ++i) { if (at(i) != ' ' && at(i) != '\t' && at(i) != '\n' && at(i) != '\r' && at(i) != char(12) && at(i) != uchar(0)) { st = i; break; } } if (st < 0) return; for (int i = length() - 1; i >= 0; --i) { if (at(i) != ' ' && at(i) != '\t' && at(i) != '\n' && at(i) != '\r' && at(i) != char(12) && at(i) != uchar(0)) { fn = i; break; } } } uint PIString::hash() const { return piHashData((const uchar*)PIDeque::data(), size() * sizeof(PIChar)); } PIByteArray PIString::toUTF8() const { if (isEmpty()) return PIByteArray(1,'\0'); buildData(__utf8name__); return PIByteArray(data_, strlen(data_)); } PIByteArray PIString::toCharset(const char * c) const { if (isEmpty()) return PIByteArray(1,'\0'); buildData( #ifdef PIP_ICU c #else # ifdef WINDOWS __utf8name__ # endif #endif ); return PIByteArray(data_, strlen(data_)); } PIString & PIString::operator +=(const char * str) { if (!str) return *this; appendFromChars(str, -1, __syslocname__); return *this; } PIString::~PIString() { deleteData(); } PIString & PIString::operator +=(const wchar_t * str) { if (!str) return *this; int i = -1; while (str[++i]) { push_back(PIChar(str[i])); } return *this; } PIString & PIString::operator +=(const PIString & str) { PIDeque::append(*(const PIDeque*)&str); return *this; } bool PIString::operator ==(const PIString & str) const { uint l = str.size(); if (size() != l) return false; for (uint i = 0; i < l; ++i) { if (str[i] != at(i)) return false; } return true; } bool PIString::operator !=(const PIString & str) const { uint l = str.size(); if (size() != l) return true; for (uint i = 0; i < l; ++i) { if (str[i] != at(i)) return true; } return false; } bool PIString::operator <(const PIString & str) const { uint l = str.size(); if (size() < l) return true; if (size() > l) return false; for (uint i = 0; i < l; ++i) { if (at(i) == str[i]) continue; if (at(i) < str[i]) return true; else return false; } return false; } bool PIString::operator >(const PIString & str) const { uint l = str.size(); if (size() < l) return false; if (size() > l) return true; for (uint i = 0; i < l; ++i) { if (at(i) == str[i]) continue; if (at(i) < str[i]) return false; else return true; } return false; } //! \~\details //! \~english //! If "len" < 0 then returns substring from symbol "start" to end. //! \~russian //! Если "len" < 0 тогда возвращается подстрока от символа "start" и до конца. //! \~\code //! PIString s("0123456789"); //! piCout << s.mid(-2, -1); // s = "0123456789" //! piCout << s.mid(-2, 4); // s = "01" //! piCout << s.mid(3, -1); // s = "3456789" //! piCout << s.mid(3, 4); // s = "3456" //! piCout << s.mid(7, 1); // s = "7" //! piCout << s.mid(7, 4); // s = "789" //! \endcode //! \~\sa \a left(), \a right() PIString PIString::mid(const int start, const int len) const { //PIString str; int s = start, l = len; if (l == 0 || s >= length()) return PIString(); if (s < 0) { l += s; s = 0; } if (l < 0) { return PIString(&(at(s)), size_s() - s); } else { if (l > length() - s) l = length() - s; return PIString(&(at(s)), l); } return PIString(); } //! \~\details //! \~\code //! PIString s("0123456789"); //! s.cutMid(1, 3); //! piCout << s; // s = "0456789" //! s.cutMid(-1, 3); //! piCout << s; // s = "56789" //! s.cutMid(3, -1); //! piCout << s; // s = "567" //! \endcode //! \~\sa \a cutLeft(), \a cutRight() PIString & PIString::cutMid(const int start, const int len) { int s = start, l = len; if (l == 0) return *this; if (s < 0) { l += s; s = 0; } if (l < 0) { remove(s, size() - s); } else { if (l > length() - s) l = length() - s; remove(s, l); } return *this; } //! \~\details //! \~english Remove spaces, tabulations, line feeds and null symbols: //! \~russian Удаляет пробелы, табуляцию, переводы строк и нулевые символы: //! \~ ' ', '\\n', '\\r', '\\t', '\\0' //! \~\code //! PIString s(" \t string \n"); //! s.trim(); //! piCout << s; // s = "string" //! \endcode //! \~\sa \a trimmed() PIString & PIString::trim() { int st = -1, fn = 0; trimsubstr(st, fn); if (st < 0) { clear(); return *this; } if (fn < size_s() - 1) cutRight(size_s() - fn - 1); if (st > 0) cutLeft(st); return *this; } PIString PIString::trimmed() const { int st = -1, fn = 0; trimsubstr(st, fn); if (st < 0) return PIString(); return mid(st, fn - st + 1); } //! \~\details //! \~\code //! PIString s("0123456789"); //! s.replace(2, 3, "_cut_"); //! piCout << s; // s = "01_cut_56789" //! s.replace(0, 1, "one_"); //! piCout << s; // s = "one_1_cut_56789" //! \endcode //! \~\sa \a replaced(), \a replaceAll() PIString & PIString::replace(int from, int count, const PIString & with) { count = piMini(count, length() - from); if (count == with.size_s()) { memcpy(PIDeque::data(from), static_cast>(with).data(), count * sizeof(PIChar)); } else { remove(from, count); PIDeque::insert(from, with); } return *this; } //! \~\details //! \~english If "ok" is not null, it set to "true" if something was replaced //! \~russian Если "ok" не null, то устанавливает в "true" если замена произведена //! \~\code //! PIString s("pip string"); //! bool ok; //! s.replace("string", "conf", &ok); //! piCout << s << ok; // s = "pip conf", true //! s.replace("PIP", "PlInPr", &ok); //! piCout << s << ok; // s = "pip conf", false //! \endcode //! \~\sa \a replaced(), \a replaceAll() PIString & PIString::replace(const PIString & what, const PIString & with, bool * ok) { if (what.isEmpty()) { if (ok != 0) *ok = false; return *this; } int s = find(what); if (s >= 0) replace(s, what.length(), with); if (ok != 0) *ok = (s >= 0); return *this; } //! \~\details //! \~\code //! PIString s("substrings"); //! s.replaceAll("s", "_"); //! piCout << s; // s = "_ub_tring_" //! \endcode //! \~\sa \a replace(), \a replaced(), \a replacedAll() PIString & PIString::replaceAll(const PIString & what, const PIString & with) { if (what.isEmpty() || what == with) return *this; if (with.isEmpty()) { removeAll(what); } else { int l = what.length(), dl = with.length() - what.length(); for (int i = 0; i < length() - l + 1; ++i) { bool match = true; for (int j = 0; j < l; ++j) { if (at(j + i) != what[j]) { match = false; break; } } if (!match) continue; if (dl > 0) PIDeque::insert(i, PIDeque((size_t)dl)); if (dl < 0) PIDeque::remove(i, -dl); memcpy(PIDeque::data(i), &(with.at(0)), with.length() * sizeof(PIChar)); } } return *this; } //! \~\details //! \~\code //! PIString s("substrings"); //! s.replaceAll("s", '_'); //! piCout << s; // s = "_ub_tring_" //! \endcode //! \~\sa \a replace(), \a replaced(), \a replacedAll() PIString & PIString::replaceAll(const PIString & what, const char with) { if (what.isEmpty()) return *this; int l = what.length(), dl = what.length() - 1; for (int i = 0; i < length() - l + 1; ++i) { bool match = true; for (int j = 0; j < l; ++j) { if (at(j + i) != what[j]) { match = false; break; } } if (!match) continue; if (dl > 0) PIDeque::remove(i, dl); (*this)[i] = PIChar(with); } return *this; } //! \~\details //! \~\code //! PIString s("substrings"); //! s.replaceAll('s', '_'); //! piCout << s; // s = "_ub_tring_" //! \endcode //! \~\sa \a replace(), \a replaced(), \a replacedAll() PIString & PIString::replaceAll(const char what, const char with) { int l = length(); for (int i = 0; i < l; ++i) { if (at(i) == what) (*this)[i] = with; } return *this; } PIString & PIString::removeAll(const PIString & str) { if (str.isEmpty()) return *this; int l = str.length(); for (int i = 0; i < length() - l + 1; ++i) { bool match = true; for (int j = 0; j < l; ++j) { if (at(j + i) != str[j]) { match = false; break; } } if (!match) continue; PIDeque::remove(i, l); i -= l; } return *this; } PIString & PIString::insert(int index, const PIString & str) { PIDeque::insert(index, *((const PIDeque*)&str)); return *this; } PIString & PIString::elide(int size, float pos) { static const PIString s_dotdot = PIStringAscii(".."); if (length() <= size) return *this; if (length() <= 2) { fill('.'); return *this; } pos = piClampf(pos, 0.f, 1.f); int ns = size - 2; int ls = piRoundf(ns * pos); remove(ls, length() - ns); insert(ls, s_dotdot); return *this; } PIStringList PIString::split(const PIString & delim) const { PIStringList sl; if (isEmpty() || delim.isEmpty()) return sl; PIString ts(*this); int ci = ts.find(delim); while (ci >= 0) { sl << ts.left(ci); ts.cutLeft(ci + delim.length()); ci = ts.find(delim); } if (ts.length() > 0) sl << ts; return sl; } //! \~\details //! \~\code //! PIString s("012345012345"); //! piCout << s.find('-'); // -1 //! piCout << s.find('3'); // 3 //! piCout << s.find('3', 4); // 9 //! piCout << s.find('3', 10); // -1 //! \endcode //! \~\sa \a findAny(), \a findLast(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange() int PIString::find(const char c, const int start) const { for (int i = start; i < length(); ++i) { if (at(i) == c) return i; } return -1; } //! \~\details //! \~\code //! PIString s("012345012345"); //! piCout << s.find("-"); // -1 //! piCout << s.find("34"); // 3 //! piCout << s.find("3", 4); // 9 //! piCout << s.find("3", 10); // -1 //! \endcode //! \~\sa \a findAny(), \a findLast(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange() int PIString::find(const PIString & str, const int start) const { int l = str.length(); for (int i = start; i < length() - l + 1; ++i) { if (mid(i, l) == str) return i; } return -1; } //! \~\details //! \~\code //! piCout << PIString("1.str").findAny(".,:"); // 1 //! piCout << PIString("1,str").findAny(".,:"); // 1 //! piCout << PIString("1:str").findAny(".,:"); // 1 //! \endcode //! \~\sa \a find(), \a findLast(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange() int PIString::findAny(const PIString & str, const int start) const { for (int i = start; i < length(); ++i) { if (str.contains(at(i))) return i; } return -1; } //! \~\details //! \~\code //! PIString s("012345012345"); //! piCout << s.findLast('-'); // -1 //! piCout << s.findLast('3'); // 9 //! piCout << s.findLast('3', 4); // 9 //! piCout << s.findLast('3', 10); // -1 //! \endcode //! \~\sa \a find(), \a findAny(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange() int PIString::findLast(const char c, const int start) const { for (int i = length() - 1; i >= start; --i) { if (at(i) == c) return i; } return -1; } //! \~\details //! \~\code //! PIString s("012345012345"); //! piCout << s.findLast("-"); // -1 //! piCout << s.findLast("34"); // 9 //! piCout << s.findLast("3", 4); // 9 //! piCout << s.findLast("3", 10); // -1 //! \endcode //! \~\sa \a find(), \a findAny(), \a findAnyLast(), \a findWord(), \a findCWord(), \a findRange() int PIString::findLast(const PIString & str, const int start) const { int l = str.length(); for (int i = length() - l; i >= start; --i) { if (mid(i, l) == str) return i; } return -1; } //! \~\details //! \~\code //! piCout << PIString(".str.0").findAnyLast(".,:"); // 4 //! piCout << PIString(".str,0").findAnyLast(".,:"); // 4 //! piCout << PIString(".str:0").findAnyLast(".,:"); // 4 //! \endcode //! \~\sa \a find(), \a findAny(), \a findLast(), \a findWord(), \a findCWord(), \a findRange() int PIString::findAnyLast(const PIString & str, const int start) const { for (int i = length() - 1; i >= start; --i) { if (str.contains(at(i))) return i; } return -1; } //! \~\details //! \~\code //! PIString s("this is "); //! piCout << s.findWord("this"); // 0 //! piCout << s.findWord("is"); // 5 //! piCout << s.findWord("PIP", 4); // -1 //! piCout << s.findWord("", 4); // 8 //! \endcode //! \~\sa \a find(), \a findAny(), \a findLast(), \a findAnyLast(), \a findCWord(), \a findRange() int PIString::findWord(const PIString & word, const int start) const { int f = start - 1, tl = length(), wl = word.length(); while ((f = find(word, f + 1)) >= 0) { bool ok = true; PIChar c; if (f > 0) { c = (*this)[f - 1]; if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) { ok = false; continue; } } if (f + wl < tl) { c = (*this)[f + wl]; if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) { ok = false; continue; } } if (ok) return f; } return -1; } //! \~\details //! \~\code //! PIString s("this::is "); //! piCout << s.findCWord("this"); // 0 //! piCout << s.findCWord("is"); // 6 //! piCout << s.findCWord("PIP", 4); // 10 //! piCout << s.findCWord("", 4); // 9 //! \endcode //! \~\sa \a find(), \a findAny(), \a findLast(), \a findAnyLast(), \a findWord(), \a findRange() int PIString::findCWord(const PIString & word, const int start) const { int f = start - 1, tl = length(), wl = word.length(); while ((f = find(word, f + 1)) >= 0) { bool ok = true; PIChar c; if (f > 0) { c = (*this)[f - 1]; if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || (c != '_' && !c.isAlpha() && !c.isDigit()))) { ok = false; continue; } } if (f + wl < tl) { c = (*this)[f + wl]; if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || (c != '_' && !c.isAlpha() && !c.isDigit()))) { ok = false; continue; } } if (ok) return f; } return -1; } //! \~\details //! \~\code //! PIString s(" {figures{inside}}"); //! int len = -1; //! piCout << s.findRange('{', '}', '\\', 0, &len) << len << s.mid(2, len); // 2 15 figures{inside} //! s = "\"text\\\"shielded\" next"; //! piCout << s.findRange('"', '"', '\\', 0, &len) << len << s.mid(1, len); // 1 14 text\"shielded //! \endcode //! \~\sa \a find(), \a findAny(), \a findLast(), \a findAnyLast(), \a findWord(), \a findCWord() int PIString::findRange(const PIChar start, const PIChar end, const PIChar shield, const int start_index, int * len) const { if (len) *len = 0; bool trim_ = (start != ' ' && start != '\t' && start != '\n' && start != '\r'), eq = (start == end); int sz = size_s(), ls = -1, le = -1, cnt = 0; for (int i = start_index; i < sz; ++i) { PIChar c = at(i); if (c == shield) { ++i; continue; } if (trim_) { if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; trim_ = false; } if (eq) { if (c == start) { if (cnt == 0) ls = i; else {le = i; cnt = 0; break;} cnt++; } } else { if (c == start) { if (cnt == 0) ls = i; cnt++; } if (c == end) { cnt--; if (cnt == 0) le = i; } } if (cnt <= 0) break; } //piCout << ls << le << cnt; if (le < ls || ls < 0 || le < 0 || cnt != 0) return -1; if (len) *len = le - ls - 1; return ls + 1; } int PIString::entries(const PIChar c) const { int sz = size_s(), ret = 0; for (int i = 0; i < sz; ++i) { if (at(i) == c) ++ret; } return ret; } bool PIString::startsWith(const PIString & str) const { if (size() < str.size()) return false; return str == left(str.size()); } bool PIString::endsWith(const PIString & str) const { if (size() < str.size()) return false; return str == right(str.size()); } //! \~\details //! \~\code //! piCout << PIString("true").toBool(); // true //! piCout << PIString("Yes").toBool(); // true //! piCout << PIString(" TRUE ").toBool(); // true //! piCout << PIString(" 1 ").toBool(); // true //! piCout << PIString("0").toBool(); // false //! piCout << PIString("0.1").toBool(); // true //! piCout << PIString("-1").toBool(); // false //! piCout << PIString("").toBool(); // false //! \endcode bool PIString::toBool() const { static const PIString s_true = PIStringAscii("true"); static const PIString s_yes = PIStringAscii("yes" ); static const PIString s_on = PIStringAscii("on" ); static const PIString s_ok = PIStringAscii("ok" ); PIString s(*this); s = s.trimmed().toLowerCase(); if (s == s_true || s == s_yes || s == s_on || s == s_ok) return true; if (atof(s.toNativeDecimalPoints().data()) > 0.) return true; return false; } //! \~\details //! \~\code //! PIString s("\t ! word"); //! piCout << s.takeSymbol(); // "!" //! piCout << s.takeSymbol(); // "w" //! piCout << s.takeSymbol(); // "o" //! piCout << s; // "rd" //! \endcode //! \~\sa \a takeWord(), \a takeCWord(), \a takeLine(), \a takeNumber(), \a takeRange() PIString PIString::takeSymbol() { PIString ret; int sz = size_s(), ss = -1; for (int i = 0; i < sz; ++i) { PIChar c = at(i); if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; ss = i; break; } if (ss < 0) return ret; ret = mid(ss, 1); cutLeft(ss + 1); return ret; } //! \~\details //! \~\code //! PIString s("some words\nnew line "); //! piCout << s.takeWord(); // "some" //! piCout << s.takeWord(); // "words" //! piCout << s.takeWord(); // "new" //! piCout << s; // " line " //! \endcode //! \~\sa \a takeSymbol(), \a takeCWord(), \a takeLine(), \a takeNumber(), \a takeRange() PIString PIString::takeWord() { int sz = size_s(), ws = -1, we = -1; for (int i = 0; i < sz; ++i) { PIChar c = at(i); if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { if (we < 0 && ws >= 0) { we = i; break; } } else { if (ws < 0) ws = i; if (we >= 0) break; } } PIString ret = mid(ws, we - ws); cutLeft(we < 0 ? sz : we); return ret; } //! \~\details //! \~\code //! \endcode //! \~\sa \a takeSymbol(), \a takeWord(), \a takeLine(), \a takeNumber(), \a takeRange() PIString PIString::takeCWord() { PIString ret; int sz = size_s(), ws = -1, we = -1; for (int i = 0; i < sz; ++i) { PIChar c = at(i); if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { if (we < 0 && ws >= 0) { we = i; break; } } else { if (ws < 0) { if (c.isAlpha() || c == '_') { ws = i; } else { return ret; } } else { if (!c.isAlpha() && !c.isDigit() && c != '_') { we = i; break; } } if (we >= 0) break; } } ret = mid(ws, we - ws); cutLeft(we < 0 ? sz : we); return ret; } //! \~\details //! \~\code //! PIString s("some words\nnew line \n\nend"); //! piCout << s.takeLine(); // "some words" //! piCout << s.takeLine(); // "new line " //! piCout << s.takeLine(); // "" //! piCout << s; // "end" //! \endcode //! \~\sa \a takeSymbol(), \a takeWord(), \a takeCWord(), \a takeNumber(), \a takeRange() PIString PIString::takeLine() { int sz = size_s(), le = -1; for (int i = 0; i < sz; ++i) { PIChar c = at(i); if (c == '\n') { le = i; break; } } PIString ret = left(le); if (!ret.isEmpty()) { if (ret.back() == '\r') { ret.cutRight(1); } } cutLeft(le < 0 ? sz : le + 1); return ret; } //! \~\details //! \~\code //! PIString s(" 0xFF -99 1.2E+5f 1000L"); //! piCout << s.takeNumber(); // "0xFF" //! piCout << s.takeNumber(); // "-99" //! piCout << s.takeNumber(); // "1.2E+5f" //! piCout << s.takeNumber(); // "1000L" //! piCout << s; // "" //! \endcode //! \~\sa \a takeSymbol(), \a takeWord(), \a takeCWord(), \a takeLine(), \a takeRange() PIString PIString::takeNumber() { PIString ret; int sz = size_s(), ls = -1, le = -1, phase = 0; for (int i = 0; i < sz; ++i) { if (phase > 7) break; PIChar c = at(i); //piCout << "char " << c << "phase" << phase; switch (phase) { case 0: // trim if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { continue; } phase = 7; case 7: // sign if (c == '-' || c == '+') { ls = i; phase = 1; break; } case 1: // search start if ((c >= '0' && c <= '9') || c == '.') { le = i; if (ls < 0) ls = i; if (c == '.') phase = 3; else phase = 2; break; } phase = 9; break; case 2: // integer if (c == '.') { le = i; phase = 3; break; } if (c == 'e' || c == 'E') { le = i; phase = 4; break; } if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == 'x') { le = i; break; } phase = 6; break; case 3: // point if (c == 'e' || c == 'E') { le = i; phase = 4; break; } if (c >= '0' && c <= '9') { le = i; break; } phase = 6; break; case 4: // exp if ((c >= '0' && c <= '9') || c == '-' || c == '+') { le = i; phase = 5; break; } phase = 6; break; case 5: // power if (c >= '0' && c <= '9') { le = i; break; } phase = 6; break; case 6: // suffix if (c == 'f' || c == 's' || c == 'u' || c == 'l' || c == 'L') { le = i; break; } phase = 9; break; } if (phase == 6) { if (c == 'f' || c == 's' || c == 'u' || c == 'l' || c == 'L') le = i; else phase = 9; } } //piCout << ls << le; if (le < ls) return ret; ret = mid(ls, le - ls + 1); cutLeft(le + 1); return ret; } //! \~\details //! \~english "shield" symbol prevent analysis of the next symbol //! \~russian Символ "shield" экранирует следующий символ //! \~\code //! PIString s(" {figures{inside}}"); //! piCout << s.takeRange('{', '}'); // "figures{inside}" //! piCout << s; // "" //! s = "\"text\\\"shielded\" next"; //! piCout << s.takeRange('"', '"'); // "text\"shielded" //! piCout << s; // " next" //! \endcode //! \~\sa \a takeSymbol(), \a takeWord(), \a takeLine(), \a takeNumber() PIString PIString::takeRange(const PIChar start, const PIChar end, const PIChar shield) { PIString ret; bool trim_ = (start != ' ' && start != '\t' && start != '\n' && start != '\r'), eq = (start == end); int sz = size_s(), ls = -1, le = -1, cnt = 0; for (int i = 0; i < sz; ++i) { PIChar c = at(i); if (c == shield) { ++i; continue; } if (trim_) { if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { continue; } trim_ = false; } if (eq) { if (c == start) { if (cnt == 0) { ls = i; } else { le = i; cnt = 0; break; } cnt++; } } else { if (c == start) { if (cnt == 0) ls = i; cnt++; } if (c == end) { cnt--; if (cnt == 0) le = i; } } if (cnt <= 0) break; } //piCout << ls << le << cnt; if (le < ls || ls < 0 || le < 0 || cnt != 0) return ret; ret = mid(ls + 1, le - ls - 1); cutLeft(le + 1); return ret; } //! \~\details //! \~\code //! PIString s("a(b(c)d)e"); //! piCout << s.inBrackets('(', ')'); // "b(c)d" //! piCout << s; // s = "a(b(c)d)e" //! \endcode PIString PIString::inBrackets(const PIChar start, const PIChar end) const { int slen = length(); int st = -1, bcnt = 0; PIChar cc; for (int i = 0; i < slen; i++) { cc = at(i); if (cc == start) { if (bcnt == 0) st = i; bcnt++; } if (cc == end && st >= 0) { bcnt--; if (bcnt == 0) return mid(st+1, i-st-1); } } return PIString(); } //! \~\details //! \~english //! This function fill internal buffer by sequence //! of chars. Minimum length of this buffer is count //! of symbols. Returned pointer is valid until next //! execution of this function //! \~russian //! Этот метод заполняет внутренный байтовый буфер. Минимальный размер //! этого буфера равен количеству символов строки. Возвращаемый указатель //! действителен до следующего вызова этого метода //! \~\code //! piCout << PIString("0123456789").data(); // 0123456789 //! piCout << PIString("№1").data(); // №1 //! \endcode //! \~\sa \a dataConsole(), \a dataUTF8() const char * PIString::data() const { if (isEmpty()) return ""; buildData(__syslocname__); return data_; } //! \~\details //! \~english //! This function fill internal buffer by sequence //! of chars. Minimum length of this buffer is count //! of symbols. Returned pointer is valid until next //! execution of this function //! \~russian //! Этот метод заполняет внутренный байтовый буфер. Минимальный размер //! этого буфера равен количеству символов строки. Возвращаемый указатель //! действителен до следующего вызова этого метода //! \~\sa \a data(), \a dataUTF8() const char * PIString::dataConsole() const { if (isEmpty()) return ""; buildData(__sysoemname__ ); return data_; } //! \~\details //! \~english //! This function fill internal buffer by sequence //! of chars. Minimum length of this buffer is count //! of symbols. Returned pointer is valid until next //! execution of this function //! \~russian //! Этот метод заполняет внутренный байтовый буфер. Минимальный размер //! этого буфера равен количеству символов строки. Возвращаемый указатель //! действителен до следующего вызова этого метода //! \~\sa \a data(), \a dataConsole() const char * PIString::dataUTF8() const { if (isEmpty()) return ""; buildData(__utf8name__); return data_; } //! \~\details //! \~english //! This function fill internal buffer by sequence //! of chars. Length of this buffer is count //! of symbols. Returned pointer is valid until next //! execution of this function //! \~russian //! Этот метод заполняет внутренный байтовый буфер. Размер //! этого буфера равен количеству символов строки. Возвращаемый указатель //! действителен до следующего вызова этого метода //! \~\sa \a dataConsole(), \a dataUTF8() const char * PIString::dataAscii() const { if (isEmpty()) return ""; deleteData(); data_ = (char*)malloc(size()+1); for (int i = 0; i < size_s(); ++i) { data_[i] = uchar(at(i).ch); } data_[size()] = '\0'; return data_; } PIString PIString::toUpperCase() const { PIString str(*this); int l = str.size(); for (int i = 0; i < l; ++i) { str[i] = str[i].toUpper(); } return str; } PIString PIString::toLowerCase() const { PIString str(*this); int l = str.size(); for (int i = 0; i < l; ++i) { str[i] = str[i].toLower(); } return str; } PIString PIString::toNativeDecimalPoints() const { #ifdef HAS_LOCALE PIString s(*this); if (currentLocale == 0) return s; return s.replaceAll('.', currentLocale->decimal_point).replaceAll(',', currentLocale->decimal_point); #else return PIString(*this).replaceAll(',', '.'); #endif } char PIString::toChar() const { PIString s(toNativeDecimalPoints()); char v; sscanf(s.data(), "%c", &v); return v; } float PIString::toFloat() const { return (float)atof(toNativeDecimalPoints().data()); } double PIString::toDouble() const { return atof(toNativeDecimalPoints().data()); } ldouble PIString::toLDouble() const { return atof(toNativeDecimalPoints().data()); } //! \~\details //! \~english //! Example: //! \~russian //! Пример: //! \~\code //! PIString s; //! s.setReadableSize(512); //! piCout << s; // 512 B //! s.setReadableSize(5120); //! piCout << s; // 5.0 kB //! s.setReadableSize(512000); //! piCout << s; // 500.0 kB //! s.setReadableSize(5120000); //! piCout << s; // 4.8 MB //! s.setReadableSize(512000000); //! piCout << s; // 488.2 MB //! s.setReadableSize(51200000000); //! piCout << s; // 47.6 GB //! \endcode PIString & PIString::setReadableSize(llong bytes) { clear(); if (bytes < 1024) { *this += (PIString::fromNumber(bytes) + PIStringAscii(" B")); return *this; } double fres = bytes / 1024.; llong res = bytes / 1024; fres -= res; if (res < 1024) { *this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" kB")); return *this; } fres = res / 1024.; res /= 1024; fres -= res; if (res < 1024) { *this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" MB")); return *this; } fres = res / 1024.; res /= 1024; fres -= res; if (res < 1024) { *this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" GB")); return *this; } fres = res / 1024.; res /= 1024; fres -= res; if (res < 1024) { *this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" TB")); return *this; } fres = res / 1024.; res /= 1024; fres -= res; *this += (PIString::fromNumber(res) + PIStringAscii(".") + PIString::fromNumber(llong(fres * 10)).left(1) + PIStringAscii(" PB")); return *this; } const static PIString _versionDelims_ = PIStringAscii("._-+"); void parseVersion(PIString s, PIVector & codes, PIStringList & strs) { s.trim(); if (s.isEmpty()) { codes.resize(3, 0); return; } int mccnt = 2 - s.entries('.'); if (mccnt > 0) { int ind = s.findLast('.') + 1; while (!_versionDelims_.contains(s[ind])) { ++ind; if (ind > s.size_s() - 1) break; } for (int i = 0; i < mccnt; ++i) { s.insert(ind, PIStringAscii(".0")); } } PIStringList comps; while (!s.isEmpty()) { int ind = s.findAny(_versionDelims_); if (ind >= 0) { comps << s.takeLeft(ind); s.cutLeft(1).trim(); } else { comps << s; s.clear(); } } for (int i = 0; i < comps.size_s(); ++i) { if (comps[i].isEmpty()) comps[i] = '0'; bool ok = false; int val = comps[i].toInt(-1, &ok); if (ok) { codes << val; } else { strs << comps[i]; } } //piCout << codes << strs; } int versionLabelValue(PIString s) { int ret = -10000; if (s.isEmpty()) return 0; if (s.startsWith(PIStringAscii("pre"))) { s.cutLeft(3); ret -= 1; } if (s.startsWith(PIStringAscii("rc"))) { s.cutLeft(2); ret += s.toInt(); } if (s.startsWith(PIStringAscii("r"))) { s.cutLeft(1); ret += 10000 + s.toInt(); } if (s == PIStringAscii("alpha")) ret -= 4; if (s == PIStringAscii("beta" )) ret -= 2; return ret; } //! \relatesalso PIString //! \~\details //! \~english //! This function parse version to number codes and labels. Then it //! compare no more than "components" codes. If there is no difference, compare //! labels. Each label has corresponding integer value, so //! "prealpha" < "alpha" < "prebeta" < "beta" < "rc[N]" < "" < "r[N]". //! Example: //! \~russian //! Этот метод разбирает версии на числовые части и метку. Затем сравнивает //! не более чем "components" частей. Если различий нет, то сравниваются //! метки. Каждой метке соответствует своё значение так, что //! "prealpha" < "alpha" < "prebeta" < "beta" < "rc[N]" < "" < "r[N]". //! Пример: //! \~\code //! piCout << versionCompare("1.0.0_rc2-999", "1.0.1_rc2-999"); // -1, < //! piCout << versionCompare("1.0.0", "0.9.2"); // 1, > //! piCout << versionCompare("1.0.0_r1", "1.0.0"); // 1, > //! piCout << versionCompare("1.0.0_r1", "1.0.0", 3); // 0, = //! piCout << versionCompare("1.0.0_r2", "1.0.0", 3); // 0, = //! piCout << versionCompare(".2-alpha", "0.2_alpha"); // 0, = //! piCout << versionCompare("1_prebeta", "1.0_alpha"); // 1, > //! \endcode //! \~\return //! \~english //! * 0 - equal //! * 1 - v0 > v1 //! * -1 - v0 < v1 //! \~russian //! * 0 - равны //! * 1 - v0 > v1 //! * -1 - v0 < v1 int versionCompare(const PIString & v0, const PIString & v1, int components) { PIStringList strs[2]; PIVector codes[2]; parseVersion(v0.toLowerCase(), codes[0], strs[0]); parseVersion(v1.toLowerCase(), codes[1], strs[1]); //piCout << codes[0] << strs[0]; int mc = piMaxi(codes[0].size_s(), codes[1].size_s()); if (codes[0].size_s() < mc) codes[0].resize(mc, 0); if (codes[1].size_s() < mc) codes[1].resize(mc, 0); mc = piMaxi(strs[0].size_s(), strs[1].size_s()); if (strs[0].size_s() < mc) strs[0].resize(mc, ""); if (strs[1].size_s() < mc) strs[1].resize(mc, ""); int comps = piMini(components, codes[0].size_s(), codes[1].size_s()); if (comps < 1) return (v0 == v1 ? 0 : (v0 > v1 ? 1 : -1)); for (int c = 0; c < comps; ++c) { if (codes[0][c] > codes[1][c]) return 1; if (codes[0][c] < codes[1][c]) return -1; } mc = piClampi(mc, 0, components - comps); for (int c = 0; c < mc; ++c) { int lv0 = versionLabelValue(strs[0][c]); int lv1 = versionLabelValue(strs[1][c]); if (lv0 > lv1) return 1; if (lv0 < lv1) return -1; } return 0; } //! \relatesalso PIString //! \~\details //! \~english //! Parse version as described in \a versionCompare() and returns //! classic view of codes and labels: major.minor.revision[-build][_label]. //! Example: //! \~russian //! Разбирает версию по описанию \a versionCompare() и возвращает //! классическое представление версии и метки: major.minor.revision[-build][_label]. //! Пример: //! \~\code //! piCout << versionNormalize(""); // 0.0.0 //! piCout << versionNormalize("1"); // 1.0.0 //! piCout << versionNormalize("1.2"); // 1.2.0 //! piCout << versionNormalize("1.2.3"); // 1.2.3 //! piCout << versionNormalize("1.2+rc1.99"); // 1.2.99_rc1 //! piCout << versionNormalize("1.2-alpha"); // 1.2.0_alpha //! piCout << versionNormalize("1..4_rc2-999"); // 1.0.4-999_rc2 //! \endcode PIString versionNormalize(const PIString & v) { PIStringList strs; PIVector codes; parseVersion(v.toLowerCase(), codes, strs); PIString ret; for (int i = 0; i < codes.size_s(); ++i) { if (i > 0) { if (i < 3) ret += '.'; else ret += '-'; } ret += PIString::fromNumber(codes[i]); } for (int i = 0; i < strs.size_s(); ++i) { ret += '_'; ret += strs[i]; } return ret; } PICout operator <<(PICout s, const PIString & v) { s.space(); s.quote(); s.writePIString(v); s.quote(); return s; }