View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0002979 | Far Manager | Editor | public | 2015-04-02 01:09 | 2015-04-05 08:31 |
Reporter | SEt | Assigned To | |||
Priority | normal | Severity | feature | Reproducibility | N/A |
Status | new | Resolution | open | ||
Product Version | 3.0 | ||||
Summary | 0002979: Редактирование любых файлов без потерь в режиме UTF-8 | ||||
Description | Сейчас редактор Far'а крайне убого работает с файлами в многобайтовых кодировках (в частности, UTF-8), содержащими непреобразуемые символы: при чтении они заменяются на один и тот же стандартный символ-заменитель, при записи он же и пишется, напрочь убивая что там было двоичное вокруг текста. На самом деле, никаких серьёзных причин вести себя так нет и 'неконвертируемые' символы вполне можно сохранять. Например, для UTF-8<->UTF-16 преобразования есть модификация, использующая непарные вторые части суррогатных пар для сохранения 'неконвертируемых' символов, что позволяет в лучшем случае (если все преобразования - модифицированные) выполнить без потерь конвертацию UTF-8->UTF-16->UTF-8 для любых данных, а в худшем (часть преобразований - обычные) - получить стандартное поведение с символом-заменителем. Прилагается демо-патч к Far'у, с минимальными модификациями заменяющий его конверсии на модифицированные, что позволяет без потерь редактировать что угодно в UTF-8. | ||||
Tags | No tags attached. | ||||
Build | |||||
|
lossless_utf-8.diff (4,971 bytes)
Index: unicode_far/codepage.cpp =================================================================== --- unicode_far/codepage.cpp (revision 13032) +++ unicode_far/codepage.cpp (working copy) @@ -1205,6 +1205,12 @@ //################################################################################################ +#if 1 +#define INVALID_CHAR 0xDC00 + c1 +#else +#define INVALID_CHAR -1 +#endif + int Utf8::ToWideChar(const char *s, int nc, wchar_t *w1,wchar_t *w2, int wlen, int &tail ) { bool need_one = wlen <= 0; @@ -1220,7 +1226,7 @@ if (c1 < 0x80) // simple ASCII wc = (wchar_t)c1; else if ( c1 < 0xC2 || c1 >= 0xF5 ) // illegal 1-st byte - wc = -1; + wc = INVALID_CHAR; else { // multibyte (2, 3, 4) if (ic + 0 >= nc ) @@ -1245,7 +1251,7 @@ || (0xF4 == c1 && c2 >= 0x90) // illegal 4-byte (out of unicode range) ) { - wc = -1; + wc = INVALID_CHAR; } else if ( c1 < 0xE0 ) { // legal 2-byte @@ -1258,11 +1264,16 @@ goto unfinished; unsigned char c3 = ((const unsigned char *)s)[ic+1]; if ( 0x80 != (c3 & 0xC0) ) // illegal 3-rd byte - wc = -1; + wc = INVALID_CHAR; else if ( c1 < 0xF0 ) { // legal 3-byte + wc = ((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F); +#if 1 + if (wc - 0xD800 <= 0xDFFF - 0xD800) // invalid: surrogate area code + wc = INVALID_CHAR; + else +#endif ic += 2; - wc = ((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F); } else { // 4-byte @@ -1271,7 +1282,7 @@ unsigned char c4 = ((const unsigned char *)s)[ic+2]; if ( 0x80 != (c4 & 0xC0) ) // illegal 4-th byte - wc = -1; + wc = INVALID_CHAR; else { // legal 4-byte (produce 2 WCHARs) ic += 3; Index: unicode_far/fileedit.cpp =================================================================== --- unicode_far/fileedit.cpp (revision 13032) +++ unicode_far/fileedit.cpp (working copy) @@ -1750,6 +1750,70 @@ return loaded != FALSE; } +size_t UTF16toUTF8B(const wchar_t *src, size_t len, char *dst) +{ + const wchar_t *end = src + len; + if (!dst) // buf counting mode + { + size_t result = 0; + while (src < end) + { + unsigned int c = *src++; + if (c < 0x80) + result += 1; + else if (c < 0x800) + result += 2; + else if (c - 0xD800 > 0xDFFF - 0xD800) // not surrogates + result += 3; + else if (c - 0xDC80 <= 0xDCFF - 0xDC80) // embedded raw byte + result += 1; + else if (c - 0xD800 <= 0xDBFF - 0xD800 && src < end && *src - 0xDC00u <= 0xDFFF - 0xDC00) // valid surrogate pair + result += 4, src++; + else + result += 3; // invalid + } + return result; + } + // conversion mode + while (src < end) + { + unsigned int c = *src++; + if (c < 0x80) + *dst++ = c; + else if (c < 0x800) + { + dst[0] = 0xC0 + (c >> 6); + dst[1] = 0x80 + (c & 0x3F); + dst += 2; + } + else if (c - 0xD800 > 0xDFFF - 0xD800) // not surrogates + { + l: + dst[0] = 0xE0 + (c >> 12); + dst[1] = 0x80 + (c >> 6 & 0x3F); + dst[2] = 0x80 + (c & 0x3F); + dst += 3; + } + else if (c - 0xDC80 <= 0xDCFF - 0xDC80) // embedded raw byte + *dst++ = c & 0xFF; + else if (c - 0xD800 <= 0xDBFF - 0xD800 && src < end && *src - 0xDC00u <= 0xDFFF - 0xDC00) // valid surrogate pair + { + c = 0x3C10000 + ((c - 0xD800) << 10) + (unsigned int)*src++ - 0xDC00; + dst[0] = c >> 18; + dst[1] = 0x80 + (c >> 12 & 0x3F); + dst[2] = 0x80 + (c >> 6 & 0x3F); + dst[3] = 0x80 + (c & 0x3F); + dst += 4; + } + else + { + c = 0xFFFD; // invalid: mapped to 'Replacement Character' + goto l; + } + } + return 0; +} + //TextFormat � codepage ��������� ������, ���� bSaveAs = true! int FileEditor::SaveFile(const string& Name,int Ask, bool bSaveAs, int TextFormat, uintptr_t codepage, bool AddSignature) { @@ -2095,7 +2159,11 @@ if (Length) { - size_t length = codepage == CP_REVERSEBOM? Length * sizeof(wchar_t) : WideCharToMultiByte(codepage, 0, SaveStr, static_cast<int>(Length), nullptr, 0, nullptr, nullptr); + size_t length = codepage == CP_REVERSEBOM? Length * sizeof(wchar_t) : +#if 1 + codepage == CP_UTF8 ? UTF16toUTF8B(SaveStr, Length, nullptr) : +#endif + WideCharToMultiByte(codepage, 0, SaveStr, static_cast<int>(Length), nullptr, 0, nullptr, nullptr); char_ptr SaveStrCopy(length); if (SaveStrCopy) @@ -2103,6 +2171,11 @@ if (codepage == CP_REVERSEBOM) SwapBytes(SaveStr, SaveStrCopy.get(), length); else +#if 1 + if (codepage == CP_UTF8) + UTF16toUTF8B(SaveStr, Length, SaveStrCopy.get()); + else +#endif WideCharToMultiByte(codepage, 0, SaveStr, static_cast<int>(Length), SaveStrCopy.get(), static_cast<int>(length), nullptr, nullptr); if (!Cache.Write(SaveStrCopy.get(),length)) |
|
> что позволяет без потерь редактировать что угодно в UTF-8 открываю файл с содержимым: EF BB BF F4 FB E2 E0 сохраняю: EF BB BF F4 FB E2 EF BF BD безпотерьности не наблюдаю. |
|
lossless_utf-8_v2.diff (5,246 bytes)
Index: unicode_far/codepage.cpp =================================================================== --- unicode_far/codepage.cpp (revision 13032) +++ unicode_far/codepage.cpp (working copy) @@ -1205,6 +1205,12 @@ //################################################################################################ +#if 1 +#define INVALID_CHAR 0xDC00 + c1 +#else +#define INVALID_CHAR -1 +#endif + int Utf8::ToWideChar(const char *s, int nc, wchar_t *w1,wchar_t *w2, int wlen, int &tail ) { bool need_one = wlen <= 0; @@ -1220,7 +1226,7 @@ if (c1 < 0x80) // simple ASCII wc = (wchar_t)c1; else if ( c1 < 0xC2 || c1 >= 0xF5 ) // illegal 1-st byte - wc = -1; + wc = INVALID_CHAR; else { // multibyte (2, 3, 4) if (ic + 0 >= nc ) @@ -1231,9 +1237,16 @@ else { tail = 0; +#if 1 + wc = 0xDC00 + c1; + w1[0] = wc; + if (w2) + w2[0] = wc; +#else w1[0] = Utf::REPLACE_CHAR; if (w2) w2[0] = L'?'; +#endif nw = 1; } return nw; @@ -1245,7 +1258,7 @@ || (0xF4 == c1 && c2 >= 0x90) // illegal 4-byte (out of unicode range) ) { - wc = -1; + wc = INVALID_CHAR; } else if ( c1 < 0xE0 ) { // legal 2-byte @@ -1258,11 +1271,16 @@ goto unfinished; unsigned char c3 = ((const unsigned char *)s)[ic+1]; if ( 0x80 != (c3 & 0xC0) ) // illegal 3-rd byte - wc = -1; + wc = INVALID_CHAR; else if ( c1 < 0xF0 ) { // legal 3-byte + wc = ((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F); +#if 1 + if (wc - 0xD800 <= 0xDFFF - 0xD800) // invalid: surrogate area code + wc = INVALID_CHAR; + else +#endif ic += 2; - wc = ((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F); } else { // 4-byte @@ -1271,7 +1289,7 @@ unsigned char c4 = ((const unsigned char *)s)[ic+2]; if ( 0x80 != (c4 & 0xC0) ) // illegal 4-th byte - wc = -1; + wc = INVALID_CHAR; else { // legal 4-byte (produce 2 WCHARs) ic += 3; Index: unicode_far/fileedit.cpp =================================================================== --- unicode_far/fileedit.cpp (revision 13032) +++ unicode_far/fileedit.cpp (working copy) @@ -1750,6 +1750,70 @@ return loaded != FALSE; } +size_t UTF16toUTF8B(const wchar_t *src, size_t len, char *dst) +{ + const wchar_t *end = src + len; + if (!dst) // buf counting mode + { + size_t result = 0; + while (src < end) + { + unsigned int c = *src++; + if (c < 0x80) + result += 1; + else if (c < 0x800) + result += 2; + else if (c - 0xD800 > 0xDFFF - 0xD800) // not surrogates + result += 3; + else if (c - 0xDC80 <= 0xDCFF - 0xDC80) // embedded raw byte + result += 1; + else if (c - 0xD800 <= 0xDBFF - 0xD800 && src < end && *src - 0xDC00u <= 0xDFFF - 0xDC00) // valid surrogate pair + result += 4, src++; + else + result += 3; // invalid + } + return result; + } + // conversion mode + while (src < end) + { + unsigned int c = *src++; + if (c < 0x80) + *dst++ = c; + else if (c < 0x800) + { + dst[0] = 0xC0 + (c >> 6); + dst[1] = 0x80 + (c & 0x3F); + dst += 2; + } + else if (c - 0xD800 > 0xDFFF - 0xD800) // not surrogates + { + l: + dst[0] = 0xE0 + (c >> 12); + dst[1] = 0x80 + (c >> 6 & 0x3F); + dst[2] = 0x80 + (c & 0x3F); + dst += 3; + } + else if (c - 0xDC80 <= 0xDCFF - 0xDC80) // embedded raw byte + *dst++ = c & 0xFF; + else if (c - 0xD800 <= 0xDBFF - 0xD800 && src < end && *src - 0xDC00u <= 0xDFFF - 0xDC00) // valid surrogate pair + { + c = 0x3C10000 + ((c - 0xD800) << 10) + (unsigned int)*src++ - 0xDC00; + dst[0] = c >> 18; + dst[1] = 0x80 + (c >> 12 & 0x3F); + dst[2] = 0x80 + (c >> 6 & 0x3F); + dst[3] = 0x80 + (c & 0x3F); + dst += 4; + } + else + { + c = 0xFFFD; // invalid: mapped to 'Replacement Character' + goto l; + } + } + return 0; +} + //TextFormat � codepage ��������� ������, ���� bSaveAs = true! int FileEditor::SaveFile(const string& Name,int Ask, bool bSaveAs, int TextFormat, uintptr_t codepage, bool AddSignature) { @@ -2095,7 +2159,11 @@ if (Length) { - size_t length = codepage == CP_REVERSEBOM? Length * sizeof(wchar_t) : WideCharToMultiByte(codepage, 0, SaveStr, static_cast<int>(Length), nullptr, 0, nullptr, nullptr); + size_t length = codepage == CP_REVERSEBOM? Length * sizeof(wchar_t) : +#if 1 + codepage == CP_UTF8 ? UTF16toUTF8B(SaveStr, Length, nullptr) : +#endif + WideCharToMultiByte(codepage, 0, SaveStr, static_cast<int>(Length), nullptr, 0, nullptr, nullptr); char_ptr SaveStrCopy(length); if (SaveStrCopy) @@ -2103,6 +2171,11 @@ if (codepage == CP_REVERSEBOM) SwapBytes(SaveStr, SaveStrCopy.get(), length); else +#if 1 + if (codepage == CP_UTF8) + UTF16toUTF8B(SaveStr, Length, SaveStrCopy.get()); + else +#endif WideCharToMultiByte(codepage, 0, SaveStr, static_cast<int>(Length), SaveStrCopy.get(), static_cast<int>(length), nullptr, nullptr); if (!Cache.Write(SaveStrCopy.get(),length)) |
|
Да, было такое, не всё исправил в текущем коде Far'а - в v2 версии патча концовка должна обрабатываться правильно. Ещё безпотерьность может запороть условие после //??? caller should be fixed to avoid this... но я не знаю, как его адекватно вправить. |
|
> но я не знаю, как его адекватно вправить. Этот код выполняться не должен. Если такое всё ещё происходит, то адекватной будет правка вызывателя. |
|
4332. |
|
Скорее всего будут проблемы в нетекстовых режимах просмотра. Там нужно 'честное' преобразование. |
|
Это вполне 'честное' преобразование, но, конечно, желательно везде использовать один и тот же вариант преобразования. Сейчас шестнадцатеричный режим просмотра съезжает, так как там используется int clen = WideCharToMultiByte(CP_UTF8, 0, w2.data()+iw, 1, nullptr,0, nullptr,nullptr); При замене на int clen = Utf8::ToMultiByte(w2.data()+iw, 1, nullptr); всё нормализуется. Странному коду чуть выше по угадыванию продолжения символа в начале строки это не помогает, и его хорошо бы как-то более адекватно реализовать. |
|
Теперь любое преобразование в UTF-8 успешное. Ошибок не бывает. А иногда именно их и надо найти. Можно, конечно, сделать поиск по диапазону, но это дополнительное неочевидное телодвижение. Часть кода в старом варианте преобразования нужна только для нахождения ошибок. Вариант с двумя выходными буферами нужен (был) для того, чтобы при обратном преобразовании получить ту же длину строки, что и в оригинале. В новом варианте, если мы на нём останавливаемся, это всё не нужно. Можно упростить, но нужна ревизия кода, где используется преобразование. |
|
Ещё надо бы проверить смену кодировки. Скорее всего там нужны отдельные приседания для utf-8. |
Date Modified | Username | Field | Change |
---|---|---|---|
2015-04-02 01:09 | SEt | New Issue | |
2015-04-02 01:09 | SEt | File Added: lossless_utf-8.diff | |
2015-04-02 14:44 | zg | Note Added: 0013607 | |
2015-04-02 17:54 | SEt | File Added: lossless_utf-8_v2.diff | |
2015-04-02 18:00 | SEt | Note Added: 0013608 | |
2015-04-02 22:06 | 2useven10 | Note Added: 0013609 | |
2015-04-03 01:00 | zg | Note Added: 0013610 | |
2015-04-03 04:46 | 2useven10 | Note Added: 0013611 | |
2015-04-03 18:22 | SEt | Note Added: 0013612 | |
2015-04-05 07:36 | 2useven10 | Note Added: 0013614 | |
2015-04-05 07:47 | 2useven10 | Note Added: 0013615 | |
2015-04-05 08:31 | 2useven10 | Note Edited: 0013614 |