Java: Росіяни букви і не тільки ... h2>
Введення b>
p>
Деякі
проблеми настільки складні, що потрібно бути дуже розумним і дуже добре
поінформованим, щоб не бути впевненим у їх вирішенні. p>
Лоренс
Дж. Пітер p>
Peter's Almanac p>
Кодування h2>
Коли
я тільки починав програмувати на мові C, першою моєю програмою (не рахуючи
HelloWorld) була програма перекодування текстових файлів з основної кодування
ГОСТ-а (пам'ятаєте таку? :-) В альтернативне. Було це в далекому 1991-му році. З
тих пір багато чого змінилося, але за минулі 10 років подібні програмки свою
актуальність, на жаль, не втратили. Занадто багато вже накопичено даних у
різноманітних кодуваннях і занадто багато використовується програм, які вміють
працювати тільки з однією. Для російської мови існує не менше десятка
різних кодувань, що робить проблему ще більш заплутаною. p>
Звідки
ж узялися всі ці кодування і для чого вони потрібні? Комп'ютери за своєю природою
можуть працювати тільки з числами. Для того, щоб зберігати букви в пам'яті
комп'ютера треба поставити у відповідність кожній букві певне число (приблизно
такий же принцип використовувався і до появи комп'ютерів - згадайте про ту ж
азбуку Морзе). Причому число бажано трохи менше - чим менше двійкових розрядів
буде задіяно, тим ефективніше можна буде використовувати пам'ять. Ось це
відповідність набору символів та чисел власне і є кодування. Бажання будь-якої
ціною заощадити пам'ять, а так само роз'єднаність різних груп комп'ютерників і
призвела до нинішнього стану справ. Найпоширенішим способом кодування
зараз є використання для одного символу одного байти (8 біт), що
визначає загальну кількість символів в 256. Набір перші 128 символів
стандартизований (набір ASCII) і є однаковими у всіх поширених
кодуваннях (ті кодування, де це не так вже практично вийшли з
вжитку). Англіцкіе літери і символи пунктуації знаходяться в цьому
діапазоні, що й визначає їх вражаючу живучість в комп'ютерних системах
:-). Інші мови знаходяться не в настільки щасливому становищі - їм усім
доводиться тулитися в останніх 128 числах. p>
Unicode h2>
В
Наприкінці 80-х багато хто усвідомив необхідність створення єдиного стандарту на
кодування символів, що й призвело до появи Unicode. Unicode - це спроба
раз і назавжди зафіксувати конкретне число за конкретним символом. Зрозуміло,
що в 256 символів тут не укластися при всьому бажанні. Досить довгий час
здавалося, що вже 2-х то байт (65536 символів) повинно вистачити. Але ж ні --
остання версія стандарту Unicode (3.1) визначає вже 94140 символів. Для
такого кол-ва символів, напевно, вже доведеться використовувати 4 байти (4294967296
символів). Може бути і вистачить на деякий час ... :-) P>
В
набір символів Unicode входять всілякі літери з усякими рисками і
пріпендюлькамі, грецькі, математичні, ієрогліфи, символи псевдографіки і
пр. і пр. У тому числі і так улюблені нами символи кирилиці (діапазон значень
0x0400-0x04ff). Так що з цього боку ніякої дискримінації немає. p>
Якщо
Вам цікаві конкретні коду символів, для їх перегляду зручно використовувати
програму "Таблиця символів" з WinNT. Ось, наприклад, діапазон
кирилиці: p>
p>
Якщо
у Вас інша OS або Вас цікавить офіційне тлумачення, то повну розкладку
символів (charts) можна знайти на офіційному сайті Unicode
(http://www.unicode.org/charts/web.html). p>
Типи char і byte h2>
В
Java для символів виділений окремий тип даних char розміром у 2 байти. Це часто породжує плутанину в
умах початківців (особливо якщо вони раніше програмували на інших мовах,
наприклад на C/C + +). Справа в тому, що в більшості інших мов для обробки
символів використовуються типи даних розміром в 1 байт. Наприклад, в C/C + + тип char в більшості випадків використовується
як для обробки символів, так і для обробки байтів - там немає поділу. У
Java для байтів є свій тип - тип byte.
Таким чином C-ішному char
відповідає Java-київськи byte,
а Java-вскому char зі світу C
найближче тип wchar_t.
Треба чітко розділяти поняття символів і байтів - інакше нерозуміння і проблеми
гарантовані. p>
Java
практично з самого свого народження використовує для кодування символів
стандарт Unicode. Бібліотечні функції Java очікують побачити в змінних типу char символи, представлені кодами
Unicode. В принципі, Ви, звичайно, можете запхати туди що завгодно - цифри є
цифри, процесор все стерпить, але при будь-якій обробці бібліотечні функції будуть
діяти виходячи з припущення що їм передали кодування Unicode. Так що
можна спокійно вважати, що у типу char
кодування зафіксована. Але це всередині JVM. Коли дані читаються ззовні або
передаються назовні, то вони можуть бути представлені тільки одним типом - типом byte. Всі інші типи конструюються з
байтів в залежності від використовуваного формату даних. Ось тут на сцену і
виходять кодування - в Java це просто формат даних для передачі символів,
який використовується для формування даних типу char. Для кожної кодової сторінки в бібліотеці є по
2 класу перекодування (ByteToChar і CharToByte). Класи ці лежать в пакеті sun.io. Якщо, при перекодуванні з char в byte не було знайдено відповідного символу, він
замінюється на символ?. p>
До речі,
ці файли кодових сторінок в деяких ранніх версіях JDK 1.1 містять помилки,
викликають помилки перекодіровок, а то й взагалі виключення при виконанні.
Наприклад, це стосується кодування KOI8_R. Найкраще, що можна при цьому зробити --
змінити версію на більш пізню. Судячи з Sun-івської опису, більшість цих
проблем було вирішено у версії JDK 1.1.6. p>
До
появи версії JDK 1.4 набір доступних кодувань визначався тільки
виробником JDK. Починаючи з 1.4 з'явилося нове API (пакет java.nio.charset), за допомогою якого
Ви вже можете створити свою власну систему кодування (наприклад підтримати рідко
використовувану, але моторошно необхідну саме Вам). p>
Клас String h2>
В
більшості випадків для подання рядків в Java використовується об'єкт типу java.lang.String. Це звичайний клас,
який всередині себе зберігає масив символів (char []),
і який містить багато корисних методів для маніпуляції символами. Найкращі
цікаві - це конструктори, що мають перший параметром масив байтів (byte []) і методи getBytes (). За допомогою цих методів Ви
можете виконувати перетворення з масиву байтів в рядки і назад. Для того,
щоб вказати будь кодування при цьому використовувати у цих методів є
рядковий параметр, який задає її ім'я. Ось, наприклад, як можна виконати
перекодування байтів з КОИ-8 у Windows-1251: p>
//
Дані в кодуванні КОИ-8 p>
byte []
koi8Data = ...; p>
// Перетворимо з
КОИ-8 в Unicode p>
String string = new
String (koi8Data, "KOI8_R "); p>
//
Перетворимо з Unicode в Windows-1251 p>
byte [] winData =
string.getBytes ( "Cp1251 "); p>
Список
8-ми бітових кодувань, доступних в сучасних JDK і підтримують російські
букви Ви можете знайти нижче, в розділі "8-ми бітові кодування росіян
букв ". p>
Так
як кодування - це формат даних для символів, крім знайомих 8-ми бітових
кодувань в Java також на рівних присутні і багатобайтове кодування. До
таких відносяться UTF-8, UTF-16, Unicode і пр. Наприклад ось так можна отримати
байти у форматі UnicodeLittleUnmarked (16-ти бітове кодування Unicode,
молодший байт першим, без ознаки порядку байтів): p>
// Рядок Unicode p>
String string = "..."; p>
// Перетворимо з Unicode в
UnicodeLittleUnmarked p>
byte [] data =
string.getBytes ( "UnicodeLittleUnmarked "); p>
При
подібних перетвореннях легко помилитися - якщо кодування байтових даних не
відповідають вказаним параметром при перетворенні з byte в char, то перекодування буде виконано неправильно.
Іноді після цього можна витягнути правильні символи, але частіше за все частина
даних буде безповоротно втрачена. p>
В
реальної програмі явно вказувати кодову сторінку не завжди зручно (хоча більше
надійно). Для цього була введена кодування за замовчуванням. За умовчанням вона
залежить від системи і її установки (для російських віндів прийнята кодування Cp1251),
і в старих JDK її можна змінити установкою системного властивості file.encoding.
В JDK 1.3 зміна цієї налаштування іноді спрацьовує, іноді - ні. Викликано
це в такий: спочатку file.encoding ставиться з регіональних налаштувань
комп'ютера. Посилання на кодування за замовчуванням запам'ятовується в нутрії при першій
перетворення. При цьому використовується file.encoding, але це перетворення
відбувається ще до використання аргументів запуску JVM (собсно, при їх
розборі). Взагалі-то, як стверджують в Sun, ця властивість відображає системну
кодування, і вона не повинна змінюватися у командному рядку (див., наприклад,
коментарі до BugID 4163515) Тим не менше у JDK 1.4 Beta 2 зміна цієї налаштування
знову почала надавати ефект. Що це, свідома зміна або побічний
ефект, який може знову зникнути - Sun-вівці чіткої відповіді поки що не дали. p>
Ця
кодування використовується тоді, коли явно не вказано назву сторінки. Про це
треба завжди пам'ятати - Java не буде намагатися передбачити кодування байтів,
які ви передаєте для створення рядка String (так само вона не зможе прочитати
Ваші думки з цього приводу :-). Вона просто використовує поточну кодування за
замовчуванням. Оскільки ця установка один на всі перетворення, іноді можна
наразитися на неприємності. p>
Для
перетворення з байтів в символи і назад слід користуватися тільки цими
методами. Просте приведення типу використовувати в більшості випадків не можна --
кодування символів при цьому не буде враховуватися. Наприклад, однією з найбільш
поширених помилок є читання даних побайтно за допомогою методу
read () з InputStream, а потім приведення отриманого значення до типу char: p>
InputStream is = ..; p>
int b; p>
StringBuffer sb = new
StringBuffer (); p>
while ((b = is.read ())!=- 1) p>
( p>
sb.append ((char) b);// p>
Якщо
ж Вам не пощастило, і у Вас більш стара версія - для досягнення результату
доведеться поізвращаться: p>
Оригінальний
спосіб роботи з кодуваннями пропонує Russian Apache - тут розписано, як
саме. p>
Своє
вирішення проблеми так само запропонував В'ячеслав Педак. p>
Ну
а самий найпростіший варіант витягти таки символи - передавати в комплекті
параметрів ім'я кодування (або, якщо ви впевнені в поточній кодуванні броузера,
використовувати зумовлену кодування) і використовувати метод перекодування
символів: p>
public void doPost (HttpServletRequest request, HttpServletResponse
response) p>
throws ServletException, IOException p>
( p>
//Кодування повідомлень, використана engine p>
//Деякі використовують ISO-8859-1, деякі кодування p>
// за замовчуванням - однаковості тут немає p>
String requestEnc = "ISO-8859-1"; p>
// Кодування, встановлена в броузері p>
String clientEnc = request.getParameter ( "charset "); p>
if (clientEnc == null) clientEnc = "Cp1251"; p>
//Отримання параметра p>
String value = request.getParameter ( "value "); p>
// P>
if (value! = null) p>
value = new String (value.getBytes (requestEnc), clientEnc); p>
... p>
p>
JSP b>
p>
Технологія
JSP (Java Server Pages) дуже схожа на сервлети. По суті справи сервер, при
запиті в перший раз на льоту генерує з jsp-сторінок код сервлети, компілює
його і запускає його як звичайний сервлет. Тому в JSP виникають схожі
проблеми при роботі з російськими літерами. Однак вирішуються вони трохи по іншому.
Є три місця де можуть виникнути труднощі - українські літери всередині самої
jsp-сторінки, у відповіді клієнта і в запиті від клієнта. Перші два вирішуються
завданням на початку сторінки тега page: p>
p>
Побачивши
цю директиву сервер розуміє, що сторінка записана у зазначеній кодуванні, і
що в сгенеренний код треба додати виклик response.setContentType () з зазначеним
contentType. Якщо сервер підтримує специфікацію Servlet 2.3, то він також
додасть і виклик request.setCharacterEncoding () з потрібної кодуванням, таким
чином автоматом вирішуючи і третю проблему. Для більш старих серверів для
розкодування параметрів в запиті клієнта треба застосовувати хитрощі,
аналогічні описаним у розділі присвяченому сервлети. p>
Для
приклад, для того, щоб налаштувати JSP-форум Jive для роботи з російськими літерами
треба відкоригувати наступні файли: p>
/jive/header.jsp p>
/jive/admin/header.jsp p>
В
них треба в початок додати рядок p>
p>
Замість
UTF-8 можна використовувати будь-яку кодування, яка підтримує українські літери - все
залежить від смаків та уподобань. Інформацію про це прислав Олексій Епишкин,
за що йому окреме спасибі. p>
В
деяких серверах зустрічаються баги, пов'язані з російськими літерами в JSP.
Наприклад, сервер Orion не любить російську літеру "Т" - він замість неї в
сгенеренний сервлет підставляє символ лапки. Там в нутрощах є
приблизно такий код: p>
... p>
switch (
charstring.c1 (i)) p>
... p>
public final char c1 (int i) p>
( p>
if (i <0 | | i> = length) p>
throw new StringIndexOutOfBoundsException (i); p>
else p>
return (char) (data [offset + i] & 0xff); p>
data
- Це масив типу char []. Як видно, помилка тут тривіальна - розробник
чомусь був впевнений що символи з кодами більше 255 - це помилка природи. :-) P>
JavaMail b>
p>
Пакет
JavaMail призначений для роботи з електронними листами. За допомогою цього
пакету Ви можете відправляти та отримувати листи через різні протоколи.
Різні протоколи по різному обробляють національні символи. Найкращі
поширені на даний момент протоколи Internet засновані на старому
стандарті RFC-822. Відповідно до цього стандарту в службових полях (заголовках)
листів дозволено посилати тільки символи кодування ASCII, тобто тільки латинські
літери (перші 128 символів Unicode). Очевидно, що це незручно, тому що часто
дуже хочеться писати, наприклад в поле Subject (тема листа) або в полях From/To
(ім'я та адреса відправника/отримувача) російський текст. Для того, щоб вирішити цю
проблему був придуманий стандарт кодування MIME (RFC 2047). Він дозволяє в
деяких полях заголовка (не у всіх) використовувати національні символи при
допомогою спеціального кодування (Base64 або QuotedPrintable). p>
Для
подання листів у JavaMail використовується клас javax.mail.Message. Це
абстрактний клас, реальне ж поведінка визначається спадкоємцями. Методи,
визначені в ньому працюють тільки зі звичайними Java-рядками (String). Для
протоколів Internet звичайно використовується спадкоємець
javax.mail.internet.MimeMessage, який крім базових методів додає
методи, в яких можна додатково вказувати кодування, яку слід
використовувати для листів. Для кодування використовується допоміжний клас
javax.mail.internet.MimeUtility. Клас MimeMessage зазвичай сам звертається до нього
для кодування/розкодування заголовків, але, якщо Ви безпосередньо звертаєтеся до
заголовків (методи getHeader ()/setHeader ()/addHeader ()), то для їх
кодування/розкодування Вам доведеться звертатися до методів MimeUtility
самому. p>
Якщо
Ви не вказуєте кодування листа, то буде використана кодування за замовчуванням
- Зазвичай використовується file.encoding, але її можна перекрити спеціальної
системної налаштуванням "mail.mime.charset". Це розумно, тому що часто
кодування за замовчуванням в системі відрізняється від стандартної кодування Internet.
Для російськомовних листів в Internet стандартом де-факто стала кодування КОИ-8.
Ви, звичайно, можете вказати і іншу, але шанс, що приймаюча сторона не
зможе прочитати такий лист дуже великий. p>
Треба
враховувати також, що в JavaMail розрізняються два стандарти найменування
кодувань - стандарт MIME і стандарт Java. Для більшості кодувань імена MIME
вже підтримуються в Java за допомогою механізму синонімів. Наприклад, для
кодування "Cp1251" (назва Java) існує синонім
"Windows-1251" (назва MIME). Для тих кодувань, для яких такі
синоніми відсутні, вони підтримуються всередині JavaMail. Для цього завантажується
файл javamail.charset.map з підкаталогу "/ META-INF" з того
jar-файлу, звідки був завантажений пакет JavaMail. Для вказівки кодування при
виклику методів JavaMail слід використовувати тільки MIME-імена, в іншому
разі одержувач не зможе розпізнати спожиту кодування (якщо тільки на
іншому кінці не теж Java :-). p>
Ось
простий приклад відправки листа за допомогою JavaMail: p>
import java.util.Properties; p>
import javax.mail.Session; p>
import javax.mail.Message; p>
import javax.mail.Transport; p>
import
javax.mail.internet.MimeMessage; p>
import
javax.mail.internet.InternetAddress; p>
public class MailTest p>
( p>
static final String ENCODING =
"koi8-r"; p>
static final String FROM =
"[email protected]"; p>
static final String TO =
"[email protected]"; p>
public static void main (String args []) throws
Exception p>
( p>
Properties mailProps = new Properties (); p>
mailProps.put ( "mail.store.protocol", "pop3 "); p>
mailProps.put ( "mail.transport.protocol", "smtp "); p>
mailProps.put ( "mail.user", "myaccount "); p>
mailProps.put ( "mail.pop3.host", "mail.mydomail.ru "); p>
mailProps.put ( "mail.smtp.host", "mail.mydomail.ru "); p>
Session session = Session.getDefaultInstance (mailProps); p>
MimeMessage message = new MimeMessage (session); p>
message.setFrom (new InternetAddress (FROM )); p>
message.setRecipient (Message.RecipientType.TO, new InternetAddress (TO )); p>
message.setSubject ( "Тестове лист", ENCODING); p>
message.setText ( "Текст тестового листа", ENCODING); p>
Transport.send (message); p>
) p>
) p>
XML/XSL b>
p>
При
розробці формату XML особлива увага приділялася підтримці різних кодувань
символів. Для вказівки того, яка кодування була використана використовується
заголовок XML-документа. Приклад: p>
p>
Якщо
кодування вказана не була, то за умовчанням передбачається кодування 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 ()), то парсер пiсля пошуку
таблиці буде намагатися використовувати стандартний Java-київськи механізм і
відповідно Java-київськи набір кодувань. У разі використання інтерфейсу
SAX зробити це можна таким, наприклад, чином (при використанні JAXP): p>
SAXParserFactory parserFactory =
SAXParserFactory.newInstance (); p>
SAXParser parser =
parserFactory.newSAXParser (); p>
parser.getXMLReader (). setFeature ( "http://apache.org/xml/features/allow-java-encodings", true); p>
Для
DOM, на жаль, такого механізму feature-ів не передбачено, але можна
замість JAXP для створення DOM безпосередньо використовувати клас
org.apache.xerces.parsers.DOMParser, у якого вже є метод setFeature (). p>
Якщо
ж Xerces використовується не прямо, а за допомогою іншого пакета, то необхідно
налаштувати цей пакет щоб він сам виставляв цей feature. Якщо ж такий
можливості не передбачено, то залишається тільки один вихід - ред ручками.
Для цього можна або підправити список кодувань в класі
org.apache.xerces.readers.MIME2Java або встановити зазначений feature як true
за умовчанням. p>
Для
читання документа XML з потоку даних звичайно використовується клас
org.xml.sax.InputSource. Власне сам потік може бути представлений або у вигляді
байтового потоку (java.io.InputStream) або у вигляді потоку символів
(java.io.Reader). Відповідно відповідальність за коректне розпізнавання
кодування покладається або на парсер або на того, хто створює об'єкт Reader. У
класу InputSource є так само метод setEncoding (), за допомогою якого можна
явно задати кодування у випадку використання потоку байтів. p>
Працює
це все таким чином: p>
Якщо
було задано потік символів (Reader), то він буде використаний для читання даних.
Кодування, встановлена методом setEncoding () при цьому ігнорується, як
ігнорується та кодування, зазначена в заголовку XML-документа. p>
Якщо
замість потоку символів було задано потік байтів (InputStream), то використовується
він. Якщо встановлена кодування методом setEncoding (), то використовується вона, а
якщо ні - то парсер використовує кодування, вказану в заголовку XML-документа.
p>
Якщо
при читанні заголовка XML-документа виявляється розбіжність між заданої
кодуванням і кодуванням із заголовка, то парсер можуть поступати по різному.
Crimson, наприклад, при цьому видає попередження, а Xerces мовчки пропускає. p>
З
читанням XML-документів ми розібралися, тепер перейдемо до їх створення. Єдиного
стандарту на створення документів, на відміну від читання, поки немає.
Передбачається, що, наступна версія рекомендацій комітету W3C буде включати
в себе і створення документів, але поки що творці парсерів роблять хто на що
здатний. p>
В
випадку з Crimson зберегти створений документ DOM можна за допомогою методу
write () у класу org.apache.crimson.tree.XmlDocument. Як аргумент
можна передати або потік символів (Writer) або потік байтів (OutputStream).
Разом з потоком можна передати і необхідну кодування. Якщо використаний потік
байтів, а кодування вказана не була, то використовується UTF-8. Якщо використаний
потік символів разом з ім'ям кодування, то ім'я використовується тільки для запису
в заголовок документа. Якщо Writer переданий без кодування, то робиться перевірка
- Якщо це примірників OutputStreamWriter, то для з'ясування що писати в заголовок
зветься його метод getEncoding (). Якщо ж це інше Writer, то кодування в
заголовок записана не буде, що за стандартом означає кодування UTF-8. Приклад:
p>
XmlDocument
doc = ...; p>
OutputStream
os = ...; p>
doc.write (os, "Windows-1251 "); p>
В
Xerces для створення документів використовуються класи з пакета
org.apache.xml.serialize. Власне для запису використовується клас
XMLSerializer, а для налаштування вихідного формату - клас OutputFormat. У
конструкторі XMLSerializer можна передавати як потоки байтів, так і потоки
символів. У разі потоків символів використовується кодування повинна збігатися з
заданої в OutputFormat. Важливо не забути поставити використовувану кодування
OutputFormat - в іншому випадку українські літери будуть представлені у вигляді
кодів, типу такого: "АБВ" для символів
"АБВ". Приклад: p>
OutputStream os = ...; p>
OutputFormat format = new
OutputFormat (Method.XML, "Windows-1251", true) p>
XMLSerializer serializer = new
XMLSerializer (os, format); p>
serializer.serialize (doc); p>
Castor
XML p>
Пакет
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. Приклад: p>
# Comma separated list of SAX 2
features that should be enabled p>
# for the default parser. p>
# p>
# org.exolab.castor.features = p>
org.exolab.castor.sax.features = http://apache.org/xml/features/allow-java-encodings p>
Варто
відзначити, що за замовчуванням там стоїть мінлива org.exolab.castor.features, але
це, очевидно, друкарська помилка - якщо подивитися в вихідні коди, то там аналізується
org.exolab.castor.sax.features (це справедливо для Castor версії 0.9.3 від
03.07.2001). Приклад читання з
використанням потоків байтів: p>
public static Object load (Class cls,
String mappingFile, InputStream is) p>
throws Exception p>
( p>
Mapping mapping =
loadMapping (cls, mappingFile); p>
Unmarshaller unmarshaller = new
Unmarshaller (cls); p>
unmarshaller.setMapping (mapping); p>
return unmarshaller.unmarshal (new
InputSource (is )); p>
) p>
Для
створення XML-файлів необхідно правильно вказати формат для Xerces. Приклад: p>
public static void save (Object obj,
String mappingFile, OutputStream os, String encoding) p>
throws Exception p>
( p>
Mapping mapping =
loadMapping (obj.getClass (), mappingFile); p>
try p>
( p>
XMLSerializer serializer = new XMLSerializer (os, new OutputFormat (
Method.XML, encoding, true )); p>
Marshaller marshaller = new Marshaller (serializer); p>
marshaller.setMapping (mapping); p>
marshaller.marshal (obj); p>
) p>
finally (os.flush ();) p>
) p>
Для
завантаження файлів маппінг в цих прикладах можна використовувати такий код: p>
private static Mapping
loadMapping (Class cls, String mappingFile) p>
throws Exception p>
( p>
ClassLoader loader = cls.getClassLoader (); p>
p>
Mapping mapping = new Mapping (loader); p>
mapping.loadMapping (new
InputSource (loader.getResourceAsStream (mappingFile))); p>
return mapping; p>
) p>
XSL h2>
Специфікація
XSL описує стандарт на перетворення XML-документів. Коли за допомогою XSL
виконується перетворення з одного XML-документа в інший, особливих причин для
занепокоєння немає - і той і інший є Unicode-документами, тому немає
перетворень із символів в байти і назад, які можуть вплинути на результат.
Інша справа, коли виконується перетворення з XML в HTML або взагалі в
текстовий файл. Формат вихідного файлу задається налаштуванням тега xsl: output, в
якому можна задати використовувану кодування. Приклад: p>
p>
Якщо
XSLT-процесор не знає зазначеної кодування, то він повинен або видати помилку або
використовувати UTF-8 (або UTF-16). Якщо формується HTML, то XSLT-процесор
повинен додати тег meta, в якому буде вказано реально використана
кодування: p>
p>
Всі
б добре, але деякі XSLT-процесори не підтримують цей тег (по
специфікації вони і не зобов'язані). Зокрема пакет Cocoon його не підтримує,
тому що за словами розробників він суперечить внутрішньої архітектурі цього
пакету. Замість цього там підтримується вказівку вихідного формату за допомогою
інструкції препроцесора cocoon-format. Приклад вставки цієї інструкції XSL: p>
p>
type = "text/html" p>
p>
Таким
чином можна динамічно змінювати вихідний формат. Якщо це не потрібно, то
можна записати інструкцію і статично (у вихідному XML-документі): p>
p>
Власне
використовується кодування настроюється для кожного формату окремо у файлі
cocoon.properties. p>
Нова
версія Cocoon 2.0 крім керування кодуваннями дозволяє зробити в плані
локалізації вже гараздо більше. Подробиці Ви можете дізнатися на їхньому сайті. p>
В
разі використання JAXP для генерації вихідного потоку (пакет
javax.xml.transform) крім використання тега xsl: output можна використовувати
методи setOutputProperty об'єкта Transformer. Приклад збереження документа в
належним кодуванням: p>
TransformerFactory
trFactory = TransformerFactory.newInstance (); p>
Transformer transformer =
trFactory.newTransformer (); p>
transformer.setOutputProperty (OutputKeys.DOCTYPE_PUBLIC,
docPublic); p>
transformer.setOutputProperty (OutputKeys.DOCTYPE_SYSTEM,
docSystem); p>
transformer.setOutputProperty (
OutputKeys.INDENT, "yes"); p>
transformer.setOutputProperty (
OutputKeys.ENCODING, encoding); p>
OutputStream os = ...; p>
StreamResult result = new
StreamResult (os); p>
transformer.transform (
source, result); p>
Тут
є один підводний камінь - реалізація Transformer повинна підтримувати потрібну
кодування. Xalan зі складу JDK 1.4.0_x і 1.4.1_x підтримує тільки два
російські кодування - KOI8-R та ISO-8859-5. Якщо хочеться використовувати
Windows-1251, то можна скористатися механізмом endorsed: p>
Створюєте
каталог% JAVA_HOME% jrelibendorsed
p>
Копіюєте
туди jar з виправленими класом: XalanRusChars.jar p>
В
JDK 1.4.2 Beta включена нова версія Xalan, яка ніби як вже підтримує
кодування 1251. p>
FOP
p>
Пакет
FOP призначений для обробки документів за стандартом XSL FO (Formating
Objects). Зокрема він дозволяє створювати PDF-документи на базі документів
XML. Для перетворення з вихідного XML в FO пакет FOP за замовчуванням використовує
XSLT-процесор Xalan в парі з Xerces. Для створення підсумкового зображення в FOP
необхідно підключити шрифти, що підтримують російські букви. Ось як можна
виконати це для версії 0.20.1: p>
В
підкаталог 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. p>
Згенерувати
файли описів шрифтів (типу arial.xml). Для цього для кожного шрифту потрібно
виконати команду (це для Arial normal/normal, все в один рядок): p>
java-cp
.; c: fop-0.20.1buildfop.jar; c: fop-0.20.1libbatik.jar; p>
c: fop-0.20.1libxalan-2.0.0.jar; c: fop-0.20.1libxerces.jar; p>
c: fop-0.20.1libjimi-1.0.jar p>
org.apache.fop.fonts.apps.TTFReader
fontsarial.ttf fontsarial.xml p>
В
FOP додати conf/userconfig.xml опис шрифту з російськими літерами, типу: p>
p>
p>
p>
p>
Аналогічно додаються Arial normal/bold, italic/normal і italic/bold. p>
При
виклику FOP з командного рядка після org.apache.fop.apps.Fop писати-c
c: fop-0.20.1confuserconfig.xml Якщо потрібно використовувати FOP з сервлети, то
потрібно в сервлет після рядки p>
Driver driver = new Driver (); p>
додати рядки: p>
//
Каталог fonts (c: weblogicfonts) був створений виключно для зручності. P>
String userConfig =
"fonts/userconfig.xml"; p>
File userConfigFile = new
File (userConfig); p>
Options options = new Options (userConfigFile); p>
Тоді
розташування файлів ttf у файлі userconfig.xml можна вказати щодо кореня
сервера додатки, без вказівки абсолютного шляху: p>
p>
p>
p>
p>
В
фото FO (або XML і XSL) перед використанням шрифту писати: p>
font-family = "Arial" p>
font-weight = "bold" (Якщо використовується Arial bold) p>
font-style = "italic" (Якщо використовується Arial italic) p>
Даний
алгоритм надіслав Олексій Тюрін, за що йому окреме спасибі. p>
Якщо
Ви використовуєте вбудований в FOP переглядач, то необхідно врахувати його
особливості. Зокрема, хоча передбачається, що написи в ньому
русифіковані, насправді зроблено це з помилкою (у версії 0.19.0). Для
завантаження написів з файлів ресурсів в пакеті org.apache.fop.viewer.resources
використовується власний завантажувач (клас
org.apache.fop.viewer.LoadableProperties). Кодування читання там жорстко
зафіксована (8859_1, як і у випадку Properties.load ()), проте підтримка
запису виду "uXXXX" не реалізована. Я повідомив про цю помилку
розробникам, вони включили її виправлення в свої плани. p>
Крім
усього іншого існує сайт присвячений русифікації FOP
(http://www.openmechanics.net/rusfop/) Там Ви зможете знайти дистрибутив FOP з
вже виправленими помилками та підключеними російськими шрифтами. p>
POI b>
p>
Пакет
Jakarta POI призначений для роботи з документами Microsoft Office. Поки що
Особливої складності в роботі з російською мовою немає, але треба враховувати нюанс, що
для роботи з ячекой використовується клас org.apache.poi.hssf.usermodel.Cell, у
якого є метод setEncoding (short encoding), проте замість звичних
"Cp1255" і "Cp866", необхідно ісользовать константи
ENCODING_COMPRESSED_UNICODE (0) і ENCODING_UTF_16 (1). За замовчуванням включений
перший режим, а для нормальної роботи з російською мовою необхідно використовувати
ENCODING_UTF_16. Причому що найважливіше, цю установку необхідно виконувати
для кожної, що створюється клітинки. Приклад коду: p>
HSSFWorkbook wb = new
HSSFWorkbook (); p>
HSSFSheet sheet =
wb.createSheet ( "Sheet1 "); p>
HSSFRow row = sheet.createRow (
(short) 0); p>
for (int i = 0; i <10; i + +) p>
( p>
HSSFCell cell = row.createCell ((short) i); p>
cell.setEncoding ((short) cell.ENCODING_UTF_16); p>
cell.setCellValue ( "Тест російської мови "); p>
) p>
Створити
лист з назвою містить російські символи, на жаль, не вдається. Дане
опис надіслав В'ячеслав Яковенко, за що йому окреме спасибі. p>
CORBA b>
p>
В
стандарті CORBA передбачений тип, відповідний Java-івської типу String. Це
тип wstring. Все б добре, але деякі CORBA-сервера не підтримують його повною
мірою. Типові виключення, що виникають при спотикання на російських буквах:
org.omg.CORBA.MARSHAL: minor code 5 completed No або
org.omg.CORBA.DATA_CONVERSION. Краще за все, звичайно, замінити CORBA-сервер. До
жаль у мене немає статистики, тому я не можу сказати, з якими проблем не
буде. Якщо змінити систему не представляється можливим, можна замість типу
wstring використовувати тип string у парі з нашим улюбленим перетворенням: p>
// Серверна частина p>
a = new Answer (new String (
src.getBytes ( "Cp1251"), "ISO-8859-1" )); p>
... p>
// Клієнтська частина p>
Answer answer = serverRef.getAnswer (); p>
res = new String (
answer.msg.getBytes ( "ISO-8859-1"), "Cp1251"); p>
Тип
wstring при цьому краще не використовувати, тому що тим самим Ви крівость сервера
компенсуватимете крівостью своїх компонентів, а це практично завжди
загрожує різноманітними проблемами в майбутньому. p>
Замість
Cp1251 можна використовувати будь-яку кодування російських букв, за бажанням. Це буде
кодування, в якій будуть передаватися рядки у компоненти на інших мовах.
Також, аналогічний код може знадобитися, якщо необхідно організувати зв'язок
з готовими не-Java компонентами, які вже використовували тип string. p>
Чесно
кажучи, не лежить у мене душа до таких рішень, ну та що поробиш, іноді воно
єдине. p>
JNI
p>
JNI
(Java Native Interface) - це стандарт по взаємодії з C/C + +-вим кодом. Як
і слід було очікувати, на цьому вододілі теж відбувається зіткнення байтів і
символів. Більшість C/C + +-них програм пишеться без урахування Unicode, багато
програмісти навіть не знають про нього. Я сам, за 7 років письменства на C/C + +, поки
не почав писати на Java, про Unicode знав лише з чуток. Більшість
строкових операцій в C/C + + зроблені для 8-бітового сішного типу char. У принципі,
є деякі зрушення в цьому напрямку, зокрема для Windows NT можна
відкомпілювати код, який буде взаємодіяти з Unicode-варіантами Win32
API, але, на жаль, цього часто недостатньо. p>
Таким
чином головне завдання - отримати тип char * з типу jstring (JNI-шное
відображення String) і навпаки. Практично у всіх описах і прикладах JNI
для цього використовується пара функцій
GetStringUTFChars ()/ReleaseStringUTFChars (). Підступні буржуїни і тут
приготували засідку - ці функції формують масив байтів за стандартом UTF,
який відповідає очікуваному тільки для ASCII-символів (перші 128
значень). Російські літери знову в прольоті. Сішние рядка char * дуже добре
лягають на Java-івської тип byte [], але при цьому виникає проблема в вигляді нуль-символу.
Його потрібно додавати при перетворенні byte [] -> char * і враховувати при
зворотному перетворенні. Приклад:
p>
public void action (String msg)
throws java.io.IOException p>
( p>
int res = nAction (msg); p>
if (res! = 0) throw new java.io.IOException (
nGetErrorString (res)); p>
) p>
private native int nAction (String
msg); p>
private native String
nGetErrorString (int error); p>
... p>
jbyteArray getStringBytes (JNIEnv
* env, jstring str) p>
( p>
if (! str) return NULL; p>
jmethodID getBytes =
env-> GetMethodID (env-> GetObjectClass (str), "getBytes ","()[ B "); p>
jbyteArray buf =
(jbyteArray) env-> CallObjectMethod (str, getBytes); p>
if (! buf) return NULL; p>
// Додаємо нуль-символ p>
jsize len = env-> GetArrayLength (buf); p>
jbyteArray nbuf = env-> NewByteArray (len +1); p>
if (len! = 0) p>
( p>
jbyte * cbuf = env-> GetByteArrayElements (buf, NULL); p>
env-> SetByteArrayRegion (nbuf, 0, len, cbuf); p>
env-> ReleaseByteArrayElements (buf, cbuf, JNI_ABORT); p>
) p>
env-> DeleteLocalRef (buf); p>
return nbuf; p>
) p>
JNIEXPORT jint JNICALL
Java_Test_nAction p>
(JNIEnv * env, jobject obj, jstring msg) p>
( p>
jbyteArray bmsg = getStringBytes (env, msg); p>
if (! bmsg) return -1; p>
jbyte * cmsg =
env-> GetByteArrayElements (bmsg, NULL); p>
printf (cmsg); p>
jint res = do_something (cmsg); p>
env-> ReleaseByteArrayElements (bmsg, cmsg, JNI_ABORT); p>
return res; p>
) p>
jstring newString (JNIEnv * env,
jbyteArray jbuf, int len) p>
( p>
jclass stringClass =
env-> FindClass ( "java/lang/String "); p>
if (! stringClass) return NULL; p>
jmethodID init =
env-> GetMethodID (stringClass ,"","([ BII) V "); p>
if (! init) return NULL; p>
return
(jstring) env-> NewObject (stringClass, init, jbuf, 0, len); p>
) p>
jstring newString (JNIEnv * env, const
char * buf) p>
( p>
if (! buf) return NULL; p>
p>
int bufLen = strlen (buf); p>
if (bufLen == 0) p>
( p>
return env-> NewString ((const jchar *) L "", 0); p>
) p>
jbyteArray jbuf =
env-> NewByteArray (bufLen); p>
if (! jbuf) return NULL; p>
env-> SetByteArrayRegion (jbuf, 0, bufLen, (jbyte *) buf); p>
jstring jstr = newString (env, jbuf, bufLen); p>
env-> DeleteLocalRef (jbuf); p>
return jstr; p>
) p>
JNIEXPORT jstring JNICALL
Java_Test_nGetErrorString p>
(JNIEnv * env, jobject obj, jint error) p>
( p>
char cmsg [256]; p>
memset (cmsg, 0, sizeof (cmsg )); p>
get_error_string (error, cmsg, sizeof (cmsg)); p>
return newString (env, cmsg); p>
) p>
Тут
використовується перетворення символів за замовчуванням, що цілком природно при
взаємодіях із системним API. Якщо ж Вам необхідна певна кодова
сторінка, відповідно потрібно додати її назву. p>
GUI (AWT, Swing) b>
p>
Багато
пов'язують неправильний висновок російських букв з неправильною установкою шрифту. На
Насправді в Java все складніше і рідко дійсно пов'язано зі шрифтами. p>
Де
ж дійсно лежать найбільші підводні камені? В основному це пов'язано з
неправильної перекодувала символів. Частина цих проблем і методи їх вирішення
описані вище. Якщо у Вас все перетворення виконуються коректно, і для виведення
використовується шрифт Unicode, то є дуже великий шанс, що Ваша програма
буде працювати правильно. p>
Якщо
проблеми все ж таки залишилися, тут потрібно з'ясувати, де вони виникають. Спробуйте
запустити додаток під різними JVM, під різними платформами, на різних
броузерах. Приклад досить універсального алгоритму пошуку проблем запропонований
нижче, в розділі Типові помилки. p>
Якщо
програма не працює ніде - значить проблема тільки в ній і у Ваших руках.
Уважно перечитайте все, що було написано вище, і шукайте. Якщо ж проблема
виявляється тільки в конкретному оточенні - значить справа, можливо в налаштуваннях.
Де саме - залежить від того, який графічною бібліотекою Ви користуєтеся.
Якщо AWT - допомогти може правильна настройка файлу font.properties.ru. Приклад
коректного файлу можна взяти з Java 2. Якщо у Вас немає цієї версії, можете
завантажити його з даного сайту: версія для Windows, версія для Linux (див. також
розділ з Linux нижче). Цей файл задає використовуються шрифти і кодові сторінки.
Якщо у Вас встановлена російська версія OS - просто додайте цей файл туди, де
лежить файл font.properties. Якщо ж це англіцкая версія, то треба, або
переписати цей файл замість font.properties або додатково змінити поточні
регіональні настройки на російські. Іноді може спрацювати настройка-Duser.language = ru,
але частіше - ні. Тут приблизно ті ж проблеми, що і з file.encoding - спрацює
чи ні, залежить від JDK (див. помилку за номером 4152725). p>
Якщо
крім російських букв Вам також треба виводити, наприклад, грецькі, то зазвичай
достатньо просто правильно вказати їх коду. Працює все це приблизно таким
способом: p>
За
умовчанням в AWT і Swing використовуються віртуальні шрифти, нас