Смекни!
smekni.com

Java: Русские буквы и не только… (стр. 6 из 8)

TransformerFactory trFactory = TransformerFactory.newInstance();

Transformer transformer = trFactory.newTransformer();

transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, docPublic);

transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, docSystem);

transformer.setOutputProperty( OutputKeys.INDENT, "yes" );

transformer.setOutputProperty( OutputKeys.ENCODING, encoding );

OutputStream os = ...;

StreamResult result = new StreamResult( os );

transformer.transform( source, result );

Тут есть один подводный камень - реализация Transformer должна поддерживать нужную кодировку. Xalan из состава JDK 1.4.0_x и 1.4.1_x поддерживает только две русские кодировки - KOI8-R и ISO-8859-5. Если хочется использовать Windows-1251, то можно воспользоваться механизмом endorsed:

Создаёте каталог %JAVA_HOME%\jre\lib\endorsed

Копируете туда jar с пропатченым классом: XalanRusChars.jar

В JDK 1.4.2 Beta включена новая версия Xalan, которая вроде как уже поддерживает кодировку 1251.

FOP

Пакет FOP предназначен для обработки документов по стандарту XSL FO (Formating Objects). В частности он позволяет создавать PDF-документы на базе документов XML. Для преобразования из исходного XML в FO пакет FOP по умолчанию использует XSLT-процессор Xalan в паре с Xerces. Для создания итогового изображения в FOP необходимо подключить шрифты, поддерживающие русские буквы. Вот как можно проделать это для версии 0.20.1:

В подкаталог conf\fonts (например, в c:\fop-0.20.1\conf\fonts\) скопировать файлы ttf из системного каталога Windows. Для Arial normal/normal, normal/bold, italic/normal и italic/bold нужны файлы arial.ttf, arialbd.ttf, ariali.ttf и arialbi.ttf.

Сгенерировать файлы описаний шрифтов (типа arial.xml). Для этого для каждого шрифта нужно выполнить команду (это для Arial normal/normal, всё в одну строку):

java -cp .;c:\fop-0.20.1\build\fop.jar;c:\fop-0.20.1\lib\batik.jar;

c:\fop-0.20.1\lib\xalan-2.0.0.jar;c:\fop-0.20.1\lib\xerces.jar;

c:\fop-0.20.1\lib\jimi-1.0.jar

org.apache.fop.fonts.apps.TTFReader fonts\arial.ttf fonts\arial.xml

В FOP добавить в conf/userconfig.xml описание шрифта с русскими буквами, типа:

<font metrics-file="c:&bsol;fop-0.20.1&bsol;conf&bsol;fonts&bsol;arial.xml" kerning="yes"

embed-file="c:&bsol;fop-0.20.1&bsol;conf&bsol;fonts&bsol;arial.ttf">

<font-triplet name="Arial" style="normal" weight="normal"/>

<font-triplet name="ArialMT" style="normal" weight="normal"/>

</font>

Аналогично добавляются Arial normal/bold, italic/normal и italic/bold.

При вызове FOP из командной строки после org.apache.fop.apps.Fop писать -c c:&bsol;fop-0.20.1&bsol;conf&bsol;userconfig.xml Если нужно использовать FOP из сервлета, то нужно в сервлете после строчки

Driver driver = new Driver();

добавить строчки:

// Каталог fonts (c:&bsol;weblogic&bsol;fonts) был создан исключительно для удобства.

String userConfig = "fonts/userconfig.xml";

File userConfigFile = new File(userConfig);

Options options = new Options(userConfigFile);

Тогда расположение файлов ttf в файле userconfig.xml можно указать относительно корня сервера приложения, без указания абсолютного пути:

<font metrics-file="fonts/arial.xml" kerning="yes"

embed-file="fonts/arial.ttf">

<font-triplet name="Arial" style="normal" weight="normal"/>

<font-triplet name="ArialMT" style="normal" weight="normal"/>

</font>

В файле FO (или XML и XSL) перед использованием шрифта писать:

font-family="Arial"

font-weight="bold" (Если используется Arial bold)

font-style="italic" (Если используется Arial italic)

Данный алгоритм прислал Алексей Тюрин, за что ему отдельное спасибо.

Если Вы используете встроенный в FOP просмотрщик, то необходимо учесть его особенности. В частности, хотя предполагается, что надписи в нём русифицированы, на самом деле сделано это с ошибкой (в версии 0.19.0). Для загрузки надписей из файлов ресурсов в пакете org.apache.fop.viewer.resources используется собственный загрузчик (класс org.apache.fop.viewer.LoadableProperties). Кодировка чтения там жёстко зафиксирована (8859_1, как и в случае Properties.load()), однако поддержка записи вида "&bsol;uXXXX" не реализована. Я сообщил об этой ошибке разработчикам, они включили её исправление в свои планы.

Кроме всего прочего существует сайт посвящённый русификации FOP (http://www.openmechanics.net/rusfop/) Там Вы сможете найти дистрибутив FOP с уже исправленными ошибками и подключенными русскими шрифтами.

POI

Пакет Jakarta POI предназначен для работы с документами Microsoft Office. Пока что более-менее работающей там является только поддержка файлов MS Excel (xls). Особой сложности в работе с русским языком нет, но надо учитывать нюанс, что для работы с ячекой используется класс org.apache.poi.hssf.usermodel.Cell, у которого есть метод setEncoding(short encoding), однако вместо привычных "Cp1255" и "Cp866", необходимо исользовать константы ENCODING_COMPRESSED_UNICODE (0) и ENCODING_UTF_16 (1). По умолчанию включен первый режим, а для нормальной работы с русским языком необходимо использовать ENCODING_UTF_16. Причем что самое важное, эту установку необходимо выполнять для каждой, создаваемой ячейки. Пример кода:

HSSFWorkbook wb = new HSSFWorkbook();

HSSFSheet sheet = wb.createSheet("Sheet1");

HSSFRow row = sheet.createRow( (short)0 );

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

{

HSSFCell cell = row.createCell( (short)i );

cell.setEncoding( (short)cell.ENCODING_UTF_16 );

cell.setCellValue("Тест русского языка");

}

Создать лист с названием содержащим русские символы, к сожалению, не удаётся. Данное описание прислал Вячеслав Яковенко, за что ему отдельное спасибо.

CORBA

В стандарте CORBA предусмотрен тип, соответствующий Java-овскому типу String. Это тип wstring. Всё бы хорошо, но некоторые CORBA-сервера не поддерживают его в полной мере. Типичные исключения, возникающие при спотыкании на русских буквах: org.omg.CORBA.MARSHAL: minor code 5 completed No или org.omg.CORBA.DATA_CONVERSION. Лучше всего, конечно, заменить CORBA-сервер. К сожалению у меня нет статистики, поэтому я не могу сказать, с какими проблем не будет. Если сменить систему не представляется возможным, можно вместо типа wstring использовать тип string в паре с нашим любимым преобразованием:

// Серверная часть

a = new Answer(new String( src.getBytes("Cp1251"),"ISO-8859-1" ));

...

// Клиентская часть

Answer answer=serverRef.getAnswer();

res = new String( answer.msg.getBytes("ISO-8859-1"),"Cp1251" );

Тип wstring при этом лучше не использовать, потому как тем самым Вы кривость сервера будете компенсировать кривостью своих компонентов, а это практически всегда чревато разнообразными проблемами в будущем.

Вместо Cp1251 можно использовать любую кодировку русских букв, по желанию. Это будет кодировка, в которой будут передаваться строки в компоненты на других языках. Также, аналогичный код может потребоваться, если необходимо организовать связь с готовыми не-Java компонентами, которые уже использовали тип string.

Честно говоря, не лежит у меня душа к таким решениям, ну да что поделаешь, иногда оно единственное.

JNI

JNI (Java Native Interface) - это стандарт по взаимодействию с C/C++-ным кодом. Как и следовало ожидать, на этом водоразделе тоже происходит столкновение байтов и символов. Большинство C/C++-ных программ пишется без учёта Unicode, многие программисты даже не знают о нём. Я сам, за 7 лет писательства на C/C++, пока не начал писать на Java, про Unicode знал только по наслышке. Большинство строковых операций в C/C++ сделаны для 8-битового сишного типа char. В принципе, есть некоторые подвижки в этом направлении, в частности для Windows NT можно откомпилировать код, который будет взаимодействовать с Unicode-вариантами Win32 API, но, к сожалению, этого часто недостаточно.

Таким образом главная задача - получить тип char* из типа jstring (JNI-шное отображение String) и наоборот. Практически во всех описаниях и примерах JNI для этого используется пара функций GetStringUTFChars()/ReleaseStringUTFChars(). Коварные буржуины и здесь приготовили засаду - эти функции формируют массив байтов по стандарту UTF, который соответствует ожидаемому только для ASCII-символов (первых 128 значений). Русские буквы опять в пролёте. Сишные строки char* очень хорошо ложатся на Java-овский тип byte[], но при этом возникает загвоздка в виде ноль-символа. Его нужно добавлять при преобразовании byte[]->char* и учитывать при обратном преобразовании. Пример:

public void action(String msg) throws java.io.IOException

{

int res = nAction( msg );

if( res!=0 ) throw new java.io.IOException( nGetErrorString(res) );

}

private native int nAction(String msg);

private native String nGetErrorString(int error);

...

jbyteArray getStringBytes(JNIEnv *env, jstring str)

{

if( !str ) return NULL;

jmethodID getBytes = env->GetMethodID(env->GetObjectClass(str),"getBytes","()[B");

jbyteArray buf = (jbyteArray)env->CallObjectMethod(str,getBytes);

if( !buf ) return NULL;

// Добавляем ноль-символ

jsize len = env->GetArrayLength(buf);

jbyteArray nbuf = env->NewByteArray(len+1);

if( len!=0 )

{

jbyte *cbuf = env->GetByteArrayElements(buf,NULL);

env->SetByteArrayRegion(nbuf,0,len,cbuf);

env->ReleaseByteArrayElements(buf,cbuf,JNI_ABORT);

}

env->DeleteLocalRef(buf);

return nbuf;

}

JNIEXPORT jint JNICALL Java_Test_nAction

(JNIEnv *env, jobject obj, jstring msg)

{

jbyteArray bmsg = getStringBytes(env,msg);

if( !bmsg ) return -1;

jbyte *cmsg = env->GetByteArrayElements(bmsg,NULL);

printf(cmsg);

jint res = do_something(cmsg);

env->ReleaseByteArrayElements(bmsg,cmsg,JNI_ABORT);

return res;

}

jstring newString(JNIEnv *env, jbyteArray jbuf, int len)

{

jclass stringClass = env->FindClass("java/lang/String");

if( !stringClass ) return NULL;

jmethodID init = env->GetMethodID(stringClass,"","([BII)V");

if( !init ) return NULL;

return (jstring)env->NewObject(stringClass,init,jbuf,0,len);

}

jstring newString(JNIEnv *env, const char *buf)

{

if( !buf ) return NULL;

int bufLen = strlen(buf);

if( bufLen==0 )

{

return env->NewString( (const jchar *)L"", 0 );

}

jbyteArray jbuf = env->NewByteArray(bufLen);

if( !jbuf ) return NULL;

env->SetByteArrayRegion(jbuf,0,bufLen,(jbyte*)buf);

jstring jstr = newString(env,jbuf,bufLen);

env->DeleteLocalRef(jbuf);

return jstr;

}

JNIEXPORT jstring JNICALL Java_Test_nGetErrorString

(JNIEnv *env, jobject obj, jint error)

{

char cmsg[256];

memset(cmsg,0,sizeof(cmsg));

get_error_string( error,cmsg,sizeof(cmsg) );

return newString(env,cmsg);

}

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

GUI (AWT, Swing)

Многие связывают неправильный вывод русских букв с неправильной установкой шрифта. На самом деле в Java всё сложнее и редко действительно связанно со шрифтами.

Где же действительно лежат наибольшие подводные камни? В основном это связанно с неправильной перекодировкой символов. Часть этих проблем и методы их решения описаны выше. Если у Вас все преобразования выполняются корректно, и для вывода используется шрифт Unicode, то есть очень большой шанс, что Ваша программа будет работать правильно.

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