|
|
[](https://github.com/GyverLibs/StringUtils/releases/latest/download/StringUtils.zip)
|
|
|
[](https://alexgyver.ru/)
|
|
|
[](https://alexgyver.ru/support_alex/)
|
|
|
[](https://github-com.translate.goog/GyverLibs/StringUtils?_x_tr_sl=ru&_x_tr_tl=en)
|
|
|
|
|
|
[](https://t.me/GyverLibs)
|
|
|
|
|
|
# StringUtils
|
|
|
Набор инструментов для работы со строками
|
|
|
- Быстрые функции конвертации
|
|
|
- Парсинг, разбивание по разделителям
|
|
|
- Несколько классов-конвертеров данных в строку и обратно для использования в других библиотеках
|
|
|
- Кодирование и раскодирование base64, url, unicode, йцукен/qwerty-раскладки
|
|
|
|
|
|
### Совместимость
|
|
|
Совместима со всеми Arduino платформами (используются Arduino-функции)
|
|
|
|
|
|
## Содержание
|
|
|
- [Документация](#docs)
|
|
|
- [Версии](#versions)
|
|
|
- [Установка](#install)
|
|
|
- [Баги и обратная связь](#feedback)
|
|
|
|
|
|
<a id="docs"></a>
|
|
|
|
|
|
## Документация
|
|
|
### su::Text
|
|
|
Класс-обёртка для всех типов строк. Может быть создана в конструкторе из:
|
|
|
- `"const char"` - строки
|
|
|
- `char[]` - строки
|
|
|
- `F("f-строки")`
|
|
|
- `PROGMEM` - строки
|
|
|
- `String` - строки
|
|
|
|
|
|
Особенности:
|
|
|
- Хранит тип и длину строки
|
|
|
- Позволяет **печататься**, **конвертироваться** в любой целочисленный формат и **сравниваться** с переменными всех стандартных типов, а также сравниваться с любыми другими строками
|
|
|
- Вывод в подстроки разными способами, поиск и разделение
|
|
|
- **Не может изменять исходную строку**, все операции только "для чтения"
|
|
|
- **Не создаёт копию строки** и работает с оригинальной строкой, т.е. *оригинальная строка должна быть в памяти на время существования Text*
|
|
|
- Если создана из String строки, то оригинальная String строка не должна меняться в процессе работы экземпляра Text
|
|
|
|
|
|
```cpp
|
|
|
// ====== КОНСТРУКТОР ======
|
|
|
su::Text(String& str);
|
|
|
su::Text(const String& str);
|
|
|
su::Text(const __FlashStringHelper* str, int16_t len = 0);
|
|
|
su::Text(const char* str, int16_t len = 0, bool pgm = 0);
|
|
|
|
|
|
// ======== СИСТЕМА ========
|
|
|
bool valid(); // Статус строки, существует или нет
|
|
|
bool pgm(); // Строка из Flash памяти
|
|
|
uint16_t length(); // Длина строки
|
|
|
uint16_t lengthUnicode();// Длина строки с учётом unicode символов
|
|
|
uint16_t readLen(); // посчитать и вернуть длину строки (const)
|
|
|
void calcLen(); // пересчитать и запомнить длину строки (non-const)
|
|
|
Type type(); // Тип строки
|
|
|
const char* str(); // Получить указатель на строку. Всегда вернёт ненулевой указатель
|
|
|
const char* end(); // указатель на конец строки. Всегда вернёт ненулевой указатель
|
|
|
bool terminated(); // строка валидна и оканчивается \0
|
|
|
|
|
|
// ======== ХЭШ ========
|
|
|
size_t hash(); // хэш строки size_t
|
|
|
uint32_t hash32(); // хэш строки 32 бит
|
|
|
|
|
|
// ======== PRINT ========
|
|
|
size_t printTo(Print& p); // Напечатать в Print (c учётом длины)
|
|
|
|
|
|
// ======== СРАВНЕНИЕ И ПОИСК ========
|
|
|
// сравнивается со всеми типами строк через ==
|
|
|
|
|
|
// Сравнить со строкой, начиная с индекса
|
|
|
bool compare(Text s, uint16_t from = 0);
|
|
|
|
|
|
// Сравнить со строкой, начиная с индекса, с указанием количества символов
|
|
|
bool compareN(Text s, uint16_t amount, uint16_t from = 0);
|
|
|
|
|
|
// Найти позицию символа в строке, начиная с индекса
|
|
|
int16_t indexOf(char sym, uint16_t from = 0);
|
|
|
|
|
|
// Найти позицию строки в строке
|
|
|
int16_t indexOf(Text txt, uint16_t from = 0);
|
|
|
|
|
|
// Найти позицию символа в строке с конца
|
|
|
int16_t lastIndexOf(char sym);
|
|
|
|
|
|
// Найти позицию строки в строке с конца
|
|
|
int16_t lastIndexOf(Text txt);
|
|
|
|
|
|
// найти символ и получить указатель на первое вхождение
|
|
|
const char* find(char sym, uint16_t from = 0);
|
|
|
|
|
|
// начинается со строки
|
|
|
bool startsWith(const Text& txt);
|
|
|
|
|
|
// заканчивается строкой
|
|
|
bool endsWith(const Text& txt);
|
|
|
|
|
|
// ======== РАЗДЕЛЕНИЕ И ПАРСИНГ ========
|
|
|
// вернёт новую строку с убранными пробельными символами с начала и конца
|
|
|
Text trim();
|
|
|
|
|
|
// Посчитать количество подстрок, разделённых символом (количество разделителей +1)
|
|
|
uint16_t count(char sym);
|
|
|
|
|
|
// Посчитать количество подстрок, разделённых строками (количество разделителей +1)
|
|
|
uint16_t count(Text txt);
|
|
|
|
|
|
// Разделить по символу-разделителю в массив любого типа
|
|
|
uint16_t split(T* arr, uint16_t len, char div);
|
|
|
|
|
|
// Получить подстроку из списка по индексу
|
|
|
Text getSub(uint16_t idx, char div);
|
|
|
|
|
|
// Получить подстроку из списка по индексу
|
|
|
Text getSub(uint16_t idx, Text div);
|
|
|
|
|
|
// выделить подстроку (начало, конец не включая). Отрицательные индексы работают с конца строки
|
|
|
Text substring(int16_t start, int16_t end = 0);
|
|
|
|
|
|
// Получить символ по индексу
|
|
|
char charAt(uint16_t idx);
|
|
|
|
|
|
// ======== ВЫВОД. СТРОКИ ========
|
|
|
// Получить как String строку
|
|
|
String toString(bool decodeUnicode = false);
|
|
|
|
|
|
// Вывести в String строку. Вернёт false при неудаче
|
|
|
bool toString(String& s, bool decodeUnicode = false);
|
|
|
|
|
|
// Добавить к String строке. Вернёт false при неудаче
|
|
|
bool addString(String& s, bool decodeUnicode = false);
|
|
|
|
|
|
// Вывести в char массив. Вернёт длину строки. terminate - завершить строку нулём
|
|
|
uint16_t toStr(char* buf, int16_t bufsize = -1, bool terminate = true);
|
|
|
|
|
|
// ======== ВЫВОД. B64 ========
|
|
|
// размер данных (байт), если они b64
|
|
|
size_t sizeB64();
|
|
|
|
|
|
// вывести в переменную из b64
|
|
|
bool decodeB64(void* var, size_t size);
|
|
|
|
|
|
// ======== ВЫВОД. ЧИСЛА ========
|
|
|
bool toBool(); // получить значение как bool
|
|
|
int16_t toInt16(); // получить значение как int16
|
|
|
int32_t toInt32(); // получить значение как int32
|
|
|
int64_t toInt64(); // получить значение как int64
|
|
|
uint32_t toInt32HEX(); // получить значение как uint 32 из HEX строки
|
|
|
float toFloat(); // получить значение как float
|
|
|
|
|
|
// также автоматически конвертируется и сравнивается с
|
|
|
bool
|
|
|
char + unsigned
|
|
|
short + unsigned
|
|
|
int + unsigned
|
|
|
long + unsigned
|
|
|
long long + unsigned
|
|
|
float
|
|
|
double
|
|
|
String
|
|
|
|
|
|
// для ручного управления строкой
|
|
|
const char* _str; // указатель на строку
|
|
|
uint16_t _len; // длина
|
|
|
```
|
|
|
|
|
|
#### Пример
|
|
|
```cpp
|
|
|
// конструктор
|
|
|
su::Text v0("-123456");
|
|
|
su::Text v1 = "-123456";
|
|
|
v1 = F("-123456");
|
|
|
String s("abcd");
|
|
|
su::Text v2(s);
|
|
|
v2 = s;
|
|
|
|
|
|
// сравнение
|
|
|
v2 == v1;
|
|
|
v2 == F("text");
|
|
|
v1 == -123456;
|
|
|
|
|
|
// авто конвертация
|
|
|
int v = v0;
|
|
|
String s2 = v2;
|
|
|
|
|
|
// вывод в массив
|
|
|
char buf[20];
|
|
|
v1.toStr(buf);
|
|
|
|
|
|
// парсинг и разделение
|
|
|
su::Text list("abc/123/def");
|
|
|
Serial.println(list.getSub(0, '/')); // abc
|
|
|
Serial.println(list.getSub(2, '/')); // def
|
|
|
|
|
|
Serial.println(list.substring(4, 6)); // 123
|
|
|
|
|
|
su::Text arr[3];
|
|
|
list.split(arr, 3, '/');
|
|
|
Serial.println(arr[0]);
|
|
|
Serial.println(arr[1]);
|
|
|
Serial.println(arr[2]);
|
|
|
|
|
|
// парсить можно в любой тип
|
|
|
int arr2[3]; // float, byte...
|
|
|
list.split(arr2, 3, '/');
|
|
|
Serial.println(arr2[0]);
|
|
|
Serial.println(arr2[1]);
|
|
|
Serial.println(arr2[2]);
|
|
|
|
|
|
// так делать НЕЛЬЗЯ
|
|
|
Text t1(String("123")); // строка будет выгружена из памяти!
|
|
|
// t1.... программа сломается
|
|
|
|
|
|
String s;
|
|
|
Text t1(s);
|
|
|
s += String("123"); // адрес строки изменится!
|
|
|
// t1.... программа сломается
|
|
|
|
|
|
// в то же время вот так - можно
|
|
|
void foo(const Text& text) {
|
|
|
// String существует тут
|
|
|
print(text);
|
|
|
}
|
|
|
foo(String("123"));
|
|
|
```
|
|
|
|
|
|
Встроенный разделитель и хэш-функции позволяют очень просто и эффективно разбирать различные текстовые протоколы. Например пакет вида `key=value`, где `key` может отсылать к переменной в коде. Пакет можно разделить, ключ хешировать и опросить через switch для присвоения н ужной переменной:
|
|
|
```cpp
|
|
|
Text txt("key1=1234");
|
|
|
int val = txt.getSub(1, '='); // значение в int
|
|
|
|
|
|
switch (txt.getSub(0, '=').hash()) { // хэш ключа
|
|
|
case su::SH("key1"):
|
|
|
var1 = val;
|
|
|
break;
|
|
|
case su::SH("key2"):
|
|
|
var2 = val;
|
|
|
break;
|
|
|
case su::SH("key3"):
|
|
|
var2 = val;
|
|
|
break;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
или протокол вида `name/index/value`, где `name` - текстовый ключ, `index` - порядковый номер:
|
|
|
```cpp
|
|
|
Text txt("key/3/1234");
|
|
|
|
|
|
int val = txt.getSub(2, '/');
|
|
|
|
|
|
switch (txt.getSub(0, '/').hash()) {
|
|
|
case su::SH("key"):
|
|
|
switch(txt.getSub(1, '/').toInt16()) {
|
|
|
case 0: break;
|
|
|
case 1: break;
|
|
|
case 2: break;
|
|
|
//.....
|
|
|
}
|
|
|
break;
|
|
|
case su::SH("keykey"):
|
|
|
//...
|
|
|
break;
|
|
|
case su::SH("anotherKey"):
|
|
|
//...
|
|
|
break;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### su::Value
|
|
|
Добавка к `Text`, поддерживает все остальные стандартные типы данных. Имеет буфер 22 байта, при создании конвертирует число в него:
|
|
|
```cpp
|
|
|
su::Value(bool value);
|
|
|
su::Value(char + unsigned value, uint8_t base = DEC);
|
|
|
su::Value(short + unsigned value, uint8_t base = DEC);
|
|
|
su::Value(int + unsigned value, uint8_t base = DEC);
|
|
|
su::Value(long + unsigned value, uint8_t base = DEC);
|
|
|
su::Value(long long + unsigned value, uint8_t base = DEC);
|
|
|
su::Value(double value, uint8_t dec = 2);
|
|
|
|
|
|
// аналогично с ручным размером буфера
|
|
|
su::ValueT<размер буфера>();
|
|
|
```
|
|
|
|
|
|
#### Пример
|
|
|
```cpp
|
|
|
su::Value v0("-123456"); // все строки также можно
|
|
|
su::Value v1(123);
|
|
|
su::Value v2(3.14);
|
|
|
su::Value v3((uint64_t)12345678987654321);
|
|
|
|
|
|
// конвертируется из числа в текст
|
|
|
v1 = 10;
|
|
|
v1 = 3.14;
|
|
|
v1 = 12345654321ull;
|
|
|
|
|
|
Serial.println(v0); // печатается в Serial
|
|
|
Serial.println(v1 == v2); // сравнивается
|
|
|
|
|
|
// сравнивается с любыми строками
|
|
|
su::Text s("123");
|
|
|
String ss = "123";
|
|
|
Serial.println(s == "123");
|
|
|
Serial.println(s == F("123"));
|
|
|
Serial.println(s == ss);
|
|
|
|
|
|
// конвертируется в любой тип
|
|
|
float f = v2; // f == 3.14
|
|
|
int i = v1; // i = 123
|
|
|
|
|
|
// выводится в String
|
|
|
String S;
|
|
|
v0.toString(s);
|
|
|
|
|
|
// выводится в char[]
|
|
|
char buf[v1.length() + 1]; // +1 для '\0'
|
|
|
v1.toStr(buf);
|
|
|
```
|
|
|
|
|
|
#### Использование в библиотеках
|
|
|
На базе `Text` построены следующие библиотеки:
|
|
|
- [GSON](https://github.com/GyverLibs/GSON)
|
|
|
- [GyverHub](https://github.com/GyverLibs/GyverHub)
|
|
|
- [Pairs](https://github.com/GyverLibs/Pairs)
|
|
|
- [FastBot2](https://github.com/GyverLibs/FastBot2)
|
|
|
|
|
|
##### Передача текста в функцию
|
|
|
- Строки любого типа
|
|
|
- Без аллокаций, что чрезвычайно критично при сборке String
|
|
|
- Без создания десятка перегруженных функций
|
|
|
|
|
|
Например нужна функция, принимающая строку в любом виде. В ванильном фреймворке Arduino можно сделать так:
|
|
|
```cpp
|
|
|
void setText(const String& str) {
|
|
|
// и например прибавить к строке
|
|
|
s += str;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Такая функция сможет принимать любые строки:
|
|
|
```cpp
|
|
|
setText("const literal");
|
|
|
setText(F("F-string"));
|
|
|
|
|
|
char str[] = "buffer string";
|
|
|
setText(str);
|
|
|
|
|
|
String s = "Arduino String";
|
|
|
setText(s);
|
|
|
```
|
|
|
Но эта строка будет *продублирована* в конструкторе `String`, и самое страшное - в динамической памяти! Таким образом при прибавлении к условно-глобальной String в этой области определения случится переаллокация и фрагментирование памяти. `Text` позволяет полностью этого избежать:
|
|
|
|
|
|
```cpp
|
|
|
void setText(const Text& str) {
|
|
|
// и например прибавить к строке
|
|
|
str.addString(s);
|
|
|
}
|
|
|
```
|
|
|
Теперь эта функция так же умеет принимать строки в любом формате, но **не создаёт их копии**, и например прибавление к строке становится быстрым и безопасным.
|
|
|
|
|
|
##### Вывод текста
|
|
|
Также Text удобен для вывода, например в классе, который хранит буфер и сам наполняет его данными и знает их длину:
|
|
|
|
|
|
```cpp
|
|
|
class MyClass {
|
|
|
public:
|
|
|
su::Text get() {
|
|
|
return su::Text(buffer, len);
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
char buffer[20];
|
|
|
int len;
|
|
|
};
|
|
|
|
|
|
MyClass s;
|
|
|
Serial.println(s.get());
|
|
|
```
|
|
|
|
|
|
Вариант с наследованием:
|
|
|
```cpp
|
|
|
class MyClass : public su::Text {
|
|
|
public:
|
|
|
void foo() {
|
|
|
su::Text::_str = buffer;
|
|
|
su::Text::_len = somelen;
|
|
|
}
|
|
|
|
|
|
private:
|
|
|
char buffer[20];
|
|
|
};
|
|
|
|
|
|
MyClass s;
|
|
|
Serial.println(s);
|
|
|
```
|
|
|
|
|
|
Если вместо `Text` использовать `Value` - функция сможет принимать также любые численные данные.
|
|
|
|
|
|
### su::TextList
|
|
|
Разделитель `Text` списков на `Text` подстроки.
|
|
|
|
|
|
#### Статический
|
|
|
```cpp
|
|
|
TextListT<int16_t cap>(Text list, char div);
|
|
|
TextListT<int16_t cap>(Text list, Text div);
|
|
|
|
|
|
// количество построк
|
|
|
uint16_t length();
|
|
|
|
|
|
// размер буфера
|
|
|
uint16_t capacity();
|
|
|
|
|
|
// получить подстроку под индексом
|
|
|
const Text& get(uint16_t idx);
|
|
|
const Text& operator[](int idx);
|
|
|
```
|
|
|
|
|
|
#### Динамический
|
|
|
```cpp
|
|
|
TextList(Text list, char div);
|
|
|
TextList(Text list, Text div);
|
|
|
// количество построк
|
|
|
uint16_t length();
|
|
|
|
|
|
// получить подстроку под индексом
|
|
|
const Text& get(uint16_t idx);
|
|
|
const Text& operator[](int idx);
|
|
|
```
|
|
|
|
|
|
### Устаревшие парсеры
|
|
|
<details>
|
|
|
<summary>Развернуть</summary>
|
|
|
|
|
|
### su::Parser
|
|
|
Разделение строки на подстроки по разделителю в цикле. **Изменяет** исходную строку, но после завершения возвращает разделители на место.
|
|
|
|
|
|
```cpp
|
|
|
su::Parser p(String& str, char div = ';');
|
|
|
su::Parser p(const char* str, char div = ';');
|
|
|
|
|
|
bool next(); // парсить следующую подстроку. Вернёт false, если парсинг закончен
|
|
|
uint8_t index(); // индекс текущей подстроки
|
|
|
const char* str(); // получить подстроку
|
|
|
Text get(); // получить подстроку как Text
|
|
|
```
|
|
|
|
|
|
#### Пример
|
|
|
```cpp
|
|
|
char buf[] = "123;456;abc";
|
|
|
|
|
|
su::Parser p(buf);
|
|
|
while (p.next()) {
|
|
|
Serial.print(p.index());
|
|
|
Serial.print(": ");
|
|
|
Serial.println(p.get());
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### su::Splitter
|
|
|
Разделение строки на подстроки по разделителю в цикле. **Изменяет** исходную строку! После удаления объекта строка восстанавливается, либо вручную вызвать `restore()`
|
|
|
```cpp
|
|
|
su::SplitterT<макс. подстрок> spl(String& str, char div = ';');
|
|
|
su::SplitterT<макс. подстрок> spl(const char* str, char div = ';');
|
|
|
|
|
|
su::Splitter spl(String& str, char div = ';'); // авто-размер (выделяется в heap)
|
|
|
su::Splitter spl(const char* str, char div = ';'); // авто-размер (выделяется в heap)
|
|
|
|
|
|
void setDiv(char div); // установить разделитель
|
|
|
void restore(); // восстановить строку (вернуть разделители)
|
|
|
uint8_t length(); // количество подстрок
|
|
|
const char* str(uint16_t idx); // получить подстроку по индексу
|
|
|
Text get(uint16_t idx); // получить подстроку по индексу как Text
|
|
|
```
|
|
|
|
|
|
#### Пример
|
|
|
```cpp
|
|
|
char buf[] = "123;456;abc";
|
|
|
|
|
|
su::Splitter spl(buf);
|
|
|
for (uint8_t i = 0; i < spl.length(); i++) {
|
|
|
Serial.print(i);
|
|
|
Serial.print(": ");
|
|
|
Serial.println(spl[i]);
|
|
|
}
|
|
|
|
|
|
spl.restore();
|
|
|
```
|
|
|
|
|
|
### su::list функции
|
|
|
```cpp
|
|
|
// Получить количество подстрок в списке
|
|
|
uint16_t su::list::length(Text list, char div = ';');
|
|
|
|
|
|
// Получить индекс подстроки в списке
|
|
|
int16_t su::list::indexOf(Text list, Text str, char div = ';');
|
|
|
|
|
|
// Проверка содержит ли список подстроку
|
|
|
bool su::list::includes(Text list, Text str, char div = ';');
|
|
|
|
|
|
// Получить подстроку из списка по индексу
|
|
|
Text su::list::get(Text list, uint16_t idx, char div = ';');
|
|
|
|
|
|
// распарсить в массив указанного типа и размера. Вернёт количество записанных подстрок
|
|
|
template <typename T>
|
|
|
uint16_t su::list::parse(Text list, T* buf, uint16_t len, char div = ';');
|
|
|
```
|
|
|
|
|
|
#### Пример
|
|
|
```cpp
|
|
|
Serial.println(su::list::length("123;456;333")); // 3
|
|
|
Serial.println(su::list::includes("123;456;333", "456")); // true
|
|
|
Serial.println(su::list::indexOf("123;456;333", "333")); // 2
|
|
|
Serial.println(su::list::get("123;456;333", 1)); // 456
|
|
|
|
|
|
// распарсить в массив
|
|
|
float arr[3];
|
|
|
su::list::parse(F("3.14;2.54;15.15"), arr, 3);
|
|
|
```
|
|
|
|
|
|
### su::List класс
|
|
|
Получение подстрок по разделителям **без модификации исходной строки**, работает также с PROGMEM строками.
|
|
|
```cpp
|
|
|
List(Text);
|
|
|
|
|
|
// установить разделитель
|
|
|
void setDiv(char div);
|
|
|
|
|
|
// получить размер списка
|
|
|
uint16_t length();
|
|
|
|
|
|
// получить индекс подстроки в списке или -1 если её нет
|
|
|
int16_t indexOf(Text str);
|
|
|
|
|
|
// проверить наличие подстроки в списке
|
|
|
bool includes(Text str);
|
|
|
|
|
|
// получить подстроку под индексом
|
|
|
Text get(uint16_t idx);
|
|
|
|
|
|
// распарсить в массив указанного типа и размера. Вернёт количество записанных подстрок
|
|
|
template <typename T>
|
|
|
uint16_t parse(T* buf, uint16_t len);
|
|
|
```
|
|
|
|
|
|
#### Пример
|
|
|
```cpp
|
|
|
su::List list(F("123;456;333"));
|
|
|
Serial.print("len: ");
|
|
|
Serial.println(list.length()); // 3
|
|
|
Serial.print("index 2: ");
|
|
|
Serial.println(list[2]); // 333
|
|
|
Serial.print("index of '456':");
|
|
|
Serial.println(list.indexOf(F("456"))); // 1
|
|
|
Serial.print("index of '789':");
|
|
|
Serial.println(list.indexOf("789")); // -1
|
|
|
|
|
|
// переписать в массив
|
|
|
int arr[3];
|
|
|
list.parse(arr, 3);
|
|
|
```
|
|
|
</details>
|
|
|
|
|
|
### su::PrintString
|
|
|
```cpp
|
|
|
// строка, в которую можно делать print/println
|
|
|
su::PrintString prs;
|
|
|
prs += "как обычный String";
|
|
|
|
|
|
prs.print(10);
|
|
|
prs.println("hello");
|
|
|
prs.print(F("print!"));
|
|
|
|
|
|
Serial.println(prs);
|
|
|
```
|
|
|
|
|
|
### QWERTY
|
|
|
```cpp
|
|
|
// Изменить раскладку (RU в QWERTY) - String
|
|
|
String su::toQwerty(const String& ru);
|
|
|
|
|
|
// Изменить раскладку (RU в QWERTY) - char* (qw длина как ru + 1, функция добавит '\0')
|
|
|
char* su::toQwerty(const char* ru, char* qw);
|
|
|
```
|
|
|
|
|
|
### Base64
|
|
|
```cpp
|
|
|
// размер закодированных данных по размеру исходных
|
|
|
size_t su::b64::encodedLen(size_t len);
|
|
|
|
|
|
// будущий размер декодированных данных по строке b64 и её длине
|
|
|
size_t su::b64::decodedLen(const char* b64, size_t len);
|
|
|
|
|
|
// закодировать данные в String
|
|
|
void su::b64::encode(String* b64, uint8_t* data, size_t len, bool pgm = false);
|
|
|
|
|
|
// закодировать данные в char[] (библиотека не добавляет '\0' в конец)
|
|
|
void su::b64::encode(char* b64, uint8_t* data, size_t len, bool pgm = false);
|
|
|
|
|
|
// раскодировать данные из строки b64 в буфер data
|
|
|
void su::b64::decode(uint8_t* data, const char* b64, size_t len);
|
|
|
void su::b64::decode(uint8_t* data, const String& b64);
|
|
|
```
|
|
|
|
|
|
### Unicode
|
|
|
Декодер строки, содержащей unicode символы вида `\u0abc`. Также делает unescape символов `\t\r\n`!
|
|
|
```cpp
|
|
|
// декодировать строку.Зарезервировать строку на длину len. Иначе - по длине строки
|
|
|
String su::unicode::decode(const char* str, uint16_t len = 0);
|
|
|
|
|
|
// декодировать строку
|
|
|
String su::unicode::decode(const String& str);
|
|
|
|
|
|
// кодировать unicode символ по его коду. В массиве должно быть 5 ячеек
|
|
|
void su::unicode::encode(char* str, uint32_t c);
|
|
|
|
|
|
// кодировать unicode символ по его коду
|
|
|
String su::unicode::encode(uint32_t code);
|
|
|
```
|
|
|
|
|
|
### URL
|
|
|
```cpp
|
|
|
// символ должен быть urlencoded
|
|
|
bool su::url::needsEncode(char c);
|
|
|
|
|
|
// закодировать в url
|
|
|
void su::url::encode(const char* src, uint16_t len, String& dest);
|
|
|
void su::url::encode(const String& src, String& dest);
|
|
|
String su::url::encode(const String& src);
|
|
|
|
|
|
// раскодировать url
|
|
|
void su::url::decode(const char* src, uint16_t len, String& dest);
|
|
|
void su::url::decode(const String& src, String& dest);
|
|
|
String su::url::decode(const String& src);
|
|
|
```
|
|
|
|
|
|
### Hash
|
|
|
Вместо сравнения строк можно сравнивать хэш этих строк, что делает программу компактнее, легче и в большинстве случаев быстрее. Функции, указанные ниже как "считается компилятором" - считаются компилятором, то есть **строка даже не попадает в код программы** - вместо неё подставляется хэш-число:
|
|
|
|
|
|
```cpp
|
|
|
// считается компилятором
|
|
|
constexpr su::size_t su::SH(const char* str); // (String Hash) размер size_t
|
|
|
constexpr su::size_t SH32(const char* str); // (String Hash) размер 32 бит
|
|
|
|
|
|
// считается в рантайме
|
|
|
size_t su::hash(const char* str, int16_t len = -1); // Размер зависит от платформы и соответствует size_t
|
|
|
uint32_t su::hash32(const char* str, int16_t len = -1); // Размер 32 бит
|
|
|
|
|
|
size_t su::hash_P(PGM_P str, int16_t len = -1); // PROGMEM строка, размер size_t
|
|
|
uint32_t su::hash32_P(PGM_P str, int16_t len = -1); // PROGMEM строка, размер 32 бит
|
|
|
```
|
|
|
|
|
|
> На ESP-платах `SH`, `hash` и `hash_P` по умолчанию являются 32-битными!
|
|
|
|
|
|
По проведённому тесту 32-битная версия хэша имеет 7 коллизий из 234450 английских слов, 16-битная версия - 170723 коллизий (что есть 73% - чисто статистическое количество коллизий из расчёта 16 бит - 65536 значений)
|
|
|
|
|
|
#### Пример
|
|
|
Определить, какая строка была "получена". Классический способ со сравнением строк из PROGMEM:
|
|
|
|
|
|
```cpp
|
|
|
char buf[] = "some_text";
|
|
|
|
|
|
if (!strcmp_P(buf, PSTR("abcdef"))) Serial.println(0);
|
|
|
else if (!strcmp_P(buf, PSTR("12345"))) Serial.println(1);
|
|
|
else if (!strcmp_P(buf, PSTR("wrong text"))) Serial.println(2);
|
|
|
else if (!strcmp_P(buf, PSTR("some text"))) Serial.println(3);
|
|
|
else if (!strcmp_P(buf, PSTR("hello"))) Serial.println(4);
|
|
|
else if (!strcmp_P(buf, PSTR("some_text"))) Serial.println(5);
|
|
|
```
|
|
|
|
|
|
Способ с хэшем строки:
|
|
|
```cpp
|
|
|
using su::SH;
|
|
|
using su::hash;
|
|
|
|
|
|
char buf[] = "some_text";
|
|
|
|
|
|
switch (hash(buf)) {
|
|
|
case su::SH("abcdef"): Serial.println(0); break;
|
|
|
case su::SH("12345"): Serial.println(1); break;
|
|
|
case su::SH("wrong text"): Serial.println(2); break;
|
|
|
case su::SH("some text"): Serial.println(3); break;
|
|
|
case su::SH("hello"): Serial.println(4); break;
|
|
|
case su::SH("some_text"): Serial.println(5); break;
|
|
|
}
|
|
|
```
|
|
|
> Один расчёт хэша занимает чуть большее время, чем сравнение со строкой. Но итоговая конструкция из примера выполняется в 2 раза быстрее (на ESP).
|
|
|
|
|
|
> `SH("строки")` в данном примере вообще не попадают в код программы - вместо них подставляется их хэш
|
|
|
|
|
|
### Прочие утилиты
|
|
|
```cpp
|
|
|
// Длина строки с русскими символами
|
|
|
uint16_t su::strlenRu(const char* str);
|
|
|
|
|
|
// Получить длину целого числа
|
|
|
uint8_t su::intLen(int32_t val);
|
|
|
|
|
|
// Получить длину float числа
|
|
|
uint8_t su::floatLen(double val, uint8_t dec);
|
|
|
|
|
|
// Преобразовать строку в целое число
|
|
|
template <typename T>
|
|
|
T su::strToInt(const char* str, uint8_t len = 0);
|
|
|
|
|
|
// Преобразовать PROGMEM строку в целое число
|
|
|
template <typename T>
|
|
|
T su::strToInt_P(const char* str, uint8_t len = 0);
|
|
|
|
|
|
// Преобразовать float в строку с указанием кол-ва знаков после точки
|
|
|
uint8_t su::floatToStr(double val, char* buf, uint8_t dec = 2);
|
|
|
|
|
|
// Преобразовать HEX строку в целое число. Опционально длина
|
|
|
uint32_t su::strToIntHex(const char* str, int8_t len = -1);
|
|
|
|
|
|
// Длина символа в количестве байт
|
|
|
uint8_t su::charSize(char sym);
|
|
|
|
|
|
// Конвертация числа в char* массив. Пишет от начала массива, добавляет '\0', вернёт длину строки
|
|
|
// для int64 макс. длина буфера - 22 символа, для int32 - 12
|
|
|
uint8_t su::uintToStr(uint32_t n, char* buf, uint8_t base = DEC);
|
|
|
uint8_t su::intToStr(int32_t n, char* buf, uint8_t base = DEC);
|
|
|
uint8_t su::uint64ToStr(uint64_t n, char* buf, uint8_t base = DEC);
|
|
|
uint8_t su::int64ToStr(int64_t n, char* buf, uint8_t base = DEC);
|
|
|
|
|
|
// конвертация из строки во float
|
|
|
float su::strToFloat(const char* s);
|
|
|
|
|
|
// конвертация из PROGEMEM строки во float
|
|
|
float su::strToFloat_P(PGM_P s);
|
|
|
|
|
|
// быстрый целочисленный логарифм 10 (длина числа в кол-ве символов)
|
|
|
uint8_t su::getLog10(uint32_t value);
|
|
|
uint8_t su::getLog10(int32_t value);
|
|
|
|
|
|
// быстрое возведение 10 в степень
|
|
|
uint32_t su::getPow10(uint8_t value);
|
|
|
```
|
|
|
|
|
|
<a id="versions"></a>
|
|
|
|
|
|
## Версии
|
|
|
- v1.0
|
|
|
- v1.1.0 - оптимизация, добавлены фичи, исправлены уязвимости
|
|
|
- v1.2 - добавлены хэш-функции
|
|
|
- v1.2.x - мелкие исправления и улучшения
|
|
|
- v1.3 - оптимизация const, добавлены фичи, сравнения
|
|
|
- v1.3.1 - добавлен substring
|
|
|
- v1.3.2 - поддержка ESP8266 версий 2.x
|
|
|
- v1.3.5 - uintToStr: HEX теперь в нижнем регистре как у си-функций
|
|
|
- v1.3.6 - в Text добавлены toInt32HEX(), count(), split() и getSub(). Добавлен парсер TextList
|
|
|
- v1.3.7 - исправлены варнинги на AVR
|
|
|
- v1.4.0 - AnyText переименован в Text, пространство имён sutil - в su, добавлены функции в Text, результат конвертации и substring приведены к Си и JS функциям
|
|
|
|
|
|
<a id="install"></a>
|
|
|
|
|
|
## Установка
|
|
|
- Библиотеку можно найти по названию **StringUtils** и установить через менеджер библиотек в:
|
|
|
- Arduino IDE
|
|
|
- Arduino IDE v2
|
|
|
- PlatformIO
|
|
|
- [Скачать библиотеку](https://github.com/GyverLibs/StringUtils/archive/refs/heads/main.zip) .zip архивом для ручной установки:
|
|
|
- Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64)
|
|
|
- Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32)
|
|
|
- Распаковать и положить в *Документы/Arduino/libraries/*
|
|
|
- (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив
|
|
|
- Читай более подробную инструкцию по установке библиотек [здесь](https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA)
|
|
|
### Обновление
|
|
|
- Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
|
|
|
- Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
|
|
|
- Вручную: **удалить папку со старой версией**, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!
|
|
|
|
|
|
<a id="feedback"></a>
|
|
|
## Баги и обратная связь
|
|
|
При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru)
|
|
|
Библиотека открыта для доработки и ваших **Pull Request**'ов!
|
|
|
|
|
|
При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
|
|
|
- Версия библиотеки
|
|
|
- Какой используется МК
|
|
|
- Версия SDK (для ESP)
|
|
|
- Версия Arduino IDE
|
|
|
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
|
|
|
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
|
|
|
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код |