Чтение RSS
Рефераты:
 
Рефераты бесплатно
 

 

 

 

 

 

     
 
Java: Русские буквы и не только…

Java: Русские буквы и не только...

Введение

Некоторые проблемы настолько сложны, что нужно быть очень умным и очень хорошо информированным, чтобы не быть уверенным в их решении.

Лоренс Дж. Питер

Peter's Almanac

Кодировки

Когда я только начинал программировать на языке C, первой моей программой (не считая HelloWorld) была программа перекодировки текстовых файлов из основной кодировки ГОСТ-а (помните такую? :-) в альтернативную. Было это в далёком 1991-ом году. С тех пор многое изменилось, но за прошедшие 10 лет подобные программки свою актуальность, к сожалению, не потеряли. Слишком много уже накоплено данных в разнообразных кодировках и слишком много используется программ, которые умеют работать только с одной. Для русского языка существует не менее десятка различных кодировок, что делает проблему ещё более запутанной.

Откуда же взялись все эти кодировки и для чего они нужны? Компьютеры по своей природе могут работать только с числами. Для того чтобы хранить буквы в памяти компьютера надо поставить в соответствие каждой букве некое число (примерно такой же принцип использовался и до появления компьютеров - вспомните про ту же азбуку Морзе). Причём число желательно поменьше - чем меньше двоичных разрядов будет задействовано, тем эффективнее можно будет использовать память. Вот это соответствие набора символов и чисел собственно и есть кодировка. Желание любой ценой сэкономить память, а так же разобщённость разных групп компьютерщиков и привела к нынешнему положению дел. Самым распространённым способом кодирования сейчас является использование для одного символа одного байта (8 бит), что определяет общее кол-во символов в 256. Набор первых 128 символов стандартизован (набор ASCII) и является одинаковыми во всех распространённых кодировках (те кодировки, где это не так уже практически вышли из употребления). Англицкие буковки и символы пунктуации находятся в этом диапазоне, что и определяет их поразительную живучесть в компьютерных системах :-). Другие языки находятся не в столь счастливом положении - им всем приходится ютиться в оставшихся 128 числах.

Unicode

В конце 80-х многие осознали необходимость создания единого стандарта на кодирование символов, что и привело к появлению Unicode. Unicode - это попытка раз и навсегда зафиксировать конкретное число за конкретным символом. Понятно, что в 256 символов тут не уложишься при всём желании. Довольно долгое время казалось, что уж 2-х то байт (65536 символов) должно хватить. Ан нет - последняя версия стандарта Unicode (3.1) определяет уже 94140 символов. Для такого кол-ва символов, наверное, уже придётся использовать 4 байта (4294967296 символов). Может быть и хватит на некоторое время... :-)

В набор символов Unicode входят всевозможные буквы со всякими чёрточками и припендюльками, греческие, математические, иероглифы, символы псевдографики и пр. и пр. В том числе и так любимые нами символы кириллицы (диапазон значений 0x0400-0x04ff). Так что с этой стороны никакой дискриминации нет.

Если Вам интересны конкретные кода символов, для их просмотра удобно использовать программу "Таблица символов" из WinNT. Вот, например, диапазон кириллицы:

Если у Вас другая OS или Вас интересует официальное толкование, то полную раскладку символов (charts) можно найти на официальном сайте Unicode (http://www.unicode.org/charts/web.html).

Типы char и byte

В Java для символов выделен отдельный тип данных char размером в 2 байта. Это часто порождает путаницу в умах начинающих (особенно если они раньше программировали на других языках, например на C/C++). Дело в том, что в большинстве других языков для обработки символов используются типы данных размером в 1 байт. Например, в C/C++ тип char в большинстве случаев используется как для обработки символов, так и для обработки байтов - там нет разделения. В Java для байтов имеется свой тип - тип byte. Таким образом C-ишному char соответствует Java-вский byte, а Java-вскому char из мира C ближе всего тип wchar_t. Надо чётко разделять понятия символов и байтов - иначе непонимание и проблемы гарантированны.

Java практически с самого своего рождения использует для кодирования символов стандарт Unicode. Библиотечные функции Java ожидают увидеть в переменных типа char символы, представленные кодами Unicode. В принципе, Вы, конечно, можете запихнуть туда что угодно - цифры есть цифры, процессор всё стерпит, но при любой обработке библиотечные функции будут действовать исходя из предположения что им передали кодировку Unicode. Так что можно спокойно полагать, что у типа char кодировка зафиксирована. Но это внутри JVM. Когда данные читаются извне или передаются наружу, то они могут быть представлены только одним типом - типом byte. Все прочие типы конструируются из байтов в зависимости от используемого формата данных. Вот тут то на сцену и выходят кодировки - в Java это просто формат данных для передачи символов, который используется для формирования данных типа char. Для каждой кодовой страницы в библиотеке имеется по 2 класса перекодировки (ByteToChar и CharToByte). Классы эти лежат в пакете sun.io. Если, при перекодировке из char в byte не было найдено соответствующего символа, он заменяется на символ ?.

Кстати, эти файлы кодовых страниц в некоторых ранних версиях JDK 1.1 содержат ошибки, вызывающие ошибки перекодировок, а то и вообще исключения при выполнении. Например, это касается кодировки KOI8_R. Лучшее, что можно при этом сделать - сменить версию на более позднюю. Судя по Sun-овскому описанию, большинство этих проблем было решено в версии JDK 1.1.6.

До появления версии JDK 1.4 набор доступных кодировок определялся только производителем JDK. Начиная с 1.4 появилось новое API (пакет java.nio.charset), при помощи которого Вы уже можете создать свою собственную кодировку (например поддержать редко используемую, но жутко необходимую именно Вам).

Класс String

В большинстве случаев для представления строк в Java используется объект типа java.lang.String. Это обычный класс, который внутри себя хранит массив символов (char[]), и который содержит много полезных методов для манипуляции символами. Самые интересные - это конструкторы, имеющие первым параметром массив байтов (byte[]) и методы getBytes(). При помощи этих методов Вы можете выполнять преобразования из массива байтов в строки и обратно. Для того, чтобы указать какую кодировку при этом использовать у этих методов есть строковый параметр, который задаёт её имя. Вот, например, как можно выполнить перекодировку байтов из КОИ-8 в Windows-1251:

// Данные в кодировке КОИ-8

byte[] koi8Data = ...;

// Преобразуем из КОИ-8 в Unicode

String string = new String(koi8Data,"KOI8_R");

// Преобразуем из Unicode в Windows-1251

byte[] winData = string.getBytes("Cp1251");

Список 8-ми битовых кодировок, доступных в современных JDK и поддерживающих русские буквы Вы можете найти ниже, в разделе "8-ми битовые кодировки русских букв".

Так как кодировка - это формат данных для символов, кроме знакомых 8-ми битовых кодировок в Java также на равных присутствуют и многобайтовые кодировки. К таким относятся UTF-8, UTF-16, Unicode и пр. Например вот так можно получить байты в формате UnicodeLittleUnmarked (16-ти битовое кодирование Unicode, младший байт первый, без признака порядка байтов):

// Строка Unicode

String string = "...";

// Преобразуем из Unicode в UnicodeLittleUnmarked

byte[] data = string.getBytes("UnicodeLittleUnmarked");

При подобных преобразованиях легко ошибиться - если кодировка байтовых данных не соответствуют указанному параметру при преобразовании из byte в char, то перекодирование будет выполнено неправильно. Иногда после этого можно вытащить правильные символы, но чаще всего часть данных будет безвозвратно потеряна.

В реальной программе явно указывать кодовую страницу не всегда удобно (хотя более надёжно). Для этого была введена кодировка по умолчанию. По умолчанию она зависит от системы и её настроек (для русских виндов принята кодировка Cp1251), и в старых JDK её можно изменить установкой системного свойства file.encoding. В JDK 1.3 изменение этой настройки иногда срабатывает, иногда - нет. Вызвано это следующим: первоначально file.encoding ставится по региональным настройкам компьютера. Ссылка на кодировку по умолчанию запоминается в нутрях при первом преобразовании. При этом используется file.encoding, но это преобразование происходит ещё до использования аргументов запуска JVM (собсно, при их разборе). Вообще-то, как утверждают в Sun, это свойство отражает системную кодировку, и она не должна изменяться в командной строке (см., например, комментарии к BugID 4163515) Тем не менее в JDK 1.4 Beta 2 смена этой настройки опять начала оказывать эффект. Что это, сознательное изменение или побочный эффект, который может опять исчезнуть - Sun-овцы ясного ответа пока не дали.

Эта кодировка используется тогда, когда явно не указанно название страницы. Об этом надо всегда помнить - Java не будет пытаться предсказать кодировку байтов, которые Вы передаёте для создания строки String (так же она не сможет прочитать Ваши мысли по этому поводу :-). Она просто использует текущую кодировку по умолчанию. Т.к. эта настройка одна на все преобразования, иногда можно наткнуться на неприятности.

Для преобразования из байтов в символы и обратно следует пользоваться только этими методами. Простое приведение типа использовать в большинстве случаев нельзя - кодировка символов при этом не будет учитываться. Например, одной из самых распространённых ошибок является чтение данных побайтно при помощи метода read() из InputStream, а затем приведение полученного значения к типу char:

InputStream is = ..;

int b;

StringBuffer sb = new StringBuffer();

while( (b=is.read())!=-1 )

  {

   sb.append( (char)b );  //

Если же Вам не повезло, и у Вас более старая версия - для достижения результата придётся поизвращаться:

Оригинальный способ работы с кодировками предлагает Russian Apache - здесь расписано, как именно.

Своё решение проблемы так же предложил Вячеслав Педак.

Ну а самый простейший вариант извлечь таки символы - передавать в комплекте параметров имя кодировки (или, если вы уверены в текущей кодировке броузера, использовать предопределённую кодировку) и использовать метод перекодировки символов:

  public void doPost(HttpServletRequest request,HttpServletResponse response)

    throws ServletException, IOException

  {

   // Кодировка сообщений, использованная engine

   // Некоторые используют ISO-8859-1, некоторые кодировку

   // по умолчанию - единообразия тут нет

   String requestEnc = "ISO-8859-1";

   // Кодировка, установленная в броузере

   String clientEnc = request.getParameter("charset");

   if( clientEnc==null ) clientEnc="Cp1251";

   // Получение параметра

   String value = request.getParameter("value");

   //

   if( value!=null )

      value = new String(value.getBytes(requestEnc),clientEnc);

   ...

 

JSP

Технология JSP (Java Server Pages) очень похожа на сервлеты. По сути дела сервер, при запросе в первый раз на лету генерит из jsp-страниц код сервлета, компилирует его и запускает его как обычный сервлет. Поэтому у JSP возникают схожие проблемы при работе с русскими буквами. Однако решаются они немного по другому. Есть три места где могут возникнуть трудности - русские буквы внутри самой jsp-страницы, в ответе клиенту и в запросе от клиента. Первые два решаются заданием в начале страницы тега page:

Увидев эту директиву сервер понимает, что страница записана в указанной кодировке, и что в сгенерённый код надо добавить вызов response.setContentType() с указанным contentType. Если сервер поддерживает спецификацию Servlet 2.3, то он также добавит и вызов request.setCharacterEncoding() с нужной кодировкой, таким образом автоматом решая и третью проблему. Для более старых серверов для раскодирования параметров в запросе клиента надо применять ухищрения, аналогичные описанным в разделе по сервлетам.

Для примера, для того, чтобы настроить JSP-форум Jive для работы с русскими буквами надо откорректировать следующие файлы:

/jive/header.jsp

/jive/admin/header.jsp

В них надо в начало добавить строчку

Вместо UTF-8 можно использовать любую кодировку, поддерживающую русские буквы - всё зависит от вкусов и предпочтений. Информацию об этом прислал Алексей Епишкин, за что ему отдельное спасибо.

В некоторых серверах встречаются баги, связанные с русскими буквами в JSP. Например, сервер Orion не любит русскую букву "Т" - он вместо неё в сгенерённый сервлет подставляет символ кавычки. Там во внутренностях есть примерно такой код:

...

switch( charstring.c1(i) )

...

public final char c1(int i)

{

 if(i < 0 || i >= length)

    throw new StringIndexOutOfBoundsException(i);

 else

    return (char)(data[offset + i] & 0xff);

data - это массив типа char[]. Как видно, ошибка тут тривиальна - разработчик почему-то был уверен что символы с кодами больше 255 - это ошибка природы. :-)

JavaMail

Пакет JavaMail предназначен для работы с электронными письмами. При помощи этого пакета Вы можете отправлять и принимать письма через различные протоколы. Разные протоколы по разному обрабатывают национальные символы. Самые распространённые на данный момент протоколы Internet основаны на старом стандарте RFC-822. Согласно этому стандарту в служебных полях (заголовках) писем разрешено посылать только символы кодировки ASCII, т.е. только латинские буквы (первые 128 символов Unicode). Очевидно, что это неудобно, т.к. часто очень хочется писать, например в поле Subject (тема письма) или в полях From/To (имя и адрес отправителя/получателя) русский текст. Для того, чтобы решить эту проблему был придуман стандарт кодирования MIME (RFC 2047). Он позволяет в некоторых полях заголовка (не во всех) использовать национальные символы при помощи специального кодирования (Base64 или QuotedPrintable).

Для представления писем в JavaMail используется класс javax.mail.Message. Это абстрактный класс, реальное же поведение определяется наследниками. Методы, определённые в нём работают только с обычными Java-строками (String). Для протоколов Internet обычно используется наследник javax.mail.internet.MimeMessage, который помимо базовых методов добавляет методы, в которых можно дополнительно указывать кодировку, которую следует использовать для писем. Для кодирования используется вспомогательный класс javax.mail.internet.MimeUtility. Класс MimeMessage обычно сам обращается к нему для кодирования/раскодирования заголовков, но, если Вы напрямую обращаетесь к заголовкам (методы getHeader()/setHeader()/addHeader()), то для их кодирования/раскодирования Вам придётся обращаться к методам MimeUtility самому.

Если Вы не указываете кодировку письма, то будет использована кодировка по умолчанию - обычно используется file.encoding, но её можно перекрыть специальной системной настройкой "mail.mime.charset". Это разумно, т.к. часто кодировка по умолчанию в системе отличается от стандартной кодировки Internet. Для русскоязычных писем в Internet стандартом де-факто стала кодировка КОИ-8. Вы, конечно, можете указать и другую, но шанс, что принимающая сторона не сможет прочитать такое письмо очень велик.

Надо учитывать также, что в JavaMail различаются два стандарта наименования кодировок - стандарт MIME и стандарт Java. Для большинства кодировок имена MIME уже поддерживаются в Java при помощи механизма синонимов. Например, для кодировки "Cp1251" (название Java) существует синоним "Windows-1251" (название MIME). Для тех кодировок, для которых такие синонимы отсутствуют, они поддерживаются внутри JavaMail. Для этого загружается файл javamail.charset.map из подкаталога "/META-INF" из того jar-файла, откуда был загружен пакет JavaMail. Для указания кодировки при вызове методов JavaMail следует использовать только MIME-имена, в противном случае получатель не сможет распознать использованную кодировку (если только на другом конце не тоже Java :-).

Вот простой пример отправки письма при помощи JavaMail:

import java.util.Properties;

import javax.mail.Session;

import javax.mail.Message;

import javax.mail.Transport;

import javax.mail.internet.MimeMessage;

import javax.mail.internet.InternetAddress;

public class MailTest

{

 static final String ENCODING = "koi8-r";

 static final String FROM = "[email protected]";

 static final String TO = "[email protected]";

 public static void main(String args[]) throws Exception

 {

  Properties mailProps = new Properties();

  mailProps.put("mail.store.protocol","pop3");

  mailProps.put("mail.transport.protocol","smtp");

  mailProps.put("mail.user","myaccount");

  mailProps.put("mail.pop3.host","mail.mydomail.ru");

  mailProps.put("mail.smtp.host","mail.mydomail.ru");

  Session session = Session.getDefaultInstance(mailProps);

  MimeMessage message = new MimeMessage(session);

  message.setFrom(new InternetAddress(FROM));

  message.setRecipient(Message.RecipientType.TO, new InternetAddress(TO));

  message.setSubject("Тестовое письмо",ENCODING);

  message.setText("Текст тестового письма",ENCODING);

  Transport.send(message);

 }

}

XML/XSL

При разработке формата XML особое внимание уделялось поддержке различных кодировок символов. Для указания того, какая кодировка была использована используется заголовок XML-документа. Пример:

Если кодировка указана не была, то по умолчанию предполагается кодировка UTF-8. На XML-парсер возложена обязанность корректно прочитать заголовок и использовать соответствующую кодировку для получения Unicode-символов. Разные парсеры могут поддерживать разные наборы кодировок, но UTF-8 обязаны поддерживать все. Здесь также, как и в случае с JavaMail наименования кодировок, описанные в стандарте XML могут расходится с наименованиями, принятыми в Java. Разные парсеры по разному выходят из положения. Crimson просто использует некоторое кол-во дополнительных синонимов, а в остальном полагается на синонимы кодировок из Java. Xerces же по умолчанию использует внутреннюю таблицу (класс org.apache.xerces.readers.MIME2Java), а если не находит там кодировку, то бросает исключение о неподдерживаемой кодировке. В Xerces версии 1.4.0 русских кодировок там всего две - KOI8-R и ISO-8859-5. Однако это поведение по умолчанию можно изменить при помощи разрешения у парсера специального feature "http://apache.org/xml/features/allow-java-encodings". Если этот feature разрешён (при помощи метода setFeature()), то парсер после поиска в таблице будет пытаться использовать стандартный Java-вский механизм и соответственно Java-вский набор кодировок. В случае использования интерфейса SAX сделать это можно таким, например, образом (при использовании JAXP):

SAXParserFactory parserFactory = SAXParserFactory.newInstance();

SAXParser parser = parserFactory.newSAXParser();

parser.getXMLReader().setFeature("http://apache.org/xml/features/allow-java-encodings",true);

Для DOM, к сожалению, подобного механизма feature-ов не предусмотрено, но можно вместо JAXP для создания DOM напрямую использовать класс org.apache.xerces.parsers.DOMParser, у которого уже есть метод setFeature().

Если же Xerces используется не напрямую, а посредством другого пакета, то необходимо настроить этот пакет дабы он сам выставлял этот feature. Если же такой возможности не предусмотрено, то остаётся только один выход - править ручками. Для этого можно или подправить список кодировок в классе org.apache.xerces.readers.MIME2Java или установить указанный feature как true по умолчанию.

Для чтения документа XML из потока данных обычно используется класс org.xml.sax.InputSource. Собственно сам поток может быть представлен или в виде байтового потока (java.io.InputStream) или в виде потока символов (java.io.Reader). Соответственно ответственность за корректное распознавание кодировки возлагается или на парсер или на того, кто создаёт объект Reader. У класса InputSource есть так же метод setEncoding(), при помощи которого можно явно задать кодировку в случае использования потока байтов.

Работает это всё таким образом:

Если был задан поток символов (Reader), то он будет использован для чтения данных. Кодировка, установленная методом setEncoding() при этом игнорируется, как игнорируется и кодировка, указанная в заголовке XML-документа.

Если вместо потока символов был задан поток байтов (InputStream), то используется он. Если установлена кодировка методом setEncoding(), то используется она, а если нет - то парсер использует кодировку, указанную в заголовке XML-документа.

Если при чтении заголовка XML-документа обнаруживается расхождение между заданной кодировкой и кодировкой из заголовка, то парсеры могут поступать по разному. Crimson, например, при этом выдаёт предупреждение, а Xerces молча пропускает.

С чтением XML-документов мы разобрались, теперь перейдём к их созданию. Единого стандарта на создание документов, в отличии от чтения, пока нет. Предполагается, что, следующая версия рекомендаций комитета W3C будет включать в себя и создание документов, но пока что создатели парсеров делают кто во что горазд.

В случае с Crimson сохранить созданный документ DOM можно при помощи метода write() у класса org.apache.crimson.tree.XmlDocument. В качестве аргумента можно передать или поток символов (Writer) или поток байтов (OutputStream). Вместе с потоком можно передать и необходимую кодировку. Если использован поток байтов, а кодировка указана не была, то используется UTF-8. Если использован поток символов вместе с именем кодировки, то имя используется только для записи в заголовок документа. Если Writer передан без кодировки, то делается проверка - если это экземляр OutputStreamWriter, то для выяснения что писать в заголовок зовётся его метод getEncoding(). Если же это другой Writer, то кодировка в заголовок записана не будет, что по стандарту означает кодировку UTF-8. Пример:

XmlDocument doc = ...;

OutputStream os = ...;

doc.write(os,"Windows-1251");

В Xerces для создания документов используются классы из пакета org.apache.xml.serialize. Собственно для записи используется класс XMLSerializer, а для настройки выходного формата - класс OutputFormat. В конструкторе XMLSerializer можно передавать как потоки байтов, так и потоки символов. В случае потоков символов используемая кодировка должна совпадать с заданной в OutputFormat. Важно не забыть задать используемую кодировку в OutputFormat - в противном случае русские буквы будут представлены в виде кодов, типа такого: "АБВ" для символов "АБВ". Пример:

OutputStream os = ...;

OutputFormat format = new OutputFormat( Method.XML, "Windows-1251", true )

XMLSerializer serializer = new XMLSerializer(os,format);

serializer.serialize(doc);

Castor XML

Пакет Castor предназначен для решения проблем долговременного хранения объектов. В числе прочего он содержит в себе подсистему Castor XML, которая по сути дела является надстройкой над XML-парсером и позволяет автоматизировать чтение и запись XML-файлов. Castor XML по умолчанию использует парсер Xerces, поэтому проблемы Xerces перекочёвывают и сюда. В документации к Castor в примерах используются потоки символов (Reader и Writer), а это может привести к рассогласованности между используемой в потоке кодировки и реальной кодировки XML-файла. Как уже говорилось выше, чтобы прочитать при помощи Xerces XML-файл в произвольной кодировке нужно, во первых, использовать потоки байтов, а во вторых, установить специальный feature. К счастью эта возможность предусмотрена в Castor. Для этого нужно скопировать файл castor.properties (взять его можно из каталога orgexolabcastor в файле castor-0.9.3-xml.jar) в подкаталог lib в JRE, и установить там переменную org.exolab.castor.sax.features. Пример:

# Comma separated list of SAX 2 features that should be enabled

# for the default parser.

#

#org.exolab.castor.features=

org.exolab.castor.sax.features=http://apache.org/xml/features/allow-java-encodings

Стоит отметить, что по умолчанию там стоит переменная org.exolab.castor.features, но это, очевидно, опечатка - если посмотреть в исходники, то там анализируется org.exolab.castor.sax.features (это справедливо для Castor версии 0.9.3 от 03.07.2001). Пример чтения с использованием потоков байтов:

public static Object load(Class cls, String mappingFile, InputStream is)

 throws Exception

{

 Mapping mapping = loadMapping(cls,mappingFile);

 Unmarshaller unmarshaller = new Unmarshaller(cls);

 unmarshaller.setMapping(mapping);

 return unmarshaller.unmarshal(new InputSource(is));

}

Для создания XML-файлов необходимо правильно указать формат для Xerces. Пример:

public static void save(Object obj, String mappingFile, OutputStream os, String encoding)

 throws Exception

{

 Mapping mapping = loadMapping(obj.getClass(),mappingFile);

 try

   {

    XMLSerializer serializer = new XMLSerializer(os,new OutputFormat( Method.XML, encoding, true ));

    Marshaller marshaller = new Marshaller(serializer);

    marshaller.setMapping(mapping);

    marshaller.marshal(obj);

   }

 finally { os.flush(); }

}

Для загрузки файлов маппинга в этих примерах можно использовать такой код:

private static Mapping loadMapping(Class cls,String mappingFile)

 throws Exception

{

 ClassLoader loader = cls.getClassLoader();

 

 Mapping mapping = new Mapping(loader);

 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%jrelibendorsed

Копируете туда 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:

В подкаталог conffonts (например, в c:fop-0.20.1conffonts) скопировать файлы 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.1buildfop.jar;c:fop-0.20.1libbatik.jar;

 c:fop-0.20.1libxalan-2.0.0.jar;c:fop-0.20.1libxerces.jar;

 c:fop-0.20.1libjimi-1.0.jar

 org.apache.fop.fonts.apps.TTFReader fontsarial.ttf fontsarial.xml

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

 

 

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

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

Driver driver = new Driver();

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

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

String userConfig = "fonts/userconfig.xml";

File userConfigFile = new File(userConfig);

Options options = new Options(userConfigFile);

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

 

 

В файле 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);

}

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

GUI (AWT, Swing)

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

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

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

Если программа не работает нигде - значит проблема только в ней и в Ваших руках. Внимательно перечитайте всё, что было написано выше, и ищите. Если же проблема проявляется только в конкретном окружении - значит дело, возможно в настройках. Где именно - зависит от того, какой графической библиотекой Вы пользуетесь. Если AWT - помочь может правильная настройка файла font.properties.ru. Пример корректного файла можно взять из Java 2. Если у Вас нет этой версии, можете скачать его с данного сайта: версия для Windows, версия для Linux (см. также раздел по Linux ниже). Этот файл задаёт используемые шрифты и кодовые страницы. Если у Вас установлена русская версия OS - просто добавьте этот файл туда, где лежит файл font.properties. Если же это англицкая версия, то нужно, или переписать этот файл вместо font.properties или дополнительно сменить текущие региональные настройки на русские. Иногда может сработать настройка -Duser.language=ru, но чаще - нет. Тут примерно те же проблемы, что и с file.encoding - сработает или нет, зависит от JDK (см. ошибку за номером 4152725).

Если кроме русских букв Вам также надо выводить, к примеру, греческие, то обычно достаточно просто правильно указать их кода. Работает всё это примерно таким способом:

По умолчанию в AWT и Swing используются виртуальные шрифты, нас

 
     
Бесплатные рефераты
 
Банк рефератов
 
Бесплатные рефераты скачать
| мероприятия при чрезвычайной ситуации | Чрезвычайная ситуация | аварийно-восстановительные работы при ЧС | аварийно-восстановительные мероприятия при ЧС | Интенсификация изучения иностранного языка с использованием компьютерных технологий | Лыжный спорт | САИД Ахмад | экономическая дипломатия | Влияние экономической войны на глобальную экономику | экономическая война | экономическая война и дипломатия | Экономический шпионаж | АК Моор рефераты | АК Моор реферат | ноосфера ба забони точики | чесменское сражение | Закон всемирного тяготения | рефераты темы | иохан себастиян бах маълумот | Тарых | шерхо дар борат биология | скачать еротик китоб | Семетей | Караш | Influence of English in mass culture дипломная | Количественные отношения в английском языках | 6466 | чистонхои химия | Гунны | Чистон
 
Рефераты Онлайн
 
Скачать реферат
 
 
 
 
  Все права защищены. Бесплатные рефераты и сочинения. Коллекция бесплатных рефератов! Коллекция рефератов!