Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
28-06-2013 13:07
Я решил эту проблему так
var
Utf2WinTable : array [0..65, 0..1] of string = (
(#208#144,#192), (#208#145,#193), (#208#146,#194),
(#208#147,#195), (#208#148,#196), (#208#149,#197),
(#208#129,#168), (#208#150,#198), (#208#151,#199),
(#208#152,#200), (#208#153,#201), (#208#154,#202),
(#208#155,#203), (#208#156,#204), (#208#157,#205),
(#208#158,#206), (#208#159,#207), (#208#160,#208),
(#208#161,#209), (#208#162,#210), (#208#163,#211),
(#208#164,#212), (#208#165,#213), (#208#166,#214),
(#208#167,#215), (#208#168,#216), (#208#169,#217),
(#208#170,#218), (#208#171,#219), (#208#172,#220),
(#208#173,#221), (#208#174,#222), (#208#175,#223),
(#208#176,#224), (#208#177,#225), (#208#178,#226),
(#208#179,#227), (#208#180,#228), (#208#181,#229),
(#209#145,#184), (#208#182,#230), (#208#183,#231),
(#208#184,#232), (#208#185,#233), (#208#186,#234),
(#208#187,#235), (#208#188,#236), (#208#189,#237),
(#208#190,#238), (#208#191,#239), (#209#128,#240),
(#209#129,#241), (#209#130,#242), (#209#131,#243),
(#209#132,#244), (#209#133,#245), (#209#134,#246),
(#209#135,#247), (#209#136,#248), (#209#137,#249),
(#209#138,#250), (#209#139,#251), (#209#140,#252),
(#209#141,#253), (#209#142,#254), (#209#143,#255) );
//////////////////////////////////////////////////////////
function FixString(const AData: String): String;
var
S: RawByteString;
X: Integer;
begin
SetLength(S, Length(AData)); // выделили память под ANSI-буфер
for X := 1 to Length(AData) do // переносим данные из вида 00A400B700E700D5 в A4B7E7D5
S[X] := AnsiChar(AData[X]);
SetCodePage(S, 1251, False); // пометим, что данные A4B7E7D5имеют кодировку Win1251.
// False указывает на то, что сами данные менять не надо -
// мы просто указываем, в какой они кодировке
Result := S; // здесь компилятор автоматически преобразует строку из ANSI/Win1251 в unicode
end;
function Utf8ToWin(s : string) : string;
var i : integer;
res :string;
begin
res:=s;
for I := 0 to 65 do
if pos(Utf2WinTable[i,0],res)>0
then res := StringReplace(res, Utf2WinTable[i,0], Utf2WinTable[i,1], [rfReplaceAll]);
Кстати, чем делать этот метод через одно место с исправлением строки - проще воспользоваться перегруженным вариантом метода Get, который возвращает бинарный поток без преобразования - тогда мы сможем перегнать его в строку (RawByteString), а FixString будет не нужен (останется только вызов SetCodePage на буфер в RawByteString).
Альтернативно, можно воспользоваться тем же, чем пользуется Internet Explorer: MLang-ом. Например.
Это намного проще и универсальнее (окей: проще, когда уже написано кем-то другим), поскольку обрабатывает любую кодировку.
12-01-2010 11:18 | Комментарий к предыдущим ответам
>>> Эта функция у меня выводит мусор
Да, к сожалению, там проблема в первой строке. При таком присвоении некоторые байты могут меняться. Это не то, что надо (надо побайтовое копирование без изменение). Так что только циклом.
>>> Также следующий код работает у меня нормально и без FixString
Я написал, что сейчас происходит в Indy, из-за чего возникает проблема. А в Indy происходит именно передача en8Bit, а не TEncoding.GetEncoding(1251).
Понятно, что если вы поставите туда нужную кодировку, то будет работать. Но это означает правку и перекомпиляцию Indy.
12-01-2010 09:34 | Комментарий к предыдущим ответам
Эта функция у меня выводит мусор:
function FixString(const AData: String): String;
type
TW1252 = type AnsiString(1252);
var
W1252: TW1252;
S: RawByteString;
begin
W1252 := AData; // В этой строке выполняются действия, которые раньше выполнял у меня цикл - это обратные действия к тем, что выполняет Indy
// Теперь копирование данных одним блоком в буфер
SetLength(S, Length(W1252));
Move(Pointer(W1252)^, Pointer(S)^, Length(W1252));
// Помечаем, что данные на самом деле имеют кодировку 1251
SetCodePage(S, 1251, False);
// Ну и конвертация в Unicode
Result := S;
end;
В отличие от этой, предыдущей:
function FixString(const AData: String): String;
var
S: RawByteString;
X: Integer;
begin
SetLength(S, Length(AData));
for X := 1 to Length(AData) do
S[X] := AnsiChar(AData[X]);
SetCodePage(S, 1251, False);
Result := S;
end;
Почему - не вникал. Также следующий код:
var
Bytes: TIdBytes;
S: AnsiString;
begin
S := 'Европейская короткошерстная кошка: О ПОРОДЕ - Породы кошек - CATS-портал';
SetLength(Bytes, Length(S));
Move(Pointer(S)^, Bytes[0], Length(S));
ShowMessage(AnsiString(BytesToString(Bytes, 0, Length(Bytes), en8Bit)));
end;
работает у меня нормально и без FixString, если писать:
Видимо, это результат внедрения поддержки TEncoding в последних билдах Indy, только неясно почему же до сих пор нужно править руками строки, раз уж все равно переписывали код.
18-12-2009 10:03 | Вопрос к автору: запрос дополнительной информации
Ребят, подскажите еще. Бьюсь третий день с аналогичной проблемой.
Ситуация у меня такая.
Вызваю метод IdHTTP.Head(URL)
с линком на файл. Получаю заголовки в IdHTTP.Response. Потом из заголовка Content-Disposition я вытягиваю имя запрашиваемого файла. Всё было хорошо на D7, а на D2009 вылезла
проблема с русским именами. Не могу понять в какой кодировке Инди мне их передаёт...
Метод от Александра Алексеева строку не меняет:( может что пропустил... сам не силён в кодировках, буду очень признателен за помощ!
К слову сказать, формально Indy возвращает, конечно же, unicode-строку, полученную как конвертирование из кодировки 1252
Упс, вот эту фразу и не заметил в предыдущий раз, sorry
27-02-2009 00:03 | Комментарий к предыдущим ответам
>>> С маленькой поправкой: она трактуется вообще как win1200, ибо именно 1200 мы получим после использования функции StringCodePage (Delphi2009):
См.
>>> К слову сказать, формально Indy возвращает, конечно же, unicode-строку, полученную как конвертирование из кодировки 1252.
А работает у вас (если работает), только благодаря невероятной случайности: ибо в строке SetLength(RBS, Length(s)); менеджер памяти выделил вам память ровно на том же месте, где была оригинальная строка, полученная Indy, до того момента, как он начал её переводить.
Мде, и правда что... Повторил эксперименты и пришёл к выводу, что это странности компилятора: неправильный код всё равно работает и приводит к правильному (!) результату
Поэтому в итоге мы получаем строку, в которой лежит строка (KOI8 или Win1251), но при этом она трактуется как Win1252.
С маленькой поправкой: она трактуется вообще как win1200, ибо именно 1200 мы получим после использования функции StringCodePage (Delphi2009):
26-02-2009 04:28 | Комментарий к предыдущим ответам
К слову сказать, формально Indy возвращает, конечно же, unicode-строку, полученную как конвертирование из кодировки 1252. Только вот в строку он загоняет байты как они пришли без конвертации. Поэтому в итоге мы получаем строку, в которой лежит строка (KOI8 или Win1251), но при этом она трактуется как Win1252.
Поэтому можно использовать и вот такой вариант:
function FixString(const AData: String): String;
type
TW1252 = type AnsiString(1252);
var
W1252: TW1252;
S: RawByteString;
begin
W1252 := AData; // В этой строке выполняются действия, которые раньше выполнял у меня цикл - это обратные действия к тем, что выполняет Indy
// Теперь копирование данных одним блоком в буфер
SetLength(S, Length(W1252));
Move(Pointer(W1252)^, Pointer(S)^, Length(W1252));
// Помечаем, что данные на самом деле имеют кодировку 1251
SetCodePage(S, 1251, False);
// Ну и конвертация в Unicode
Result := S;
end;
25-02-2009 17:24 | Комментарий к предыдущим ответам
Добавьте сюда еще кучу мелко-мобильных устройств, которые тоже должны уметь шарить в вебе, и о LZW можно будет забыть.
JPEG на финальном этапе сжимает коэффициенты ДКП примерно этим алгоритмом (LZW). Повторюсь, 1 приличная картинка содержит столько текста, сколько надо читать месяц. Поэтому, любое устройство, которое способно показывать сжатые картинки, справится и со сжатыми текстами уже точно. Из текстов ведь видео не делают :) (хотя есть такие увлечения у народа)
>>> От себя хочу добавить к этому решению, что оно прекрасно работает и без этого участка (избыточный код)
Это вы, батенька, изрядно погорячились.
Если вы посмотрите на свой новый код, то увидите, что строка RBS у вас никак не связана с S. И данные из S в RBS не попадают вообще!
А работает у вас (если работает), только благодаря невероятной случайности: ибо в строке SetLength(RBS, Length(s)); менеджер памяти выделил вам память ровно на том же месте, где была оригинальная строка, полученная Indy, до того момента, как он начал её переводить.
Ещё раз, по строкам:
function FixString(const AData: String): String;
var
S: RawByteString;
X: Integer;
begin
SetLength(S, Length(AData)); // выделили память под ANSI-буфер
for X := 1 to Length(AData) do // переносим данные из вида 00A400B700E700D5 в A4B7E7D5
S[X] := AnsiChar(AData[X]);
SetCodePage(S, 1251, False); // пометим, что данные A4B7E7D5имеют кодировку Win1251.
// False указывает на то, что сами данные менять не надо -
// мы просто указываем, в какой они кодировке
Result := S; // здесь компилятор автоматически преобразует строку из ANSI/Win1251 в unicode
end;
От себя хочу добавить к этому решению, что оно прекрасно работает и без этого участка (избыточный код):
for X := 1 to Length(AData) do
S[X] := AnsiChar(AData[X]);
То есть окончательный вариант выглядит так:
// Функция
function E1251(const s: string): string;
var RBS: RawByteString;
begin
SetLength(RBS, Length(s));
SetCodePage(RBS, 1251, false);
Result:=RBS;
end;
// Пример использования
Memo.Text:=E1251(IdHTTP.Get('http://ya.ru'));
25-02-2009 04:15 | Комментарий к предыдущим ответам
Ура! Александр Алексеев, спасибо за подробное раследование и обстоятельное разъяснение причин странного поведения "Индейки" :)
С данной проблемой (вместо 1252 получаем набор символов в 1251 через IdHTTP) столкнулся ещё месяца 3-4 назад, переворошил кучу статей в интернете и примеров с изменением кодировок в D2009, прочитал про SetCodePage, перебрал все возможные варианты... Потратил на изучение информации два дня, окончательно запутался в "дельфийских юникодах", а в итоге от безысходности написал самодельную функцию "1251 -> 1252" %-) Вот этот ужас, для сравнения с элегентным решением из Ответа от 02-02-2009 09:09, самому страшно:
03-02-2009 13:28 | Комментарий к предыдущим ответам
Хотя если подумать, наверное там важнее скорость парсинга тегов, и объемы занимаемой памяти. В случае с UTF8 большая часть символов будет представлена одним байтом.
03-02-2009 13:19 | Комментарий к предыдущим ответам
То, о чем вы говорите, называется UTF16 и UTF32 :) UTF8, как я понял, предназначен в основном для веба, где по сей день существуют медленные модемные соединения. Добавьте сюда еще кучу мелко-мобильных устройств, которые тоже должны уметь шарить в вебе, и о LZW можно будет забыть.
P.S. Еще где-то в начале 90-х, когда я поставил Windows 3.0 и узнал, что у него стандартная кодировка Win-1251, а не OEM866, я удивлялся, зачем они буквы с места на место переставляют и создают проблемы. А если учесть, что есть еще туча национальных кодировок в мире, то, наверное, и они переделались в новые с выходом винды. И сказал знакомому, а че бы 16 битами не кодировать букву и сделать одну кодировку вообще?
Но когда появилась UTF-8, я вообще офигел - это ж надо додуматься сделать переменное кол-во байт на символ! От 1 до 6, если мне не изменяет память.
Вот, например, при росте цветности изображений просто брали 1, 2, 4, 8, 16, 24, 32, 64 бита и все. Если надо кодировать, то есть разные эффективные алгоритмы сжатия, которые делают это именно оптимально (GIF, PNG), а если не надо - то банально несколько несколько бит на точку и все.
Если сравнивать с UTF-8, то можно было бы сделать так: "для совместимости со старыми палитровыми режимами мы будем кодировать родные для них цвета в 4 или 8 бит, а остальные большим числом бит, до 128, скажем. А фиг ли, пусть потом программеры мучаются. А еще сделаем 50 кодировок - одну для фоток, другую для гравюр, третью для факсов, четвертую для детских рисунков фломастерами..." Именно такой принцип и заложен в UTF-8. Кто мешает сделать одну кодировку на все случаи жизни, а для передачи/хранения сжимать LZW и т.д.
И все это при том, что объем текстовых данных обычно меньше графических в миллионы раз в реальных применениях (Библия на англ. языке в 8-битной кодировке занимает 5Мб, меньше чем одна картинка несжатая в 1600x1200x24). Если мало 16 бит, пусть берут 32 и делают что хотят с ними - столько букв на свете нету :))) Не понятно мне, зачем весь мир нагибать мучаться с разными кодировками.
>>> это несовместимость Indy10 с уникодом или это нормально?
Я не знаю, что это. Если смотреть на наличие директив для D2009 в исходниках Indy, то видно, что над unicode явно работали. Может быть, это баг. А может быть, это так и должно быть. Я не знаю.
>>> Александр, подскажите, если не трудно, а в какой кодировке возвращался результат Get?
Насколько я понимаю - ни в какой. Indy в этом сценарии кодировку не учитывает никак. Кодировка передаётся только в ContentTypeStrToEncoding, из которой может выходить только en8bit или enUTF8. Поскольку у нас не UTF8, то значит, по мнению Indy, кодировка - en8bit.
Байты контента просто вслепую загонялись в строку string. Так, байт $A4 становился unicode-символом #$00A4. Это может и работало в D2007, потому что там байт $A4 становился Ansi-символом #$A4 - что и требовалось.
Соответственно, решение: сделать это же в обратную сторону, т.к. откинуть старший байт и получить Ansi-символ #$A4, а затем пометить всю строку нужной кодировкой (в нашем случае - 1251), конвертирование же в unicode выполнится автоматически при присвоении Result-а в FixString.
Чудо! Работает! :)
Александр, подскажите, если не трудно, а в какой кодировке возвращался результат Get?
И вообще, это несовместимость Indy10 с уникодом или это нормально?
function FixString(const AData: String): String;
var
S: RawByteString;
X: Integer;
begin
SetLength(S, Length(AData));
for X := 1 to Length(AData) do
S[X] := AnsiChar(AData[X]);
SetCodePage(S, 1251, False);
Result := S;
end;
var
S: String;
begin
S := IdHTTP1.Get('http://cat.mau.ru/eur/');
S := FixString(S);
ShowMessage(S);
end;
Разумеется, вместо 1251 надо подставлять нужную кодировку.
29-01-2009 04:10 | Вопрос к автору: запрос дополнительной информации
Вопрос автору вопроса: а что находится в строке? Покажите.
Попробуйте также сохранить её в текстовый файл и поподбирать кем-нибудь кодировку (например, Word-ом).
Не подумал про это - по названию он у меня с D2009 никак не ассоциируется.
Тогда получается, что используется первая ветка, а там либо цикл, либо:
function GetEncoder(AEncoding: TIdEncoding): SysUtils.TEncoding;
{$IFDEF USEINLINE}inline;{$ENDIF}
begin
case AEncoding of
en7Bit: Result := TEncoding.ASCII;
enUTF8: Result := TEncoding.UTF8;
en8Bit: begin
if Id8BitEncoder = nil then begin
Id8BitEncoder := TEncoding.GetEncoding(1252); // Windows-1252
end;
Result := Id8BitEncoder;
end;
else Result := nil;
end;
end;
Т.е. опять что-то странное. Всегда используется либо UTF8, либо Windows-1252.
function ContentTypeStrToEncoding (const aContentType: string): TIdEncoding;
//TODO: Figure out what should happen with Unicode content type.
var
LCharSet: String;
begin
LCharSet := ExtractHeaderSubItem(aContentType, 'CHARSET'); {do not localize}
if LCharSet <> '' then
begin
if PosInStrArray(LCharSet, ['UTF-8', 'UTF8'], False) <> -1 then begin
Result := enUTF8;
Exit;
end;
end;
{JPM - I have decided to temporarily make this en8bit because I'm concerned
about how binary files will be handled by the en7bit encoder (where there may
be 8bit byte-values. In addition, there are numerous charsets for various
languages and code that does some special mapping for them would be a mess.}
Result := en8bit; //en7Bit;
end;
А ReadStringFromStream выглядит так:
function ReadStringFromStream(AStream: TStream; ASize: Integer = -1;
const AEncoding: TIdEncoding = en7Bit): string;
var
LBytes: TIdBytes;
begin
ASize := TIdStreamHelper.ReadBytes(AStream, LBytes, ASize);
Result := BytesToString(LBytes, 0, ASize, AEncoding);
end;
Всё конвертирование находится в BytesToString:
function BytesToString(const AValue: TIdBytes; const AStartIndex: Integer;
const ALength: Integer = -1; const AEncoding: TIdEncoding = en7Bit): string; overload;
var
LLength: Integer;
{$IFDEF DOTNET_OR_TEncoding}
i : Integer;
{$ENDIF}
begin
ValidEncoding(AEncoding); //This raises an exception if the AEncoding parameter is enDefault
LLength := IndyLength(AValue, ALength, AStartIndex);
if LLength > 0 then
begin
{$IFDEF DOTNET_OR_TEncoding}
{
IMPORTANT!!!
We do not use the TEncoding interface at all for en8bit. The Windows-1252
code page translates bytes from range #80-#9F to Unicode so that you can
display them. Unfortunately, that behavior creates problems for non-printable
binary (9bit) data where translation to displayable strings is not desirable.
}
if AEncoding = en8bit then
begin
SetLength( Result, LLength);
for i := AStartIndex to LLength - 1 do begin
Result[ i + 1] := Char(AValue[ i]);
end;
end
else
begin
Result := GetEncoder(AEncoding).GetString(AValue, AStartIndex, LLength);
end;
{$ELSE}
if AEncoding = enUTF8 then begin
Result := UTF8BytesToString(AValue, AStartIndex, LLength);
end else
begin
// For VCL we just do a byte to byte copy with no translation. VCL uses ANSI or MBCS.
// With MBCS we still map 1:1
SetLength(Result, LLength); // <- !!!! И это UnicodeString?!!!
Move(AValue[AStartIndex], Result[1], LLength); // <- !!!!
end;
{$ENDIF}
end else begin
Result := '';
end;
end;
Явный баг. Похоже, индю просто перекомпилили, без изменений кода.
Попробуйте произвести со строкой такое преобразование:
function FixString(const AData: String): String;
var
Data: RawByteString;
begin
if AData <> '' then
begin
SetLength(Data, Length(AData));
Move(Pointer(AData)^, Pointer(Data)^, Length(AData));
SetCodePage(Data, 1251); // <- указать нужную кодировку
Result := Data;
end
else
Result := '';
end;
var
html: String;
begin
html := FixString(http.Get(labelURL.Caption));
end;
Или (не совсем корректный вариант, но для теста сойдёт):
function FixString(const AData: String): String;
var
Data: AnsiString;
begin
if AData <> '' then
begin
SetLength(Data, Length(AData));
Move(Pointer(AData)^, Pointer(Data)^, Length(AData));
Result := Data; // Кодировка - всегда текущая
end
else
Result := '';
end;
var
html: String;
begin
html := FixString(http.Get(labelURL.Caption));
end;
Обязательно отпишитесь, ибо вопрос сильно актуальный.
У SetCodePage возможен третий параметр, он должен быть True если вы хотите перекодировать строку в другую кодировку, а если нужно просто указать правильную кодировку для строки, то False. По умолчанию вроде бы ставится True. Попробуйте в своем коде поставить False. Возможно у вашей строки просто неправильно указана кодировка.
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.