Парсинг String с русскими символами
- Войдите на сайт для отправки комментариев
Втр, 21/01/2020 - 13:31
До сих пор не сталкивался, думал все просто, а получилось как всегда - не выходит аленький цветочек! После долгих мучений дошло, что проблема именно в типе String, начал разбираться, для чего черкнул простенький пример:
String s ="Привет";
Serial.println(s); //печатаю всю строку, печатается "Привет"
Serial.println(s.length()); //печатаю длину строки, печатается 12, хотя по факту 6 символов... Видимо русский символ считается за 2.
for (int i = 0; i <= s.length(); i++) { Serial.print(s[i]); } //печатаю посимвольно - печатает квадратики, символов нет...
Вопрос знающим: как мне взять и напечатать отдельно взятый символ (несколько символов) из русской строки? У меня есть в голове пару вариантов, но все громоздкие костыли. Не может быть, что бы не было простого решения для столь тривиальной проблемы.
за UTF-8 знаешь чонибуть?
за UTF-8 знаешь чонибуть?
Немного знаю, но это не меняет вопроса: есть ли простой стандартный способ выдрать нужный русский символ из String? Например из такой "Hello1234Привет!".
Нет.
хранить и обрабатывать все в однобайтовой кодировке, например в WIN1251, а уже перед выводом преобразовывать как надо.
String s ="Привет"; Serial.println(s); //печатаю всю строку, печатается "Привет" Serial.println(s.length()); //печатаю длину строки, печатается 12, хотя по факту 6 символов... Видимо русский символ считается за 2. for (int i = 0; i <= s.length(); i++) { Serial.print(s[i]); } //печатаю посимвольно - печатает квадратики, символов нет...FoxJone - Вас ваш собственный комментарий в строке 3 ни на какие мысли не наталкивает? "Русский символ" состоит из звух байт, а вы печаете только один - конечно получается ерунда...
FoxJone - Вас ваш собственный комментарий в строке 3 ни на какие мысли не наталкивает? "Русский символ" состоит из звух байт, а вы печаете только один - конечно получается ерунда...
Да знаю я, что национальные символы два байта занимают. Вопрос состоит в том, как быстро и без геммора этот символ распознать и вычленить. Была надежда, что это все делается штатными средствами языка.
Но не больно то и хотелось, мне этот стринг только для отладки нужен, пока шрифты не прорисованы. Пожалуй, начну со шрифтов - их все равно рисовать.
У меня просто идет поток байтов (традиционно не от меня, а от чужой железки), в котором идет текст. Русский, английский, цифры... У них там своя кодировка, каждый байт означает один символ. К примеру, пробел у них байт со значением 0 (Sic!). Руки бы поотрывал...
Мне этот поток надо превратить в символы на табло. Ничего сложного, просто фонт прорисовать. Но я хотел предварительно в мониторе на все это посмотреть. Но не судьба.
Мне этот поток надо превратить в символы на табло. Ничего сложного, просто фонт прорисовать. Но я хотел предварительно в мониторе на все это посмотреть. Но не судьба.
Вы хотите рисовать кастомный фонт под чужую кодировку? - а не проще перекодировать чужую кодировку в кодировку фонта? - по мне так второй метод на порядок менее трудоемкий, к тому же позволяет легко использовать кучу фонтов. а не один единственный.
Вы хотите рисовать кастомный фонт под чужую кодировку? - а не проще перекодировать чужую кодировку в кодировку фонта? - по мне так второй метод на порядок менее трудоемкий, к тому же позволяет легко использовать кучу фонтов. а не один единственный.
Фонт все равно рисовать, его еще не существует в принципе. И вариант с фонтом мне кажется более оптимизированный в плане того, что там просто вызываешь нужный символ по значению байта и никаких лишних телодвижений. А нарисовать фонт - делов то на час, я же не каждый символ буду отрисовывать, а в основном копи-паст.
А нарисовать фонт - делов то на час
ну тут кому что проще :) Написать перекодировщик - это 10, ну может 20 строк на Си... А фонтов готовых в интернете сотни...
ну тут кому что проще :) Написать перекодировщик - это 10, ну может 20 строк на Си... А фонтов готовых в интернете сотни...
Повторюсь: вопрос то в оптимизации. У меня там ДМД работает (а он тяжелый), там 485-й с буфером, там фонты (3 штуки разных одновременно). А давайте еще 20 строк на Си добавим, которые на лету будут перекодировать байтовую посылку (а ведь еще надо таблицу перекодировки...).
А чип то у меня до сих пор 328, мозгов то, как улитки... Давно пора перейти на СТМ, но платы то у меня под 328 разведены (за деньги и новые разводить - тоже деньги...) и на заводе платном мои шаблоны есть, а новые шаблоны тоже денег стоят...
Так и живем)
Чтобы выцепить нужный символ в строке в кодировке UTF-8, надо, побайтово:
1. прочитали байт, вычислили, сколько следующих байт занимает символ;
2. Прочитали эти байты в буфер, получили байты, которые кодируют очередной символ в строке.
Собственно, всё. Кодировщиков/раскодировщиков UTF-8 - как г-на за баней. Если у вас задача работать с UTF-8 для вывода русских слов на дисплей - то тут очень много специфики, например, какая либа используется для работы с TFT, какие шрифты используются, и т.п. В общем случае задача вывода решается введением таблицы перекодировки русских символов выводимой строки (которая в UTF-8), в коды символов используемого шрифта.
Всё решаемо, но зависит от специфики. У меня во всех прошивках UTF-8, русские строки выводятся на дисплей именно по описанному алгоритму.
Вот, если интересно - выдрано из проекта работы с DMD:
int utf8_strlen(const String& str) // возвращает длину в символах строки в кодировке UTF-8 { int c,i,ix,q; for (q=0, i=0, ix=str.length(); i < ix; i++, q++) { c = (unsigned char) str[i]; if (c>=0 && c<=127) i+=0; else if ((c & 0xE0) == 0xC0) i+=1; else if ((c & 0xF0) == 0xE0) i+=2; else if ((c & 0xF8) == 0xF0) i+=3; //else if (($c & 0xFC) == 0xF8) i+=4; // 111110bb //byte 5, unnecessary in 4 byte UTF-8 //else if (($c & 0xFE) == 0xFC) i+=5; // 1111110b //byte 6, unnecessary in 4 byte UTF-8 else return 0; //invalid utf8 } return q; } //-------------------------------------------------------------------------------------------------------------------------------- unsigned int utf8GetCharSize(unsigned char bt) { if (bt < 128) return 1; else if ((bt & 0xE0) == 0xC0) return 2; else if ((bt & 0xF0) == 0xE0) return 3; else if ((bt & 0xF8) == 0xF0) return 4; else if ((bt & 0xFC) == 0xF8) return 5; else if ((bt & 0xFE) == 0xFC) return 6; return 1; } //-------------------------------------------------------------------------------------------------------------------------------- bool utf8ToUInt(const String& bytes, unsigned int& target) { unsigned int bsize = bytes.length(); target = 0; bool result = true; if (bsize == 1) { if ((bytes[0] >> 7) != 0) { result = false; } else target = bytes[0]; } else { unsigned char cur_byte = bytes[0]; target = ((cur_byte & (0xFF >> (bsize + 1))) << (6 * (bsize - 1))); unsigned int i = 1; while (result && (i < bsize)) { cur_byte = bytes[i]; if ((cur_byte >> 6) != 2) { result = false; } else target |= ((cur_byte & 0x3F) << (6 * (bsize - 1 - i))); i++; } } return result; } //-------------------------------------------------------------------------------------------------------------------------------- String convertFromUTF8ToDisplayFont(const String& src) { String result; const char* ptr = src.c_str(); while(*ptr) { byte curChar = (byte) *ptr; unsigned int charSz = utf8GetCharSize(curChar); // у нас максимальная длина символа в UTF-8 - 6 байт, поэтому используем промежуточный буфер в 7 байт static char curBytes[7] = {0}; for(byte i=0;i<charSz;i++) { curBytes[i] = *ptr++; } curBytes[charSz] = '\0'; // добавляем завершающий 0 // Serial.print(curBytes); // Serial.print(" = "); unsigned int myChar; if(utf8ToUInt(curBytes,myChar)) { if(myChar <= 0x7F) result += (char) myChar; else if(myChar == 0x401) // Ё result += (char) 0xF0; else if(myChar == 0x451) // ё result += (char) 0xF1; else if(myChar >= 0x410 && myChar <= 0x43F) // А-Яa-п result += (char) (myChar - 0x390); else if(myChar >= 0x440 && myChar <= 0x44F) // р-я result += (char) (myChar - 0x360); // Serial.print("0x"); // Serial.println(myChar,HEX); } } // while return result; } //--------------------------------------------------------------------------------------------------------------------------------Пример использования:
String displayText = convertFromUTF8ToDisplayFont(F("Привет, Васся!"));Как видно, в функции convertFromUTF8ToDisplayFont реализован ремаппинг UTF-8 в коды символов шрифта. Кстати до кучи - есть функция utf8_strlen - которая возвращает длину текста в символах, а не в байтах ;)
Это все я в курсе. Делать не буду, лениво и не особо надо. Если читали, мне это надо было только для отладки.
Я просто пробил, вдруг есть какой то способ одной командой, а я то его и не знаю...
Как видно, в функции convertFromUTF8ToDisplayFont реализован ремаппинг UTF-8 в коды символов шрифта. Кстати до кучи - есть функция utf8_strlen - которая возвращает длину текста в символах, а не в байтах ;)
Оппа! Точно! Я ведь этот проект и знаю хорошо, а чет даже и в голову не пришло.
Спасибо, коллега, сейчас голову поковыряю себе.
PS. Коллективный разум - это сила! Особливо распределенная БД по программированию)
PS. Коллективный разум - это сила! Особливо распределенная БД по программированию)
const uint8_t utf8_rus_charmap[] PROGMEM = { 'A', // А 128, // Б 'B', // В 129, // Г 130, // Д 'E', // Е 132, // Ж 133, // З 134, // И 135, // Й 'K', // К 136, // Л 'M', // М 'H', // Н 'O', // О 137, // П 'P', // Р 'C', // С 'T', // Т 138, // У 139, // Ф 'X', // Х 140, // Ц 141, // Ч 142, // Ш 143, // Щ 144, // Ъ 145, // Ы 146, // Ь 147, // Э 148, // Ю 149, // Я 'a', // а 150, // б 151, // в 152, // г 153, // д 'e', // е 155, // ж 156, // з 157, // и 158, // й 159, // к 160, // л 161, // м 162, // н 'o', // о 163, // п 'p', // р 'c', // с 164, // т 'y', // у 165, // ф 'x', // х 166, // ц 167, // ч 168, // ш 169, // щ 170, // ъ 171, // ы 172, // ь 173, // э 174, // ю 175, // я 'E', // Ё 154, // ё };int TFTRus::print(const char* st,int x, int y, word bgColor, word fgColor, bool computeTextWidthOnly) { if(!st) { return 0; } int stl, i, tw = 0; stl = strlen(st); if(!computeTextWidthOnly) { pDisplay->setTextColor(fgColor, bgColor); } uint8_t utf_high_byte = 0; unsigned int ch; String buff; buff.reserve(stl); for (i = 0; i < stl; i++) { ch = st[i]; if ( ch >= 128) { if ( utf_high_byte == 0 && (ch ==0xD0 || ch == 0xD1)) { utf_high_byte = ch; continue; } else { if ( utf_high_byte == 0xD0) { if (ch == 0x81) { //Ё ch = RUS_YO_BIG; // индекс буквы Ё в массиве перекодировки } else { if(ch <= 0xAF) // [А-Я] { ch -= 0x90; } else if( ch <= 0xBF) // [а-п] { ch -= 0x90; } else { // ch -= (0x90 - 1); } } ch = pgm_read_byte((utf8_rus_charmap + ch)); } else if (utf_high_byte == 0xD1) { if (ch == 0x91) { //ё ch = RUS_YO_SMALL; // индекс буквы ё в массиве перекодировки } else // [р-я] { ch -= 0x80; ch += 48; } ch = pgm_read_byte((utf8_rus_charmap + ch)); } utf_high_byte = 0; } } else { utf_high_byte = 0; } buff += char(ch); } // for if(computeTextWidthOnly) { return pDisplay->textWidth(buff,1); } else { return pDisplay->drawString(buff,x,y,1); } }Тут, как видно, попроще - только табличку ремаппинга подобрать ручками - и всё.