уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Сколько постов написано в блогах, сколько вопросов задано на форумах о кодировках и ещё большее количество подобных вопросов осталось не отвеченными или ответом было что-то наподобие «Поиском пользовались?» или «Сто раз обсуждалось!!!». Честно говоря, никогда не понимал таких «ответчиков», ИМХО не хочешь отвечать — лучше жуй и молчи, ответят те, кто захочет.
Понятное дело, что обучать иногда приходится не только с помощью пряника, но и с помощью кнута, но, раз уж такие вопросы всё время всплывают, следовательно они остаются актуальными.
Сегодня я попробую рассказать Вам всё, что мне известно о работе с кодировками в тексте. Вполне возможно, что эта статья поможет Вам при разработке своих проектов в Delphi да и у меня уже пару раз возникало желание немного систематизировать ту информацию. которая накопилась за время существования блога.

1. Delphi и Unicode

Если говорить о работе с Unicode в Delphi, то начать стоит с того, что полноценная поддержка unicode в Delphi стала возможна лишь после выхода Delphi 2009. Такое событие не могло пройти незамеченным, так как благодаря поддержке Unicode и, соответственно, для облегчения работы с кодировками текста в Delphi были реализованы новые возможности, методы, типы данных, о которых написано большое количество статей. На мой взгляд одной из лучших публикаций на эту темя является цикл из трех статей «Delphi и кодировка Unicode» где достаточно чётко и доступно рассказано о нововведениях Delphi 2009 для работы с unicod’ом. Думаю, что нет смысла подробно останавливаться на всех новшествах при наличии ссылки на целых три статьи на эту тему. Остается только упомянуть о том, с чем мы сегодня будем работать для представления веб-страницы в нормальном читаемом виде.
Для первого способа работы с кодировкой мы воспользуемся:

  1. Класс TEncoding, который и дает нам возможность без лишних хлопот работать с кодировками
  2. Тип данных TBytes — массива байтов строк
  3. RawByteString — тип для передачи строковых данных для любой кодовой страницы без каких-либо преобразований последней

В одной из статей блога рассматривалась работа с MLang и сегодня, в качестве второго способа, я продемонстрирую Вам пример её применения при работе с кодировками.
Ну и в качестве третьего способа работы с кодировками, воспользуемся «штатными» методами модуля system. Все три варианта работы с кодировками приведут к одному и тому же результату — текст веб-страницы будет читаемым, без «кракозябров» и вопросительных знаков. Какой способ лучше — решать только Вам.

2. Подготовка исходных данных для работы

Прежде, чем начать что-то кодировать и перекодировать, необходимо это «что-то» получить. В нашем случае — текст веб-страницы. Чтобы не залезать слишком глубоко в вопросы, касающиеся новых типов данных будем сохранять все данные из Сети не в строки или TStringList’ы, как мы к этому привыкли, а воспользуемся типом TBytes.
Используя Synapse исходник любой страницы можно получить, например так:

  with THTTPSend.Create do
    begin
      if HTTPMethod('GET',URL)then
        begin
           //в свойстве Document объекта THTTPSend содержится исходник страницы
           //обрабатываем Document 
        end;
    end;

Теперь, получив данные (свойство Document:TMemoryStream) скопируем эти данные в массив байтов строки TBytes, например так (способ достаточно медленный, приведен исключительно для примера):

var B: TBytes;
     i:integer;
begin
//загружаем контент страницы
  with THTTPSend.Create do
    begin
      if HTTPMethod('GET',URL) then
        begin
          Document.Position:=0;
          SetLength(B,Document.Size);
          for i := 0 to Document.Size - 1 do
            Document.Read(B[i],1);
        end;
    end;

Первый этап подготовки можно считать законченным — у нас есть массив байтов строки, которые необходимо представить в виде текста в правильной кодировке. А как узнать, что наш массив TBytes надо перевести в новую «правильную» кодировку? Естественно узнать в какой кодировке была исходная страница. Здесь можно пойти двумя путями:
1. Узнать кодировку из мета-тегов. Обычно кодировка страницы описывается мета-тегом следующего содержания:

META http-equiv="content-type" content="text/html; charset=windows-1251

. Пример того как узнать кодировку из мета-тегов страницы Вы можете посмотреть в модуле HtmlCPConvert, который я выкладывал в блоге, когда рассматривал работу с MLang.
2. Узнать кодировку текста из заголовков сервера. Если рассматривать этот пример, используя для работу с HTTP-протоколом библиотеку Synapse, то заголовки сервера будут содержаться в свойстве Headers:TStringList. Найти элемент, содержащий сведения о кодировке и получить имя кодовой страницы можно, например так:

function GetCharset(Headers: TStringList): string;
var i:integer;
begin
  if Headers.Count=0 then Exit;
  for I := 0 to Headers.Count - 1 do
    begin
      //Content-Type: text/html; charset=UTF-8
      if Pos('content-type',LowerCase(Headers[i]))>0 then
        if pos('=',Headers[i])>0 then
          Result:=LowerCase(Copy(Headers[i],pos('=',Headers[i])+1,Length(Headers[i])-pos('=',Headers[i])))
        else
          Result:=DefCharset;
    end;
end;

Так как в заголовке может отсутствовать сведения о кодировке, то переменная DefCharset должна содержать имя какой-либо дефолтной кодовой страницы, например windows-1251.
Теперь у нас есть все исходные данные:
1. Массив байтов строки B:TBytes;
2. Сведения о кодировке.
Приступим к преобразованию байтов в читабельный текст.

3. Массив байтов — в нормальный текст

3.1. Работа с TEncoding

Разработчики Delphi предусмотрели использование двух взаимопротивоположных метода:

  1. function BytesOf(const Val: RawByteString): TBytes; — получение байтов из строки
  2. function StringOf(const Bytes: TBytes): UnicodeString; — получение строки из массива байтов

Пусть Вас не пугает то, что StringOf возвращает UnicodeString, т.к., начиная с Delphi 2009

type
  UnicodeString = string

Теперь, что касается непосредственно работы с TEncoding. Во-первых, от нас не требуется создавать отдельный экземпляр класса, наподобие такого:

var Enc: TEncoding;
begin
  Enc:=TEncoding.Create;
end;

Во-вторых, с помощью TEncoding мы можем менять кодовую страницу не преобразовывая массив байтов в строку. Для этого класс содержит следующие классовые методы:

class function Convert(Source, Destination: TEncoding; Bytes: TBytes): TBytes; overload;
    class function Convert(Source, Destination: TEncoding; Bytes: TBytes; StartIndex, Count: Integer): TBytes; overload;

Одним из этих методов мы и воспользуемся. Для того, чтобы продемонстрировать работу с TEncoding попробуем получить текст прямо из этого блога. Кодировка UTF-8, именно она наиболее часто является «проблемной» и возвращает в TMemo или ещё куда-либо «кракозябры».
Создадим простое приложение как показано на рисунке:

В ComboBox сразу занесем все варианты работы с текстом:

  1. Без преобразования
  2. TEncoding (модуль SysUtils)
  3. MLang
  4. Utf8ToAnsi (модуль System)

По нажатию кнопки TButton будем грузить страницу в массив байтов и, зависимости от выбранного варианта работы с текстом, преобразовывать массив в строку и выводить в Memo. Для первого варианта (без преобразования) код может быть следующий:

with THTTPSend.Create do
    begin
      if HTTPMethod('GET',Edit1.Text)then
        begin
          Document.Position:=0;
          SetLength(B,Document.Size);
          for i := 0 to Document.Size - 1 do
            Document.Read(B[i],1);
          label4.Caption:=GetCharset(Headers);
        end;
    end;
Label6.Caption:=IntToStr(Length(B))+' байт текста';
case ComboBox1.ItemIndex of
 0:Memo1.Text:=StringOf(B);
[...]
end;

Запускаем приложение и видим:

Как и ожидалось — вместо русских букв кракозябры. Преобразуем их с помощью классового метода TEncoding.Convert:

var astr: String;
     B: TBytes;
[...]
case ComboBox1.ItemIndex of
 0:Memo1.Text:=StringOf(B);
 1:begin
     B:=TEncoding.Convert(TEncoding.UTF8,TEncoding.GetEncoding(1251),B);
     astr:=StringOf(B);
     Memo1.Lines.Add(astr);
   end;
[...]
end;

Здесь следует отметить, что TEncoding.GetEncoding(1251) возвращает кодовую страницу кириллического текста. Если необходимо получить другую кодовую страницу, то можете либо воспользоваться классовыми свойствами TEncoding либо определить кодовую страницу как я, используя данные с MSDN о доступных в Windows кодовых страницах.
Проверим, что получилось в итоге. Грузим ту же самую страницу:

Как видите — кракозябры преобразовались в нормальный русский текст.
Какие плюсы дает нам использование TEncoding? Думаю, что ответ вполне очевиден — у нас появляется под рукой инструмент, позволяющий перекодировать строки из любых кодировок в любые, т.е. фактически возможности работы с текстом ограничиваются количеством доступных в Windows кодовых страниц, а их по данным MSDN 152 штуки. Прикиньте сколько вариантов получится, если в Convert используется пара кодовых страниц. Много :).
Но, наряду с таким большим и жирным плюсом существует и минус — TEncoding есть только в версиях Delphi 2009 и выше, что исключает его использование в более ранних версиях. Как быть?
Если хотите получить не менее впечатляющие возможности работы с кодировками — используйте возможности MLang. Вот как раз и пример его использования.

3.2. Использование возможностей MLang для работы с кодовыми страницами

Позволю себе ещё раз напомнить, что скачать всё необходимое для реализации возможностей MLang Вы можете с этой страницы или, перейдя по этой ссылке. В архиве содержатся всего два модуля: MLang.pas и HtmlCPConvert.pas. Подключаем оба модуля к проекту и для получения читабельного текста пишем:

Memo1.Text:=RawHTMLToHTML(StringOf(B));

RawHTMLToHTML из модуля HtmlCPConvert переводит текст в кодировку windows-1251. Также есть и обратный метод для перевода текста в RawByteString и называется этот метод HTMLToRawHTML.
Результат преобразования текста с помощью MLang абсолютно идентичен предыдущему варианту:

Если Вам необходимо работать со множеством кодировок, как при использовании TEncoding, то придётся самостоятельно немного доработать модуль HtmlCPConvert. В целом возможности TEncoding и MLang вполне сопоставимы.
И, наконец, третий вариант — использование методов модуля System.pas.

3.3. Использование методов System.pas для перекодирования текста

В модуле System.pas можно найти следующие полезные функции для работы с кодировками:

Utf8Encode(const US: UnicodeString): RawByteString;
function UTF8EncodeToShortString(const WS: WideString): ShortString;
function Utf8Decode(const S: RawByteString): WideString;
function UTF8ToUnicodeString(const S: RawByteString): UnicodeString;
function UTF8ToString(const S: RawByteString): string;
function AnsiToUtf8(const S: string): RawByteString;
function Utf8ToAnsi(const S: RawByteString): string;

Как видите, методы завязаны на работе с конкретными кодовыми страницами UTF-8, ANSI, а также на работе с новым типом данных RawByteString. Поэтому возможности третьего способы по сравнению с первыми двумя резко ограничены. Хотя в реалиях нынешнего Рунета их вполне достаточно, т.к. веб-страницы в кодировке koi8-r уже больше раритет, чем обыденность. Использовать методы достаточно просто. Например так:

var RS: RawByteString;
[...]
begin
   RS:=StringOf(B);
   Memo1.Text:=Utf8ToAnsi(RS);
end;

я перевел массив байтов в стоку RawByteString и затем представил её в кодировке Ansi, получив в Memo читабельный русский текст.
Вот три способа работы с различными кодировками текста, которые могут применяться где угодно. И это только часть всех возможностей, которые предоставляет нам Delphi. Какой способ использовать — решать Вам.
Кстати, при работе с кодировками следует обратить внимание на то, что «кракозябры» в тексте — это следствие представления строки с использованием неправильной кодовый страницы. Если же вместо русских букв появляются знаки вопроса, то это следствие того, что в текущей кодовой странице текста нет тех символов, которые вы пробуете показать. Например, представляя текст ANSI в кодировке UTF-8 мы получаем кракозябры, а если попробовать представить текст ANSI Cyrillic (windows-1251) с кодовой страницей ANSI Latin 1 (windows-1252), то вместо русских букв получим знаки вопроса, т.к. в windows-1252 отсутствуют русские буквы и т.д.

5 2 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
10 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Александр
04/04/2010 18:46

1. По последнему методу: использование функций — это скорее устаревший способ, оставленный для обратной совместимости. В новых Delphi нужно использовать RawByteString + SetCodePage для произвольной кодировки (задаваемой в run-time) или AnsiString(CodePage) для фиксированной (например, type TString1251 = AnsiString(1251)). Например, если мы точно знаем, что целевая кодировка — UTF-8, то мы объявляем S типа UTF8String, загружаем в неё контент, а дальше — простое присвоение. Вся конвертация выполнится автоматически и под капотом. Если же кодировка не фиксирована и определяется в run-time, то мы объявляем S типа RawByteString и грузим в неё контент, после чего делаем, например, SetCodePage(S, 1251, False) — понятно, что… Подробнее »

Алексей (Минск)
Алексей (Минск)
01/06/2010 17:33

Странно у меня для ANSI пишет 932 со 100%
А для dos 866 но вероятность маленькая

Аленсандр
07/10/2010 15:15

А зачем Вы во втором варианте получали getcharset(headers), если потом явно преобразововали utf8 в 1251? Не подскажите, как получить getcharset, а потом преобразовать ее в 1251 не зная кодировки страници. Спасибо. Статьи Ваши очень познавательны.

Fr0sT
Fr0sT
26/10/2010 17:49

[code] Document.Position:=0; SetLength(B,Document.Size); for i := 0 to Document.Size - 1 do Document.Read(B[i],1); [/code] => [code] Document.Position:=0; SetLength(B,Document.Size); Document.Read(B[0],Length(B));[/code] ? >существует и минус – TEncoding есть только в версиях Delphi 2009 и выше, что исключает его использование в более ранних версиях. Как быть? Ну, во-первых, вполне можно выдрать код TEncoding и после небольших подпиливаний заюзать его в D<2009, либо не мучиться с обёртками и юзать самую основу — WideCharToMultiByte/MultiByteToWideChar, на которой, собственно, и сидят все остальные реализации. Только придётся ещё самому обрабатывать случаи utf16/32: эти функции с ними не работают. Но там всё просто: utf16le = widechar utf16be = swapbytes(widechar)… Подробнее »

wait
wait
04/02/2011 04:52

от себя добавлю еще один удобный способ, когда кодировка содержимого известна (в данном случае UTF8), но естественно способ можно переделать и с динамическим определением:
var
Response: TStringStream;
<…>
begin
Response := TStringStream.Create(», TEncoding.UTF8);
if HttpGetBinary(‘путь_до_сайта’, Response) then
Memo1.Text:=Response.DataString;
end;

trackback

[…] один из способов, предложенных, например, в статье "3 варианта работы с кодировками веб-страниц в Delphi." и писать, например, так: […]

trackback

[…] в ANSI-кодировке. О том как конвертировать строки я уже говорил. В модуле synacode отсутствует функция, возвращающая […]

Vitus
Vitus
19/02/2015 05:24

Спасибо за статью, очень помогла!!!

Макс
Макс
02/11/2015 01:07

Собственно, вопрос: каким образом можно всё это действо запустить обратно?

То есть мне необходимо перегнать из делфи запросом в URL через idHTTP. Третий день бьюсь, в одну сторону работает, в другую — нет.