Работа с кодировкой cp1251 в J2ME


Работа с кодировкой cp1251 в J2ME

 

Введение

С тех пор как появилась компьютерная техника, началась война
кодировок. Это объяснялось как постоянным расширением круга поддерживаемых
языков, так и стремлением создать кодировку, частично совместимую с
какой-нибудь другой (характерный пример — появление альтернативной кодировки для русского
языка, обусловленное эксплуатацией западных программ, созданных для кодировки CP437). В результате
появилась необходимость решения нескольких задач:

Проблема «крокозябров» (отображения документов в неправильной
кодировке): её можно было решить либо последовательным внедрением методов
указания используемой кодировки, либо внедрением единой для всех кодировки.

Проблема ограниченности набора символов: её можно было
решить либо переключением шрифтов внутри документа, либо внедрением «широкой»
кодировки. Переключение шрифтов издавна практиковалось в текстовых процессорах, причём часто
использовались шрифты с нестандартной кодировкой, т. н. «dingbat fonts» —
в итоге при попытке перенести документ в другую систему все нестандартные
символы превращалось в крокозябры.

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

Проблема дублирования шрифтов: традиционно для каждой
кодировки делался свой шрифт, даже если эти кодировки частично (или полностью)
совпадали по набору символов: эту проблему можно было решить, делая «большие»
шрифты, из которых потом выбираются нужные для данной кодировки символы —
однако это требует создания единого реестра символов, чтобы определять, чему
что соответствует.

 Вражда усилилась с возникновением
веб технологий. Единого мнения по выбору предпочтительной кодировки не
существует. Сейчас входит в массы сравнительно новая универсальная кодировка Юникод
(unicode) - стандарт кодирования символов, позволяющий представить знаки
практически всех письменных языков. Она способна решить проблему раз и
навсегда.

Индивидуальность затронула и мобильную технику, особенно в
период возрождения веба в братьях малых. Самая, на мой взгляд, популярная кириллица
в российском вебе – cp1251. Я думаю, все начинали с этой кодировки, и в
последующем переходили на UTF-8. Содержимое данной статьи посвящено работе с cp1251
в приложении на базе J2ME в различных моделях телефонных аппаратов. Данная
тема, на данный момент, плохо освещена. Все сталкиваются с данной проблемой, но
единых методов решения не опубликовано. Данная статья, в основном, направлена
на новичков в J2ME. Проблема с кодировкой в J2ME относится не только к cp1251.
Это массовая проблема, в частности с, набирающим обороты, семейством UTF, т.к.
не на всех телефонах есть поддержка всего семейства. Мы рассмотрим http connection,
попытаемся вывести кросс платформенное решение. Базу решения рассмотрим на
аппарате SonyEricsson K320i.

Получаем cp1251 по http в SonyEricsson

Для определенности будем считать,
что заголовки с сервера приходят следующие:

Content-Type:
text/plain; charset=windows-1251

Content-Length: … НЕ УСТАНОВЛЕН (просто для
определенности)

 

Если Content-Length не установлен, то в J2ME httpconnection.getLength() вернет -1.
Приведем целевой код
программы:

public Test(String str) {
HttpConnection hc = null;
DataOutputStream dos = null;

InputStream dis = null;
String messagebuffer = new String();
try {

 hc = (HttpConnection) Connector.open(URL,
Connector.READ_WRITE, true);
} catch (Exception e) {}


if (hc != null) {

try {

hc.setRequestMethod(HttpConnection.POST);
hc.setRequestProperty("IF-Modified-Since", "20 Jan 2001 16:19:14
GMT");
hc.setRequestProperty("User-Agent", "Mobile");
hc.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");

dos = hc.openDataOutputStream();
dos.write(str.getBytes());

dos.close();

 

dis = hc.openInputStream();

byte[] b = new byte[1];

 while ( dis.read( b ) != -1) {

int c = b[0];

if (c < 0) {

c += 256;

}

messagebuffer.append((char)c);

 }

} catch (IOException ioe) {

} finally {

 try {

 if (hc != null)
hc.close();

 } catch (IOException ignored) {}

 try {

 if (dis !=
null) dis.close();

 } catch (IOException ignored) {}

 try {

if (dos != null) dos.close();

 } catch (IOException ignored) {}

}

}

return messagebuffer.toString();

}

Рассмотрим строку «АаЯяЁё». Посмотрим, что мы получим на SonyEricsson.

Итак, по http приходит строка
в cp1251. Для начала
приведу не хороший, но 100% работающий метод решения задачи.

В cp1251
сохраним файл charset.txt, содержащий все буквы русского алфавита:

АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя

При инициализации приложения вызовем следующую функцию:

private String cp1251chars;//русские буквы
в компьютере

private String phoneChars;//русские буквы в
телефоне

private byte CHAR_COUNT = 64;//кол-во
русских букв(мал и бол)


public void loadSettings() {

phoneChars =
"АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя";
try {

InputStream
in = getClass().getResourceAsStream("/charset.txt");
byte[] bchars = new byte[CHAR_COUNT];
in.read(bchars,0,CHAR_COUNT);
cp1251chars = new String(bchars);
in.close();

} catch (Exception io) {}

}

 

Далее, как вы уже наверно догадались, мы будем заменять полученный в
cp1251 символ символом в phoneChars:

public
String getRusStr(String s) {

if ( s != null) {

for(int i = 0; i < CHAR_COUNT; i++)

s = s.replace( cp1251chars.charAt(i),
phoneChars.charAt(i));

}
return s;

}

 

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

На SonyEricsson стандартная
кодировка ISO-8859, кириллице соответствует ISO-8859-5. Приведем таблицу
соответствия кодов символов:

 

 

cp1251

ISO-8859-5

А-Я (без Ё )

192-223

1040-1071

а-я (без ё )

224-255

1072-1103

Ё

168

1025

ё

184

1105

 

Полностью посмотреть их можно на:

Исправим ситуацию:

byte[] b = new byte[1];

 while (
dis.read( b ) != -1) {

int c = b[0];

if (c < 0) {

c += 256;

}

If (c > 191 && c <
256) {

с += 848;

} else if (c ==168) {

с += 857;

} else If (c == 184) {

с += 921;

}

 

messagebuffer.append((char)c);

 }

 

Теперь предположим, что мы не знаем какая у нас основная кодировка.
Потому, что выше была ISO-8859,
а где-то будет UTF-8, в
Nokia вообще
UTF-16. Как же быть в
этой ситуации? Из полученного выше кода ясно, что нам нужна величина смещения кодов
основных символов, т.е. ‘А’, ‘Ё’ и ‘ё’. Если в качестве глобальной переменной
принять:

 private
int[] intPhoneCharDiv = {(int)'
А' - 192, (int)'Ё' - 168, (int)'ё' - 184};

то полученный при инициализации приложения массив intPhoneCharDiv будет
содержать нужные нам смещения. Тогда универсальный подход будет следующим:

byte[] b = new byte[1];

 while (
dis.read( b ) != -1) {

int c = b[0];

if (c < 0) {

c += 256;

}

If (c > 191 && c <
256) {

с += intPhoneCharDiv[0];

} else if (c ==168) {

с += intPhoneCharDiv[1];

} else If (c == 184) {

с += intPhoneCharDiv[2];

}

 

messagebuffer.append((char)c);

 }

Профайлинг двух выше приведенных методов показал, что последний работает больше чем в 2 раза быстрей. Думаю статья будет очень полезна для новичков, т.к все с этим
сталкиваются).