Использование Qt-2.2.x в русскоязычных программах
Автор : Сукиязов Сергей Александрович
Эта статья содержит информацию об одной из популярных библиотек для организации графического пользовательского интерфейса (GUI) - Qt Toolkit, разработаной норвежской фирмой Troll Tech.
Библиотека Qt представляет собой законченное и глубоко проработанное многоплатформенное объектноориентированное окружение для разработки GUI-приложений с использованием языка C++. Qt также хорошо интегрируется с библиотеками OpenGL/Mesa 3D.
Qt является бесплатной (free)
библиотекой для разработки бесплатного программного обеспечения
(freeware) в X Window System. Она
включает в себя полный исходный код X-версии библиотеки
и make-файлы для операционных систем Linux,
Solaris, SunOS,
FreeBSD и др. Эта редакция (free)
Qt может модифицироваться и
распространяться с соблюдением условий перечисленных в файле
LICENSE.QPL
.
В настоящее время Qt используется в сотнях проектов по разработке программного обеспечения по всему миру, включая популярную оболочку K Desktop Environment. Для более полной информации смотрите ссылку http://www.trolltech.com/qtprogs.html.
Qt можно загрузить по адресу http://www.trolltech.com/dl/ или через анонимный FTP с сервера ftp.trolltech.com. На этом сервере также доступны нестабильные версии, находящиеся на стадии разработки, в виде ежедневных "снапшотов".
Qt содержит замечательную документацию: более 750 страниц в формате Postscript и HTML. Документация также доступна через WEB: http://doc.trolltech.com/.
Qt является полностью объектноориентированной библиотекой. Все "виджеты" представляют собой C++ объекты, и, используя наследование, создание новых "виджетов" получается простым и естественным.
В рамках этой статьи мы не будем подробно разбирать все детали программирования с использованием библиотеки Qt. Как говорилось выше Qt содержит подробную документацию и множество примеров. В этой статье мы остановимся на проблемах корректной локализации (интернационализации) программ, разработанных с использованием Qt, и попытаемся выработать некоторые советы по использованию классов библиотеки Qt, которые помогут избежать проблем с интернационализацией программ.
Т.к. библиотека Qt для представления текстовых данных использует UNICODE, то при некорректном преобразовании текстовых данных из однобайтовых кодировок в UNICODE и проявляются проблемы с отображением национальных символов.
Внешне эти проблемы выглядят следующим образом: вместо национальных символов (коды более 127) выводится символ '?' или вместо строки, содержащей национальные UNICODE-символы (коды более U+00FF), в результате преобразований получаются пустые однобайтовые строки.
Может сложиться впечатление, что в Qt вопрос преобразования из однобайтовых строк к UNICODE-строкам недостаточно продуман. На самом деле это не так. В Qt преобразование строк достачно хорошо продуманно и описано в документации. К единственному недостатку докуметации можно отнести тот факт, что документацию писали в большинстве своем англоязычные программисты и, соответственно, вопросам использования национальных UNICODE-символов уделено незначительное внимание.
Дело в том, что англоязычные программисты используют кодировку ISO-8859-1 (или US-ASCII), коды символов которой совпадают с UNICODE-кодами этих символов. В этом случае преобразование из однобайтовой строки в UNICODE-строку сводится к простому расширению однобайтового значения до двухбайтового (просто в сташий байт заносится значение 0). Для национальных символов преобразование не столь тривиально: русская буква 'А' имеет код в кодировке ISO-8859-5 равный 0xB0, в кодировке UNICODE - код равный U+0410. В результате простого расширения однобайтового значения русская буква 'А' получит UNICODE код U+00B0 вместо U+0410, что далеко не одно и тоже.
Далее мы проанализируем причины приводящие к возникновению этих ошибок и разберем несколько советов по их устранению.
В библиотеке Qt, как говорилось выше, внутренний формат строк - UNICODE. Поэтому почти все методы и функции библиотеки Qt в качестве своих фактических параметров желают иметь именно UNICODE строки.
Для хранения кажого символа в кодировке UNICODE в Qt
отводится два байта. Для представления UNICODE символа в
Qt используется класс
QChar
. Полное и подробное описание конструкторов, методов
и операторов этого класса можно посмотреть в документации по библиотеке
Qt. Преобразование по умолчанию
из одобайтового символа (C тип char
) в UNICODE символ
(Qt тип QChar
)
выполняется простым расширением значения. Т.е. русская буква 'А' (код в
ISO-8859-5 - 0xB0) получает UNICODE код U+00B0.
Для представления UNICODE строк в Qt
используется класс QString
, который в общем представляет
собой массив символов типа QChar
. Полное и подробное описание конструкторов, методов
и операторов этого класса также можно посмотреть в документации по библиотеке
Qt. Преобразование по умолчанию
из одобайтовой строки (C тип char *
) в UNICODE строку
(Qt тип QString
) также
выполняется простым расширением значения. Т.е. русская буква 'А' (код в
ISO-8859-5 - 0xB0 ) получает UNICODE код U+00B0.
Для хранения однобайтовых строк, в библиотеке Qt,
используется класс QCString
, наследуемый от класса
QByteArray
. В этом классе строки представлены как
массивы однобайтовых символов.
Для корректного преобразования из однобайтовых строк в UNICODE-строки и
обратно с учетом особенностей национальных кодировок, в
Qt используется класс QTextCodec
.
Полное и подробное описание конструкторов, методов и операторов этого
класса также можно посмотреть в документации по библиотеке Qt.
Мы только остановимся на некоторых наиболее важных методах этого класса:
static QTextCodec* QTextCodec::codecForName(const char* hint, int accuracy=0);
QTextCodec
, и
возвращает указатель на объект QTextCodec
, имя которого
наиболее совпадает с именем, переданным через параметр hint.
Если кодек (Codec) не найден возвращает NULL
. Параметр accuracy
определяет точность совпадения имени кодека, значение 0 определяет точное
совпадение.
static QTextCodec* QTextCodec::codecForLocale();
virtual QString QTextCodec::toUnicode(const char* chars, int len) const;
virtual QCString QTextCodec::fromUnicode(const QString& uc, int& lenInOut) const;
QChar
) начиная с первого символа строки uc.
Возвращает объект типа QCString
, и также возвращает длинну
результата в lenInOut.
QCString QTextCodec::fromUnicode(const QString& uc) const;
QString QTextCodec::toUnicode(const char* chars) const;
В качестве примера использования класса QTextCodec
для
преобразования строк, можно привести следующий пример (предполагается
что используется локаль "ru" и она настроена):
1: char *str = "Привет мир!!!"
2: QTextCodec *c;
3: QString wstr;
4:
5: wstr = str;
6: qWarning("%s", (const char *)wstr.local8Bit() );
7: //^^ Будет напечатано : ?????? ???!!!
8: qWarning("%s", wstr.latin1() );
9: //^^ Будет напечатано : Привет мир!!!
10:
11: c = QTextCodec::codecForLocale();
12: // или c = QTextCodec::codecForName("ISO-8859-5");
13: if ( c )
14: {
15: wstr = c->toUnicode(str);
16: qWarning("%s", (const char *)c->fromUnicode(wstr) );
17: //^^ Будет напечатано : Привет мир!!!
18: // c->fromUnicode(wstr) эквивалентно wstr.local8Bit()
19: qWarning("%s", (const char *)wstr.latin1() );
20: //^^ Будет напечатана пустая строка
21: }
22: else
23: {
24: qWarning("Кодек не найден");
25: }
Разберем подробнее приведенный пример.
В строке 5 используется преобразование по умолчанию (вызывается конструктор
QString( const char * )
) из одобайтовой строки (C тип
char *
) в UNICODE строку QString
- т.е.
простое расширением значения. В этом случае русская буква 'А' (код в
ISO-8859-5 - 0xB0) получает UNICODE код U+00B0.
В строке 6 производится преобразование из строки QString
в
однобайтовую строку с использованикм метода QCString QString::local8Bit()
.
Этот метод выполняет преобразование с учетом установок локали, и
подробнее это метод будет рассмотрен ниже. Т.к. для кириллицы в
UNICODE не определены символы с кодами от U+0080 до U+00FF, то в
результате преобразования вместо русских букв получаются символы '?'.
То же самое происходит и с другими языками, для которых UNICODE коды
символов больше U+00FF.
В строке 8 производится преобразование из строки QString
в
однобайтовую строку с использованикм метода const char *QString::latin1()
.
Этот метод выполняет преобразование простым сжатием двухбайтового значения
до однобайтового. При сжатии для значений, старший октет которых равен 0,
берется значение младшего октета, а для значений, старший октет которых
отличен от нуля, берется значение 0 (Октет - последовательность из 8 бит
или байт. На различных платформах размер байта может отличаться.).
Подробнее это метод будет рассмотрен ниже. Т.к. при отбросе старшего
нулевого октета для U+00B0 получается 0xB0 (код русской буквы 'А' в
ISO-8859-5) то в строке 7 русские символы выводятся правильно.
В строке 11 мы находим кодек, который будет использоваться по умолчанию
для текущих установок локали. Если метод QTextCodec::codecForLocale()
вернет ненулевое значение то кодек найден и можно выполнять преобразования.
В строке 15 используется преобразование с помощью найденного кодека. В этом случае русская буква 'А' (код в ISO-8859-5 - 0xB0) получает UNICODE код U+0410. Поэтому в строке 16 будут корректно напечатаны русские буквы. А строке 19 при отбросе старшего не нулевого октета для U+0410 (старший октет 0x04) получается значение 0x00 (признак конца строки в языке C) и, соответственно, выводится пустая строка.
Для корректного преобразования из однобайтовых строк в UNICODE-строки и
обратно с учетом особенностей национальных кодировок кроме класса
QTextCodec
, в классе QString
помимо конструкторов
введены специалные методы, которые позволяют конструировать объекты
QString
с учетом особенностей определенных однбайтовых
кодировок. Это следующие методы:
const char* latin1() const;
QString
представлена в кодировке ISO-8859-1.
static QString fromLatin1(const char*, int len=-1);
char *
) в UNICODE строку QString
кодек
для кодировки ISO-8859-1. Т.о. преобразование выполняется простым
расширением значения. В этом случае русская буква 'А' (код в ISO-8859-5
- 0xB0) получает UNICODE код U+00B0. Если задан параметр len
то преобразуются только len символов исходной строки.
QCString utf8() const;
static QString fromUtf8(const char*, int len=-1);
char *
) в UNICODE строку QString
кодек
для кодировки UTF-8. Если задан параметр len то преобразуются
только len символов исходной строки.
QCString local8Bit() const;
QString
представлена в "чистом" UNICODE.
Если для текущей локали не найден подходящий кодек то, используется
кодек для кодировки ISO-8859-1.
static QString fromLocal8Bit(const char*, int len=-1);
char *
) в UNICODE строку QString
кодек
для текущих установок локали. Т.о. преобразование выполняется с учетом
особенностей кодировки, определяемой установками текущей локали. Например,
если в текущей локали используется кодировка ISO-8859-5, то будет
использован кодек для этой кодировки. В этом случае русская буква 'А'
(код в ISO-8859-5 - 0xB0) получает UNICODE код U+0410. Если задан
параметр len, то преобразуются только len символов исходной строки. Если для
текущей локали не найден подходящий кодек, то используется кодек для
кодировки ISO-8859-1.
C использованием методов класса QString
приведенный выше
пример можно изменить следующим образом:
1: char *str = "Привет мир!!!"
2: QTextCodec *c;
3: QString wstr;
4:
5: wstr = str;
6: qWarning("%s", (const char *)wstr.local8Bit() );
7: //^^ Будет напечатано : ?????? ???!!!
8: qWarning("%s", wstr.latin1() );
9: //^^ Будет напечатано : Привет мир!!!
10:
11: wstr = QString::fromLocal8Bit(str);
12: qWarning("%s", (const char *)c->fromUnicode(wstr) );
13: //^^ Будет напечатано : Привет мир!!!
14: // c->fromUnicode(wstr) еквивалентно wstr.local8Bit()
15: qWarning("%s", (const char *)wstr.latin1() );
16: //^^ Будет напечатана пустая строка
Разработчики Qt реомендуют
использовать перечисленные выше методы для конструирования UNICODE
строк QString
и пребразования из QString
в однобайтовые строки, вместо преобразований по умолчанию.
Явный вызов вышеперечисленных методов, для конструирования строк типа
QString
, позволит быть уверенным в том, что в программе
используется "чистый" UNICODE.
Т.к. большинство программ написаны англоязычными программистами, которые используют в качестве основной кодировку ISO-8859-1, в таких программах мало внимания уделяется преобразованиям символов. Как уже говорилось выше, для кодировок ISO-8859-1 (LATIN1) и US-ASCII, преобразования из однобайтовой строки в UNICODE и обратно не имеют особого значения, т.к. коды символов (значения кодов) в этих кодировках совпадают. Например, латинская буква 'A' имеет код в кодировке ISO-8859-1 (US-ASCII) 0x65 и в UNICODE код латинской буквы 'A' будет равным U+0065. Т.е. <Код символа ISO-8859-1>==<Код символа UNICODE>. Соответственно, для кодировок ISO-8859-1 (LATIN1) и US-ASCII, преобразование простым расширением/сжатием значения выполняется корректно и программа работает с "чистым" UNICODE.
Более того, англоязычные программисты не могут заметить и, соответственно, исправить такие ошибки, т.к. в большинстве случаев для отладки программ они используют тексты, содержащие латинские символы, и локаль с кодировкой ISO-8859-1. Нереально представить себе ситуацию, когда каждый программист, например, из Великобритании для отладки программы устанавливает у себя русские шрифты и настраивает русскую локаль, а ведь кроме русского языка существует еще множество других языков.
Поэтому, чтобы избавить себя и пользователей программы от проблем с локализацией, нужно более четко определить ситуации когда возникают эти ошибки и сформулировать некоторые принципы написания кода программы. Все рекомендации, которые будут сформулированны ниже, в общем виде, применимы не только к программам использующим библиотеку Qt, но и ко всем другим программам которые хранят и обрабатывают строки в формате UNICODE или WideChar.
Чаще всего ошибки с преобразованием национальных символов в библиотеке
Qt возникают из-за использования
неявного преобразования типов к типу QString
. Неприятность
этих ошибок заключается в том, что на этапе компиляции не выдается никаких
сообщений и предупреждений, т.к. с точки зрения синтаксиса и правил языка
программирования ошибки действительно нет. Подобная ошибка проявляется только
на этапе выполнения программы, причем к исключительной ситуации не приводит.
В каких местах программы возникают такие ошибки ?
Наиболее частое место возникновения этой ошибки - преобразование типа
при передаче параметров в процедуру или функцию. Например, есть некоторая
функция, которая в качестве параметра требует строку типа QString
,
а при вызове этой функции ей в качестве фактического параметра передается
обчная C-like строка:
1: #include <qstring.h>
2:
3: void foo( const QString & str )
4: {
5: qWarning("%s",(const char *)str.local8Bit());
6: }
7:
8: int main( int argc, char *argv[] )
9: {
10: foo( "Привет мир!!!" );
11: //^^ Будет напечатана строка : ?????? ???!!!
12: }
В этом примере, в строке 10, выполняется неявное преобразование типов
из const char *
в QString
с использованием
конструктора QString::QString( const char * )
. В результате
такого преобразования, как говорилось выше, происходит простое расширение
однобайтового значения до двухбайтового. Т.е. строка "Привет мир!!!"
в формате UNICODE будет иметь следующее значение:
"U+00BF,U+00E0,U+00D8,U+00D2,U+00D5,U+00E2,U+0020,U+00DC,
U+00D8,U+00E0,U+0021,U+0021,U+0021"
"U+041F,U+0440,U+0438,U+0432,U+0435,U+0442,U+0020,
U+043C,U+0438,U+0440,U+0021,U+0021,U+0021"
В качестве другого примера можно привести функцию, которая в качестве
параметра требует C-like строку, а при вызове этой функции ей в качестве
фактического параметра передается строка типа QString
:
1: #include <qstring.h>
2:
3: void foo( const char * str )
4: {
5: qWarning("%s",str);
6: }
7:
8: int main( int argc, char *argv[] )
9: {
10: QString qstr = QString::fromLocal8Bit( "Привет мир!!!" );
11: //^^ Инициализируем qstr в "чистый" UNICODE
12:
13: foo( qstr );
14: //^^ Будет напечатана пустая строка
15: }
В этом примере в строке 13 выполняется неявное преобразование типов
из QString
в const char *
с использованием
оператора преобразования типа QString::operator const char *() const;
,
который определен в файле qstring.h
как:
1: class Q_EXPORT QString
2: {
3: ...
4: operator const char *() const { return latin1(); }
5: ...
6: };
Как видно из определения оператора QString::operator const char *() const
,
для преобразования типов будет вызван метод const char* QString::latin1() const
.
Этот метод для национальных символов с кодами большими U+00FF возвращает
нулевое значение, т.о. функция void foo( const char * str )
,
из примера выше, в качестве параметра получит C-like строку, первый символ
которой равен '\0', что, в свою очередь, является признаком конца строки
для C-like строк. Поэтому в результате выполнения строки 5 будет напечатана
пустая строка.
Из-за того, что в этих примерах в той или иной степени используется "не чистый" UNICODE, и возникают проблемы с обработкой национальных символов. Впрочем, если мы установим локаль в "en_US" (export LC_ALL=en_US), то приведенные выше примеры будут корректно выводить русские символы.
Этот тип ошибок очень часто встречается когда, например, из программы
использующей библиотеку Qt,
обращаются к системным вызовам ядра (например fopen(..), open(..), mkdir(..),
access(..), unlink(..)
и т.д.), которые требуют в качестве своих параметров
C-like строку, а при вызове этой функции ей в качестве фактических
параметров передаеются строки типа QString
. В случае
вызова функций fopen(..), open(..), mkdir(..), access(..), unlink(..)
,
из-за этой ошибки невозможно работать с файлами и директориями, содержащими
национальные символы в именах.
Небольшое замечание: Применение метода QString::fromLatin1(...)
для преобразования однобайтовой строки в UNICODE-строку, оправдано исключительно
в случае, если однобайтовая строка содержит только символы в кодировке
ISO-8859-1. В примерах строки 2 и 5, действительно строки "Hello world!!!"
и " " содержат только ISO-8859-1 символы. В строке 1, строка
"Привет мир!!!" содержит только русские символы, поэтому мы
используем метод QString::fromLocal8Bit(...)
. В строке 6
переменная buff может содержать не только латинские символы,
поэтому наиболее безопасное решение - применять метод QString::fromLocal8Bit(...)
.
Это даст гарантию того, что все символы из строки buff будут
корректно преобразованы в UNICODE.
В некоторых случаях бывает невозможно использовать ключи -DQT_NO_CAST_ASCII
и -DQT_NO_ASCII_CAST
при компиляции программы. Например,
если вы используете другую билиотеку, базирующуюся на Qt,
которая разрабатывалась без использования ключей -DQT_NO_CAST_ASCII
и -DQT_NO_ASCII_CAST
при компиляции. В этом случае,
вам нужно придерживаться советов, рассмотренных ниже, они помогут избежать
ошибок. Но если вы начинаете разрабатывать свою собственную программу с
использованием библиотеки Qt, то
обязательно используйте ключи -DQT_NO_CAST_ASCII
и -DQT_NO_ASCII_CAST
при компиляции программы.
При проектировании программы нужно обязательно определить в каком формате
будут храниться и обрабатываться строки: в однобайтовом или UNICODE формате.
И в дальнешем, при программировании, стараться внутри функций и классов не
смешивать однобайтовые и UNICODE строки. Т.е. если некоторый класс содержит
несколько членов данных с текстовой информацией, нужно стараться чтобы
все эти члены данные были одного типа, а для доступа к этим членам данным
использовать перегружаемые методы, которые, в свою очередь, гарантируют
корректность преобразования, даже если при компиляции не заданы ключи
-DQT_NO_CAST_ASCII
и -DQT_NO_ASCII_CAST
.
Например:
1: class Foo
2: {
3: public:
4: Foo( const Foo & foo )
5: {
6: m_d1 = foo.m_d1;
7: m_d2 = foo.m_d2;
8: };
9: Foo( const QString & d1, const QString & d2 = QString::null )
10: {
11: m_d1 = d1;
12: m_d2 = d2;
13: };
14: Foo( const char * d1, const char * d2 = NULL )
15: {
16: m_d1 = QString::fromLocal8Bit(d1);
17: m_d2 = d2 ? QString::fromLocal8Bit(d2) : QString::null;
18: };
19: vod setD1( const QString & d1 )
20: {
21: m_d1 = d1;
22: };
23: void setD1( const char * d1 )
24: {
25: m_d1 = QString::fromLocal8Bit(d1);
26: };
27: vod setD2( const QString & d2 )
28: {
29: m_d2 = d2;
30: };
31: void setD2( const char * d2 )
32: {
33: m_d2 = QString::fromLocal8Bit(d2);
34: };
35: QString do()
36: {
37: return m_d1 + QString::fromLatin1(" ") + m_d2;
38: };
39: private:
40: QString m_d1;
41: QString m_d2;
42: };
Наличие перегруженных конструкторов и методов setD1/setD2
в этом классе гарантирует, что внутри этого класса всегда будет использоваться
"чистый" UNICODE, независимо от того, каким образом мы инициализируем
объекты класса Foo
:
1: Foo f1( "Привет","мир!!!" );
2: QString s1 = QString::fromLocal8Bit("Привет");
3: Foo f2;
4:
5: f2.setD1( s1 );
6: f2.setD2( "мир!!!" );
7: qWarning("%s", (const char *)f1.do().local8Bit());
8://^^ Напечатает строку : Привет мир!!!
9: qWarning("%s", (const char *)f2.do().local8Bit());
10://^^ Напечатает строку : Привет мир!!!
Еще более безопасное решение - добавление в класс Foo
двух конструкторов:
1: ...
2: Foo( const QString & d1, const char * d2 )
3: {
4: m_d1 = d1;
5: m_d2 = QString::fromLocal8Bit(d2);
6: };
7: Foo( const char * d1, const QString & d2 )
8: {
9: m_d1 = QString::fromLocal8Bit(d1);
10: m_d2 = d2;
11: };
12: ...
13: QString s1 = QString::fromLocal8Bit("Привет");
14: Foo f(s1,"мир!!!");
15: ...
Если мы не будем перегружать конструкторы и методы setD1/setD2
,
то мы получим все те ошибки, о которых говорилось выше:
1: class Foo
2: {
3: public:
4: Foo( const Foo & foo )
5: {
6: m_d1 = foo.m_d1;
7: m_d2 = foo.m_d2;
8: };
9: Foo( const QString & d1, const QString & d2 = QString::null )
10: {
11: m_d1 = d1;
12: m_d2 = d2;
13: };
14: vod setD1( const QString & d1 )
15: {
16: m_d1 = d1;
17: };
18: vod setD2( const QString & d2 )
19: {
20: m_d2 = d2;
21: };
22: QString do()
23: {
24: return m_d1 + QString::fromLatin1(" ") + m_d2;
25: };
26: private:
27: QString m_d1;
28: QString m_d2;
29: };
30:
31: Foo f1( "Привет","мир!!!" ); // Неявное преобразование
32: QString s1 = QString::fromLocal8Bit("Привет");
33: Foo f2;
34:
35: f2.setD1( s1 );
36: f2.setD2( "мир!!!" ); // Неявное преобразование
37: qWarning("%s", (const char *)f1.do().local8Bit());
38://^^ Напечатает строку : ?????? ???!!!
39: qWarning("%s", (const char *)f2.do().local8Bit());
40://^^ Напечатает строку : Привет ???!!!
В реальных программах кроме отдельных строк очень часто приходится
использовать еще и списки строк. В библиотеке Qt
для представления списков строк исоользуются два класса QStringList
и QStrList
. Первый представляет строки типа QString
,
второй строки типа QCString
. В этой ситуации самой распространенной
ошибкой, связанной с преобразованием строк, является попытка добавить или
преобразовать строку типа QString
к списку типа
QStrList
, и наоборот извлечь строку из списка QStrList
в переменную типа QString
. Это справедливо относительно
строк типа QCString
и списков QStringList
.
Поэтому использованию списков строк тоже нужно уделять особое внимание.
Конечно, как говорилось выше, использование ключей -DQT_NO_CAST_ASCII
и -DQT_NO_ASCII_CAST
при компиляции, защитит от таких
ошибок, но к сожалению не всегда возможно применение этих ключей.
В заключении этой темы, нужно отметить: Т.к. библиотека
Qt внутри ориентированна на
использование UNICODE строк, то использование класса QString
вместо char *
и QCString
для передставления
строк внутри программы, а также использование класса QStringList
вместо класса QStrList
для представления списков строк
будет самым безопасным решением с точки зрения появления ошибок.
Если в своей программе вы используете системные вызовы для доступа
к файлам или директориям (например fopen(..), open(..), mkdir(..),
access(..), unlink(..)
и т.д.) более безопасным решением для
преобразования имени файла/директории из QString
в
const char *
использовать методы класса QFile
:
static QCString QFile::encodeName( const QString & fileName );
QString
,
в однобайтовое представление типа QCString
с использованием
специфики файловой системы. По умолчанию используется преобразование
QCString QString::local8Bit() const
, но может быть переопределено
с помощью метода static void QFile::setEncodingFunction( EncoderFn )
.
static QString decodeName( const QCString & localFileName );
QCString
,
в представление UNICODE типа QString
с использованием
специфики файловой системы. По умолчанию используется преобразование
QString QString::fromLocal8Bit(const char*, int)
, но может быть переопределено
с помощью метода static void QFile::setDecodingFunction( DecoderFn )
.
1: int renameFile( const QString & old, const QString & new )
2: {
3: ::rename( QFile::encodeName(old), QFile::encodename(new) );
4: }
5: ...
6: QStringList readDir( const QString & dName )
7: {
8: DIR *dp = 0L;
9: struct dirent *ep;
10: QStringList dList;
11:
12: dp = opendir( QFile::encodeName(dName) );
13: if ( dp )
14: {
15: while ( ep=readdir( dp ) )
16: {
17: dList.append(QFile::decodeName(ep->d_name));
18: }
19: closedir( dp );
20: }
21: return dList;
22: }
23: ...
Если нужно проверить является строка, представленная типом QString
или QCString
, пустой или нулевой, нужно использовать методы:
bool QString::isEmpty() const
,bool QCString::isEmpty() const
str.isEmpty()==TRUE
, то это не
означает, что будет выполняться и условие str.isNull()==TRUE
.
bool QString::isNull() const
,bool QCString::isNull() const
str.isNull()==TRUE
, то обязательно будет
выполняться условие str.isEmpty()==TRUE
.
В программах написанных с ипользованием языка C++ очень часто используют потоки (streams) для ввода/вывода текстовых данных из/в строки, из/в файла или какого-либо другого устройства ввода/вывода. Действительно, с помощью потока доступ к буферу (строке), файлу или другому устройству организуется одинаково.
В обычных тестовых файлах подавляющего большинства операционных систем, информация представлена в виде однобайтовых строк в кодировке, определяемой установками локали. В некоторых случаях в тестовых файлах информация может быть представлена представлена в виде однобайтовых строк в кодировке UTF8, реже в виде двухбайтовых строк в кодировке UNICODE.
В библиотеке Qt для
этих целей используется класс QTextStream
. Но т.к. библиотека
Qt внутри работает со строками
в формате UNICODE, то этот класс имеет свои особенности. Эти особенности
мы рассмотрим ниже.
В качестве примера использования класса QTextStream
можно привести
следующий фрагмент кода:
1: ...
2: QFile file(QFile::decodeName("TheFile"));
3: QString s;
4:
5: if (file.exists())
6: {
7: if (file.open(IO_ReadOnly))
8: {
9: QTextStream t(&file);
10:
11: while ((s=t.readLine()) != QString::null)
12: {
13: qWarning((const char *)s.local8Bit());
14: }
15: file.close();
16: }
17: }
18: ...
Если файл TheFile содержит текст, представленный ниже, т.е.
текстовые однобайтовые строки в кодировке, определяемой установками
локали:
1: Строка 1
2: Строка 2
3: Строка 3
то соответственно в результате выполнения этого фрагмента кода содержимое
файла будет напечатано без искажений.
Для демонстрации момента, в котором возникает искажение национальных
символов, приведем другой пример (в этом примере я намеренно использую
класс QStrList
):
1: ...
2: QFile file(QFile::decodeName("TheFile"));
3: QString s;
4: QStrList strList;
5:
6: strList.append("Строка 1");
7: strList.append("Строка 2");
8: strList.append("Строка 3");
9:
10: if (file.open(IO_WriteOnly))
11: {
12: QTextStream t( & file);
13: QStrListIterator it(strList);
14:
15: const char * tmp;
16: while ( (tmp=it.current()) )
17: {
18: ++it;
19: t << tmp << "\n";
20: }
21: file.close();
22: }
23: ...
В этом примере в поток вставляются однобайтовые строки. В результате
выполнения этого фрагмента кода, файл TheFile, будет содержать
строки:
1: ?????? 1
2: ?????? 2
3: ?????? 3
Если вместо класса QStrList
использовать класс QStringList
или вместо файла связать поток с классом QCString
(QByteArray
),
то строки будут выводиться правильно. Почему так происходит ?
Разработчики библиотеки Qt реализовали
класс QTextStream
следующим образом: предполагается, что
в файле, связанном с потоком, текст хранится в виде однобайтовых строк
в кодировке, определенной для текущей локали. Поэтому при создании потока,
связанного с файлом, устанавливается флаг Encoding == QTextStream::Locale
с помощью метода QTextStream::setEncoding( QTextStream::Encoding e)
c параметром
e = QTextStream::Locale.
При выводе однобайтового символа в поток применяется следующее преобразование:
QChar <= unsigned short <= int <= char
(Смотри реализацию методов QTextStream::ts_putc(...)
и
QTextStream::writeBlock( const char*, uint)
). В результате
такого преобразования русская буква 'А' (код в ISO-8859-5 - 0xB0)
получает UNICODE код U+00B0.
В методе QTextStream::ts_putc(QChar)
при непосредственной
записи в файл происходит преобразование к однобатовому символу с помощью
метода QTextCodec::fromUnicode(...)
. Если мы вставляем в
поток, связанный с файлом, строки типа QString
, то преобразование
выполняется правильно и русские буквы выводятся без искажений. Если
мы вставляем в поток однобайтовые строки, то преобразование выполняется
некорректно (с точки зрения использования UNICODE) и русские символы
искажаются.
В свою очередь, для текстового потока QTextStream
, связанного
с однобайтовой строкой (QByteArray
или QCString),
устанавливается флаг Encoding == QTextStream::Latin1, который
выполняет преобразование из UNICODE в однобайтовую строку по следующему
алгоритму:
1: QChar c;
2:
3: if( c.row() ) // Если старший октет отличен от нуля
4: {
5: dev->putch( '?' ); // Выводим символ '?'
6: }
7: else
8: {
10: dev->putch( c.cell() ); // Младший октет
11: }
Если мы вставляем в поток, связанный с однобайтовой строкой, строку
типа QString
то выполняется метод
QTextStream::operator<<( const QString & s )
,
который для Encoding == QTextStream::Latin1 производит преобразование
'Русская буква' ==> '?'. Для символов в кодировках ISO-8859-1 или
US-ASCII преобразования не имеют эффекта, коды этих символов не
меняются: в UNICODE старший байт равен 0, младший байт равен коду
символа. Для национальных символов в UNICODE старший байт всегда отличен
от 0 и в общем случае младший байт не совпадает с однобайтовым кодом
символа для кодировки определенной в локали, и такие строки подвергаются
изменениям. Если тестировать программы, используя только строки в
кодировке ISO-8859-1 или US-ASCII, то ошибки преобразования "UNICODE
<==> однобайтовая строка" не видны, т.к. фактически
никаках преобразоаний не происходит.
Для того чтобы избежать искажения национальных символов при использовании
класса QTextStream
, нужно придерживаться простого правила:
после создания потока нужно явно установить для него тип кодирования
текста с помощь метода QTextStream::setEncoding( QTextStream::Encoding e)
.
Наш пример будет выглядеть следубщим образом:
1: ...
2: QFile file(QFile::decodeName("TheFile"));
3: QString s;
4: QStrList strList;
5:
6: strList.append("Строка 1");
7: strList.append("Строка 2");
8: strList.append("Строка 3");
9:
10: if (file.open(IO_WriteOnly))
11: {
12: QTextStream t( & file);
13: t.setEncoding( QTextStream::Latin1 );
14: //^^^Сообщаем потоку, что однобайтовые строки преобразовывать
15: // не нужно
16: QStrListIterator it(strList);
17:
18: const char * tmp;
19: while ( (tmp=it.current()) )
20: {
21: ++it;
22: t << tmp << "\n";
23: }
24: file.close();
25: }
26: ...
В случае если мы используем поток, связанный с однобайтовой строкой, и в
этот поток вставляем строки типа QString
, то нужно указать
Encoding == QTextStream::Locale:
1: ...
2: QCString buff;
3: QString s;
4: QStringList strList;
5:
6: strList.append(QString::fromLocal8Bit("Строка 1"));
7: strList.append(QString::fromLocal8Bit("Строка 2"));
8: strList.append(QString::fromLocal8Bit("Строка 3"));
9:
10: QTextStream t( & buff );
11: t.setEncoding( QTextStream::Locale );
12: //^^^Сообщаем потоку, что UNICODE строки нужно преобразовывать
13: // в соответствии с установками локали
14: for ( QStringList::Iterator it = strList.begin(); it != strList.end(); ++it )
15: {
16: t << *it << QString::fromLatin1("+");
17: }
18: ...
В результате выполнения этого фрагмента кода переменная buff
получит значение "Строка 1+Строка 2+Строка 3". Если мы уберем
из примера строку 11, то переменная buff получит значение
"?????? 1+?????? 2+?????? 3".
Все выше сказанное можно сформулировать в виде следующих правил:
Если в поток типа QTextStream
с помощью оператора
вставки в поток (QTextStream::operator<<(...)
)
будут вставляться однобайтовые строки в кодировке, определяемой
установками локали или UTF8, то перед вызовом оператора вставки
в поток необходимо установить тип кодирования текстовой информации
в значение QTextStream::Latin1 с помощью вызова метода
QTextStream::setEncoding( QTextStream::Encoding e)
.
Если в поток типа QTextStream
с помощью оператора
вставки в поток (QTextStream::operator<<(...)
)
будут вставляться строки типа QString
, то перед вызовом
оператора вставки в поток необходимо явно установить тип кодирования
текстовой информации c помощью вызова метода
QTextStream::setEncoding( QTextStream::Encoding e)
.
Тип кодирования текстовой информации долженен принимать следующие значения:
Если поток используется с одним типом строк, то тип кодирования
текстовой информации можно задавать только один раз - после создания
потока. Если поток используется с разными типами строк, то тип
кодирования текстовой информации можно задавать каждый раз перед измением
типа строк. Например:
1: ...
2: QCString mbs = "Строка 1", buff;
3: QString wcs = QString::fromLocal8Bit("Строка 2");
4:
5: QTextStream t( & buff );
6:
7: t.setEncoding( QTextStream::Latin1 );
8: //^^^Сообщаем потоку, что однобайтовые строки преобразовывать
9: // не нужно
10: t << mbs;
11:
12: t.setEncoding( QTextStream::Locale );
13: //^^^Сообщаем потоку, что UNICODE строки нужно преобразовывать
14: // в соответствии с установками локали
15: t << wcs;
16:
17: ...
Все вышесказанное справедливо и для оператора извлечения из потока
QTextStream::operator>>(...)
.
Патчи, исправляющие описанные проблемы в KDE 2.1.1, вы можете скачать здесь.