You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by un...@apache.org on 2003/12/25 14:56:38 UTC
cvs commit: cocoon-2.2/src/java/org/apache/cocoon/transformation I18nTransformer.java
unico 2003/12/25 05:56:38
Modified: src/java/org/apache/cocoon/transformation
I18nTransformer.java
Log:
copy changes from 2.1
Revision Changes Path
1.17 +197 -207 cocoon-2.2/src/java/org/apache/cocoon/transformation/I18nTransformer.java
Index: I18nTransformer.java
===================================================================
RCS file: /home/cvs/cocoon-2.2/src/java/org/apache/cocoon/transformation/I18nTransformer.java,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -r1.16 -r1.17
--- I18nTransformer.java 6 Dec 2003 21:22:07 -0000 1.16
+++ I18nTransformer.java 25 Dec 2003 13:56:38 -0000 1.17
@@ -50,34 +50,48 @@
*/
package org.apache.cocoon.transformation;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.MessageFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Set;
+import java.util.StringTokenizer;
+
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
-import org.apache.avalon.framework.service.Serviceable;
-import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.ProcessingException;
-import org.apache.cocoon.sitemap.PatternException;
-import org.apache.cocoon.components.treeprocessor.variables.PreparedVariableResolver;
import org.apache.cocoon.caching.CacheableProcessingComponent;
+import org.apache.cocoon.components.treeprocessor.variables.PreparedVariableResolver;
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.transformation.helpers.MirrorRecorder;
+import org.apache.cocoon.sitemap.PatternException;
+import org.apache.cocoon.xml.ParamSaxBuffer;
+import org.apache.cocoon.xml.SaxBuffer;
import org.apache.excalibur.source.SourceValidity;
-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 java.io.IOException;
-import java.text.*;
-import java.util.*;
-
/**
* Internationalization transformer is used to transform i18n markup into text
* based on a particular locale.
@@ -511,25 +525,34 @@
*/
public static final String I18N_NUMBER_ELEMENT = "number";
- /** currency element */
+ /**
+ * Currency element name
+ */
public static final String I18N_CURRENCY_ELEMENT = "currency";
- /** percent element */
+ /**
+ * Percent element name
+ */
public static final String I18N_PERCENT_ELEMENT = "percent";
- /** integer currency element */
- public static final String I18N_INT_CURRENCY_ELEMENT
- = "int-currency";
-
- /** currency without unit element */
- public static final String I18N_CURRENCY_NO_UNIT_ELEMENT =
- "currency-no-unit";
-
- /** integer currency without unit element */
- public static final String I18N_INT_CURRENCY_NO_UNIT_ELEMENT =
- "int-currency-no-unit";
+ /**
+ * Integer currency element name
+ */
+ public static final String I18N_INT_CURRENCY_ELEMENT = "int-currency";
+
+ /**
+ * Currency without unit element name
+ */
+ public static final String I18N_CURRENCY_NO_UNIT_ELEMENT = "currency-no-unit";
+ /**
+ * Integer currency without unit element name
+ */
+ public static final String I18N_INT_CURRENCY_NO_UNIT_ELEMENT = "int-currency-no-unit";
+
+ //
// i18n general attributes
+ //
/**
* This attribute is used with i18n:text element to indicate the key of
@@ -551,7 +574,9 @@
*/
public static final String I18N_ATTR_ATTRIBUTE = "attr";
+ //
// i18n number and date formatting attributes
+ //
/**
* This attribute is used with date and number formatting elements to
@@ -647,14 +672,15 @@
*/
public static final String I18N_CATALOGUE_ATTRIBUTE = "catalogue";
+ //
// Configuration parameters
+ //
/**
* This configuration parameter specifies the default locale to be used.
*/
public static final String I18N_LOCALE = "locale";
-
/**
* This configuration parameter specifies the message catalog name.
*/
@@ -686,24 +712,16 @@
public static final String I18N_CACHE_STARTUP = "cache-at-startup";
/**
- * This constant specifies the XPath prefix that will be used
- * to retrieve a value from a message catalogue. The resulting XPath is
- * looks like this:
- * <code>/catalogue/message[@key='key_value']</code>
- *<br/>
- * FIXME (KP): We need a more generic way of key interpretation: to use
- * XPath expression as keys, or keys with non-XML dictionaries.
- */
- public static final String I18N_CATALOGUE_PREFIX = "/catalogue/message";
-
- /**
* <code>fraction-digits</code> attribute is used with
* <code>i18:number</code> to
* indicate the number of digits behind the fraction
*/
public static final String I18N_FRACTION_DIGITS_ATTRIBUTE = "fraction-digits";
+ //
// States of the transformer
+ //
+
private static final int STATE_OUTSIDE = 0;
private static final int STATE_INSIDE_TEXT = 10;
private static final int STATE_INSIDE_PARAM = 20;
@@ -716,7 +734,6 @@
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;
@@ -754,30 +771,79 @@
}
- // Component manager for this component
protected ServiceManager manager;
- private SourceResolver sourceResolver;
+ //
+ // i18n configuration variables
+ //
+
+ /**
+ * Default catalogue id
+ */
+ private String defaultCatalogueId;
+
+ /**
+ * Default (global) untranslated message value
+ */
+ private String globalUntranslated;
+
+ /**
+ * Current (local) untranslated message value
+ */
+ private String untranslated;
+
+ /**
+ * SaxBuffer containing the contents of {@link #untranslated}.
+ */
+ private ParamSaxBuffer untranslatedRecorder;
+
+ /**
+ * Cache at startup configuration parameter value
+ */
+ private boolean cacheAtStartup;
+
+ // Default catalogue
+ private Bundle defaultCatalogue;
+
+ // All catalogues (hashed on catalgue id). The values are instances of CatalogueInfo.
+ private Map catalogues = new HashMap();
+
+ // Dictionary loader factory
+ protected BundleFactory factory;
+ //
+ // Current state of the transformer
+ //
+
protected Map objectModel;
- // Current state of the transformer. The value is STATE_OUTSIDE by default.
+ /**
+ * Current state of the transformer. Default value is STATE_OUTSIDE.
+ */
private int current_state;
- // Previous state. Used in text translation inside params and translate
- // elements.
+ /**
+ * Previous state of the transformer.
+ * Used in text translation inside params and translate elements.
+ */
private int prev_state;
- // Character data buffer, used to concat chunked character data
+ /**
+ * Character data buffer. used to concat chunked character data
+ */
private StringBuffer strBuffer;
- // The i18n:key attribute is stored for the current element.
- // If no translation found for the key then the character data of element is
- // used as default value.
+ /**
+ * The i18n:key attribute is stored for the current element.
+ * If no translation found for the key then the character data of element is
+ * used as default value.
+ */
private String current_key;
- // Contains the id of the current catalogue if it was explicitely mentioned
- // on an i18n:text element, otherwise it is null.
+ /**
+ * Contains the id of the current catalogue if it was explicitely mentioned
+ * on an i18n:text element, otherwise it is null.
+ */
private String currentCatalogueId;
// A flag for copying the node when doing in-place translation
@@ -787,14 +853,14 @@
// when doing in-place translation whitin i18n:choose
private boolean translate_end;
- // Translated text
- private MirrorRecorder tr_text_recorder;
+ // Translated text. Inside i:translate, collects character events.
+ private ParamSaxBuffer tr_text_recorder;
- // Current "i:text" events
- private MirrorRecorder text_recorder;
+ // Current "i18n:text" events
+ private ParamSaxBuffer text_recorder;
// Current parameter events
- private MirrorRecorder param_recorder;
+ private SaxBuffer param_recorder;
// Param count when not using i:param name="..."
private int param_count;
@@ -817,34 +883,6 @@
// Date and number elements and params formatting attributes with values.
private HashMap formattingParams;
- // Default catalogue
- private Bundle defaultCatalogue;
-
- // All catalogues (hashed on catalgue id). The values are instances of CatalogueInfo.
- private Map catalogues = new HashMap();
-
- // Dictionary loader factory
- protected BundleFactory factory;
-
- //
- // i18n configuration variables
- //
-
- // id of the default catalogue
- private String defaultCatalogueId;
-
- // Untranslated message value
- private String untranslated;
-
- /* A MirrorRecorder containing the contents of {@link #untranslated}. */
- private MirrorRecorder untranslatedRecorder;
-
- // Cache at startup setting value
- private boolean cacheAtStartup;
-
- // Helper variable used to hold the old untranslated value
- private String globalUntranslated;
-
/**
* Returns the current locale setting of this transformer instance.
@@ -887,9 +925,8 @@
// Bundle implementations.
return org.apache.excalibur.source.impl.validity.NOPValidity.SHARED_INSTANCE;
}
-
+
/**
- * Implementation of composable interface.
* Looksup the Bundle Factory to be used.
*/
public void service(ServiceManager manager) throws ServiceException {
@@ -933,9 +970,9 @@
}
// obtain default text to use for untranslated messages
- untranslated = conf.getChild(I18N_UNTRANSLATED).getValue(null);
+ globalUntranslated = conf.getChild(I18N_UNTRANSLATED).getValue(null);
if (getLogger().isDebugEnabled()) {
- getLogger().debug("Default untranslated text is '" + untranslated + "'");
+ getLogger().debug("Default untranslated text is '" + globalUntranslated + "'");
}
// obtain config option, whether to cache messages at startup time
@@ -954,38 +991,22 @@
Parameters parameters)
throws ProcessingException, SAXException, IOException {
- this.sourceResolver = resolver;
this.objectModel = objectModel;
try {
- // check parameters to see if anything has been locally overloaded
- String localUntranslated = null;
- String lc = null;
- String localDefaultCatalogueId = null;
-
- if (parameters != null) {
- localUntranslated =
- parameters.getParameter(I18N_UNTRANSLATED, null);
- lc = parameters.getParameter(I18N_LOCALE, null);
- localDefaultCatalogueId = parameters.getParameter(I18N_DEFAULT_CATALOGUE_ID, null);
- }
-
- // if untranslated-text has been overridden, save the original
- // value so it can be restored when this object is recycled.
- if (localUntranslated != null) {
- globalUntranslated = untranslated;
- untranslated = localUntranslated;
- }
-
+ untranslated = parameters.getParameter(I18N_UNTRANSLATED, globalUntranslated);
if (untranslated != null) {
- untranslatedRecorder = new MirrorRecorder();
+ untranslatedRecorder = new ParamSaxBuffer();
untranslatedRecorder.characters(untranslated.toCharArray(), 0, untranslated.length());
}
+ String lc = parameters.getParameter(I18N_LOCALE, null);
+ String localDefaultCatalogueId = parameters.getParameter(I18N_DEFAULT_CATALOGUE_ID, null);
+
// Get current locale
Locale locale = I18nUtils.parseLocale(lc);
if (getLogger().isDebugEnabled()) {
- getLogger().debug("Using locale " + locale.toString());
+ getLogger().debug("Using locale '" + locale.toString() + "'");
}
// Initialize instance state variables
@@ -996,7 +1017,7 @@
this.currentCatalogueId = null;
this.translate_copy = false;
this.tr_text_recorder = null;
- this.text_recorder = new MirrorRecorder();
+ this.text_recorder = new ParamSaxBuffer();
this.param_count = 0;
this.param_name = null;
this.param_value = null;
@@ -1008,7 +1029,6 @@
// give the defaultCatalogue variable its value -- first look if it's locally overridden
// and otherwise use the component-wide defaults.
if (localDefaultCatalogueId != null) {
- // then if new local configuration style
CatalogueInfo catalogueInfo = (CatalogueInfo)catalogues.get(localDefaultCatalogueId);
if (catalogueInfo == null) {
throw new ProcessingException("I18nTransformer: '" +
@@ -1174,7 +1194,7 @@
}
if (current_key != null) {
- tr_text_recorder = getMirrorRecorder(current_key, null);
+ tr_text_recorder = getMessage(current_key, (ParamSaxBuffer)null);
}
} else if (I18N_TRANSLATE_ELEMENT.equals(name)) {
@@ -1204,7 +1224,7 @@
param_name = String.valueOf(param_count++);
}
- param_recorder = new MirrorRecorder();
+ param_recorder = new SaxBuffer();
setFormattingParams(attr);
current_state = STATE_INSIDE_PARAM;
} else if (I18N_CHOOSE_ELEMENT.equals(name)) {
@@ -1422,28 +1442,25 @@
}
}
- private void i18nCharacters(String textValue)
- throws SAXException {
-
- if (textValue == null) {
- return;
- }
+ private void i18nCharacters(String textValue) throws SAXException {
// Trim text values to avoid parsing errors.
textValue = textValue.trim();
- if (textValue.length() == 0)
+ if (textValue.length() == 0) {
return;
+ }
if (getLogger().isDebugEnabled()) {
getLogger().debug( "i18n message text = '" + textValue + "'" );
}
+ char[] ch = textValue.toCharArray();
switch (current_state) {
case STATE_INSIDE_TEXT:
- text_recorder.characters(textValue);
+ text_recorder.characters(ch, 0, ch.length);
break;
case STATE_INSIDE_PARAM:
- param_recorder.characters(textValue);
+ param_recorder.characters(ch, 0, ch.length);
break;
case STATE_INSIDE_WHEN:
@@ -1453,9 +1470,9 @@
case STATE_INSIDE_TRANSLATE:
if(tr_text_recorder == null) {
- tr_text_recorder = new MirrorRecorder();
+ tr_text_recorder = new ParamSaxBuffer();
}
- tr_text_recorder.characters(textValue);
+ tr_text_recorder.characters(ch, 0, ch.length);
break;
case STATE_INSIDE_CHOOSE:
@@ -1475,16 +1492,14 @@
break;
default:
- throw new IllegalStateException(
- this.getClass().getName()
- + " developer's fault: characters not handled"
- + "Current state: " + current_state);
+ throw new IllegalStateException(getClass().getName() +
+ " developer's fault: characters not handled. " +
+ "Current state: " + current_state);
}
}
// Translate all attributes that are listed in i18n:attr attribute
private Attributes translateAttributes(String name, Attributes attr) {
-
if (attr == null) {
return attr;
}
@@ -1515,15 +1530,13 @@
// check if the text2translate contains a colon, if so the text before
// the colon denotes a catalogue id
int colonPos = text2translate.indexOf(":");
- String catalogueId = null;
+ String catalogueID = null;
if (colonPos != -1) {
- catalogueId = text2translate.substring(0, colonPos);
+ catalogueID = text2translate.substring(0, colonPos);
text2translate = text2translate.substring(colonPos + 1, text2translate.length());
}
- String result =
- getString(text2translate, catalogueId, (untranslated == null)
- ? text2translate
- : untranslated);
+ String result = getString(catalogueID, text2translate,
+ untranslated == null? text2translate : untranslated);
// set the translated value
if (result != null) {
@@ -1550,18 +1563,18 @@
case STATE_OUTSIDE:
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(), text_recorder);
+ // Use the text as key. Not recommended for large strings,
+ // especially if they include markup.
+ tr_text_recorder = getMessage(text_recorder.toString(), text_recorder);
} else {
- // We have the key, but couldn't find a transltation
+ // We have the key, but couldn't find a translation
if (getLogger().isDebugEnabled()) {
- getLogger().debug("translation not found for key " + current_key);
+ getLogger().debug("Translation not found for key '" + current_key + "'");
}
- // use the untranslated-text only when the content of the i18n:text
+
+ // Use the untranslated-text only when the content of the i18n:text
// element was empty
- if (text_recorder.empty() && untranslatedRecorder != null) {
+ if (text_recorder.isEmpty() && untranslatedRecorder != null) {
tr_text_recorder = untranslatedRecorder;
} else {
tr_text_recorder = text_recorder;
@@ -1570,7 +1583,7 @@
}
if (tr_text_recorder != null) {
- tr_text_recorder.send(this.contentHandler);
+ tr_text_recorder.toSAX(this.contentHandler);
}
text_recorder.recycle();
@@ -1581,10 +1594,12 @@
case STATE_INSIDE_TRANSLATE:
if (tr_text_recorder == null) {
- if (!text_recorder.empty()) {
- tr_text_recorder = getMirrorRecorder(text_recorder.text(), text_recorder);
- if (tr_text_recorder == text_recorder) // if the default value was returned
- tr_text_recorder = (MirrorRecorder) text_recorder.clone();
+ if (!text_recorder.isEmpty()) {
+ tr_text_recorder = getMessage(text_recorder.toString(), text_recorder);
+ if (tr_text_recorder == text_recorder) {
+ // If the default value was returned, make a copy
+ tr_text_recorder = new ParamSaxBuffer(text_recorder);
+ }
}
}
@@ -1592,11 +1607,11 @@
break;
case STATE_INSIDE_PARAM:
- // We send the translated text to the param recorder,after trying to translate it.
+ // 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(), text_recorder).send(param_recorder);
+ if (!text_recorder.isEmpty()) {
+ getMessage(text_recorder.toString(), text_recorder).toSAX(param_recorder);
text_recorder.recycle();
}
break;
@@ -1646,16 +1661,17 @@
return;
}
- indexedParams.put(param_name, param_recorder.clone());
+ indexedParams.put(param_name, param_recorder);
+ param_recorder = null;
}
private void endTranslateElement() throws SAXException {
if (tr_text_recorder != null) {
if (getLogger().isDebugEnabled()) {
- getLogger().debug("End of translate with params");
- getLogger().debug("Fragment for substitution : " + tr_text_recorder.text());
+ getLogger().debug("End of translate with params. " +
+ "Fragment for substitution : " + tr_text_recorder);
}
- tr_text_recorder.send(super.contentHandler, indexedParams);
+ tr_text_recorder.toSAX(super.contentHandler, indexedParams);
tr_text_recorder = null;
text_recorder.recycle();
}
@@ -1994,20 +2010,20 @@
//-- Dictionary handling routins
/**
- * Helper method to retrieve a message Node from the dictionary.
- * A default value is returned if message is not found.
+ * Helper method to retrieve a message from the dictionary.
*
- * @param catalogueId if not null, this catalogue will be used instead of the default one.
+ * @param catalogueID if not null, this catalogue will be used instead of the default one.
+ * @return SaxBuffer containing message, or null if not found.
*/
- private Node getNode(String catalogueId, String key) {
+ private ParamSaxBuffer getMessage(String catalogueID, String key) {
try {
Bundle catalogue = defaultCatalogue;
- if (catalogueId != null) {
- CatalogueInfo catalogueInfo = (CatalogueInfo)catalogues.get(catalogueId);
+ if (catalogueID != null) {
+ CatalogueInfo catalogueInfo = (CatalogueInfo)catalogues.get(catalogueID);
if (catalogueInfo == null) {
- if (getLogger().isDebugEnabled()) {
- getLogger().debug("Catalogue not found: " + catalogueId +
- ", could not translate key " + key);
+ if (getLogger().isWarnEnabled()) {
+ getLogger().warn("Catalogue not found: " + catalogueID +
+ ", will not translate key " + key);
}
return null;
}
@@ -2021,8 +2037,7 @@
return null;
}
}
- return (Node)catalogue.getObject(I18N_CATALOGUE_PREFIX +
- "[@key='" + key + "']");
+ return (ParamSaxBuffer)catalogue.getObject(key);
} catch (MissingResourceException e) {
getLogger().debug("Untranslated key: '" + key + "'");
return null;
@@ -2036,55 +2051,30 @@
*
* @param catalogueId if not null, this catalogue will be used instead of the default one.
*/
- private String getString(String key, String catalogueId, String defaultValue) {
- final Node res = getNode(catalogueId, key);
+ private String getString(String catalogueID, String key, String defaultValue) {
+ final SaxBuffer res = getMessage(catalogueID, key);
if (res == null) {
return defaultValue;
}
- return getTextValue(res);
- }
-
- // 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();
+ return res.toString();
}
/**
- * Helper method to retrieve a message from the dictionary.
+ * Helper method to retrieve a message from the current dictionary.
* A default value is returned if message is not found.
+ *
+ * @return SaxBuffer containing message, or defaultValue if not found.
*/
- private MirrorRecorder getMirrorRecorder(String key, MirrorRecorder defaultValue) {
- Node value = getNode(currentCatalogueId, key);
+ private ParamSaxBuffer getMessage(String key, ParamSaxBuffer defaultValue) {
+ SaxBuffer value = getMessage(currentCatalogueId, key);
if (value == null) {
return defaultValue;
}
- return new MirrorRecorder(value);
+ return new ParamSaxBuffer(value);
}
public void recycle() {
- // restore untranslated-text if necessary
- if (globalUntranslated != null) {
- untranslated = globalUntranslated;
- }
untranslatedRecorder = null;
// clean up default catalogue
@@ -2092,23 +2082,22 @@
defaultCatalogue = null;
// clean up the other catalogues
- Iterator catalogueIt = catalogues.values().iterator();
- while (catalogueIt.hasNext()) {
- CatalogueInfo catalogueInfo = (CatalogueInfo)catalogueIt.next();
+ Iterator i = catalogues.values().iterator();
+ while (i.hasNext()) {
+ CatalogueInfo catalogueInfo = (CatalogueInfo)i.next();
catalogueInfo.releaseCatalog();
}
- sourceResolver = null;
objectModel = null;
-
super.recycle();
}
public void dispose() {
- if (this.manager != null) {
- this.manager.release(this.factory);
+ if (manager != null) {
+ manager.release(this.factory);
}
factory = null;
+ manager = null;
}
/**
@@ -2148,7 +2137,7 @@
public Bundle getCatalogue() throws Exception {
if (catalogue == null) {
resolve();
- catalogue = (Bundle) factory.select(resolvedLocation, resolvedName, locale);
+ catalogue = factory.select(resolvedLocation, resolvedName, locale);
}
return catalogue;
}
@@ -2162,4 +2151,5 @@
resolvedLocation = null;
}
}
+
}