Files
pip/libs/main/text/pistring.cpp
peri4 caa7880cc4 get rid of piForeach
apply some code analyzer recommendations
ICU flag now check if libicu exists
prepare for more accurate growth of containers (limited PoT, then constantly increase size)
2024-11-20 20:01:47 +03:00

1986 lines
50 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#include "pistring.h"
#include "piincludes_p.h"
#include "piliterals.h"
#include "pimathbase.h"
#include "pistringlist.h"
#include "pitranslator.h"
#ifdef PIP_ICU
# define U_NOEXCEPT
# include "unicode/ucnv.h"
#endif
#ifdef WINDOWS
# include <stringapiset.h>
#endif
#include <codecvt>
#include <locale>
#include <string>
//! \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<typename T>
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<int> 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);
if (s <= 0) return;
#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<std::codecvt_utf8<char16_t>, 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<std::codecvt_utf8<char16_t>, 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;
}
}
}
PIString PIString::minArgPlaceholder() {
if (size() < 2) return {};
int min = -1;
PIString ret, tmp;
for (int i = 0; i < size_s(); ++i) {
if (at(i) == '%') {
int j = 0;
for (j = i + 1; j < size_s(); ++j) {
if (!at(j).isDigit()) break;
}
bool ok = false;
tmp = mid(i + 1, j - i - 1);
int cur = tmp.toInt(10, &ok);
if (!ok) continue;
if (min < 0 || min > cur) {
min = cur;
ret = tmp;
}
}
}
if (ret.isEmpty()) return {};
return "%" + ret;
}
uint PIString::hash() const {
return piHashData((const uchar *)d.data(), d.size() * sizeof(PIChar));
}
PIByteArray PIString::toAscii() const {
if (isEmpty()) return PIByteArray();
PIByteArray ret;
ret.resize(size());
for (int i = 0; i < size_s(); ++i) {
ret[i] = uchar(at(i).ch);
}
return ret;
}
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<PIChar>((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 <PIP>");
//! piCout << s.findWord("this"); // 0
//! piCout << s.findWord("is"); // 5
//! piCout << s.findWord("PIP", 4); // -1
//! piCout << s.findWord("<PIP>", 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 <PIP>");
//! piCout << s.findCWord("this"); // 0
//! piCout << s.findCWord("is"); // 6
//! piCout << s.findCWord("PIP", 4); // 10
//! piCout << s.findCWord("<PIP>", 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;
}
int PIString::entries(const PIString & str) const {
int sz = size_s(), ssz = str.size_s();
if (ssz == 0 || sz < ssz) return 0;
int btc = str.size_s() * sizeof(PIChar), ret = 0;
for (int i = 0; i < sz - ssz + 1; ++i) {
if (piCompareBinary(d.data(i), str.d.data(0), btc)) ++ret;
}
return ret;
}
int PIString::lineNumber(int pos) const {
if (isEmpty()) return 0;
if (pos < 0 || pos >= size_s()) pos = size_s() - 1;
int line = 1;
for (int i = 0; i < pos; ++i) {
if (at(i) == '\n') ++line;
}
return line;
}
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;
default: 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<float>(*this);
}
double PIString::toDouble() const {
return toDecimal<double>(*this);
}
ldouble PIString::toLDouble() const {
return toDecimal<ldouble>(*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) + " "_a + "B"_tr("PIString"));
return *this;
}
double fres = bytes;
llong res = bytes;
auto checkRange = [this, &fres, &res](const PIString & unit, bool last = false) {
fres = res / 1024.;
res /= 1024;
fres -= res;
if (res < 1024 || last) {
*this += (PIString::fromNumber(res) + "."_a + PIString::fromNumber(llong(fres * 10)).left(1) + " "_a + unit);
return true;
}
return false;
};
if (checkRange("KiB"_tr("PIString"))) return *this;
if (checkRange("MiB"_tr("PIString"))) return *this;
if (checkRange("GiB"_tr("PIString"))) return *this;
if (checkRange("TiB"_tr("PIString"))) return *this;
if (checkRange("PiB"_tr("PIString"))) return *this;
if (checkRange("EiB"_tr("PIString"))) return *this;
if (checkRange("ZiB"_tr("PIString"))) return *this;
checkRange("YiB"_tr("PIString"), true);
return *this;
}
PIString & PIString::arg(const PIString & v) {
auto ph = minArgPlaceholder();
if (!ph.isEmpty()) replaceAll(ph, v);
return *this;
}
const static PIString _versionDelims_ = "._-+"_a;
void parseVersion(PIString s, PIVector<int> & 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<int> 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<int> 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;
}