You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by di...@apache.org on 2001/05/15 14:01:47 UTC
cvs commit: xml-cocoon2/src/org/apache/cocoon/transformation I18nTransformer2.java
dims 01/05/15 05:01:46
Added: src/org/apache/cocoon/transformation I18nTransformer2.java
Log:
I18nTransformer (with param substitution) Contribution
from Konstantin Piroumian (kpiroumian@flagship.ru)
Revision Changes Path
1.1 xml-cocoon2/src/org/apache/cocoon/transformation/I18nTransformer2.java
Index: I18nTransformer2.java
===================================================================
/**
****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
****************************************************************************
*/
package org.apache.cocoon.transformation;
import org.apache.cocoon.Roles;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.acting.LangSelect;
import org.apache.cocoon.components.parser.Parser;
import org.apache.cocoon.components.url.URLFactory;
import org.apache.avalon.excalibur.pool.Poolable;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.logger.Loggable;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.BufferedReader;
import java.util.Map;
import java.util.HashMap;
//import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.ArrayList;
import java.text.MessageFormat;
import java.net.URL;
import java.net.MalformedURLException;
/**
* I18nTransformer. Cocoon2 port of Infozone groups I18nProcessor.
* <p>
* Sitemap configuration:
* </p>
* <p>
* <map:transformer<br>
* name="translate"<br>
* src="org.apache.cocoon.transformation.I18nTransformer2"/><br>
* </p>
* <p>
* <map:match pattern="file"><br>
* <map:generate src="file.xml"/><br>
* <map:transform type="translate"><br>
* <parameter name="default_lang" value="fi"/><br>
* <parameter name="available_lang_1" value="fi"/><br>
* <parameter name="available_lang_2" value="en"/><br>
* <parameter name="available_lang_3" value="sv"/><br>
* <parameter name="src"<br>
* value="translations/file_trans.xml"/><br>
* </map:transform><br>
* </p>
* <p>
* When user requests .../file?lang=fi<br>
* transformer substitutes text surrounded <i18n:text> with
* translations from file_trans.xml.<br>
* Attributes listed in <i18n:attr> attribute are also translated
* </p>
* <p>
* file.xml:<br>
* <root xmlns:i18n="http://apache.org/cocoon/i18n"><br>
* <elem i18n:attr="title" title="translate_me">Text</elem><br>
* <elem><i18n:text>Translate me</i18n:text></elem><br>
* </root>
* </p>
* <p>
* file_trans.xml:<br>
* <translations><br>
* <entry><key>Translate me</key><br>
* <translation lang="sv">�vers�tta mej</translation><br>
* <translation lang="fi">K��nn� minut</translation><br>
* </entry><br>
* </translations><br>
* </p>
* <p>
*
*TODO -Add i18n:key support.<br>
* -Caching dictionaries in memory.<br>
* -Implementing Infozone group I18nProcessors param substitutions
* where you can enter params in the translated text.
*
*
* @author <a href="mailto:kpiroumian@flagship.ru">Konstantin Piroumian</a>
* @author <a href="mailto:lassi.immonen@valkeus.com">Lassi Immonen</a>
*/
public class I18nTransformer2 extends AbstractTransformer implements Composable, Poolable {
protected ComponentManager manager;
public Map dictionary;
//apache.org/cocoon/i18n";
public final static String I18N_NAMESPACE_URI =
"http://apache.org/cocoon/i18n";
public final static String I18N_ELEMENT = "i18n";
//
// Dictionary elements and attributes
//
public final static String I18N_DICTIONARY_ELEMENT = "dictionary";
public final static String I18N_ENTRY_ELEMENT = "entry";
// public final static String I18N_ELEMENT_KEY_ATTRIBUTE = "key";
public final static String I18N_KEY_ELEMENT = "key";
public final static String I18N_TRANSLATION_ELEMENT = "translation";
public final static String I18N_LANG = "lang";
public final static String I18N_KEY_ATTRIBUTE = "key";
public final static String I18N_ATTR_ATTRIBUTE = "attr";
public final static String I18N_TEXT_ELEMENT = "text";
public final static String I18N_TRANSLATE_ELEMENT = "translate";
public final static String I18N_PARAM_ELEMENT = "param";
// States of the transformer
private final static int STATE_OUTSIDE = 0;
private final static int STATE_INSIDE_TEXT = 1;
private final static int STATE_INSIDE_PARAM = 2;
private final static int STATE_INSIDE_TRANSLATE = 3;
private final static int STATE_INSIDE_PARAM_TEXT = 4;
private final static int STATE_INSIDE_TRANSLATE_TEXT = 5;
private final static int STATE_TRANSLATE_KEY = 6;
private final static int STATE_TRANSLATE_TEXT_KEY = 7;
// If true - then text part of the element will be translated.
/**
* Current state of the transformer.
*/
private int current_state = STATE_OUTSIDE;
/**
* Previous state. Used to translate text inside params and translate elements.
*/
private int prev_state = STATE_OUTSIDE;
/**
* The i18n:key attribute is stored for the current element.
*/
protected String current_key = null;
/**
* Translated text inside the i18n:text element.
*/
protected String translated_text = null;
/**
* If no translation found for the key then the character
* data is used as default value.
*/
// protected String default_value = null;
/**
* Translated text, ready for param substitution.
*/
protected String substitute_text = null;
/**
* Current parameter value (translated or not)
*/
protected String param_value = null;
/**
* @todo
* i18n:params are stored in a HashMap for named substitutions.
*/
protected HashMap namedParams = null;
/**
* i18n:params are stored for index substitutions.
*/
protected ArrayList indexedParams = new ArrayList();
/**
* Current language id.
*/
protected String lang;
/**
* Uses <code>org.apache.cocoon.acting.LangSelect.getLang()</code>
* to get language user has selected. First it checks is lang set in
* objectModel.
*/
public void setup(EntityResolver resolver, Map objectModel, String source,
Parameters parameters)
throws ProcessingException, SAXException, IOException {
lang = (String)(objectModel.get("lang"));
if (lang == null) {
lang = LangSelect.getLang(objectModel, parameters);
}
String translations_file = parameters.getParameter("src", null);
URL tr = null;
URLFactory urlFactory = null;
try {
urlFactory = (URLFactory) this.manager.lookup(Roles.URL_FACTORY);
tr = urlFactory.getURL(resolver.resolveEntity(null, translations_file).getSystemId());
} catch (Exception e) {
getLogger().error("cannot obtain the URLFactory", e);
throw new SAXException("cannot obtain the URLFactory", e);
} finally {
if (urlFactory != null) this.manager.release((Component)urlFactory);
}
initialiseDictionary(tr);
}
public void compose(ComponentManager manager) {
this.manager = manager;
}
public void startElement(String uri, String name, String raw,
Attributes attr) throws SAXException {
if (I18N_NAMESPACE_URI.equals(uri)) {
this.getLogger().debug("Starting i18n element: " + name);
startI18NElement(name, attr);
return;
}
super.startElement(uri, name, raw, translateAttributes(name, attr));
}
public void endElement(String uri, String name, String raw)
throws SAXException {
if (I18N_NAMESPACE_URI.equals(uri)) {
endI18NElement(name);
return;
}
super.endElement(uri, name, raw);
}
public void characters(char[] ch, int start, int len) throws SAXException {
if (current_state != STATE_OUTSIDE) {
i18nCharacters(ch, start, len);
return;
}
super.characters(ch, start, len);
}
// My own content handlers
private void startI18NElement(String name, Attributes attr) {
if (I18N_TEXT_ELEMENT.equals(name)) {
prev_state = current_state;
current_state = STATE_INSIDE_TEXT;
current_key = attr.getValue(I18N_NAMESPACE_URI, I18N_KEY_ATTRIBUTE);
}
else if (I18N_TRANSLATE_ELEMENT.equals(name)) {
current_state = STATE_INSIDE_TRANSLATE;
}
else if (I18N_PARAM_ELEMENT.equals(name)) {
current_state = STATE_INSIDE_PARAM;
}
}
private void endI18NElement(String name) throws SAXException {
this.getLogger().debug("End i18n element: " + name);
switch (current_state) {
case STATE_INSIDE_TEXT: {
endTextElement();
break;
}
case STATE_INSIDE_TRANSLATE: {
endTranslateElement();
break;
}
case STATE_INSIDE_PARAM: {
endParamElement();
break;
}
}
}
private String stripWhitespace(String s) {
String result = (s + "!").trim();
return result.substring(0, result.length() - 1);
}
private void i18nCharacters(char[] ch, int start, int len)
throws SAXException{
String text2translate = new String(ch, start, len);
text2translate = stripWhitespace(text2translate);
if (text2translate.length() == 0) {
return;
}
this.getLogger().debug("Text 2 translate: '" + text2translate + "'");
switch (current_state) {
case STATE_INSIDE_TEXT: {
if (current_key != null) {
translated_text = (String)(dictionary.get(current_key));
if (translated_text == null) {
translated_text = text2translate;
}
current_key = null;
}
else if (len > 0) {
translated_text = (String)(dictionary.get(text2translate));
}
break;
}
case STATE_INSIDE_TRANSLATE: {
// Store text for param substitution (do not translate)
if (len > 0 && substitute_text == null) {
substitute_text = text2translate;
}
break;
}
case STATE_INSIDE_PARAM: {
// Store translation for param substitution
if (len > 0 && param_value == null) {
param_value = text2translate;
}
break;
}
}
}
private Attributes translateAttributes(String name, Attributes attr)
throws SAXException {
if (attr == null) {
return attr;
}
AttributesImpl temp_attr = new AttributesImpl(attr);
// Translate all attributes from i18n:attr="name1 name2 ..."
// using their values as keys
int i18n_attr_index =
temp_attr.getIndex(I18N_NAMESPACE_URI, I18N_ATTR_ATTRIBUTE);
if (i18n_attr_index != -1) {
StringTokenizer st =
new StringTokenizer(temp_attr.getValue(i18n_attr_index));
// remove the i18n:attr attribute - we don't need it
temp_attr.removeAttribute(i18n_attr_index);
while (st.hasMoreElements()) {
// translate all listed attributes
String attr_name = st.nextToken();
int attr_index = temp_attr.getIndex(attr_name);
if (attr_index != -1) {
String text2translate = temp_attr.getValue(attr_index);
String result = (String)(dictionary.get(text2translate));
// set the translated value
if (result != null) {
temp_attr.setValue(attr_index, result);
}
else {
getLogger().warn("translation not found for attribute " + attr_name
+ " in element: " + name);
}
}
else {
getLogger().warn("i18n attribute " + attr_name
+ " not found in element: " + name);
}
}
return temp_attr;
}
return attr;
}
private void endTextElement() throws SAXException {
this.getLogger().debug("End text element, translated_text: " + translated_text);
switch (prev_state) {
case STATE_OUTSIDE: {
// simply translate text (key translation already performed)
super.contentHandler.characters(translated_text.toCharArray(),
0, translated_text.length());
break;
}
case STATE_INSIDE_TRANSLATE: {
substitute_text = translated_text;
break;
}
case STATE_INSIDE_PARAM: {
param_value = translated_text;
break;
}
}
translated_text = null;
current_state = prev_state;
prev_state = STATE_OUTSIDE;
}
private void endParamElement() {
this.getLogger().debug("Substitution param: " + param_value);
indexedParams.add(param_value);
param_value = null;
current_state = STATE_INSIDE_TRANSLATE;
}
private void endTranslateElement() throws SAXException {
if (substitute_text == null) {
return;
}
String result;
if (indexedParams.size() > 0 && substitute_text.length() > 0) {
this.getLogger().debug("Text for susbtitution: " + substitute_text);
result = MessageFormat.format(substitute_text, indexedParams.toArray());
this.getLogger().debug("Result of susbtitution: " + result);
}
else {
result = substitute_text;
}
super.contentHandler.characters(result.toCharArray(), 0, result.length());
indexedParams.clear();
substitute_text = null;
current_state = STATE_OUTSIDE;
}
/**
*Gets translations from xml file to dictionary.
*/
class I18nContentHandler extends DefaultHandler {
boolean in_entry = false;
boolean in_key = false;
boolean in_translation = false;
String key = null;
String translation = null;
public void startElement(String namespace, String name, String raw,
Attributes attr) throws SAXException {
if (name.equals(I18N_ENTRY_ELEMENT)) {
in_entry = true;
} else {
if (in_entry) {
if (name.equals(I18N_KEY_ELEMENT)) {
in_key = true;
} else {
if (name.equals(I18N_TRANSLATION_ELEMENT)
&& attr.getValue(I18N_LANG).equals(lang)) {
in_translation = true;
}
}
}
}
}
public void endElement(String namespace, String name, String raw)
throws SAXException {
if (name.equals(I18N_ENTRY_ELEMENT)) {
if (key != null && translation != null) {
dictionary.put(key, translation);
key = null;
translation = null;
}
in_entry = false;
} else if (name.equals(I18N_KEY_ELEMENT)) {
in_key = false;
} else {
if (name.equals(I18N_TRANSLATION_ELEMENT)) {
in_translation = false;
}
}
}
public void characters(char[] ary, int start, int length)
throws SAXException {
if (in_key) {
key = new String(ary, start, length);
} else {
if (in_translation) {
translation = new String(ary, start, length);
}
}
}
}
/**
*Loads translations from given URL
*/
private void initialiseDictionary(URL url)
throws SAXException, MalformedURLException, IOException {
Object object = url.getContent();
Parser parser = null;
try
{
parser = (Parser)(manager.lookup(Roles.PARSER));
InputSource input;
if (object instanceof Loggable) {
((Loggable)object).setLogger(getLogger());
}
if (object instanceof Reader) {
input = new InputSource(new BufferedReader((Reader)(object)));
} else if (object instanceof InputStream) {
input = new InputSource(new BufferedInputStream((InputStream)(object)));
} else {
throw new SAXException("Unknown object type: " + object);
}
// How this could be cached?
dictionary = new HashMap();
I18nContentHandler i18n_handler = new I18nContentHandler();
parser.setContentHandler(i18n_handler);
parser.parse(input);
} catch(SAXException e) {
getLogger().error("Error in initialiseDictionary", e);
throw e;
} catch(MalformedURLException e) {
getLogger().error("Error in initialiseDictionary", e);
throw e;
} catch(IOException e) {
getLogger().error("Error in initialiseDictionary", e);
throw e;
} catch(ComponentException e) {
getLogger().error("Error in initialiseDictionary", e);
throw new SAXException("ComponentException in initialiseDictionary");
} finally {
if(parser != null) this.manager.release((Component) parser);
}
}
}
----------------------------------------------------------------------
In case of troubles, e-mail: webmaster@xml.apache.org
To unsubscribe, e-mail: cocoon-cvs-unsubscribe@xml.apache.org
For additional commands, e-mail: cocoon-cvs-help@xml.apache.org