Смекни!
smekni.com

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

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 = "myaccount@mydomail.ru";

static final String TO = "myaccount@mydomail.ru";

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-документа. Пример:

<?xml version="1.0" encoding="Windows-1251"?>

Если кодировка указана не была, то по умолчанию предполагается кодировка 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 - в противном случае русские буквы будут представлены в виде кодов, типа такого: "&#x410;&#x411;&#x412;" для символов "АБВ". Пример:

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 (взять его можно из каталога org&bsol;exolab&bsol;castor в файле 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, в котором можно задать используемую кодировку. Пример:

<xsl:output encoding="Windows-1251" method='html' indent='yes'/>

Если XSLT-процессор не знает указанной кодировки, то он должен или выдать ошибку или использовать UTF-8 (или UTF-16). Если формируется HTML, то XSLT-процессор должен добавить тег meta, в котором будет указана реально использованная кодировка:

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

Всё бы хорошо, но некоторые XSLT-процессоры не поддерживают данный тег (по спецификации они и не обязаны). В частности пакет Cocoon его не поддерживает, т.к. по словам разработчиков он противоречит внутренней архитектуре этого пакета. Вместо этого там поддерживается указание выходного формата при помощи инструкции препроцессора cocoon-format. Пример вставки этой инструкции в XSL:

<xsl:processing-instruction name="cocoon-format">

type="text/html"

</xsl:processing-instruction>

Таким образом можно динамически менять выходной формат. Если это не требуется, то можно записать инструкцию и статически (в исходном XML-документе):

<?cocoon-format type="text/html"?>

Собственно используемая кодировка настраивается для каждого формата отдельно в файле cocoon.properties.

Новая версия Cocoon 2.0 кроме управления кодировками позволяет сделать в плане локализации уже гараздо больше. Подробности Вы можете узнать на их сайте.

В случае использования JAXP для генерации выходного потока (пакет javax.xml.transform) кроме использования тега xsl:output можно использовать методы setOutputProperty объекта Transformer. Пример сохранения документа в нужной кодировке: