30 апреля 2010 г.

Работа с текстовыми файлами в любой кодировке из Delphi до 2009

Как должно быть известно любому программисту, не существует такой вещи как "просто текстовый файл". Если вы не знаете, в какой кодировке хранится строка, вы не только не сможете её показать, но даже определить, где она заканчивается.

Мир вокруг нас уже давно не ограничивается ANSI, а уж тем более ASCII. На фоне этого ваши древние ANSI-программы выглядят не очень-то хорошо. Потому что они молчаливо игнорируют существование альтернативных кодировок вообще. Для них существует только текущая кодовая страница ANSI, не больше и не меньше.

Хотя самое нормальное решение включает в себя переход на Delphi 2010, часто вас и пользователей устраивает ваше ANSI-приложение, если бы... оно работало бы с любыми текстовыми файлами. Как это достигается? Ну, в начале текстового файла вставляется метка BOM (Byte Order Mask), указывающая на кодировку файла. Это означает, что если вы хотите загрузить произвольный текстовый файл, то вам нужно прочитать несколько байт в начале файла, после чего определить формат файла и преобразовать его в нужный вам - это довольно много работы, не так ли?

Как это реализуется в Delphi? В Delphi 2009 и выше у вас появляется класс TEncoding, позволяющий работать с различными кодировками. Класс TStrings (и TStringList) используют TEncoding для определения кодировки файла и всех преобразований.

Было бы неплохо заиметь такую штуку, скажем в Delphi 7 или Delphi 2007? К счастью, это очень просто сделать (эй, это заняло примерно 8 минут моего времени, включая проверку). Нужно просто вытащить из Delphi 2010 код TEncoding и новые методы LoadFromFile(Stream)/SaveToFile(Stream).

Представляю вашему вниманию два модуля: Encoding.pas - здесь сидит новый класс TEncoding - штука, полезная сама по себе, даже если вы не используете её для работы с текстовыми файлами.

Второй модуль, StringListUnicodeSupport.pas методом Geo добавляет в обычный TStringList поддержку произвольной кодировки, а также перегруженные варианты методов загрузки и сохранения, позволяющие указать кодировку явно (SaveToFile/Stream сохраняют в ANSI, если вам нужна другая кодировка, вы должны указать её вторым параметром).

Вам достаточно подключить StringListUnicodeSupport в uses и вы волшебным образом получаете возможность работы с любыми текстовыми файлами:
uses
  StringListUnicodeSupport;

procedure TForm1.Button1Click(Sender: TObject);
var
  Str: TStringList; // использует новый TStringList из StringListUnicodeSupport
begin
  Str := TStringList.Create;
  try
    Str.LoadFromFile('C:\utf8_encoded_File.txt');

    // покажет содержимое файла вместо дракозябров (если это возможно в текущей кодовой странице ANSI)
    Memo1.Lines.Assign(Str); 

    // Примечание: прямой вызов Memo1.Lines.LoadFromFile работать не будет, потому что там не используется новый TStringList
  finally
    FreeAndNil(Str);
  end;
end;
Как пользователи динозаврических Delphi вы, вероятно, не знакомы с TEncoding и перегруженным вариантом методов TStrings. Что ж, к счастью, вы можете воспользоваться online-справкой: TEncoding, использование TEncoging, LoadFromFile, SaveToFile.

Скачать всё одним архивом.

26 комментариев :

  1. Прилично ли в русскоязычном блоге спрашивать про копирайты? :P

    ОтветитьУдалить
  2. Эммм... а что такое? :)

    Если вопрос про авторство - то это мой код, который я недавно написал в качестве ответа на вопрос Круглого Стола на DelphiKingdom.

    Если вопрос про то, можно ли это выкладывать/модифицировать - делайте что угодно. При перепубликации буду благодарен за ссылку на источник, вот и всё, пожалуй.

    ОтветитьУдалить
  3. О кул, спасибо. Как раз недавно на работе столкнулся с такой проблемой.

    ОтветитьУдалить
  4. Ну вот, чтож ты не сказал что Encoding выдран из джедая.
    Кстати ты переопределил TStringList а как делфи определит какой TStringList я имею в виду если подключано сразу два модуля: Clasess и StringListUnicodeSupport?

    ОтветитьУдалить
  5. Всё я врубилсо почитава твою статьтю "Шаманский метод Geo":

    Дело в том, что с большой степенью вероятности Вам в проектировании формы потребуются компоненты, которые определены в том же юните, где и оригинальный компонент. Чтобы использовался именно модифицированный компонент, нужно грамотно задать порядок подключаемых юнитов в разделе uses. По правилам языка если в uses есть два модуля, содержащих одно и то же имя, то будет использован элемент из того модуля, который в списке uses указан позже. Меняя порядок юнитов в uses, можно получать нужную комбинацию оригинальных и модифицированных компонент в форме. Однако все равно сохраняется ограничение, что в одной форме невозможно использовать и оригинальный компонент, и его модификацию.

    ОтветитьУдалить
  6. >>> Ну вот, чтож ты не сказал что Encoding выдран из джедая.
    С чего вы это взяли? Encoding.pas - это вынесенный в отдельный модуль класс TEncoding из Delphi 2010 с адаптацией под старые версии Delphi (D4 и выше). Джедаи не имеют к этому никакого отношения.

    ОтветитьУдалить
  7. Анонимный4 мая 2010 г., 01:39

    ну вот как бы одно из двух
    либо это ваш код, либо код Борлана(ок, ок, Эмбаркадера) допиленный вами о совместимости с D4.
    или - или.

    > Если вопрос про авторство - то это мой код

    > Нужно просто вытащить из Delphi 2010 код TEncoding

    ОтветитьУдалить
  8. >>> либо это ваш код, либо код Борлана(ок, ок, Эмбаркадера) допиленный вами о совместимости с D4
    Я же чётко сказал: Encoding.pas - это оформленный в отдельный модуль класс TEncoding из D2010.

    ОтветитьУдалить
  9. Спасибо Вам. Долго не мог найти ответ на свой вопрос, а именно: "Как загрузить Unicode файл в StringList"

    ОтветитьУдалить
  10. Доброго времени суток. Столкнулся с проблемой кодировки текстовых файлов. Прочел Вашу статью. Заинтересовало. В какие директории Делфи 7 следует поместить указанные модули так чтобы заработало?

    ОтветитьУдалить
  11. Отлично, я думал уже переводить проект на новую версию Delphi как раз из-за этого.
    Порывшись в исходниках не нашел где мне взять информацию, какою именно кодировку я загрузил? не подскажите?

    ОтветитьУдалить
    Ответы
    1. Не очень понятно зачем это надо, если в ANSI-версиях Delphi в строке может лежать только одна кодировка: ACP. Но если сильно хотите, то можно просто скопировать код StringListUnicodeSupport.TStringList.LoadFromStream с минимальными изменениями. В том коде у вас на руках будет Encoding - экземпляр TEncoding. Ну а его класс можно сравнить с известными: TUTF8Encoding, например.

      Удалить
  12. Хорошая статья, спасибо. Может подскажите как можно конвертировать StringList в нужную кодировку с уже загруженным текстом?

    ОтветитьУдалить
    Ответы
    1. Получается, что чтобы перекодировать, то нужно все выгрузить в поток, а затем заново из потока загрузиться?

      StringList.SaveToStream(mysream, TEncoding.UTF8);
      StringList.LoadFromStream(mysream);

      Других вариантов нет?

      Удалить
    2. Есть, конечно. Я просто задачи не вижу. Вам в памяти надо StringList из одной кодировки перегнать в другую? И всё это в старых ANSI Delphi? А из какой кодировки в какую?

      Удалить
    3. Да именно в памяти, уже после загрузки. А перекодировать нужно из любой в любую, ну конечно те которые допускает TEncoding

      Удалить
    4. Это прикол такой? Вы понимаете, что в старых ANSI версиях Delphi строки не могут хранить данные в любых кодировках? Это же однобайтовые ANSI строки. Если только как бесмысленный набор байт.

      Удалить
    5. Это я тоже не совсем понял, но как тогда я без проблем отрываю и просматриваю без абракадабры файлы в Unicode?

      Удалить
    6. В ANSI строках старых версий Delphi может находится лишь одна кодировка - ACP, которая настраивается системой ("язык для не-Unicode программ"). В "русской" Windows она равна Win-1251. Соответственно, никакими усилиями японские иероглифы (к примеру) в неё вы не запишете.

      Когда вы загружаете текст из Unicode: если в нём лежат русские буквы - они без проблем преобразуются в Win-1251 без потерь. Но если там лежат символы вне Win-1251 (западно-европейская латиница, иероглифы, хинди и др.) - эти символы нельзя преобразовать в Win-1251, поэтому они будут заменены вопросиками.

      Поэтому мне не очень понятна задача преобразования из одной ANSI-кодировки в произвольную другую. Если только целевая кодировка не OEM и не Unicode, то такое преобразование не имеет смысла - ведь общих символов у двух разных ANSI кодировок просто нет. Вам необходима поддержка Unicode, если вы хотите работать с несколькими разными кодировками. В старых Delphi вы можете это делать, но не стандартным кодом RTL: вам нужно использовать WideString (Unicode строки) вместо String (ANSI строки), а также заменить все классы (TStringList) и весь код с String на WideString. Извращение, но можно. Но проще перейти на новую Delphi с родной поддержкой Unicode.

      Удалить
  13. Спасибо теперь я все понял.

    ОтветитьУдалить
  14. Не определяет кодировку utf-8 without BOM

    ОтветитьУдалить
    Ответы
    1. Так и не должен. BOM же нет, как он кодировку определит? Словарей нет, таблиц эвристики нет.

      Удалить
  15. При сохранение все вроде хорошо конвертируется и сохраняется, к сожалению, кроме:
    StringList.SaveToFile(FileName, TEncoding.BigEndianUnicode); вылетает AV (

    ОтветитьУдалить
  16. Поставил Delphi 10.2 Starter там такого бага нет. Даже не викидывал из uses StringListUnicodeSupport, хотя он так как-бы и ненужен

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.