dims 01/05/15 05:01:46 Added: src/org/apache/cocoon/transformation I18nTransformer2.java Log: I18nTransformer (with param substitution) Contribution from Konstantin Piroumian ([EMAIL PROTECTED]) 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:[EMAIL PROTECTED]">Konstantin Piroumian</a> * @author <a href="mailto:[EMAIL PROTECTED]">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: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
