Прошло больше года с момента первой публикации статьи про “MLang в Delphi”, которая была написана по материалам, предоставленным Александром – автором блога “БЛОГ GUNSMOKER-А” и, как я понимаю, ныне координатором русского Blaise Pascal Magazine. Статья эта до сих пор пользуется успехом у посетителей, которые хотят научиться работать с кодировками веб-страниц, однако, далеко не всегда статья читается целиком, включая комментарии как мои, так и других читателей – это обычное дело и в результате возникают всякого рода проблемы при использовании модулей для MLand в Delphi. Самая частая из которых – отсутствие поддержки версий Delphi ниже 2009. Поэтому на днях Александр прислал мне обновленную версию MLang для Delphi вплоть до Delphi 4. Ну, а я выкладываю его творение у себя в блоге с небольших рерайтом уже имеющейся статьи по MLang. Повторяться о том, что такое MLang и для чего он нужен я не буду – эту информацию Вы сможете получить из предыдущей публикации на тему. А перейду сразу к рассмотрению того, что добавлено и примера использования новых модулей для работы с кодировками в Delphi 4 – XE.
Новые возможности
Названия модулей. Теперь в состав входят следующие модули:
MLang.pas – заголовочник для MLang
Encodings.pas – модуль для работы с кодировками, позволяющий работать с TEncoding в старых версиях Delphi. Также модуль включает в себя такие “вкусности” для обладателей старых версий Delphi как:
{Определение кодировки текстового файла} function GetFileEncoding(const AFileName: String): TEncoding; {Перевод строки в массив байтов} function StringToBytes(const AString: RawByteString): TBytes; {Перевод массива байтов в строку} function BytesToString(const ABytes: TBytes): RawByteString; {Перевод строки в текст с определенной кодировкой и обратно} function StringToText(const AString: RawByteString; AEncoding: TEncoding = nil): String; function TextToString(const AString: String; AEncoding: TEncoding = nil): RawByteString; {Получение текста из файла с определенной кодировкой и наоборот} function FileToText(const FileName: String; AEncoding: TEncoding = nil): String; procedure TextToFile(const FileName: string; const Contents: String; AEncoding: TEncoding = nil; Append: Boolean = False); {Загрузка списка строк из файла в определенной кодировке и наоборот} procedure LoadStringsFromFile(const AStrings: TStrings; const AFileName: String; const AEncoding: TEncoding = nil); procedure SaveStringsToFile(const AStrings: TStrings; const AFileName: String; const AEncoding: TEncoding = nil);
Большинство из этих функций давно известна пользователям той же Delphi XE, а для пользователей, скажем Delphi 7 эти функции окажутся очень полезными в работе.
Этот же модуль содержит и уже бывшие ранее функции по преобразованию кодировок web-страниц:
function RawHTMLToHTML(const ARawHTML: RawByteString; const ACharset: Cardinal = 0): UnicodeString; function HTMLToRawHTML(const AHTML: UnicodeString; const ACharset: Cardinal = 0): RawByteString;
И, наконец, добавлены аналогичные функции для работы с кодировками в XML-файлах:
function RawXMLToXML(const ARawXML: RawByteString; const ACharset: Cardinal = 0): UnicodeString; function XMLToRawXML(const AXML: UnicodeString; const ACharset: Cardinal = 0): RawByteString;
Основное изменение в новых исходниках — конвертация строки. В старых исподниках использовался метод MultiByteToWideChar, который работает только для установленных в системе кодовых страниц, что не всегда приводит к положительному результату работы.
Compatibility.pas – модуль для обеспечения совместимость со старыми версиями Delphi.
Теперь перейдем к рассмотрению примера работы с MLang.
Пример использования MLang в Delphi
Александр накидал примерчик использования новых модулей, который использует в работе Indy, поэтому если Вы используете Delphi ниже 6 версии (не удивлюсь, если окажется, что так и есть), то помните, что Indy там нету по умолчанию.
Итак, форма приложения выглядит следующим образом:
Приложение, использующее MLang, позволяет:
- Получить страницу, используя кодировку сервера (из заголовков)
- Получить страницу, используя кодировку страницы (по мета-тегу)
- Получить страницу, попробовав угадать кодировку текста, игнорируя и заголовки и мета-теги
- Получить страницу и преобразовать кодировку, указав кодовую страницу вручную.
По-моему примеров более, чем достаточно, чтобы выбрать для себя подходящий вариант работы с кодировкой веб-страницы. Пройдемся по всем функциям программы по порядку.
Получение web-страницы с использованием кодировки сервера
Функция в программе выглядит следующим образом:
procedure TfmMain.btGetByServerContentTypeClick(Sender: TObject); var Page: RawByteString;//текст страницы ServerEncoding: String; //кодировка серверв CharsetName: String; //имя кодировки Charset: Cardinal;//кодовая страница begin {загрузили страницу} Page := LoadPage(edURL.Text, ServerEncoding); {получили название и номер кодовой страницы} CharsetName := ExtractCharsetName(mlHTML, ServerEncoding, True); Charset := CharsetNameToCharset(CharsetName); {выписали в Memo результаты определения} mmResult.Lines.Text := 'Кодировка (имя): ' + CharsetName + sLineBreak + 'Кодировка (код): ' + IntToStr(Charset) + sLineBreak + 'Страница: ' + sLineBreak + sLineBreak + RawHTMLToHTML(Page, Charset);//преобразовали текст страницы end;
Функция LoadPage, используя Indy (IdHTTP) загружает контент страницы в RawByteString (строку без определенной кодировки):
function LoadPage(const AURL: String; out AServerEncoding: String): RawByteString; overload; var HTTP: TIdHTTP; Strs: TMemoryStream; begin AServerEncoding := ''; HTTP := TIdHTTP.Create(nil); try {Грузим контент} Strs := TMemoryStream.Create; try HTTP.HandleRedirects := True; HTTP.Get(AURL, Strs); SetLength(Result, Strs.Size); Move(Strs.Memory^, Pointer(Result)^, Strs.Size); finally FreeAndNil(Strs); end; // Получаем строку для передачи в функцию поиска кодировки AServerEncoding := 'charset=' + HTTP.Response.CharSet; finally FreeAndNil(HTTP); end; end;
ExtractCharsetName — извлекает из строки имя кодировки:
function ExtractCharsetName(const AType: TMarkupLanguage; const AHTML: String; const AContentTypeOnly: Boolean): String;
Здесь: AType: TMarkupLanguage — флаг, определяющий из какого файла мы получаем кодировку. Может принимать значения:
- mlInvalid — тип не определен (например, когда работаем с текстовых файлом)
- mlHTML — HTML-код
- mlXML — XML-код Функция определена в Encoding.pas. CharsetNameToCharset — По имени кодовой страницы получает её номер.
function CharsetNameToCharset(const ACharsetName: String): Cardinal;
Функция определена в Encoding.pas. RawHTMLToHTML — проводит преобразование текста по заданной кодовой странице. С этой функцией мы уже знакомы:
function RawHTMLToHTML(const ARawHTML: RawByteString; const ACharset: Cardinal): UnicodeString;
Получение web-страницы с использованием кодировки, определенной в мета-теге
Работа в данном случае выглядит на 99% также как и в предыдущем случае, за исключением способа получения названия кодовой страницы. Здесь получение значение для переменной CharsetName выглядит следующим образом:
CharsetName := ExtractCharsetName(mlHTML, Page);
В остальном весь код тот же.
Получение web-страницы с автоопределением кодовой страницы
Здесь мы уже не трогаем ни заголовки, ни мета-тега, а соответственно и вид обработчика несколько изменяется:
procedure TfmMain.btGetByAutoDetectClick(Sender: TObject); var Page: RawByteString; CharsetName: String; Charset: Cardinal; begin Page := LoadPage(edURL.Text); CharsetName := '???'; Charset := DetectHTMLCharset(Page);//угадываем кодировку mmResult.Lines.Text := 'Кодировка (имя): ' + CharsetName + sLineBreak + 'Кодировка (код): ' + IntToStr(Charset) + sLineBreak + 'Страница: ' + sLineBreak + sLineBreak + RawHTMLToHTML(Page, Charset); end;
Всё, в принципе, то же, за исключением замечательной функции DetectHTMLCharset:
function DetectHTMLCharset(const AText: RawByteString): Cardinal;
Эта функция, получив в качестве входного параметра текст со страницы и, используя интерфейс IMultiLanguage2 из MLang пробует «угадать» номер кодовой страницы в которой приведен текст. Здесь без разницы, что содержит AText — мета-теги с кодировкой, XML-код или строку — функция «угадывает» кодировку по всему объему AText. Проверил работоспособность функции на трех сайтах с разными кодировками: windows-1251, utf-8, KOI8-R — ошибок в работе не обнаружено, текст конвертируется правильно.
Получение web-страницы с указанием собственной кодировки
Этот вариант работы для самых “отчаянных“ программистов – вместо определение кодировки вы её указываете вручную, используя номер или название кодовой страницы и вызываете метод RawHTMLToHTML для преобразования.
На этом обзор новых возможностей модулей для работы с MLang в Delphi 4-XE закокнчен. Исходники обновленных модулей можно скачать по ссылке ниже:
[download id=»90″ format=»1″]Не забудьте сказать “Спасибо” Александру – это всё его работа, а я только сделал небольшой обзор в блоге и немного потестировал модули.
Книжная полка
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
а вот тут косяки:
модуль Encodings
(* 17.01.2013 удалено из за сбоя на http://copypast.ru/2012/07/31/japonskie_shkolnicy.htm*)
//OleCheck(MultiLang.DetectInputCodepage(MLDETECTCP_HTML, 0, PAnsiChar(AText), Len, DE, Scores));
и на других УРЛ это непонятно для чего установленное исключение срабатывает, пишет «Неопознанная Ошибка»
Подключил encodings как в примере:
implementation
uses
Encodings,
Compatibility;
{$I jedi.inc}
{$R *.dfm}
но на старой Delphi всё равно не работает
S.SaveToFile(‘1.txt’,Encoding.UTF8); — too many actual parameters
Что я не так делаю?