Матрёшка: обмен текстовыми данными

Строковые текстовые данные в Матрёшке всегда хранятся в виде объектов Universal_String с использованием набора символов Unicode. В то же время для хранения и обмена текстовыми данными активно используются самые разнообразные кодировки текстовых данных. Матрёшка включает специальный компонент — текстовый кодек — для выполнения преобразования текстовых данных из/во внутреннее представление.

Для представления внешних данных используется стандартный тип для представления элемента потока ввода‐вывода — Ada.Streams.Stream_Element — и массив элементов этого типа Ada.Streams.Stream_Element_Array. Матрёшка дополнительно предоставляет неограниченный вектор — League.Stream_Element_Vectors.Stream_Element_Vector. Преимущество использования последнего очевидно в случаях когда заранее неизвестно и сложно в процессе выполнения определить размер данных. Менее известно другое (пожалуй более важное) преимущество: объект Stream_Element_Vector имеет фиксированный размер и занимает фиксированный объём памяти стэка не зависимо от того, какого объёма данные в него помещены. Для современных многонитевых приложений это имеет важное значение — на размер стэка каждой нити накладываются подчас весьма серьёзные ограничения.

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

Создание текстового кодека и выбор кодировки

Для использования текстового кодека необходимо создать и инициализировать объект типа League.Text_Codecs.Text_Codec. Делается это с помощью функции League.Text_Codecs.Create, принимающей единственный параметр — имя используемой кодировки. В качестве имён можно привести «ISO-8859-1» или «Windows-1251». Полный список поддерживаемых кодировок и их имена приведены в документации на Матрёшку. При поиске необходимого кодека имя кодировки подвергается нормализации — из него удаляются служебные символы и все буквы приводятся к одному регистру; поэтому „utf8“ и „UTF-8“ есть корректное имя одной и той же кодировки.

Ниже приведён пример кода создания и инициализации текстового кодека:


          

          
   Codec : League.Text_Codecs.Text_Codec
     := League.Text_Codecs.Codec
         (League.Strings.To_Universal_String ("Windows-1251"));

Преобразование текстовых данных во внешнее представление

Предположим, что у нас имеется некоторая строка и необходимо преобразовать её во внешнее представление используя кодировку UTF-8. Для преобразования строки в вектор элементов потока предназначена функция Encode. Для полноты примера ещё потребуется функция преобразования вектора в массив — To_Stream_Element_Array.


          

          
function To_Utf8
 (Item : League.Strings.Universal_String)
    return Ada.Streams.Stream_Element_Array
is
   Codec : League.Text_Codecs.Text_Codec
     := League.Text_Codecs.Codec
         (League.Strings.To_Universal_String ("UTF-8"));

begin
   return Codec.Encode (Text).To_Stream_Element_Array;
end To_Utf8;

Преобразование текстовых данных из внешнего представления

Обратное преобразование выполняется столь же просто с использованием функции Decode. Эта функция перегружена и для удобства использования может принимать объекты Stream_Element_Array или Stream_Element_Vector.


          

          
function From_Windows_1251
 (Item : Ada.Streams.Stream_Element_Array)
    return League.Strings.Universal_String
is
   Codec : League.Text_Codecs.Text_Codec
     := League.Text_Codecs.Codec
         (League.Strings.To_Universal_String ("Windows-1251"));

begin
   return Codec.Decode (Item);
end From_Windows_1251;

Рекомендованный кодек для ввода‐вывода

Перед любым интернационализированным приложением встаёт нелёгкий выбор: в какой кодировке поступают входящие данные и какая кодировка должна использоваться для вывода данных. Хорошо если ответ на этот вопрос может быть найден некоторым способом, например в заголовке XML файла или имени кодировке в заголовке HTTP ответа. А что же делать «простым» приложениям? Ответ на этот вопрос даёт функция League.Text_Codecs.Codec_For_Application_Locale, которая возвратит кодек предназначенный для обработки данных в соответствии с используемыми настройками локали пользователя.

String, стандартная библиотека и подводные камни

Как всем известно, традиционный тип для представления текстовых данных в Ada есть тип String. Что заключают в себя объекты этого типа — неведомо никому, зато он сплошь и рядом используется в стандартной библиотеке. И хотя последующие поколения языка предложили другие типы данных (Wide_String и Wide_Wide_String) для представления текстовых данных, это почему‐то никак не повлияло на многие подпрограммы стандартной библиотеки языка, которые по прежнему работают с типом String. В частности, имя файла даже в пакете Ada.Wide_Wide_Text_IO может быть задано только с помощью типа String.

Сообщения для исключений

Ещё одним очень досадным местом использования String является текстовое сообщение оператора возбуждения исключения. Для удобства использования пакет League.Text_Codecs предоставляет подпрограмму To_Exception_Message, способную корректно преобразовать текстовые данные в понятный оператору возбуждения исключения формат.


          

          
procedure Raise_With_Message
 (Message : League.Strings.Universal_String) is
begin
   raise Program_Error
     with League.Text_Codecs.To_Excpetion_Message (Message);
end Raise_With_Message;

Грязный хак

Ввиду того, что без преобразования в String иногда не обойтись и в качестве заключения приводится реализация двух «полезных» функций преобразования между Universal_String и String. Постарайтесь НИКОГДА не использовать их без явной на то необходимости!


          

          
function To_String
 (Item : League.Strings.Universal_String) return String
is
   Aux    : constant Ada.Streams.Stream_Element_Array
     := League.Text_Codecs.Codec_For_Application_Locale.Encode (Item);
   Result : String (1 .. Aux'Length);
   for Result'Address use Aux'Address;

begin
   return Result;
end To_String;

function To_Universal_String
 (Item : String) return League.Strings.Universal_String
is
   Aux : Ada.Streams.Stream_Element_Array (1 .. Item'Length);
   for Aux'Address use Item'Address;

begin
   return
     League.Text_Codecs.Codec_For_Application_Locale.Decode (Aux);
end To_Universal_String;

Автор: Вадим Годунко
Дата: 04.08.2011