You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by kp...@apache.org on 2002/11/13 23:09:44 UTC

cvs commit: xml-cocoon2/src/java/org/apache/cocoon/transformation I18nTransformer.java

kpiroumian    2002/11/13 14:09:43

  Modified:    src/java/org/apache/cocoon/i18n I18nUtils.java
                        XMLResourceBundle.java
                        XMLResourceBundleFactory.java
               src/java/org/apache/cocoon cocoon.roles
               src/webapp/WEB-INF cocoon.xconf
               src/java/org/apache/cocoon/transformation
                        I18nTransformer.java
  Added:       src/java/org/apache/cocoon/i18n AbstractBunldeFactory.java
                        Bundle.java BundleFactory.java package.html
               src/java/org/apache/cocoon/transformation/helpers
                        EventRecorder.java MirrorRecorder.java
  Log:
  New implementation of i18n transformer and supporting stuff.
  Thanks to mattam@netcourrier.com (Matthieu Sozeau)
  
  Revision  Changes    Path
  1.6       +2 -8      xml-cocoon2/src/java/org/apache/cocoon/i18n/I18nUtils.java
  
  Index: I18nUtils.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/i18n/I18nUtils.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- I18nUtils.java	10 Jul 2002 15:41:52 -0000	1.5
  +++ I18nUtils.java	13 Nov 2002 22:09:35 -0000	1.6
  @@ -57,23 +57,17 @@
    * A helper class for i18n formatting and parsing routing.
    * Contains static methods only.
    *
  - * @author <a href="mailto:kpiroumian@flagship.ru">Konstantin Piroumian</a>
  + * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
    * @version CVS $Id$
    */
   public class I18nUtils {
   
       // Locale string delimiter
  -    private static final String LOCALE_DELIMITER   = "_-@.";
  +    private static final String LOCALE_DELIMITER   = "_";
   
       /**
        * Parses given locale string to Locale object. If the string is null
        * then the given locale is returned.
  -     * Locale string is concidered to be of the form lang_country_variant,
  -     * where '_' delimiter can be one of the '_', '-', '@' or '.'. Some
  -     * servlet containers does not parse correctly user defined locales 
  -     * (e.g. 'de-at-euro' or 'de-at@EURO'), so this function performs additional parsing to
  -     * handle those cases correctly.
  -     * 
        *
        * @param localeString a string containing locale in
        * <code>language_country_variant</code> format.
  
  
  
  1.11      +118 -223  xml-cocoon2/src/java/org/apache/cocoon/i18n/XMLResourceBundle.java
  
  Index: XMLResourceBundle.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/i18n/XMLResourceBundle.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- XMLResourceBundle.java	8 Aug 2002 07:28:32 -0000	1.10
  +++ XMLResourceBundle.java	13 Nov 2002 22:09:35 -0000	1.11
  @@ -50,36 +50,43 @@
   */
   package org.apache.cocoon.i18n;
   
  -/** JDK classes **/
  -import org.apache.avalon.framework.activity.Disposable;
  -import org.apache.avalon.framework.component.Component;
  -import org.apache.avalon.framework.component.ComponentManager;
  -import org.apache.avalon.framework.component.Composable;
  -import org.apache.avalon.framework.logger.LogEnabled;
  -import org.apache.avalon.excalibur.xml.xpath.XPathProcessor;
  -import org.apache.avalon.framework.logger.Logger;
  +import java.io.IOException;
  +import java.util.Enumeration;
  +import java.util.HashMap;
  +import java.util.Hashtable;
  +import java.util.Locale;
  +import java.util.Map;
  +import java.util.MissingResourceException;
  +import java.util.ResourceBundle;
  +
  +import javax.xml.parsers.DocumentBuilder;
  +import javax.xml.parsers.DocumentBuilderFactory;
  +import javax.xml.parsers.ParserConfigurationException;
  +
   import org.w3c.dom.Document;
   import org.w3c.dom.NamedNodeMap;
   import org.w3c.dom.Node;
   import org.w3c.dom.NodeList;
   import org.xml.sax.SAXException;
   
  -import javax.xml.parsers.DocumentBuilder;
  -import javax.xml.parsers.DocumentBuilderFactory;
  -import javax.xml.parsers.ParserConfigurationException;
  -import java.io.IOException;
  -import java.util.*;
  +import org.apache.avalon.excalibur.xml.xpath.XPathProcessor;
  +import org.apache.avalon.framework.component.ComponentException;
  +import org.apache.avalon.framework.component.ComponentManager;
  +import org.apache.avalon.framework.logger.Logger;
   
   /**
  + * Implementation of <code>Bundle</code> interface for XML resources.
  + * Represents a single XML message bundle.
  + *
    * @author <a href="mailto:mengelhart@earthtrip.com">Mike Engelhart</a>
    * @author <a href="mailto:neeme@one.lv">Neeme Praks</a>
    * @author <a href="mailto:oleg@one.lv">Oleg Podolsky</a>
  - * @version $Id$
  + * @author <a href="mailto:mattam@netcourrier.com">Matthieu Sozeau</a>
  + * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
  + * $Id$
    */
  -public class XMLResourceBundle
  -    extends ResourceBundle
  -    implements LogEnabled, Component, Composable, Disposable
  -{
  +public class XMLResourceBundle extends ResourceBundle implements Bundle {
  +
       /** DOM factory */
       protected static DocumentBuilderFactory docfactory =
           DocumentBuilderFactory.newInstance();
  @@ -111,23 +118,33 @@
       /** XPath Processor */
       private XPathProcessor processor = null;
   
  -    public void compose(ComponentManager manager) {
  +    /**
  +     * @throws ComponentException if XPath processor is not found
  +     */
  +    public void compose(ComponentManager manager) throws ComponentException {
           this.manager = manager;
  -        try {
  -            this.processor = (XPathProcessor)this.manager.lookup(XPathProcessor.ROLE);
  -        } catch (Exception e) {
  -            logger.error("cannot obtain XPathProcessor", e);
  -        }
  +        this.processor = (XPathProcessor)this.manager.lookup(XPathProcessor.ROLE);
       }
   
  -    public void dispose()
  -    {
  -        if (this.processor instanceof Component)
  -            this.manager.release((Component)this.processor);  
  +    /**
  +     * Implements Disposable interface for this class.
  +     */
  +    public void dispose() {
  +        // FIXME (KP): How to release the XPathProcessor?
  +        // this.manager.release(this.processor);
           this.processor = null;
       }
   
       /**
  +     * Implements LogEnabled interface for this class.
  +     *
  +     * @param logger the logger object
  +     */
  +    public void enableLogging(final Logger logger) {
  +        this.logger = logger;
  +    }
  +
  +    /**
        * Initalize the bundle
        *
        * @param name              name of the bundle
  @@ -135,14 +152,17 @@
        * @param locale            locale
        * @param parent            parent bundle of this bundle
        * @param cacheAtStartup    cache all the keys when constructing?
  -     * @exception IOException   if an IO error occurs while reading the file
  -     * @exception ParserConfigurationException if no parser is configured
  -     * @exception SAXException  if an error occurs while parsing the file
  -     */
  -    public void init(String name, String fileName, Locale locale, XMLResourceBundle parent, boolean cacheAtStartup)
  -        throws IOException, ParserConfigurationException, SAXException
  -    {
  -        if (logger.isInfoEnabled()) logger.info("Constructing XMLResourceBundle: " + name + ", locale: " + locale);
  +     * @throws IOException   if an IO error occurs while reading the file
  +     * @throws ParserConfigurationException if no parser is configured
  +     * @throws SAXException  if an error occurs while parsing the file
  +     */
  +    public void init(String name, String fileName, Locale locale,
  +        XMLResourceBundle parent, boolean cacheAtStartup)
  +        throws IOException, ParserConfigurationException, SAXException {
  +
  +        logger.debug("Constructing XMLResourceBundle: "
  +            + name + ", locale: " + locale);
  +
           this.name = name;
           this.doc = loadResourceBundle(fileName);
           this.locale = locale;
  @@ -163,8 +183,8 @@
        * @exception SAXException  if an error occurs while parsing the file
        */
       protected static Document loadResourceBundle(String fileName)
  -        throws IOException, ParserConfigurationException, SAXException
  -    {
  +        throws IOException, ParserConfigurationException, SAXException {
  +
           DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
           DocumentBuilder builder = factory.newDocumentBuilder();
           return builder.parse(fileName);
  @@ -175,8 +195,7 @@
        *
        * @return the name
        */
  -    public String getName()
  -    {
  +    public String getName() {
           return this.name;
       }
   
  @@ -185,8 +204,7 @@
        *
        * @return the DOM tree
        */
  -    public Document getDocument()
  -    {
  +    public Document getDocument() {
           return this.doc;
       }
   
  @@ -195,8 +213,7 @@
        *
        * @return the locale
        */
  -    public Locale getLocale()
  -    {
  +    public Locale getLocale() {
           return locale;
       }
   
  @@ -206,9 +223,9 @@
        * @param   key     the key to the value to be returned
        * @return  true if contains, false otherwise
        */
  -    private boolean cacheContains(String key)
  -    {
  -        if (logger.isDebugEnabled()) logger.debug(name + ": cache contains: " + key);
  +    private boolean cacheContains(String key) {
  +        if (logger.isDebugEnabled())
  +            logger.debug(name + ": cache contains: " + key);
           return cache.containsKey(key);
       }
   
  @@ -218,9 +235,10 @@
        * @param   key     the key to the value to be returned
        * @return  true if contains, false otherwise
        */
  -    private boolean cacheNotFoundContains(String key)
  -    {
  -        if (logger.isDebugEnabled()) logger.debug(name + ": cache_not_found contains: " + key);
  +    private boolean cacheNotFoundContains(String key) {
  +        if (logger.isDebugEnabled())
  +            logger.debug(name + ": cache_not_found contains: " + key);
  +
           return cacheNotFound.containsKey(key);
       }
   
  @@ -230,9 +248,9 @@
        * @param   key     the key
        * @param   value   the value
        */
  -    private void cacheKey(String key, String value)
  -    {
  -        if (logger.isDebugEnabled()) logger.debug(name + ": caching: " + key + " = " + value);
  +    private void cacheKey(String key, Node value) {
  +        if (logger.isDebugEnabled())
  +            logger.debug(name + ": caching: " + key + " = " + value.toString());
           cache.put(key, value);
       }
   
  @@ -241,9 +259,10 @@
        *
        * @param   key     the key
        */
  -    private void cacheNotFoundKey(String key)
  -    {
  -        if (logger.isDebugEnabled()) logger.debug(name + ": caching not_found: " + key);
  +    private void cacheNotFoundKey(String key) {
  +        if (logger.isDebugEnabled())
  +            logger.debug(name + ": caching not_found: " + key);
  +
           cacheNotFound.put(key, "");
       }
   
  @@ -253,10 +272,16 @@
        * @param   key     the key
        * @return          the value
        */
  -    private String getFromCache(String key)
  -    {
  -        if (logger.isDebugEnabled()) logger.debug(name + ": returning from cache: " + key);
  -        return (String) cache.get(key);
  +    private Node getFromCache(String key) {
  +	Object value;
  +	if (logger.isDebugEnabled())
  +            logger.debug(name + ": returning from cache: " + key);
  +
  +	value = cache.get(key);
  +	if(value == null)
  +	    logger.debug("Could not find!");
  +
  +	return (Node) value;
       }
   
       /**
  @@ -266,62 +291,41 @@
        * @param   parent          parent node, must be an element
        * @param   pathToParent    XPath to the parent node
        */
  -    private void cacheAll(Node parent, String pathToParent)
  -    {
  +    private void cacheAll(Node parent, String pathToParent) {
  +	if (logger.isDebugEnabled())
  +            logger.debug("Caching all messages");
  +
           NodeList children = parent.getChildNodes();
           int childnum = children.getLength();
   
  -        for(int i = 0; i < childnum; i++)
  -        {
  +        for(int i = 0; i < childnum; i++) {
               Node child = children.item(i);
   
  -            if(child.getNodeType() == Node.ELEMENT_NODE)
  -            {
  -                StringBuffer pathToChild = new StringBuffer(pathToParent).append('/').append(child.getNodeName());
  +            if(child.getNodeType() == Node.ELEMENT_NODE) {
  +		StringBuffer pathToChild = new StringBuffer(pathToParent)
  +                    .append('/').append(child.getNodeName());
   
                   NamedNodeMap attrs = child.getAttributes();
  -                if(attrs != null)
  -                {
  +
  +                if(attrs != null) {
                       Node temp = null;
                       String pathToAttr = null;
                       int attrnum = attrs.getLength();
  -                    for(int j = 0; j < attrnum; j++)
  -                    {
  +                    for(int j = 0; j < attrnum; j++) {
                           temp = attrs.item(j);
                           if (!temp.getNodeName().equalsIgnoreCase("xml:lang"))
                               pathToChild.append("[@").append(temp.getNodeName())
  -                                    .append("='").append(temp.getNodeValue()).append("']");
  +				.append("='").append(temp.getNodeValue())
  +                                    .append("']");
                       }
                   }
   
  -                String childValue = getTextValue(child);
  -                if(childValue != null)
  -                    cacheKey(pathToChild.toString(), childValue);
  -                else
  -                    cacheAll(child, pathToChild.toString());
  +		cacheKey(pathToChild.toString(), child);
               }
  -        }
  -    }
   
  -    /**
  -     * Get value by key and substitute variables.
  -     *
  -     * @param key           key
  -     * @param dictionary    map with variable values
  -     * @return              value with variable values substituted
  -     * @exception MissingResourceException if resource was not found
  -     */
  -    public String getString(String key, Map dictionary)
  -        throws MissingResourceException
  -    {
  -        String value = _getString(key);
  -        if (value == null)
  -            throw new MissingResourceException(
  -                    "Unable to locate resource: " + key,
  -                    XMLResourceBundle.class.getName(),
  -                    key);
  -        else
  -            return substitute(value, dictionary);
  +	    if (logger.isDebugEnabled())
  +                logger.debug("What we've cached: " + child.toString());
  +        }
       }
   
       /**
  @@ -330,20 +334,17 @@
        * @param   key     the key
        * @return          the value
        */
  -    private String _getString(String key)
  -    {
  +    private Object _getObject(String key) {
           if (key == null) return null;
  -        String value = getFromCache(key);
   
  -        if (value == null && !cacheNotFoundContains(key))
  -        {
  -        if (doc != null)
  -                value = _getString(this.doc.getDocumentElement(), key);
  +	Node value = getFromCache(key);
  +        if (value == null && !cacheNotFoundContains(key)) {
  +	    if (doc != null)
  +                value = (Node)_getObject(this.doc.getDocumentElement(), key);
   
  -            if (value == null)
  -            {
  +            if (value == null) {
                   if (this.parent != null)
  -                    value = this.parent._getString(key);
  +                    value = (Node)this.parent._getObject(key);
               }
   
               if (value != null)
  @@ -351,6 +352,7 @@
               else
                   cacheNotFoundKey(key);
           }
  +
           return value;
       }
   
  @@ -361,48 +363,8 @@
        * @param   key     the key
        * @return          the value
        */
  -    private String _getString(Node node, String key)
  -    {
  -        try {
  -            return getTextValue(_getNode(node, key));
  -        } catch (Exception e) {
  -            logger.error(name + ": error while locating resource: " + key, e);
  -        }
  -        return null;
  -    }
  -
  -    /**
  -     * Get the text value of the node.
  -     *
  -     * @param   node    the node
  -     * @return          the value
  -     */
  -    private static String getTextValue(Node element)
  -    {
  -        if (element == null) return null;
  -        NodeList list = element.getChildNodes();
  -        int listsize = list.getLength();
  -
  -        Node item = null;
  -        String itemValue = null;
  -
  -        for(int i = 0; i < listsize; i++)
  -        {
  -            item = list.item(i);
  -        if(item.getNodeType() != Node.TEXT_NODE)
  -            return null;
  -
  -            itemValue = item.getNodeValue();
  -        if(itemValue == null)
  -            return null;
  -
  -            itemValue = itemValue.trim();
  -        if(itemValue.length() == 0)
  -            return null;
  -
  -            return itemValue;
  -        }
  -        return null;
  +    private Object _getObject(Node node, String key) {
  +        return _getNode(node, key);
       }
   
       /**
  @@ -411,8 +373,7 @@
        * @param   key     the key
        * @return          the node
        */
  -    private Node _getNode(String key)
  -    {
  +    private Node _getNode(String key) {
           return _getNode(this.doc.getDocumentElement(), key);
       }
   
  @@ -424,8 +385,7 @@
        * @param   key         the key
        * @return              the node
        */
  -    private Node _getNode(Node rootNode, String key)
  -    {
  +    private Node _getNode(Node rootNode, String key) {
           try {
               return this.processor.selectSingleNode(rootNode, key);
           } catch (Exception e) {
  @@ -435,70 +395,6 @@
       }
   
       /**
  -     * Substitute the &quot;variables&quot; in the string with the values
  -     * provided in the map.
  -     *
  -     * @param value         value where to search for variables
  -     * @param dictionary    map with variable values
  -     * @return              value with variable values substituted
  -     */
  -    public String substitute(String value, Map dictionary)
  -    {
  -        if (value == null || dictionary == null) return value;
  -
  -        StringBuffer result = new StringBuffer(value.length());
  -        int startPos = 0;
  -        int endPos = 0;
  -        int lastPos = value.length();
  -        Object varValue = "";
  -        String varKey = "", oldKey = "";
  -        while (endPos < lastPos)
  -        {
  -            startPos = endPos;
  -            endPos = value.indexOf('{', startPos);
  -            if (endPos == -1)
  -                endPos = lastPos;
  -            result.append(value.substring(startPos, endPos));
  -            if (endPos < lastPos)
  -                endPos++;
  -            if (endPos < lastPos)
  -            {
  -                startPos = endPos;
  -                endPos = value.indexOf('}', startPos);
  -                if (endPos == -1)
  -                    endPos = lastPos;
  -                oldKey = varKey;
  -                varKey = value.substring(startPos, endPos);
  -                if (!oldKey.equals(varKey))
  -                    varValue = dictionary.get(varKey);
  -                if (varValue != null)
  -                {
  -                    if (logger.isDebugEnabled()) logger.debug("Substituting var: " + varKey + " --> " + varValue);
  -                    result.append(varValue);
  -                }
  -                else
  -                {
  -                    if (logger.isWarnEnabled()) logger.warn(name + ": var not found: " + varKey);
  -                    result.append('{').append(varKey).append('}');
  -                }
  -                if (endPos < lastPos)
  -                    endPos++;
  -            }
  -        }
  -        return result.toString();
  -    }
  -
  -    /**
  -     * Set the logger.
  -     *
  -     * @param logger the logger
  -     */
  -    public void enableLogging( final Logger logger )
  -    {
  -        this.logger = logger;
  -    }
  -
  -    /**
        * Return an Object by key.
        * Implementation of the ResourceBundle abstract method.
        *
  @@ -506,9 +402,9 @@
        * @return the object
        */
       protected Object handleGetObject(String key)
  -        throws MissingResourceException
  -    {
  -        return (Object) getString(key, null);
  +        throws MissingResourceException  {
  +
  +        return _getObject(key);
       }
   
       /**
  @@ -517,8 +413,7 @@
        *
        * @return the enumeration of keys
        */
  -    public Enumeration getKeys()
  -    {
  +    public Enumeration getKeys() {
           return cache.keys();
       }
   }
  
  
  
  1.9       +15 -28    xml-cocoon2/src/java/org/apache/cocoon/i18n/XMLResourceBundleFactory.java
  
  Index: XMLResourceBundleFactory.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/i18n/XMLResourceBundleFactory.java,v
  retrieving revision 1.8
  retrieving revision 1.9
  diff -u -r1.8 -r1.9
  --- XMLResourceBundleFactory.java	19 Jul 2002 13:09:31 -0000	1.8
  +++ XMLResourceBundleFactory.java	13 Nov 2002 22:09:35 -0000	1.9
  @@ -50,28 +50,23 @@
   */
   package org.apache.cocoon.i18n;
   
  +import java.io.FileNotFoundException;
  +import java.util.Collection;
  +import java.util.HashMap;
  +import java.util.Iterator;
  +import java.util.Locale;
  +import java.util.Map;
  +
   import org.apache.avalon.framework.activity.Disposable;
   import org.apache.avalon.framework.component.Component;
   import org.apache.avalon.framework.component.ComponentException;
  -import org.apache.avalon.framework.component.DefaultComponentSelector;
  -import org.apache.avalon.framework.component.Composable;
   import org.apache.avalon.framework.component.ComponentManager;
  -import org.apache.avalon.framework.configuration.Configurable;
  +import org.apache.avalon.framework.component.DefaultComponentSelector;
   import org.apache.avalon.framework.configuration.Configuration;
   import org.apache.avalon.framework.configuration.ConfigurationException;
  -import org.apache.avalon.framework.logger.LogEnabled;
  -import org.apache.avalon.framework.thread.ThreadSafe;
   import org.apache.avalon.framework.logger.Logger;
  -import org.xml.sax.SAXParseException;
   
  -import java.util.Collection;
  -import java.util.HashMap;
  -import java.util.Iterator;
  -import java.util.Locale;
  -import java.util.Map;
  -
  -import java.io.FileNotFoundException;
  -import java.io.IOException;
  +import org.xml.sax.SAXParseException;
   
   /**
    * This is the XMLResourceBundleFactory, the method for getting and
  @@ -80,13 +75,13 @@
    * @author <a href="mailto:mengelhart@earthtrip.com">Mike Engelhart</a>
    * @author <a href="mailto:neeme@one.lv">Neeme Praks</a>
    * @author <a href="mailto:oleg@one.lv">Oleg Podolsky</a>
  + * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
    * @version $Id$
    */
   
  -public class XMLResourceBundleFactory
  -    extends DefaultComponentSelector
  -    implements Configurable, LogEnabled, Composable, Disposable, ThreadSafe
  -{
  +public class XMLResourceBundleFactory extends DefaultComponentSelector
  +    implements BundleFactory {
  +
       /** Should we load bundles to cache on startup or not? */
       protected boolean cacheAtStartup = false;
   
  @@ -102,13 +97,6 @@
       /** Component Manager */
       protected ComponentManager manager = null;
   
  -    /** Constants for configuration keys */
  -    public static class ConfigurationKeys
  -    {
  -        public static final String CACHE_AT_STARTUP = "cache-at-startup";
  -        public static final String ROOT_DIRECTORY = "catalogue-location";
  -    }
  -
       /**
        * Default constructor.
        */
  @@ -137,8 +125,7 @@
        *
        * @param logger the logger
        */
  -    public void enableLogging( final Logger logger )
  -    {
  +    public void enableLogging(final Logger logger) {
           this.logger = logger;
       }
   
  
  
  
  1.1                  xml-cocoon2/src/java/org/apache/cocoon/i18n/AbstractBunldeFactory.java
  
  Index: AbstractBunldeFactory.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.i18n;
  
  import org.apache.avalon.framework.component.ComponentManager;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.avalon.framework.logger.Logger;
  
  import org.apache.excalibur.source.Source;
  import org.apache.excalibur.source.SourceResolver;
  
  /**
   * Bundle factory implementation base class.
   *
   * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
   * @version $Id: AbstractBunldeFactory.java,v 1.1 2002/11/13 22:09:35 kpiroumian Exp $
   */
  public abstract class AbstractBunldeFactory implements BundleFactory {
  
      /** Should we load bundles to cache on startup or not. */
      protected boolean cacheAtStartup = false;
  
      /** Root directory to all bundle names */
      protected String directory;
  
      protected Logger logger = null;
  
      protected ComponentManager manager = null;
  
      public void compose(ComponentManager manager) {
          this.manager = manager;
      }
  
      /**
       * Configure this component.
       *
       * @param configuration the configuration.
       */
      public void configure(Configuration configuration)
          throws ConfigurationException {
  
          this.cacheAtStartup =
              configuration.getChild(
                  ConfigurationKeys.CACHE_AT_STARTUP).getValueAsBoolean(false);
  
          try {
              this.directory = configuration.getChild(
                  ConfigurationKeys.ROOT_DIRECTORY, true).getValue();
          } catch (ConfigurationException e) {
              if (logger.isWarnEnabled()) {
                  logger.warn(
                      "Root directory not provided in configuration, "
                      + "using default (root)"
                  );
              }
              this.directory = "";
          }
  
          if (logger.isDebugEnabled()) {
              logger.debug(
                  "Bundle Factory implementation configured with: cacheAtStartup = "
                  + cacheAtStartup + ", directory = '" + directory + "'"
              );
          }
      }
  
      /**
       * Set the logger to be used with this bundle.
       *
       * @param logger the logger object.
       */
      public void enableLogging(final Logger logger) {
          this.logger = logger;
      }
  }
  
  
  
  1.1                  xml-cocoon2/src/java/org/apache/cocoon/i18n/Bundle.java
  
  Index: Bundle.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.i18n;
  
  import java.util.MissingResourceException;
  
  import org.apache.avalon.framework.component.Component;
  
  /**
   * Resource bundle component interface. 
   * Provide the minimal number of methods to be used for i18n.
   *
   * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
   * @version $Id: Bundle.java,v 1.1 2002/11/13 22:09:35 kpiroumian Exp $
   */
  public interface Bundle extends Component {
  
      String ROLE = "org.apache.cocoon.i18n.Bundle";
  
      /**
       * Get string value by key.
       *
       * @param key 
       * @return Resource as string.
       * @exception MissingResourceException if resource was not found
       */
      String getString(String key) throws MissingResourceException;
  
      /**
       * Get object value by key.
       *
       * @param key The resource key.
       * @return The resource as object.
       * @exception MissingResourceException if resource was not found
       */
      Object getObject(String key) throws MissingResourceException;
  
  }
  
  
  
  1.1                  xml-cocoon2/src/java/org/apache/cocoon/i18n/BundleFactory.java
  
  Index: BundleFactory.java
  ===================================================================
  package org.apache.cocoon.i18n;
  
  import java.util.Locale;
  
  import org.apache.avalon.framework.activity.Disposable;
  import org.apache.avalon.framework.component.Component;
  import org.apache.avalon.framework.component.ComponentException;
  import org.apache.avalon.framework.component.ComponentSelector;
  import org.apache.avalon.framework.component.Composable;
  import org.apache.avalon.framework.configuration.Configurable;
  import org.apache.avalon.framework.logger.LogEnabled;
  import org.apache.avalon.framework.thread.ThreadSafe;
  
  /**
   * Bundle Factory realizations are responsible for loading and providing
   * particular types of resource bundles, implementors of Bundle interface.
   *
   * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
   * @version $Id: BundleFactory.java,v 1.1 2002/11/13 22:09:35 kpiroumian Exp $
   */
  public interface BundleFactory extends ComponentSelector, Composable,
      Configurable, Disposable, LogEnabled, ThreadSafe {
  
      String ROLE = "org.apache.cocoon.i18n.BundleFactory";
  
      /** Constants for configuration keys */
      static class ConfigurationKeys {
          public static final String CACHE_AT_STARTUP = "cache-at-startup";
          public static final String ROOT_DIRECTORY = "catalogue-location";
      }
  
      Component select(String bundleName, Locale locale) throws ComponentException;
  }
  
  
  1.1                  xml-cocoon2/src/java/org/apache/cocoon/i18n/package.html
  
  Index: package.html
  ===================================================================
  <html>
  <head>
   <title>Internationalization</title>
  </head>
  <body>
   <h1>Internationalization support.</h1>
   <p>
   Internationalization bundle resource handling interfaces and default implementation for XML message catalogue. 
   </p>
   <p>
   Other helper classes for i18n and localization.
   </p>
  </body>
  </html>
  
  
  
  1.36      +5 -0      xml-cocoon2/src/java/org/apache/cocoon/cocoon.roles
  
  Index: cocoon.roles
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/cocoon.roles,v
  retrieving revision 1.35
  retrieving revision 1.36
  diff -u -r1.35 -r1.36
  --- cocoon.roles	21 Aug 2002 06:15:30 -0000	1.35
  +++ cocoon.roles	13 Nov 2002 22:09:43 -0000	1.36
  @@ -161,6 +161,11 @@
           shorthand="pipelines"
           default-class="org.apache.cocoon.sitemap.DefaultSitemapComponentSelector"/>
   
  +  <!-- i18n components for resource bundle handling -->
  +  <role name="org.apache.cocoon.i18n.BundleFactory"
  +        shorthand="i18n-bundles"
  +        default-class="org.apache.cocoon.i18n.XMLResourceBundleFactory"/>
  +
     <!-- DEPRECATED, use the avalon excalibur entity-resolver instead -->
     <role name="org.apache.cocoon.components.resolver.Resolver"
           shorthand="resolver"
  
  
  
  1.43      +12 -0     xml-cocoon2/src/webapp/WEB-INF/cocoon.xconf
  
  Index: cocoon.xconf
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/webapp/WEB-INF/cocoon.xconf,v
  retrieving revision 1.42
  retrieving revision 1.43
  diff -u -r1.42 -r1.43
  --- cocoon.xconf	6 Nov 2002 13:22:25 -0000	1.42
  +++ cocoon.xconf	13 Nov 2002 22:09:43 -0000	1.43
  @@ -197,6 +197,18 @@
       <parameter name="servlet-name" value="*.jsp"/>
     </jsp-engine>
   
  +  <!-- i18n Bundle Factory:
  +	BundleFactory loads Bundles with i18n resources for the given locale.
  +	Bundles are loaded from the 'catalogue_location'. Bundle base name is
  +	'catalogue_name' value.
  +	If 'cache-at-startup' is true then BundleFactory preloads bundles.
  +  -->
  +  <i18n-bundles logger="core.i18n-bundles">
  +    <catalogue-name>messages</catalogue-name>
  +    <catalogue-location>i18n/translations</catalogue-location>
  +    <cache-at-startup>true</cache-at-startup>
  +  </i18n-bundles>
  +
     <!-- Xscript:
     -->
     <xscript logger="core.xscript">
  
  
  
  1.1                  xml-cocoon2/src/java/org/apache/cocoon/transformation/helpers/EventRecorder.java
  
  Index: EventRecorder.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.transformation.helpers;
  
  import java.util.ArrayList;
  import java.lang.String;
  
  import org.apache.cocoon.xml.XMLConsumer;
  
  import org.xml.sax.Attributes;
  import org.xml.sax.ContentHandler;
  import org.xml.sax.Locator;
  import org.xml.sax.SAXException;
  import org.xml.sax.ext.LexicalHandler;
  
  /**
   * Can send recorded events and be cloned.
   *
   * @author <a href="mailto:mattam@netcourrier.com">Matthieu Sozeau</a>
   * @version CVS $Id: EventRecorder.java,v 1.1 2002/11/13 22:09:43 kpiroumian Exp $
   */
  
  public interface EventRecorder { 
      public void send(ContentHandler handler) 
  	throws SAXException;
  
      public Object clone();
  }
  
  
  
  1.1                  xml-cocoon2/src/java/org/apache/cocoon/transformation/helpers/MirrorRecorder.java
  
  Index: MirrorRecorder.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.transformation.helpers;
  
  import java.util.ArrayList;
  import java.util.Map;
  import java.lang.String;
  
  import org.apache.cocoon.xml.XMLConsumer;
  
  import org.xml.sax.Attributes;
  import org.xml.sax.helpers.AttributesImpl;
  import org.xml.sax.ContentHandler;
  import org.xml.sax.Locator;
  import org.xml.sax.SAXException;
  import org.xml.sax.ext.LexicalHandler;
  
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  import org.w3c.dom.NamedNodeMap;
  
  import org.apache.xerces.dom.AttributeMap;
  
  import org.apache.cocoon.transformation.helpers.TestShower;
  import org.apache.cocoon.transformation.helpers.EventRecorder;
  
  /**
   * Consume elements start/end and characters events and reproduce them.
   *
   * @author <a href="mailto:mattam@netcourrier.com">Matthieu Sozeau</a>
   * @version CVS $Id: MirrorRecorder.java,v 1.1 2002/11/13 22:09:43 kpiroumian Exp $
   */
  
  public class MirrorRecorder
      extends NOPRecorder
      implements EventRecorder, Cloneable {
  
      private ArrayList events;
  
      // Used for indexing (parameters)
      class NullEvent
  	implements EventRecorder
      {
  	private String s;
  	public NullEvent(String s)
  	    {
  		this.s = s;
  	    }
  
  	public String name() {
  	    return s;
  	}
  
  	public void send(ContentHandler handler)
  	    throws SAXException
  	    { }
  
  
  	public Object clone() {
  	    return new NullEvent(s);
  	}
  
  	public String toString() {
  	    return("{" + s + "}");
  	}
      }
  
      class StartEvent
  	implements EventRecorder
      {
  	protected String uri, name, raw;
  	protected Attributes attr;
  
  	public StartEvent(String namespace, String name, String raw,
  			  Attributes attr)
  	    {
  		this.uri = namespace;
  		this.name = name;
  		this.raw = raw;
  		this.attr = attr;
  	    }
  
  	public void send(ContentHandler handler)
  	    throws SAXException
  	    {
  		handler.startElement(uri, name, raw, attr);
  	    }
  
  	public Object clone() {
  	    return new StartEvent(uri, name, raw, new AttributesImpl(attr));
  	}
  
  	public String toString() {
  	    StringBuffer str = new StringBuffer("<" + raw);
  	    if(attr != null) {
  		for(int i = 0; i < attr.getLength(); ++i)
  		    str.append(" " + attr.getQName(i) + "=\"" + attr.getValue(i) + "\"");
  	    }
  
  	    return str.append(">").toString();
  	}
      }
  
      class EndEvent
  	implements EventRecorder
      {
  	protected String uri, name, raw;
  
  	public EndEvent(String namespace, String name, String raw) {
  	    this.uri = namespace;
  	    this.name = name;
  	    this.raw = raw;
  	}
  
  	public Object clone() {
  	    return new EndEvent(uri, name, raw);
  	}
  
  	public void send(ContentHandler handler)
  	    throws SAXException
  	    {
  		handler.endElement(uri, name, raw);
  	    }
  	public String toString() {
  	    return("</" + raw + ">");
  
  	}
      }
  
      class CharacterEvent
  	implements EventRecorder
      {
  	private String ch;
  
  	public CharacterEvent(char ary[], int start, int length) {
  	    ch = new String(ary, start, length);
  	}
  
  	public Object clone() {
  	    return new CharacterEvent(ch.toCharArray(), 0, ch.length());
  	}
  
  	public CharacterEvent(String str)
  	    {
  		ch = str;
  	    }
  
  	public void send(ContentHandler handler)
  	    throws SAXException
  	    {
  		handler.characters(ch.toCharArray(), 0, ch.length());
  	    }
  
  	public String toString() {
  	    return ch;
  	}
      }
  
      public MirrorRecorder() {
  	this.events = new ArrayList();
      }
  
      public MirrorRecorder(Node n) {
  	this.events = new ArrayList();
  
  	if(n != null)
  	{
  	    NodeList childs = n.getChildNodes();
  	    for(int i = 0; i < childs.getLength(); ++i)
  	    {
  		try {
  		    nodeToEvents(childs.item(i));
  		} catch(SAXException e) {
  		    //FIXME: what to do?
  		}
  	    }
  	}
      }
  
      private void nodeToEvents(Node n)
  	throws SAXException {
  	Attributes attrs;
  
  	switch(n.getNodeType()) {
  	case Node.ELEMENT_NODE:
  	    if(n.getAttributes() instanceof AttributeMap)
  	    {
  		NamedNodeMap map = n.getAttributes();
  		Node node;
  		attrs = new AttributesImpl();
  
  		for(int i = 0; i < map.getLength(); ++i)
  		{
  		    node = map.item(i);
  		    ((AttributesImpl) attrs).addAttribute(node.getNamespaceURI(), node.getLocalName(), node.getNodeName(), "CDATA", node.getNodeValue());
  		}
  	    }
  	    else
  		attrs = (Attributes) n.getAttributes();
  
  	    startElement(n.getNamespaceURI(), n.getNodeName(), n.getNodeName(), attrs);
  	    if(n.hasChildNodes()) {
  		NodeList childs = n.getChildNodes();
  		for(int i = 0; i < childs.getLength(); ++i)
  		{
  		    nodeToEvents(childs.item(i));
  		}
  	    }
  	    endElement(n.getNamespaceURI(), n.getNodeName(), n.getNodeName());
  	    break;
  	case Node.TEXT_NODE:
  	    characters(n.getNodeValue());
  	    break;
  	}
      }
  
      public MirrorRecorder(MirrorRecorder n) {
  	this.events = new ArrayList();
  	for(int i = 0; i < n.events.size(); ++i)
  	{
  	    EventRecorder e = (EventRecorder) n.events.get(i);
  	    this.events.add(e.clone());
  	}
      }
  
      public Object clone() {
  	return new MirrorRecorder(this);
      }
  
      public void startElement(String namespace, String name, String raw,
                           Attributes attr)
      throws SAXException {
  	events.add(new StartEvent(namespace, name, raw, attr));
      }
  
      public void endElement(String namespace, String name, String raw)
      throws SAXException {
  	events.add(new EndEvent(namespace, name, raw));
      }
  
      public void characters(char ary[], int start, int length)
      throws SAXException {
  	characters(new String(ary, start, length));
      }
  
      public void characters(String tmp)
  	throws SAXException {
  	int i = 0, j = 0, index = 0;
  
  	while(tmp.length() > 0) {
  
  	    i = tmp.indexOf('{', i);
  	    if(i == -1)
  	    {
  		events.add(new CharacterEvent(tmp));
  		return;
  	    }
  	    else
  	    {
  		if(i >= 0) {
  		    events.add(new CharacterEvent(tmp.substring(0, i)));
  		}
  
  		j = tmp.indexOf('}', i);
  		if(j != -1) {
  		    events.add(new NullEvent(tmp.substring(i + 1, j)));
  		    tmp = tmp.substring(j + 1, tmp.length());
  		    i = 0;
  		}
  	    }
  	}
      }
  
      public void send(ContentHandler handler)
  	throws SAXException
  	{
  	    for(int i = 0; i < events.size(); ++i)
  	    {
  		((EventRecorder) (events.get(i))).send(handler);
  	    }
  	}
  
      public void send(ContentHandler handler, Map params)
  	throws SAXException
  	{
  	    int id;
  	    EventRecorder param;
  
  	    for(int i = 0; i < events.size(); ++i)
  	    {
  		if(events.get(i) instanceof NullEvent) {
  		    param = (EventRecorder) params.get(((NullEvent) events.get(i)).name());
  		    if(param != null)
  			param.send(handler);
  		}
  		else ((EventRecorder) (events.get(i))).send(handler);
  	    }
  	}
  
      public String text() {
  	StringBuffer s = new StringBuffer();
  
  	for(int i = 0; i < events.size(); ++i)
  	{
  	    s.append(events.get(i).toString());
  	}
  	return(s.toString());
      }
  
      public String toString()
  	{
  	    StringBuffer s = new StringBuffer("MirrorRecorder: ");
  	    s.append(String.valueOf(events.size()) + " event(s)");
  	    s.append("\ntext: ");
  	    for(int i = 0; i < events.size(); ++i)
  	    {
  		if(events.get(i) instanceof CharacterEvent) {
  		    s.append(events.get(i).toString());
  		}
  	    }
  
  	    return s.toString();
  	}
  
      public void recycle()
  	{
  	    events.clear();
  	}
  
      public boolean empty()
  	{
  	    return (events.size() == 0);
  	}
  
  }
  
  
  
  1.24      +643 -282  xml-cocoon2/src/java/org/apache/cocoon/transformation/I18nTransformer.java
  
  Index: I18nTransformer.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/transformation/I18nTransformer.java,v
  retrieving revision 1.23
  retrieving revision 1.24
  diff -u -r1.23 -r1.24
  --- I18nTransformer.java	11 Sep 2002 15:23:40 -0000	1.23
  +++ I18nTransformer.java	13 Nov 2002 22:09:43 -0000	1.24
  @@ -57,9 +57,8 @@
   import java.text.NumberFormat;
   import java.text.ParseException;
   import java.text.SimpleDateFormat;
  -import java.util.ArrayList;
  -import java.util.Date;
   import java.util.Collections;
  +import java.util.Date;
   import java.util.HashMap;
   import java.util.HashSet;
   import java.util.Locale;
  @@ -68,11 +67,14 @@
   import java.util.Set;
   import java.util.StringTokenizer;
   
  +import org.w3c.dom.Node;
  +import org.w3c.dom.NodeList;
   import org.xml.sax.Attributes;
   import org.xml.sax.SAXException;
   import org.xml.sax.helpers.AttributesImpl;
   
   import org.apache.avalon.framework.activity.Disposable;
  +import org.apache.avalon.framework.component.ComponentException;
   import org.apache.avalon.framework.component.ComponentManager;
   import org.apache.avalon.framework.component.Composable;
   import org.apache.avalon.framework.configuration.Configurable;
  @@ -80,14 +82,17 @@
   import org.apache.avalon.framework.configuration.ConfigurationException;
   import org.apache.avalon.framework.configuration.DefaultConfiguration;
   import org.apache.avalon.framework.parameters.Parameters;
  +import org.apache.excalibur.source.Source;
  +import org.apache.excalibur.source.SourceValidity;
   
  +import org.apache.cocoon.caching.CacheableProcessingComponent;
   import org.apache.cocoon.ProcessingException;
   import org.apache.cocoon.ResourceNotFoundException;
  -import org.apache.excalibur.source.Source;
   import org.apache.cocoon.environment.SourceResolver;
  +import org.apache.cocoon.i18n.Bundle;
  +import org.apache.cocoon.i18n.BundleFactory;
   import org.apache.cocoon.i18n.I18nUtils;
  -import org.apache.cocoon.i18n.XMLResourceBundle;
  -import org.apache.cocoon.i18n.XMLResourceBundleFactory;
  +import org.apache.cocoon.transformation.helpers.MirrorRecorder;
   
   /**
    * Internationalization transformer is used to transform i18n markup into text
  @@ -211,30 +216,32 @@
    *
    * <p>Future work coming:
    * <ul>
  - *  <li>Many clean ups ...done on 23-Jan-2002 ;)
  - *  <li>Introduce empty translation (XMLResourceBundle)
  - *  <li>Remove 'sub-type' attribute (add possible values to 'type')
  - *  <li>Introduce new <get-locale /> element
  - *  <li>Introduce new 'currency-no-unit' and 'int-currency-no-unit' types
  - *  <li>Fix resources disposal
  - *  <li>A little clean ups... ;)
  + *  <li>Improved dictionary handling (allow 'bundle' attribute on elements)
  + *  <li>Introduce new &lt;get-locale /&gt; element
  + *  <li>Move all formatting routines to I18nUrils
    * </ul>
    *
  - * @author <a href="mailto:kpiroumian@flagship.ru">Konstantin Piroumian</a>
  + * @author <a href="mailto:kpiroumian@apache.org">Konstantin Piroumian</a>
  + * @author <a href="mailto:mattam@netcourrier.com">Matthieu Sozeau</a>
    * @author <a href="mailto:Marcus.Crafter@managesoft.com">Marcus Crafter</a>
    * @author <a href="mailto:Michael.Enke@wincor-nixdorf.com">Michael Enke</a>
  - * @author <a href="mailto:lassi.immonen@valkeus.com">Lassi Immonen</a>
  - * @version CVS $Id$
  - *
  - * @todo Move all formatting/parsing routines to I18nUtils
  + * $Id$
    */
   public class I18nTransformer extends AbstractTransformer
  -    implements Composable, Configurable, Disposable {
  +    implements CacheableProcessingComponent,
  +               Composable, Configurable, Disposable {
   
       /**
  -     * The namespace for i18n is "http://apache.org/cocoon/i18n/2.0".
  +     * The namespace for i18n is "http://apache.org/cocoon/i18n/2.1".
  +
        */
       public static final String I18N_NAMESPACE_URI =
  +        "http://apache.org/cocoon/i18n/2.1";
  +
  +    /**
  +     * The old namespace for i18n is "http://apache.org/cocoon/i18n/2.0".
  +     */
  +    public static final String I18N_OLD_NAMESPACE_URI =
           "http://apache.org/cocoon/i18n/2.0";
   
       //
  @@ -242,9 +249,10 @@
       //
   
       /**
  -     * i18n:text element is used to translate simple text, e.g.:<br/>
  +     * i18n:text element is used to translate any text, with or without markup,
  +     * e.g.:<br/>
        * <pre>
  -     *  &lt;i18n:text&gt;This is a multilanguage string&lt;/i18n:text&gt;
  +     *  &lt;i18n:text&gt;This is a &lt;strong&gt;multilanguage&lt;/strong&gt; string&lt;/i18n:text&gt;
        * </pre>
        */
       public static final String I18N_TEXT_ELEMENT            = "text";
  @@ -254,10 +262,14 @@
        * substitution, e.g.:<br/>
        * <pre>
        * &lt;i18n:translate&gt;
  -     *     This is a multilanguage string with {0} param
  +     *     &lt;i:text&gt;This is a multilanguage string with {0} param&lt;/i:text&gt;
        *     &lt;i18n:param&gt;1&lt;/i18n:param&gt;
        * &lt;/i18n:translate&gt;
        * </pre>
  +     * The &lt;text&gt; fragment can include markup and parameters at any place.
  +     * Also do parameters, which can also include i18n:text, i18n:date, etc.
  +     * elements (whithout keys only).
  +     * <p>
        *
        * @see #I18N_TEXT_ELEMENT
        * @see #I18N_PARAM_ELEMENT
  @@ -265,6 +277,84 @@
       public static final String I18N_TRANSLATE_ELEMENT       = "translate";
   
       /**
  +     * <strong>i18n:choose<strong> element is used to translate elements in-place.
  +     * The first <strong>i18n:when<strong> element matching the current locale
  +     * is selected and the others are discarded.
  +     *
  +     * <p>To specify what to do if no locale matched, simply add a node with
  +     * <code>locale="*"</code>.
  +     * <em>Note that this element must be the last child of &lt;i18n:choose&gt;.</em>
  +     * <pre>
  +     * &lt;i18n:choose&gt;
  +     *   &lt;i18n:when locale="en"&gt;
  +     *     Good Morning
  +     *   &lt;/en&gt;
  +     *   &lt;i18n:when locale="fr"&gt;
  +     *     Bonjour
  +     *   &lt;/jp&gt;
  +     *   &lt;i18n:when locale="jp"&gt;
  +     *     Aligato?
  +     *   &lt;/jp&gt;
  +     *   &lt;i18n:otherwise&gt;
  +     *     Sorry, i don't know how to say hello in your language
  +     *   &lt;/jp&gt;
  +     * &lt;i18n:translate&gt;
  +     * </pre>
  +     * <p>
  +     * You can include any markup in i18n:when nodes, minus i18n:*.
  +     * </p>
  +     *
  +     * @see #I18N_IF_ELEMENT
  +     * @see #I18N_LOCALE_ATTRIBUTE
  +     * @see #I18N_LOCALE_ATTRIBUTE_ANY
  +     * @since 2.1
  +     */
  +    public static final String I18N_CHOOSE_ELEMENT          = "choose";
  +
  +    /**
  +     * i18n:when is used to test a locale.
  +     * It can be used within &lt;i18:choose&gt; elements or alone.
  +     * <em>Note: Using <code>locale="*"</code> here has no sense.</em>
  +     * e.g.:
  +     * <pre>
  +     * &lt;greeting&gt;
  +     *   &lt;i18n:when locale="en"&gt;Hello&lt;/i18n:when&gt;
  +     *   &lt;i18n:when locale="fr"&gt;Bonjour&lt;/i18n:when&gt;
  +     * &lt;/greeting&gt;
  +     * </pre>
  +     * @see #I18N_LOCALE_ATTRIBUTE
  +     * @see #I18N_CHOOSE_ELEMENT
  +     * @since 2.1
  +     */
  +    public static final String I18N_WHEN_ELEMENT            = "when";
  +
  +    /**
  +     * i18n:if is used to test a locale.
  +     * e.g.:
  +     * <pre>
  +     * &lt;greeting&gt;
  +     *   &lt;i18n:if locale="en"&gt;Hello&lt;/i18n:when&gt;
  +     *   &lt;i18n:if locale="fr"&gt;Bonjour&lt;/i18n:when&gt;
  +     * &lt;/greeting&gt;
  +     * </pre>
  +     * @see #I18N_LOCALE_ATTRIBUTE
  +     * @see #I18N_CHOOSE_ELEMENT
  +     * @see #I18N_WHEN_ELEMENT
  +     * @since 2.1
  +     */
  +    public static final String I18N_IF_ELEMENT            = "if";
  +
  +    /**
  +     * i18n:otherwise is used to match any locale when no matching
  +     * locale has been found inside an i18n:choose block.
  +     *
  +     * @see #I18N_CHOOSE_ELEMENT
  +     * @see #I18N_WHEN_ELEMENT
  +     * @since 2.1
  +     */
  +    public static final String I18N_OTHERWISE_ELEMENT       = "otherwise";
  +
  +    /**
        * i18n:param is used with i18n:translate to provide substitution params.
        * The param can have i18n:text as its value to provide multilungual value.
        * Parameters can have additional attributes to be used for formatting:
  @@ -302,6 +392,13 @@
       public static final String I18N_PARAM_ELEMENT           = "param";
   
       /**
  +     * This attribute affects a name to the param that could be used
  +     * for substitution.
  +     * @since 2.1
  +     */
  +    public static final String I18N_PARAM_NAME_ATTRIBUTE    = "name";
  +
  +    /**
        * i18n:date is used to provide a localized date string. Allowed attributes
        * are: <code>pattern, src-pattern, locale, src-locale</code>
        * Usage examples:
  @@ -470,12 +567,14 @@
       /**
        * This attribute is used with date and number formatting elements to
        * indicate the locale that should be used to format the element value.
  +     * Also used for in-place translations.
        *
        * @see #I18N_PARAM_ELEMENT
        * @see #I18N_DATE_TIME_ELEMENT
        * @see #I18N_DATE_ELEMENT
        * @see #I18N_TIME_ELEMENT
        * @see #I18N_NUMBER_ELEMENT
  +     * @see #I18N_WHEN_ELEMENT
        */
       public static final String I18N_LOCALE_ATTRIBUTE        = "locale";
   
  @@ -491,6 +590,7 @@
        */
       public static final String I18N_SRC_LOCALE_ATTRIBUTE    = "src-locale";
   
  +
       /**
        * This attribute is used with date and number formatting elements to
        * indicate the value that should be parsed and formatted. If value
  @@ -509,6 +609,10 @@
        * indicate the parameter type: <code>date, time, date-time</code> or
        * <code>number, currency, percent, int-currency, currency-no-unit,
        * int-currency-no-unit</code>.
  +     * Also used with <code>i18:translate</code> to indicate inplace
  +     * translations: <code>inplace</code>
  +     * @deprecated since 2.1. Use nested tags instead, e.g.: 
  +     * &lt;i18n:param&gt;&lt;i18n:date/&gt;&lt;/i18n:param&gt;
        */
       public static final String I18N_TYPE_ATTRIBUTE          = "type";
   
  @@ -554,10 +658,6 @@
        */
       public static final String I18N_CATALOGUE_PREFIX    = "/catalogue/message";
   
  -    // Used to check that the catalogue location is a directory.
  -    // FIXME (KP): Why should it be a file? It can be any resource!
  -    private static final String FILE                        = "file:";
  -
       /**
        * <code>fraction-digits</code> attribute is used with
        * <code>i18:number</code> to
  @@ -565,18 +665,28 @@
        */
       public static final String I18N_FRACTION_DIGITS_ATTRIBUTE = "fraction-digits";
   
  +    // NOP Validity object: i18n transformer is cached forever
  +    // FIXME (KP): Cache validity should be generated by
  +    // Bundle implementations.
  +    private static final SourceValidity I18N_NOP_VALIDITY =
  +        new org.apache.excalibur.source.impl.validity.NOPValidity();
  +
       // States of the transformer
  -    private static final int STATE_OUTSIDE                  = 0;
  -    private static final int STATE_INSIDE_TEXT              = 1;
  -    private static final int STATE_INSIDE_PARAM             = 2;
  -    private static final int STATE_INSIDE_TRANSLATE         = 3;
  -    private static final int STATE_INSIDE_TRANSLATE_TEXT    = 4;
  -    private static final int STATE_TRANSLATE_KEY            = 5;
  -    private static final int STATE_TRANSLATE_TEXT_KEY       = 6;
  -    private static final int STATE_INSIDE_DATE              = 7;
  -    private static final int STATE_INSIDE_DATE_TIME         = 8;
  -    private static final int STATE_INSIDE_TIME              = 9;
  -    private static final int STATE_INSIDE_NUMBER            = 10;
  +    private static final int STATE_OUTSIDE                       = 0;
  +    private static final int STATE_INSIDE_TEXT                   = 10;
  +    private static final int STATE_INSIDE_PARAM                  = 20;
  +    private static final int STATE_INSIDE_PARAM_NODE             = 21;
  +    private static final int STATE_INSIDE_TRANSLATE              = 30;
  +    private static final int STATE_INSIDE_TRANSLATE_TEXT         = 31;
  +    private static final int STATE_TRANSLATE_KEY                 = 40;
  +    private static final int STATE_TRANSLATE_TEXT_KEY            = 41;
  +    private static final int STATE_INSIDE_CHOOSE                 = 50;
  +    private static final int STATE_INSIDE_WHEN                   = 51;
  +    private static final int STATE_INSIDE_OTHERWISE              = 52;
  +    private static final int STATE_INSIDE_DATE                   = 60;
  +    private static final int STATE_INSIDE_DATE_TIME              = 61;
  +    private static final int STATE_INSIDE_TIME                   = 62;
  +    private static final int STATE_INSIDE_NUMBER                 = 63;
   
       // All date-time related parameter types and element names
       private static final Set dateTypes;
  @@ -632,18 +742,34 @@
       // used as default value.
       private String current_key;
   
  -    // Translated text inside the i18n:text element.
  -    private String translated_text;
  +    // A flag for copying the node when doing in-place translation
  +    private boolean translate_copy;
   
  -    // Translated text, ready for param substitution.
  -    private String substitute_text;
  +    // A flag for copying the _GOOD_ node and not others
  +    // when doing in-place translation whitin i18n:choose
  +    private boolean translate_end;
  +
  +    // Translated text
  +    private MirrorRecorder tr_text_recorder;
  +
  +    // Current "i:text" events
  +    private MirrorRecorder text_recorder;
  +
  +    // Current parameter events
  +    private MirrorRecorder param_recorder;
  +
  +    // Param count when not using i:param name="..."
  +    private int param_count;
  +
  +    // Param name attribute for substitution.
  +    private String param_name;
  +
  +    // i18n:param's hashmap for substitution
  +    private HashMap indexedParams;
   
       // Current parameter value (translated or not)
       private String param_value;
   
  -    // i18n:params are stored for index substitutions.
  -    private ArrayList indexedParams;
  -
       // Message formatter for param substitution.
       private MessageFormat formatter;
   
  @@ -654,13 +780,10 @@
       private HashMap formattingParams;
   
       // Dictionary data
  -    private XMLResourceBundle dictionary;
  +    private Bundle dictionary;
   
  -    // FIXME: Shouldn't factory be a global component?
  -    // Now every I18nTransformer have own instance of factory
  -    // every of which in turn have own file cache.
  -    // FIXME (KP): Can we use static factory variable?
  -    private XMLResourceBundleFactory factory = new XMLResourceBundleFactory();
  +    // Dictionary loader factory
  +    private BundleFactory factory;
   
       //
       // i18n configuration variables
  @@ -690,19 +813,61 @@
       }
   
       /**
  +     * Implemenation of CachableProcessingComponents.
  +     * Generates unique key for the current locale.
  +     */
  +    public java.io.Serializable generateKey() {
  +        return catalogueLocation + '/' + catalogueName + '?' + this.locale;
  +    }
  +
  +    /**
  +     * Implementation of CachableProcessingComponent.
  +     * Generates validity object for this transformer or <code>null</code>
  +     * if this instance is not cachable.
  +     */
  +    public SourceValidity generateValidity() {
  +        return I18N_NOP_VALIDITY;
  +    }
  +
  +    /**
  +     * Implementation of composable interface.
  +     * Looksup the Bundle Factory to be used.
  +     */
  +    public void compose(ComponentManager manager) {
  +        this.manager = manager;
  +        try {
  +            this.factory =
  +                (BundleFactory)manager.lookup(BundleFactory.ROLE);
  +
  +            // Activate bundle factory logging
  +            factory.compose(manager);
  +            factory.enableLogging(getLogger());
  +            debug("BundleFactory is obtained");
  +        } catch (ComponentException ce) {
  +            this.getLogger().error("BundleFactory is not loaded", ce);
  +        }
  +    }
  +
  +    /**
  +     * Implemenation of configurable interface.
        * Configure this transformer.
        */
       public void configure(Configuration conf) throws ConfigurationException {
  +        if (factory == null) {
  +            throw new ConfigurationException("BundleFactory component is not found.");
  +        }
           if (conf != null) {
               // read in the config options from the transformer definition
   
               // obtain the base name of the message catalogue
               Configuration child = conf.getChild(I18N_CATALOGUE_NAME);
               catalogueName = child.getValue(null);
  +            debug("Default catalogue name is " + catalogueName);
   
               // obtain the directory location of message catalogues
               child = conf.getChild(I18N_CATALOGUE_LOCATION);
               catalogueLocation = child.getValue(null);
  +            debug("Default catalogue location is " + catalogueLocation);
   
               // check our mandatory parameters
               if (catalogueName == null || catalogueLocation == null)
  @@ -714,29 +879,19 @@
               // obtain default text to use for untranslated messages
               child = conf.getChild(I18N_UNTRANSLATED);
               untranslated = child.getValue(null);
  +            debug("Default untranslated text is '" + untranslated + "'");
   
               // obtain config option, whether to cache messages at startup time
               child = conf.getChild(I18N_CACHE_STARTUP);
               cacheAtStartup = child.getValueAsBoolean(false);
  -
  -            if (this.getLogger().isDebugEnabled()) {
  -                debug("Default catalogue name is " + catalogueName);
  -                debug("Default catalogue location is " + catalogueLocation);
  -                debug("Default untranslated text is '" + untranslated + "'");
  -                debug((cacheAtStartup ? "will" : "won't") +
  -                       " cache messages during startup, by default"
  -                );
  -            }
  -            
  -            // activate resource bundle logging
  -            factory.enableLogging(getLogger());
  +            debug((cacheAtStartup ? "will" : "won't") +
  +                   " cache messages during startup, by default"
  +            );
           }
       }
   
       /**
  -     *  Uses <code>org.apache.cocoon.acting.LocaleAction.getLocale()</code>
  -     *  to get language user has selected.
  -     *  FIXME (KP): Why should I18nTransformer depend on an action?
  +     * Setup current instance of transformer.
        */
       public void setup(SourceResolver resolver, Map objectModel, String source,
                         Parameters parameters)
  @@ -747,7 +902,7 @@
               String localCatLocation = null;
               String localCatName = null;
               String localUntranslated = null;
  -        String lc = null;
  +            String lc = null;
   
               if (parameters != null) {
                   localCatLocation =
  @@ -756,12 +911,11 @@
                       parameters.getParameter(I18N_CATALOGUE_NAME, null);
                   localUntranslated =
                       parameters.getParameter(I18N_UNTRANSLATED, null);
  -        lc = parameters.getParameter(I18N_LOCALE, null);
  +                lc = parameters.getParameter(I18N_LOCALE, null);
               }
   
               // if untranslated-text has been overridden, save the original
               // value so it can be restored when this object is recycled.
  -            // FIXME (KP): What about other overriden params?
               if (localUntranslated != null) {
                   globalUntranslated = untranslated;
                   untranslated = localUntranslated;
  @@ -773,17 +927,14 @@
   
               // Get current locale
               Locale locale = I18nUtils.parseLocale(lc);
  +            debug("using locale " + locale.toString());
   
               // setup everything for the current locale
  -            dictionary = (XMLResourceBundle) factory.select(
  +            dictionary = (Bundle)factory.select(
                   localCatName == null ? catalogueName : localCatName,
                   locale
               );
  -            if (this.getLogger().isDebugEnabled()) {
  -                debug("using locale " + locale.toString());
  -                debug( "selected dictionary " + dictionary );
  -            }
  -            
  +            debug( "selected dictionary " + dictionary );
   
               // Initialize instance state variables
   
  @@ -791,10 +942,14 @@
               this.current_state      = STATE_OUTSIDE;
               this.prev_state         = STATE_OUTSIDE;
               this.current_key        = null;
  -            this.translated_text    = null;
  -            this.substitute_text    = null;
  +	    this.translate_copy     = false;
  +	    this.tr_text_recorder   = null;
  +            this.text_recorder      = new MirrorRecorder();
  +	    this.param_count        = 0;
  +	    this.param_name         = null;
               this.param_value        = null;
  -            this.indexedParams      = new ArrayList();
  +            this.param_recorder     = null;
  +	    this.indexedParams      = new HashMap(3);
               this.formattingParams   = null;
               this.strBuffer          = null;
   
  @@ -803,6 +958,7 @@
               this.formatter.setLocale(locale);
   
           } catch (Exception e) {
  +            debug("exception generated, leaving unconfigured");
               throw new ProcessingException(e.getMessage(), e);
           }
       }
  @@ -810,7 +966,7 @@
       /**
        * Internal setup of XML resource bundle and factory.
        *
  -     * REVISIT: when we can get the resolver anywhere, we can pass the
  +     * REVISIT (MC): when we can get the resolver anywhere, we can pass the
        * configuration object directly to XMLResourceBundle.
        */
       private void _setup(SourceResolver resolver, String location)
  @@ -821,7 +977,7 @@
               new DefaultConfiguration("name", "location");
           DefaultConfiguration cacheConf =
               new DefaultConfiguration(
  -                XMLResourceBundleFactory.ConfigurationKeys.CACHE_AT_STARTUP,
  +                BundleFactory.ConfigurationKeys.CACHE_AT_STARTUP,
                   "location"
               );
           cacheConf.setValue(new Boolean(cacheAtStartup).toString());
  @@ -830,25 +986,16 @@
           // set the root location for message catalogues
           DefaultConfiguration dirConf =
               new DefaultConfiguration(
  -                XMLResourceBundleFactory.ConfigurationKeys.ROOT_DIRECTORY,
  +                BundleFactory.ConfigurationKeys.ROOT_DIRECTORY,
                   "location"
               );
   
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug("catalog location:" + location);
  -        }
  +        debug("catalog location:" + location);
           Source source = null;
           try {
               source = resolver.resolveURI(location);
               String systemId = source.getSystemId();
  -            if (!systemId.startsWith(FILE)) {
  -                throw new ResourceNotFoundException(
  -                    systemId + " does not denote a directory"
  -                );
  -            }
  -            if (this.getLogger().isDebugEnabled()) {
  -                debug("catalog directory:" + systemId);
  -            }
  +            debug("catalog directory:" + systemId);
               dirConf.setValue(systemId);
               configuration.addChild(dirConf);
           } finally {
  @@ -857,14 +1004,7 @@
   
           // Pass created configuration object to the factory
           factory.configure(configuration);
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug("configured");
  -        }
  -    }
  -
  -    public void compose(ComponentManager manager) {
  -        this.manager = manager;
  -        factory.compose(manager);
  +        debug("configured");
       }
   
       //
  @@ -874,51 +1014,81 @@
       public void startElement(String uri, String name, String raw,
                                Attributes attr) throws SAXException {
   
  -        if (I18N_NAMESPACE_URI.equals(uri)) {
  -            if (this.getLogger().isDebugEnabled()) {
  -                debug("Starting i18n element: " + name);
  -            }
  -            if (strBuffer != null) {
  -                i18nCharacters(strBuffer.toString());
  -                strBuffer = null;
  -            }
  -            startI18NElement(name, attr);
  -            return;
  +        // Handle previously buffered characters
  +        if (current_state != STATE_OUTSIDE && strBuffer != null) {
  +            i18nCharacters(strBuffer.toString());
  +            strBuffer = null;
           }
   
  -        super.startElement(uri, name, raw, translateAttributes(name, attr));
  -    }
  +	if (I18N_OLD_NAMESPACE_URI.equals(uri)) {
  +            this.getLogger().warn("The namespace " 
  +            + I18N_OLD_NAMESPACE_URI 
  +            + " for i18n is not supported any more, use: " 
  +            + I18N_NAMESPACE_URI); 
  +	}
   
  +        // Process start element event
  +        if (I18N_NAMESPACE_URI.equals(uri)) {
  +            debug("Starting i18n element: " + name);
  +	    startI18NElement(name, attr);
  +        } else { // We have a non i18n element event
  +	    if (current_state == STATE_OUTSIDE) {
  +		super.startElement(uri, name, raw,
  +                                   translateAttributes(name, attr));
  +	    } else if (current_state == STATE_INSIDE_PARAM) {
  +		param_recorder.startElement(uri, name, raw, attr);
  +	    } else if (current_state == STATE_INSIDE_TEXT) {
  +		text_recorder.startElement(uri, name, raw, attr);
  +	    } else if ((current_state == STATE_INSIDE_WHEN ||
  +                        current_state == STATE_INSIDE_OTHERWISE)
  +                        && translate_copy) {
  +
  +		super.startElement(uri, name, raw, attr);
  +	    }
  +	}
  +    }
   
       public void endElement(String uri, String name, String raw)
           throws SAXException {
   
  +        // Handle previously buffered characters
  +        if (current_state != STATE_OUTSIDE && strBuffer != null) {
  +            i18nCharacters(strBuffer.toString());
  +            strBuffer = null;
  +        }
  +
           if (I18N_NAMESPACE_URI.equals(uri)) {
  -            if (strBuffer != null) {
  -                i18nCharacters(strBuffer.toString());
  -                strBuffer = null;
  -            }
               endI18NElement(name);
  -            return;
  -        }
  +        } else if (current_state == STATE_INSIDE_PARAM) {
  +	    param_recorder.endElement(uri, name, raw);
  +        } else if (current_state == STATE_INSIDE_TEXT) {
  +	    text_recorder.endElement(uri, name, raw);
  +	} else if (current_state == STATE_INSIDE_CHOOSE ||
  +                    (current_state == STATE_INSIDE_WHEN ||
  +                     current_state == STATE_INSIDE_OTHERWISE)
  +                    && !translate_copy) {
   
  -        super.endElement(uri, name, raw);
  +            ; // Output nothing
  +        } else {
  +            super.endElement(uri, name, raw);
  +        }
       }
   
  -    public void characters(char[] ch, int start, int len) throws SAXException {
  +    public void characters(char[] ch, int start, int len)
  +        throws SAXException {
  +
  +	if (current_state == STATE_OUTSIDE ||
  +           ((current_state == STATE_INSIDE_WHEN ||
  +             current_state == STATE_INSIDE_OTHERWISE) && translate_copy)) {
   
  -        if (current_state != STATE_OUTSIDE) {
  +            super.characters(ch, start, len);
  +        } else {
               // Perform buffering to prevent chunked character data
               if (strBuffer == null) {
                   strBuffer = new StringBuffer();
               }
               strBuffer.append(ch, start, len);
  -
  -            return;
           }
  -
  -        super.characters(ch, start, len);
  -
       }
   
       //
  @@ -928,9 +1098,7 @@
       private void startI18NElement(String name, Attributes attr)
           throws SAXException {
   
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug("Start i18n element: " + name);
  -        }
  +        debug("Start i18n element: " + name);
   
           if (I18N_TEXT_ELEMENT.equals(name)) {
               if (current_state != STATE_OUTSIDE
  @@ -944,9 +1112,27 @@
                   );
               }
   
  -            prev_state = current_state;
  +	    prev_state = current_state;
               current_state = STATE_INSIDE_TEXT;
               current_key = attr.getValue(I18N_NAMESPACE_URI, I18N_KEY_ATTRIBUTE);
  +	    if (prev_state != STATE_INSIDE_PARAM)
  +		tr_text_recorder = null;
  +
  +	    // Better send an exception to prevent users from this?
  +	    if (current_key == null && prev_state == STATE_INSIDE_TRANSLATE) {
  +		debug("WARNING: no key for text in translate! Don't do this!");
  +		/*throw new SAXException(this.getClass().getName()
  +		  + ": you must provide a key when using i18n:text"
  +		  + " within i18n:translate"
  +		  + " Current state: " + current_state);
  +		*/
  +	    }
  +	    else if (current_key != null) {
  +		tr_text_recorder = getMirrorRecorder(current_key, null);
  +		//debug("Got translation: " + tr_text_recorder);
  +	    }
  +
  +
           } else if (I18N_TRANSLATE_ELEMENT.equals(name)) {
               if (current_state != STATE_OUTSIDE) {
                   throw new SAXException(
  @@ -957,6 +1143,7 @@
                   );
               }
   
  +	    prev_state = current_state;
               current_state = STATE_INSIDE_TRANSLATE;
           } else if (I18N_PARAM_ELEMENT.equals(name)) {
               if (current_state != STATE_INSIDE_TRANSLATE) {
  @@ -968,21 +1155,102 @@
                   );
               }
   
  +	    param_name = attr.getValue(I18N_PARAM_NAME_ATTRIBUTE);
  +	    if (param_name == null) {
  +		param_name = String.valueOf(param_count++);
  +            }
  +
  +	    param_recorder = new MirrorRecorder();
               setFormattingParams(attr);
               current_state = STATE_INSIDE_PARAM;
  -        } else if (I18N_DATE_ELEMENT.equals(name)) {
  +        } else if (I18N_CHOOSE_ELEMENT.equals(name)) {
               if (current_state != STATE_OUTSIDE) {
                   throw new SAXException(
                       this.getClass().getName()
  +                    + ": i18n:choose elements cannot be used"
  +                    + "inside of other i18n elements."
  +                );
  +            }
  +
  +	    translate_copy = false;
  +            translate_end = false;
  +	    prev_state = current_state;
  +            current_state = STATE_INSIDE_CHOOSE;
  +        } else if (I18N_WHEN_ELEMENT.equals(name) ||
  +                   I18N_IF_ELEMENT.equals(name)) {
  +
  +            if (I18N_WHEN_ELEMENT.equals(name) &&
  +                current_state != STATE_INSIDE_CHOOSE) {
  +                throw new SAXException(
  +                    this.getClass().getName()
  +                    + ": i18n:when elements are can be used only"
  +                    + "inside of i18n:choose elements."
  +                );
  +            }
  +
  +            if (I18N_IF_ELEMENT.equals(name) &&
  +                current_state != STATE_OUTSIDE) {
  +                throw new SAXException(
  +                    this.getClass().getName()
  +                    + ": i18n:if elements cannot be nested."
  +                );
  +            }
  +
  +	    String locale = attr.getValue(I18N_LOCALE_ATTRIBUTE);
  +	    if (locale == null)
  +		throw new SAXException(
  +                    this.getClass().getName()
  +                    + ": i18n:" + name
  +                    + " element cannot be used without 'locale' attribute."
  +                );
  +
  +	    if ((!translate_end && current_state == STATE_INSIDE_CHOOSE)
  +                 || current_state == STATE_OUTSIDE) {
  +
  +                // Perform soft locale matching
  +		if (this.locale.toString().startsWith(locale)) {
  +		    debug("Locale matching: " + locale);
  +		    translate_copy = true;
  +		}
  +	    }
  +
  +	    prev_state = current_state;
  +            current_state = STATE_INSIDE_WHEN;
  +
  +        } else if (I18N_OTHERWISE_ELEMENT.equals(name)) {
  +            if (current_state != STATE_INSIDE_CHOOSE) {
  +                throw new SAXException(
  +                    this.getClass().getName()
  +                    + ": i18n:otherwise elements are not allowed "
  +                    + "only inside i18n:choose."
  +                );
  +	    }
  +
  +	    debug("Matching any locale");
  +	    if (!translate_end)
  +		translate_copy = true;
  +
  +	    prev_state = current_state;
  +            current_state = STATE_INSIDE_OTHERWISE;
  +
  +	} else if (I18N_DATE_ELEMENT.equals(name)) {
  +            if (current_state != STATE_OUTSIDE
  +		&& current_state != STATE_INSIDE_TEXT
  +		&& current_state != STATE_INSIDE_PARAM) {
  +                throw new SAXException(
  +                    this.getClass().getName()
                       + ": i18n:date elements are not allowed "
                       + "inside of other i18n elements."
                   );
               }
   
               setFormattingParams(attr);
  +	    prev_state = current_state;
               current_state = STATE_INSIDE_DATE;
           } else if (I18N_DATE_TIME_ELEMENT.equals(name)) {
  -            if (current_state != STATE_OUTSIDE) {
  +            if (current_state != STATE_OUTSIDE
  +		&& current_state != STATE_INSIDE_TEXT
  +		&& current_state != STATE_INSIDE_PARAM) {
                   throw new SAXException(
                       this.getClass().getName()
                       + ": i18n:date-time elements are not allowed "
  @@ -991,9 +1259,12 @@
               }
   
               setFormattingParams(attr);
  +	    prev_state = current_state;
               current_state = STATE_INSIDE_DATE_TIME;
           } else if (I18N_TIME_ELEMENT.equals(name)) {
  -            if (current_state != STATE_OUTSIDE) {
  +            if (current_state != STATE_OUTSIDE
  +		&& current_state != STATE_INSIDE_TEXT
  +		&& current_state != STATE_INSIDE_PARAM) {
                   throw new SAXException(
                       this.getClass().getName()
                       + ": i18n:date elements are not allowed "
  @@ -1002,9 +1273,12 @@
               }
   
               setFormattingParams(attr);
  +	    prev_state = current_state;
               current_state = STATE_INSIDE_TIME;
           } else if (I18N_NUMBER_ELEMENT.equals(name)) {
  -            if (current_state != STATE_OUTSIDE) {
  +            if (current_state != STATE_OUTSIDE
  +		&& current_state != STATE_INSIDE_TEXT
  +		&& current_state != STATE_INSIDE_PARAM) {
                   throw new SAXException(
                       this.getClass().getName()
                       + ": i18n:number elements are not allowed "
  @@ -1013,6 +1287,7 @@
               }
   
               setFormattingParams(attr);
  +	    prev_state = current_state;
               current_state = STATE_INSIDE_NUMBER;
           }
       }
  @@ -1051,17 +1326,14 @@
               formattingParams.put(I18N_TYPE_ATTRIBUTE, attr_value);
           }
   
  -        attr_value = attr.getValue( I18N_FRACTION_DIGITS_ATTRIBUTE );
  -        if ( attr_value != null ) {
  -            formattingParams.put( I18N_FRACTION_DIGITS_ATTRIBUTE, attr_value );
  -        }
  -
  +	attr_value = attr.getValue(I18N_FRACTION_DIGITS_ATTRIBUTE);
  +        if (attr_value != null) {
  +            formattingParams.put(I18N_FRACTION_DIGITS_ATTRIBUTE, attr_value);
  +	}
       }
   
       private void endI18NElement(String name) throws SAXException {
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug("End i18n element: " + name);
  -        }
  +        debug("End i18n element: " + name);
           switch (current_state) {
           case STATE_INSIDE_TEXT:
               endTextElement();
  @@ -1071,6 +1343,15 @@
               endTranslateElement();
               break;
   
  +        case STATE_INSIDE_CHOOSE:
  +	    endChooseElement();
  +	    break;
  +
  +        case STATE_INSIDE_WHEN:
  +        case STATE_INSIDE_OTHERWISE:
  +	    endWhenElement();
  +	    break;
  +
           case STATE_INSIDE_PARAM:
               endParamElement();
               break;
  @@ -1090,48 +1371,41 @@
       private void i18nCharacters(String textValue)
           throws SAXException {
   
  -        textValue = textValue.trim();
  -        if (textValue == null || textValue.length() == 0) {
  +	if (textValue == null) {
               return;
           }
  +        // Trim text values to avoid parsing errors.
  +        textValue = textValue.trim();
   
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug( "i18n message text = '" + textValue + "'" );
  -        }
  +        debug( "i18n message text = '" + textValue + "'" );
   
           switch (current_state) {
           case STATE_INSIDE_TEXT:
  -            if (current_key != null) { // if i18n:key attribute were used
  -                translated_text = getString(current_key, untranslated);
  -
  -                // If no translation found and untranslated param is null
  -                if (translated_text == null) {
  -                    translated_text = textValue;  // use the key
  -                }
  -
  -                // reset the key holding variable
  -                current_key = null;
  -            } else {    // use text value as dictionary key
  -                translated_text = getString(textValue, (untranslated == null)
  -                    ? textValue : untranslated);
  -            }
  -            break;
  -
  -        case STATE_INSIDE_TRANSLATE:
  -            // Store text for param substitution (do not translate)
  -            if (substitute_text == null) {
  -                substitute_text = textValue;
  -            }
  +	    text_recorder.characters(textValue);
               break;
   
           case STATE_INSIDE_PARAM:
  -            // Store translation for param substitution
  -            if (param_value == null) {
  -                param_value = textValue;
  -            }
  +	    param_recorder.characters(textValue);
               break;
   
  -        case STATE_INSIDE_DATE:
  +	case STATE_INSIDE_WHEN:
  +	case STATE_INSIDE_OTHERWISE:
  +            // Previously handeld to avoid the String() conversion.
  +	    break;
  +
  +	case STATE_INSIDE_TRANSLATE:
  +	    if(tr_text_recorder == null) {
  +		tr_text_recorder = new MirrorRecorder();
  +	    }
  +	    tr_text_recorder.characters(textValue);
  +	    break;
  +
  +        case STATE_INSIDE_CHOOSE:
  +	    // No characters allowed. Send an exception ?
  +	    debug("No characters allowed inside <i18n:choose> tags");
  +	    break;
  +
  +	case STATE_INSIDE_DATE:
           case STATE_INSIDE_DATE_TIME:
           case STATE_INSIDE_TIME:
           case STATE_INSIDE_NUMBER:
  @@ -1145,7 +1419,8 @@
           default:
               throw new IllegalStateException(
                   this.getClass().getName()
  -                + " developer's fault");
  +                + " developer's fault: characters not handled"
  +		+ "Current state: " + current_state);
           }
       }
   
  @@ -1153,32 +1428,33 @@
       private Attributes translateAttributes(String name, Attributes attr)
           throws SAXException {
   
  -        if (attr == null) {
  -            return attr;
  -        }
  +	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);
  +	    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
  +	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 anymore
               temp_attr.removeAttribute(i18n_attr_index);
   
               // iterate through listed attributes and translate them
               while (st.hasMoreElements()) {
                   String attr_name = st.nextToken();
  -                int attr_index = temp_attr.getIndex(attr_name);
  +
  +		int attr_index = temp_attr.getIndex(attr_name);
                   if (attr_index != -1) {
                       String text2translate = temp_attr.getValue(attr_index);
  -                    String result = getString(text2translate, (untranslated == null) ? text2translate : untranslated);
  +                    String result =
  +                        getString(text2translate, (untranslated == null)
  +                                  ? text2translate
  +                                  : untranslated);
   
                       // set the translated value
                       if (result != null) {
  @@ -1201,107 +1477,134 @@
       }
   
       private void endTextElement() throws SAXException {
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug("End text element, translated_text: " + translated_text);
  -        }
           switch (prev_state) {
           case STATE_OUTSIDE:
  -            // simply translate text (key translation already performed)
  -            if (translated_text != null) {
  -                super.contentHandler.characters(translated_text.toCharArray(),
  -                                                0, translated_text.length());
  -            } else {
  -                // else - translation not found
  -                this.getLogger().warn( "--- Translation not found! ---" );
  -            }
  -            break;
  +	    if (tr_text_recorder == null) {
  +		if (current_key == null) {
  +		    // Use the text as key
  +		    // Really not recommended for big strings, moreover if they
  +                    // include markup.
  +		    tr_text_recorder = getMirrorRecorder(text_recorder.text(),
  +                        (MirrorRecorder) text_recorder);
  +		} else {
  +		    // We have the key, but couldn't find a transltation
  +		    debug("translation not found for key " + current_key);
  +		    tr_text_recorder = text_recorder;
  +		}
  +	    }
  +
  +	    if (tr_text_recorder != null) {
  +		tr_text_recorder.send(this.contentHandler);
  +	    }
  +
  +	    text_recorder.recycle();
  +	    tr_text_recorder = null;
  +	    current_key = null;
  +	    break;
  +
  +	case STATE_INSIDE_TRANSLATE:
  +	    if (tr_text_recorder == null) {
  +		if (!text_recorder.empty()) {
  +		    tr_text_recorder = (MirrorRecorder) text_recorder.clone();
  +		}
  +	    }
  +
  +	    text_recorder.recycle();
  +	    break;
  +
  +	case STATE_INSIDE_PARAM:
  +	    // We send the translated text to the param recorder,after trying to translate it.
  +	    // Remember you can't give a key when inside a param, that'll be nonsense!
  +	    // No need to clone. We just send the events.
  +	    if (!text_recorder.empty()) {
  +		getMirrorRecorder(text_recorder.text(),
  +                    (MirrorRecorder) text_recorder).send(param_recorder);
  +
  +		text_recorder.recycle();
  +	    }
  +	    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;
  +	current_state = prev_state;
           prev_state = STATE_OUTSIDE;
       }
   
       // Process substitution parameter
       private void endParamElement() throws SAXException {
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug("Substitution param: " + param_value);
  -        }
  -
           String paramType = (String)formattingParams.get(I18N_TYPE_ATTRIBUTE);
           if (paramType != null) {
               // We have a typed parameter
   
  -            if (this.getLogger().isDebugEnabled()) {
  -                debug("Param type: " + paramType);
  -            }
  +            debug("Param type: " + paramType);
               if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null &&
                   param_value != null) {
   
  -                if (this.getLogger().isDebugEnabled()) {
  -                     debug("Put param value: " + param_value);
  -                }
  +                debug("Put param value: " + param_value);
                   formattingParams.put(I18N_VALUE_ATTRIBUTE, param_value);
               }
   
               // Check if we have a date or a number parameter
               if (dateTypes.contains(paramType)) {
  -                if (this.getLogger().isDebugEnabled()) {
  -                    debug("Formatting date_time param: " + formattingParams);
  -                }
  +                debug("Formatting date_time param: " + formattingParams);
                   param_value = formatDate_Time(formattingParams);
               } else if (numberTypes.contains(paramType)) {
  -                if (this.getLogger().isDebugEnabled()) {
  -                    debug("Formatting number param: " + formattingParams);
  -                }
  +                debug("Formatting number param: " + formattingParams);
                   param_value = formatNumber(formattingParams);
               }
  +	    debug("Added substitution param: " + param_value);
           }
   
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug("Added substitution param: " + param_value);
  -        }
  -        indexedParams.add(param_value);
  -        param_value = null;
  +	param_value = null;
           current_state = STATE_INSIDE_TRANSLATE;
  +
  +	if(param_recorder == null)
  +	    return;
  +
  +	indexedParams.put(param_name, ((MirrorRecorder) param_recorder).clone());
       }
   
       private void endTranslateElement() throws SAXException {
  -        if (substitute_text == null) {
  -            return;
  -        }
  -
           String result;
  -        if (indexedParams.size() > 0 && substitute_text.length() > 0) {
  -            if (this.getLogger().isDebugEnabled()) {
  -                debug("Text for susbtitution: " + substitute_text);
  -            }
  -            result = formatter.format(substitute_text, indexedParams.toArray());
  -            if (this.getLogger().isDebugEnabled()) {
  -                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;
  +	if (indexedParams.size() > 0 && tr_text_recorder != null) {
  +	    debug("End of translate with params");
  +	    debug("Fragment for substitution : " + tr_text_recorder.text());
  +	    tr_text_recorder.send(super.contentHandler, indexedParams);
  +	    tr_text_recorder = null;
  +	    text_recorder.recycle();
  +	}
  +
  +	indexedParams.clear();
  +	param_count = 0;
  +	current_state = STATE_OUTSIDE;
  +    }
  +
  +    private void endChooseElement() throws SAXException {
  +	current_state = STATE_OUTSIDE;
  +    }
  +
  +    private void endWhenElement() throws SAXException {
  +	current_state = prev_state;
  +	if (translate_copy) {
  +	    translate_copy = false;
  +	    translate_end = true;
  +	}
       }
   
       private void endDate_TimeElement() throws SAXException {
           String result = formatDate_Time(formattingParams);
  -        super.contentHandler.characters(result.toCharArray(), 0, result.length());
  -        current_state = STATE_OUTSIDE;
  +	switch(prev_state) {
  +	case STATE_OUTSIDE:
  +	    super.contentHandler.characters(result.toCharArray(), 0,
  +                result.length());
  +	    break;
  +	case STATE_INSIDE_PARAM:
  +	    param_recorder.characters(result.toCharArray(), 0, result.length());
  +	    break;
  +	case STATE_INSIDE_TEXT:
  +	    text_recorder.characters(result.toCharArray(), 0, result.length());
  +	    break;
  +	}
  +	current_state = prev_state;
       }
   
       // Helper method: creates Locale object from a string value in a map
  @@ -1422,17 +1725,25 @@
           }
   
           // we have all necessary data here: do formatting.
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug("### Formatting date: " + dateValue + " with localized pattern "
  -                + to_fmt.toLocalizedPattern() + " for locale: " + locale);
  -        }
  +        debug("### Formatting date: " + dateValue + " with localized pattern "
  +            + to_fmt.toLocalizedPattern() + " for locale: " + locale);
           return to_fmt.format(dateValue);
       }
   
       private void endNumberElement() throws SAXException {
           String result = formatNumber(formattingParams);
  -        super.contentHandler.characters(result.toCharArray(), 0, result.length());
  -        current_state = STATE_OUTSIDE;
  +	switch(prev_state) {
  +	case STATE_OUTSIDE:
  +	    super.contentHandler.characters(result.toCharArray(), 0, result.length());
  +	    break;
  +	case STATE_INSIDE_PARAM:
  +	    param_recorder.characters(result.toCharArray(), 0, result.length());
  +	    break;
  +	case STATE_INSIDE_TEXT:
  +	    text_recorder.characters(result.toCharArray(), 0, result.length());
  +	    break;
  +	}
  +	current_state = prev_state;
       }
   
       private String formatNumber(Map params) throws SAXException {
  @@ -1449,16 +1760,20 @@
           String pattern = (String)params.get(I18N_PATTERN_ATTRIBUTE);
           // the number value
           String value = (String)params.get(I18N_VALUE_ATTRIBUTE);
  -        if(value == null) return "";
  +
  +        if (value == null) return "";
           // type
           String type = (String)params.get(I18N_TYPE_ATTRIBUTE);
           // fraction-digits
           int fractionDigits = -1;
           try {
  -         fractionDigits = Integer.parseInt((String) params.get
  -                   ( I18N_FRACTION_DIGITS_ATTRIBUTE ));
  +            fractionDigits = Integer.parseInt((String)
  +                params.get(I18N_FRACTION_DIGITS_ATTRIBUTE));
           }
  -        catch(NumberFormatException nfe) {}
  +        catch(NumberFormatException nfe) {
  +            getLogger().warn("Error in number format", nfe);
  +        }
  +
           // parsed number
           Number numberValue = null;
   
  @@ -1480,6 +1795,7 @@
           char dec = from_fmt.getDecimalFormatSymbols().getDecimalSeparator();
           int decAt = 0;
           boolean appendDec = false;
  +
           if (type == null || type.equals( I18N_NUMBER_ELEMENT )) {
               to_fmt = (DecimalFormat)NumberFormat.getInstance(loc);
               to_fmt.setMaximumFractionDigits(309);
  @@ -1500,8 +1816,8 @@
               if (value.charAt(value.length() - 1) == dec) {
                   appendDec = true;
               }
  -        } else if (type.equals( I18N_CURRENCY_ELEMENT )) {
  -            to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(loc);
  +	} else if (type.equals( I18N_CURRENCY_ELEMENT )) {
  +		to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(loc);
           } else if (type.equals( I18N_INT_CURRENCY_ELEMENT )) {
               to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(loc);
               int_currency = 1;
  @@ -1509,9 +1825,9 @@
                   int_currency *= 10;
               }
           } else if ( type.equals( I18N_CURRENCY_NO_UNIT_ELEMENT ) ) {
  -            DecimalFormat tmp = (DecimalFormat) NumberFormat.getCurrencyInstance( loc );
  -            to_fmt = (DecimalFormat) NumberFormat.getInstance( loc );
  -            to_fmt.setMinimumFractionDigits(tmp.getMinimumFractionDigits());
  +	    DecimalFormat tmp = (DecimalFormat) NumberFormat.getCurrencyInstance( loc );
  +	    to_fmt = (DecimalFormat) NumberFormat.getInstance( loc );
  +	    to_fmt.setMinimumFractionDigits(tmp.getMinimumFractionDigits());
               to_fmt.setMaximumFractionDigits(tmp.getMaximumFractionDigits());
           } else if ( type.equals( I18N_INT_CURRENCY_NO_UNIT_ELEMENT ) ) {
               DecimalFormat tmp = (DecimalFormat) NumberFormat.getCurrencyInstance( loc );
  @@ -1525,12 +1841,11 @@
               to_fmt = (DecimalFormat)NumberFormat.getPercentInstance(loc);
           } else {
               throw new SAXException("&lt;i18n:number>: unknown type: " + type);
  -        }
  -
  +	}
   
  -        if(fractionDigits > -1) {
  -            to_fmt.setMinimumFractionDigits(fractionDigits);
  -            to_fmt.setMaximumFractionDigits(fractionDigits);
  +	if(fractionDigits > -1) {
  +	    to_fmt.setMinimumFractionDigits(fractionDigits);
  +	    to_fmt.setMaximumFractionDigits(fractionDigits);
           }
   
           // pattern overwrites locale format
  @@ -1560,12 +1875,12 @@
           // we have all necessary data here: do formatting.
           String result = to_fmt.format(numberValue);
           if (appendDec) result = result + dec;
  -        if (this.getLogger().isDebugEnabled()) {
  -            debug("i18n:number result: " + result);
  -        }
  +        debug("i18n:number result: " + result);
           return result;
       }
   
  +    //-- Dictionary handling routins
  +
       // Helper method to retrieve a message from the dictionary
       // Returnes null if no message is found
       private String getString(String key) {
  @@ -1573,16 +1888,62 @@
       }
   
       // Helper method to retrieve a message from the dictionary.
  +    // mattam: now only used for i:attr.
       // A default value is returned if message is not found
       private String getString(String key, String defaultValue) {
  -        try {
  -            String value = dictionary.getString(
  -                I18N_CATALOGUE_PREFIX + "[@key='" + key + "']"
  -            );
  +	try {
  +	    Node res = (Node)dictionary.getObject(
  +                I18N_CATALOGUE_PREFIX + "[@key='" + key + "']");
   
  -            return value != null ? value : defaultValue;
  +            String value = getTextValue(res);
  +	    return value != null ? value : defaultValue;
           } catch (MissingResourceException e)  {
  -  	        this.getLogger().warn("Untranslated key: '"+key+"'");
  +            return defaultValue;
  +        }
  +    }
  +
  +    // Helper method to get the text value of the node.
  +    private static String getTextValue(Node node) {
  +        if (node == null) return null;
  +
  +        NodeList list = node.getChildNodes();
  +        int listsize = list.getLength();
  +        Node item = null;
  +        StringBuffer itemValue = new StringBuffer();
  +        for (int i = 0; i < listsize; i++) {
  +            item = list.item(i);
  +            // only TEXT and CDATA nodes are processed
  +            if (item.getNodeType() == Node.TEXT_NODE
  +                || item.getNodeType() == Node.CDATA_SECTION_NODE) {
  +
  +                itemValue.append(item.getNodeValue());
  +            }
  +        }
  +
  +        return itemValue.toString();
  +    }
  +
  +    // Helper method to retrieve a message from the dictionary
  +    // Returns null if no message is found
  +    private MirrorRecorder getMirrorRecorder(String key) {
  +        return getMirrorRecorder(key, null);
  +    }
  +
  +    // Helper method to retrieve a message from the dictionary.
  +    // A default value is returned if message is not found
  +    private MirrorRecorder getMirrorRecorder(String key, MirrorRecorder defaultValue) {
  +        try {
  +	    MirrorRecorder value = new MirrorRecorder (
  +                (Node)dictionary.getObject(
  +                    I18N_CATALOGUE_PREFIX + "[@key='" + key + "']"));
  +
  +	    if (value == null)
  +		return defaultValue;
  +
  +	    return value;
  +
  +        } catch (MissingResourceException e) {
  +	    debug("Untraslated key: '" + key + "'");
               return defaultValue;
           }
       }
  @@ -1597,16 +1958,16 @@
       }
   
       public void recycle() {
  -        super.recycle();
           // restore untranslated-text if necessary
           if (globalUntranslated != null &&
  -             !untranslated.equals(globalUntranslated)) {
  +	    !untranslated.equals(globalUntranslated)) {
   
               untranslated = globalUntranslated;
           }
   
           factory.release(dictionary);
           dictionary = null;
  +        super.recycle();
       }
   
       public void dispose() {
  
  
  

----------------------------------------------------------------------
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