46375 (607825), страница 5
Текст из файла (страница 5)
mapping.loadMapping( new InputSource(loader.getResourceAsStream(mappingFile)) );
return mapping;
}
XSL
Спецификация XSL описывает стандарт на преобразование XML-документов. Когда при помощи XSL выполняется преобразование из одного XML-документа в другой, особых причин для беспокойства нет - и тот и другой являются Unicode-документами, поэтому нет преобразований из символов в байты и обратно, могущих повлиять на результат. Другое дело, когда выполняется преобразование из XML в HTML или вообще в текстовый файл. Формат выходного файла задаётся настройкой тега xsl:output, в котором можно задать используемую кодировку. Пример:
Если XSLT-процессор не знает указанной кодировки, то он должен или выдать ошибку или использовать UTF-8 (или UTF-16). Если формируется HTML, то XSLT-процессор должен добавить тег meta, в котором будет указана реально использованная кодировка:
Всё бы хорошо, но некоторые XSLT-процессоры не поддерживают данный тег (по спецификации они и не обязаны). В частности пакет Cocoon его не поддерживает, т.к. по словам разработчиков он противоречит внутренней архитектуре этого пакета. Вместо этого там поддерживается указание выходного формата при помощи инструкции препроцессора cocoon-format. Пример вставки этой инструкции в XSL:
type="text/html"
Таким образом можно динамически менять выходной формат. Если это не требуется, то можно записать инструкцию и статически (в исходном XML-документе):
Собственно используемая кодировка настраивается для каждого формата отдельно в файле cocoon.properties.
Новая версия Cocoon 2.0 кроме управления кодировками позволяет сделать в плане локализации уже гараздо больше. Подробности Вы можете узнать на их сайте.
В случае использования JAXP для генерации выходного потока (пакет javax.xml.transform) кроме использования тега xsl:output можно использовать методы setOutputProperty объекта Transformer. Пример сохранения документа в нужной кодировке:
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 описание шрифта с русскими буквами, типа:
embed-file="c:\fop-0.20.1\conf\fonts\arial.ttf"> Аналогично добавляются Arial normal/bold, italic/normal и italic/bold. При вызове FOP из командной строки после org.apache.fop.apps.Fop писать -c c:\fop-0.20.1\conf\userconfig.xml Если нужно использовать FOP из сервлета, то нужно в сервлете после строчки Driver driver = new Driver(); добавить строчки: // Каталог fonts (c:\weblogic\fonts) был создан исключительно для удобства. String userConfig = "fonts/userconfig.xml"; File userConfigFile = new File(userConfig); Options options = new Options(userConfigFile); Тогда расположение файлов ttf в файле userconfig.xml можно указать относительно корня сервера приложения, без указания абсолютного пути: embed-file="fonts/arial.ttf"> В файле 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()), однако поддержка записи вида "\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); }