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>
   * &lt;map:transformer<br>
   *	name="translate"<br>
   *	src="org.apache.cocoon.transformation.I18nTransformer2"/&gt;<br>
   * </p>
   * <p>
   * &lt;map:match pattern="file"&gt;<br>
   *	&lt;map:generate src="file.xml"/&gt;<br>
   * 	&lt;map:transform type="translate"&gt;<br>
   *		&lt;parameter name="default_lang" value="fi"/&gt;<br>
   *		&lt;parameter name="available_lang_1" value="fi"/&gt;<br>
   *		&lt;parameter name="available_lang_2" value="en"/&gt;<br>
   *		&lt;parameter name="available_lang_3" value="sv"/&gt;<br>
   *		&lt;parameter name="src"<br>
   *			value="translations/file_trans.xml"/&gt;<br>
   *	&lt;/map:transform&gt;<br>
   * </p>
   * <p>
   * When user requests .../file?lang=fi<br>
   * transformer substitutes text surrounded &lt;i18n:text&gt; with
   * translations from file_trans.xml.<br>
   * Attributes listed in &lt;i18n:attr&gt; attribute are also translated
   * </p>
   * <p>
   * file.xml:<br>
   * &lt;root xmlns:i18n="http://apache.org/cocoon/i18n"&gt;<br>
   * 	&lt;elem i18n:attr="title" title="translate_me"&gt;Text&lt;/elem&gt;<br>
   * 	&lt;elem&gt;&lt;i18n:text&gt;Translate me&lt;/i18n:text&gt;&lt;/elem&gt;<br>
   * &lt;/root&gt;
   * </p>
   * <p>
   * file_trans.xml:<br>
   * &lt;translations&gt;<br>
   * 	&lt;entry&gt;&lt;key&gt;Translate me&lt;/key&gt;<br>
   * 		&lt;translation lang="sv"&gt;�vers�tta mej&lt;/translation&gt;<br>
   * 		&lt;translation lang="fi"&gt;K��nn� minut&lt;/translation&gt;<br>
   *	&lt;/entry&gt;<br>
   * &lt;/translations&gt;<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