/* 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 "pistring.h" #include "piincludes_p.h" #include "piliterals.h" #include "pimathbase.h" #include "pistringlist.h" #ifdef PIP_ICU # define U_NOEXCEPT # include "unicode/ucnv.h" #endif #ifdef WINDOWS # include #endif #include #include #include //! \class PIString pistring.h //! \~\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 char PIString::fromBaseN[] = { (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)0, (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31, (char)32, (char)33, (char)34, (char)35, (char)36, (char)37, (char)38, (char)39, (char)-1, (char)-1, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31, (char)32, (char)33, (char)34, (char)35, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1, (char)-1}; const float PIString::ElideLeft = 0.f; const float PIString::ElideCenter = .5f; const float PIString::ElideRight = 1.f; template T toDecimal(const PIString & s) { int part = 0, exp = 0; bool negative = false, negative_exp = false, err = false, has_digit = false; T ret = 0., frac = 0., frac_delim = 1.; for (const PIChar pc: s) { char c = pc.toAscii(); switch (part) { case 0: // sign if (pc.isSpace()) continue; if (c >= '0' && c <= '9') { has_digit = true; ret = c - '0'; part = 1; continue; } if (c == '+') { part = 1; continue; } if (c == '-') { negative = true; part = 1; continue; } if (c == '.' || c == ',') { part = 2; continue; } err = true; break; case 1: // integer if (c >= '0' && c <= '9') { has_digit = true; ret = ret * 10 + (c - '0'); continue; } if (c == '.' || c == ',') { part = 2; continue; } if ((c == 'e' || c == 'E') && has_digit) { part = 3; continue; } err = true; break; case 2: // fractional if (c >= '0' && c <= '9') { has_digit = true; frac = frac * 10 + (c - '0'); frac_delim *= 10.; continue; } if ((c == 'e' || c == 'E') && has_digit) { part = 3; continue; } err = true; break; case 3: // exponent with sign if (c == '+') { part = 4; continue; } if (c == '-') { negative_exp = true; part = 4; continue; } case 4: // exponent if (c >= '0' && c <= '9') { exp = (exp * 10) + (c - '0'); continue; } err = true; break; } if (err) break; } frac /= frac_delim; ret += frac; if (negative && has_digit) ret = -ret; if (exp > 0) { if (negative_exp) ret /= pow10((T)exp); else ret *= pow10((T)exp); } return ret; } #define pisprintf(f, v) \ char ch[256]; \ memset(ch, 0, 256); \ snprintf(ch, 256, f, v); \ return PIStringAscii(ch); 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::dtos(const double num, char format, int precision) { char f[8] = "%."; int wr = snprintf(&(f[2]), 4, "%d", precision); if (wr > 4) wr = 4; 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 = "0x"_a; 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; char 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) { // piCout << "appendFromChars"; 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) { d.enlarge(s); e = (UErrorCode)0; int sz = ucnv_toUChars(cc, (UChar *)(d.data(old_sz)), s, c, s, &e); d.resize(old_sz + sz); 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; d.enlarge(sz); MultiByteToWideChar((uint)(uintptr_t)codepage, MB_ERR_INVALID_CHARS, c, s, (LPWSTR)d.data(old_sz), sz); # else std::wstring_convert, char16_t> ucs2conv; std::u16string ucs2 = ucs2conv.from_bytes(c, c + s); d.enlarge(ucs2.size()); ucs2.copy((char16_t *)d.data(old_sz), ucs2.size()); # endif #endif changed_ = true; } PIString PIString::fromConsole(const PIByteArray & ba) { PIString ret; if (ba.isNotEmpty()) ret.appendFromChars((const char *)ba.data(), ba.size(), __sysoemname__); return ret; } PIString PIString::fromConsole(const char * s) { PIString ret; if (!s) return ret; if (s[0] != '\0') ret.appendFromChars(s, -1, __sysoemname__); return ret; } PIString PIString::fromSystem(const PIByteArray & ba) { PIString ret; if (ba.isNotEmpty()) ret.appendFromChars((const char *)ba.data(), ba.size(), __syslocname__); return ret; } PIString PIString::fromSystem(const char * s) { PIString ret; if (!s) return ret; if (s[0] != '\0') ret.appendFromChars(s, -1, __syslocname__); return ret; } PIString PIString::fromUTF8(const char * s, int l) { PIString ret; if (!s) return ret; if (s[0] != '\0') { if ((uchar)s[0] == 0xEF && (uchar)s[1] == 0xBB && (uchar)s[2] == 0xBF) { s += 3; if (l > 0) l -= 3; if (s[0] == '\0') return ret; } ret.appendFromChars(s, l, __utf8name__); } return ret; } PIString PIString::fromUTF8(const PIByteArray & ba) { PIString ret; if (ba.isNotEmpty()) { const char * data = (const char *)ba.data(); int size = ba.size(); if (ba.size() >= 3) { if (ba[0] == 0xEF && ba[1] == 0xBB && ba[2] == 0xBF) { data += 3; size -= 3; if (size == 0) return ret; } } ret.appendFromChars(data, size, __utf8name__); } return ret; } PIString PIString::fromAscii(const char * s) { return fromAscii(s, strlen(s)); } PIString PIString::fromAscii(const char * s, int len) { PIString ret; ret.resize(len); for (int l = 0; l < len; ++l) { ret[l] = s[l]; } return ret; } PIString PIString::fromAscii(const PIByteArray & ascii) { PIString ret; ret.resize(ascii.size()); for (int l = 0; l < ret.size_s(); ++l) { ret[l] = PIChar(ascii[l]); } return ret; } PIString PIString::fromCodepage(const char * s, const char * c) { PIString ret; if (s[0] > '\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 KiB //! piCout << PIString::readableSize(512000); // 500.0 KiB //! piCout << PIString::readableSize(5120000); // 4.8 MiB //! piCout << PIString::readableSize(512000000); // 488.2 MiB //! piCout << PIString::readableSize(51200000000); // 47.6 GiB //! \endcode PIString PIString::readableSize(llong bytes) { PIString s; s.setReadableSize(bytes); return s; } void PIString::buildData(const char * cp) const { if (!changed_ && (last_loc_ == cp) && data_) return; last_loc_ = cp; changed_ = false; deleteData(); #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); int sz = ucnv_fromUChars(cc, data_, len, (const UChar *)(d.data()), d.size_s(), &e); ucnv_close(cc); data_[sz] = '\0'; data_size_ = sz; return; } #else # ifdef WINDOWS int sz = WideCharToMultiByte((uint)(uintptr_t)cp, 0, (LPCWCH)d.data(), d.size_s(), 0, 0, NULL, NULL); if (sz <= 0) { data_ = (char *)malloc(1); data_[0] = '\0'; return; } data_ = (char *)malloc(sz + 1); WideCharToMultiByte((uint)(uintptr_t)cp, 0, (LPCWCH)d.data(), d.size_s(), (LPSTR)data_, sz, NULL, NULL); data_[sz] = '\0'; data_size_ = sz; return; # else std::wstring_convert, char16_t> ucs2conv; std::string u8str = ucs2conv.to_bytes((char16_t *)d.data(), (char16_t *)d.data() + d.size()); data_ = (char *)malloc(u8str.size() + 1); strcpy(data_, u8str.c_str()); data_size_ = u8str.size(); # endif #endif } void PIString::deleteData() const { if (!data_) return; free(data_); data_ = nullptr; data_size_ = 0; } void PIString::trimsubstr(int & st, int & fn) const { for (int i = 0; i < d.size_s(); ++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 = d.size_s() - 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 *)d.data(), d.size() * sizeof(PIChar)); } PIByteArray PIString::toSystem() const { if (isEmpty()) return PIByteArray(); buildData(__syslocname__); return PIByteArray(data_, strlen(data_)); } PIByteArray PIString::toUTF8() const { if (isEmpty()) return PIByteArray(); buildData(__utf8name__); return PIByteArray(data_, strlen(data_)); } PIByteArray PIString::toCharset(const char * c) const { if (isEmpty()) return PIByteArray(); buildData( #ifdef PIP_ICU c #else # ifdef WINDOWS __utf8name__ # endif #endif ); return PIByteArray(data_, strlen(data_)); } PIString PIString::simplified() const { PIString ret(*this); for (int i = 0; i < ret.size_s(); ++i) if (!ret[i].isAscii()) ret[i] = '?'; return ret; } PIString & PIString::mask(const PIString & symbols, PIChar mc) { for (int i = 0; i < size_s(); ++i) if (symbols.contains(at(i))) { insert(i, mc); ++i; } return *this; } PIString & PIString::unmask(const PIString & symbols, const PIChar mc) { for (int i = 0; i < size_s() - 1; ++i) if (at(i) == mc) { if (symbols.contains(at(i + 1))) { remove(i); } } return *this; } 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]) { d.push_back(PIChar(str[i])); } changed_ = true; return *this; } PIString & PIString::operator+=(const char16_t * str) { if (!str) return *this; int i = -1; while (str[++i]) { d.push_back(PIChar(str[i])); } changed_ = true; return *this; } PIString & PIString::operator+=(const PIString & str) { d.append(str.d); changed_ = true; return *this; } PIString & PIString::operator+=(const PIConstChars & str) { if (!str.isEmpty()) { size_t os = d.size(); d.enlarge(str.size()); for (size_t l = 0; l < d.size(); ++l) { d[os + l] = str[l]; } } changed_ = true; return *this; } bool PIString::operator==(const PIString & str) const { return d == str.d; } bool PIString::operator!=(const PIString & str) const { return d != str.d; } bool PIString::operator<(const PIString & str) const { size_t l = str.size(); if (size() < l) return true; if (size() > l) return false; for (size_t i = 0; i < l; ++i) { if (at(i) == str.at(i)) continue; if (at(i) < str.at(i)) return true; else return false; } return false; } bool PIString::operator>(const PIString & str) const { size_t l = str.size(); if (size() < l) return false; if (size() > l) return true; for (size_t i = 0; i < l; ++i) { if (at(i) == str.at(i)) continue; if (at(i) < str.at(i)) return false; else return true; } return false; } //! \~\details //! \~english //! If "len" < 0 then returns substring from symbol "start" to end. //! "start" should be >= 0. //! \~russian //! Если "len" < 0 тогда возвращается подстрока от символа "start" и до конца. //! "start" должен быть >= 0. //! \~\code //! PIString s("0123456789"); //! piCout << s.mid(0, -1); // s = "0123456789" //! piCout << s.mid(0, 2); // s = "01" //! piCout << s.mid(3); // s = "3456789" //! piCout << s.mid(3, 4); // s = "3456" //! piCout << s.mid(7, 1); // s = "7" //! piCout << s.mid(7, 4); // s = "789" //! piCout << s.mid(-1); // s = "" //! \endcode //! \~\sa \a left(), \a right() PIString PIString::mid(int start, int len) const { if (len == 0 || start >= length()) return PIString(); if (start < 0) { return PIString(); } if (len < 0) { return PIString(d.data(start), size_s() - start); } else { if (len > length() - start) len = length() - start; return PIString(d.data(start), len); } return PIString(); } //! \~\details //! \~english //! If "len" < 0 then remove substring from symbol "start" to end. //! "start" should be >= 0. //! \~russian //! Если "len" < 0 тогда удаляется подстрока от символа "start" и до конца. //! "start" должен быть >= 0. //! \~\code //! PIString s("0123456789"); //! s.cutMid(1, 3); //! piCout << s; // s = "0456789" //! s.cutMid(0, 2); //! piCout << s; // s = "56789" //! s.cutMid(3, -1); //! piCout << s; // s = "567" //! s.cutMid(-1, -1); //! piCout << s; // s = "567" //! \endcode //! \~\sa \a cutLeft(), \a cutRight() PIString & PIString::cutMid(int start, int len) { if (len == 0) return *this; if (start < 0) { return *this; } if (len < 0) { d.remove(start, size() - start); } else { if (len > length() - start) len = length() - start; d.remove(start, len); } changed_ = true; 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(d.data(from), with.d.data(), count * sizeof(PIChar)); } else { d.remove(from, count); d.insert(from, with.d); } changed_ = true; 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) d.insert(i, PIDeque((size_t)dl)); if (dl < 0) d.remove(i, -dl); memcpy(d.data(i), with.d.data(), with.size() * sizeof(PIChar)); i += dl; } } changed_ = true; 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) d.remove(i, dl); d[i] = PIChar(with); } changed_ = true; 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) d[i] = with; } changed_ = true; 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 (d.at(j + i) != str.at(j)) { match = false; break; } } if (!match) continue; d.remove(i, l); i -= l; } changed_ = true; return *this; } PIString & PIString::insert(int index, const PIString & str) { d.insert(index, str.d); changed_ = true; return *this; } PIString & PIString::elide(int size, float pos) { static const PIString s_dotdot = ".."_a; 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); d.remove(ls, length() - ns); d.insert(ls, s_dotdot.d); changed_ = true; 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; } int PIString::findLast(PIChar 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 = at(f - 1); if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) { ok = false; continue; } } if (f + wl < tl) { c = at(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 = at(f - 1); if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || (c != '_' && !c.isAlpha() && !c.isDigit()))) { ok = false; continue; } } if (f + wl < tl) { c = at(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 PIChar c) const { if (isEmpty()) return false; return front() == c; } bool PIString::startsWith(const PIString & str) const { if (size() < str.size()) return false; return str == left(str.size()); } bool PIString::endsWith(const PIChar c) const { if (isEmpty()) return false; return back() == c; } 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 = "true"_a; static const PIString s_yes = "yes"_a; static const PIString s_on = "on"_a; static const PIString s_ok = "ok"_a; PIString s(*this); s = s.trimmed().toLowerCase(); if (s == s_true || s == s_yes || s == s_on || s == s_ok) return true; if (toDouble() > 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; } PIString PIString::takeInteger() { 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) ws = i; if (!c.isDigit()) { we = i; break; } if (we >= 0) break; } } ret = mid(ws, we - ws); cutLeft(we < 0 ? sz : we); 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. //! Length of this buffer is count of symbols + end byte '\0'. //! Returned pointer is valid until next execution of this function. //! \~russian //! Этот метод заполняет внутренный байтовый буфер. Размер //! этого буфера равен количеству символов строки + завершающий байт '\0'. //! Возвращаемый указатель действителен до следующего вызова этого метода. //! \~\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. //! Length of this buffer is count of symbols + end byte '\0'. //! Returned pointer is valid until next execution of this function. //! \~russian //! Этот метод заполняет внутренный байтовый буфер. Размер //! этого буфера равен количеству символов строки + завершающий байт '\0'. //! Возвращаемый указатель действителен до следующего вызова этого метода. //! \~\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. //! Length of this buffer is count of symbols + end byte '\0'. //! Returned pointer is valid until next execution of this function. //! \~russian //! Этот метод заполняет внутренный байтовый буфер. Размер //! этого буфера равен количеству символов строки + завершающий байт '\0'. //! Возвращаемый указатель действителен до следующего вызова этого метода. //! \~\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 + end byte '\0'. //! Returned pointer is valid until next execution of this function. //! \~russian //! Этот метод заполняет внутренный байтовый буфер. Размер //! этого буфера равен количеству символов строки + завершающий байт '\0'. //! Возвращаемый указатель действителен до следующего вызова этого метода. //! \~\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.d[i] = str.d[i].toUpper(); } return str; } PIString PIString::toLowerCase() const { PIString str(*this); int l = str.size(); for (int i = 0; i < l; ++i) { str.d[i] = str.d[i].toLower(); } return str; } char PIString::toChar() const { char v; sscanf(dataAscii(), "%c", &v); return v; } float PIString::toFloat() const { return toDecimal(*this); } double PIString::toDouble() const { return toDecimal(*this); } ldouble PIString::toLDouble() const { return toDecimal(*this); } //! \~\details //! \~english //! Example: //! \~russian //! Пример: //! \~\code //! PIString s; //! s.setReadableSize(512); //! piCout << s; // 512 B //! s.setReadableSize(5120); //! piCout << s; // 5.0 KiB //! s.setReadableSize(512000); //! piCout << s; // 500.0 KiB //! s.setReadableSize(5120000); //! piCout << s; // 4.8 MiB //! s.setReadableSize(512000000); //! piCout << s; // 488.2 MiB //! s.setReadableSize(51200000000); //! piCout << s; // 47.6 GiB //! \endcode PIString & PIString::setReadableSize(llong bytes) { clear(); if (bytes < 1024) { *this += (PIString::fromNumber(bytes) + " B"_a); return *this; } double fres = bytes / 1024.; llong res = bytes / 1024; fres -= res; if (res < 1024) { *this += (PIString::fromNumber(res) + "."_a + PIString::fromNumber(llong(fres * 10)).left(1) + " KiB"_a); return *this; } fres = res / 1024.; res /= 1024; fres -= res; if (res < 1024) { *this += (PIString::fromNumber(res) + "."_a + PIString::fromNumber(llong(fres * 10)).left(1) + " MiB"_a); return *this; } fres = res / 1024.; res /= 1024; fres -= res; if (res < 1024) { *this += (PIString::fromNumber(res) + "."_a + PIString::fromNumber(llong(fres * 10)).left(1) + " GiB"_a); return *this; } fres = res / 1024.; res /= 1024; fres -= res; if (res < 1024) { *this += (PIString::fromNumber(res) + "."_a + PIString::fromNumber(llong(fres * 10)).left(1) + " TiB"_a); return *this; } fres = res / 1024.; res /= 1024; fres -= res; *this += (PIString::fromNumber(res) + "."_a + PIString::fromNumber(llong(fres * 10)).left(1) + " PiB"_a); return *this; } const static PIString _versionDelims_ = "._-+"_a; 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, ".0"_a); } } 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("pre"_a)) { s.cutLeft(3); ret -= 1; } if (s.startsWith("rc"_a)) { s.cutLeft(2); ret += s.toInt(); } if (s.startsWith("r"_a)) { s.cutLeft(1); ret += 10000 + s.toInt(); } if (s == "alpha"_a) ret -= 4; if (s == "beta"_a) 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; }