upayavira 2004/07/11 11:50:07
Modified: src/webapp sitemap.xmap
Added: src/java/org/apache/cocoon/matching I18nMatcher.java
Log:
A first pass at an I18nMatcher.
Revision Changes Path
1.54 +8 -1 cocoon-2.1/src/webapp/sitemap.xmap
Index: sitemap.xmap
===================================================================
RCS file: /home/cvs/cocoon-2.1/src/webapp/sitemap.xmap,v
retrieving revision 1.53
retrieving revision 1.54
diff -u -r1.53 -r1.54
--- sitemap.xmap 23 Jun 2004 19:32:03 -0000 1.53
+++ sitemap.xmap 11 Jul 2004 18:50:07 -0000 1.54
@@ -243,6 +243,13 @@
<map:matcher name="mount-table"
src="org.apache.cocoon.matching.MountTableMatcher">
<map:parameter name="ignore-missing-tables" value="true"/>
</map:matcher>
+ <map:matcher name="i18n" src="org.apache.cocoon.matching.I18nMatcher">
+ <request-param-name>lang</request-param-name>
+ <use-locale>true</use-locale>
+ <use-locales>true</use-locales>
+ <default-locale lang="en" country="US"/>
+ <test-blank-locale>true</test-blank-locale>
+ </map:matcher>
</map:matchers>
<!--+
1.1
cocoon-2.1/src/java/org/apache/cocoon/matching/I18nMatcher.java
Index: I18nMatcher.java
===================================================================
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.matching;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.sitemap.PatternException;
import org.apache.commons.lang.StringUtils;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceResolver;
/**
* A matcher that locates and identifies to the pipeline a source document to
be used as
* the content for an i18n site, based upon a locale provided in a range of
ways.
* <h1>Configuration</h1>
* <p>
* A sample configuration (given in the <matchers> section of the
sitemap) is given
* below.
* </p>
* <pre>
* <map:matcher name="i18n"
src="org.apache.cocoon.matching.I18nMatcher">
* <request-param-name>lang</request-param-name>
* <use-locale>true</use-locale>
* <use-locales>true</use-locales>
* <default-locale lang="pt" country="BR"/>
* <test-blank-locale>true</test-blank-locale>
* </map:matcher>
* </pre>
* <p>
* Within this configuration, it is possible to:
* <ul>
* <li>Specify the request parameter that is to be used as a locale, if
present</li>
* <li>Specify whether the primary locale provided by the user agent is
to be used</li>
* <li>Specify whether each locale provided by the user agent should be
tested in turn</li>
* <li>Specify the default locale to be used when none matches any of the
previous</li>
* <li>Specify whether a file should be looked for without a locale in
its filename, e.g.
* after looking for index.en.html, try index.html.</li>
* </ul>
* </p>
* <h1>Usage</h1>
* <p>This matcher will be used in a pipeline like so:
* </p>
* <pre>
* <map:match pattern="*.html">
* <map:match type="i18n" pattern="xml/{1}.*.xml">
* <map:generate src="{source}"/>
* ...
* </map:match>
* </map:match>
* </pre>
*
* <h1>Locale Identification</h1>
* <p>
* A source will be looked for using each of the following as a source for
locale. Where the
* full locale (language, country, variant) doesn't match, it will fall back
to first language
* and country, and then just language, before moving on to the next locale.
* <ul>
* <li>Locale provided as a request parameter</li>
* <li>Locale provided using a <map:parameter name="locale"
value="{1}"/> style parameter
* within the <map:match> node</li>
* <li>Locales provided by the user agent</li>
* <li>The default locale specified in the matcher's configuration</li>
* <li>Resources with no defined locale</li>
* </ul>
* </p>
* <h1>Sitemap Variables</h1>
* <p>
* Once a matching locale has been found, the following sitemap variables
will be available to
* sitemap elements contained within the matcher:
* <ul>
* <li>{source}: The URI of the source that matched</li>
* <li>{locale}: The locale that matched that resource</li>
* <li>{matched-locale}: The part of the locale that matched the
resource</li>
* <li>{language}: The language of the matching resource</li>
* <li>{country}: The country of the matching resource</li>
* <li>{variant}: The variant of the matching resource</li>
* </ul>
* </p>
*
* @author <a href="mailto:[EMAIL PROTECTED]">Upayavira</a>
* @version CVS $Id: I18nMatcher.java,v 1.1 2004/07/11 18:50:07 upayavira Exp
$
*/
public class I18nMatcher extends AbstractLogEnabled implements Matcher,
ThreadSafe, Serviceable, Configurable {
private ServiceManager manager;
private SourceResolver resolver;
private String requestParameterName;
private String sessionParameterName;
private boolean useLocale;
private boolean useLocales;
private Locale defaultLocale;
private boolean testBlankLocale;
private static final boolean DEFAULT_USE_LOCALE = true;
private static final boolean DEFAULT_USE_LOCALES = true;
private static final String DEFAULT_DEFAULT_LANG = "en";
private static final String DEFAULT_DEFAULT_COUNTRY = "US";
private static final String DEFAULT_DEFAULT_VARIANT = null;
private static final String DEFAULT_REQUEST_PARAM_NAME = null;
private static final boolean DEFAULT_TEST_BLANK_LOCALE = true;
private static final String MAP_LOCALE = "locale";
private static final String MAP_LOCALES = "locales";
private static final String MAP_MATCHED_LOCALE = "matched-locale";
private static final String MAP_SOURCE = "source";
private static final String MAP_COUNTRY ="country";
private static final String MAP_LANGUAGE ="language";
private static final String MAP_VARIANT = "variant";
private static final String MAP_ENCODING = "encoding";
public void service(ServiceManager manager) throws ServiceException {
this.manager = manager;
this.resolver =
(SourceResolver)this.manager.lookup(SourceResolver.ROLE);
}
public void configure(Configuration config) {
Configuration child;
child = config.getChild("use-locale");
useLocale = child == null ? DEFAULT_USE_LOCALE :
child.getValueAsBoolean(DEFAULT_USE_LOCALE);
child = config.getChild("use-locales");
useLocales = child == null ? DEFAULT_USE_LOCALES :
child.getValueAsBoolean(DEFAULT_USE_LOCALES);
child = config.getChild("default-locale");
if (child == null) {
defaultLocale = getLocale(DEFAULT_DEFAULT_LANG,
DEFAULT_DEFAULT_COUNTRY, DEFAULT_DEFAULT_VARIANT);
} else {
defaultLocale = getLocale(child.getAttribute("lang",
DEFAULT_DEFAULT_LANG),
child.getAttribute("country",
DEFAULT_DEFAULT_COUNTRY),
child.getAttribute("variant",
DEFAULT_DEFAULT_VARIANT));
}
child = config.getChild("request-param-name");
requestParameterName = child == null ? DEFAULT_REQUEST_PARAM_NAME :
child.getValue(DEFAULT_REQUEST_PARAM_NAME);
child = config.getChild("test-blank-locale");
testBlankLocale = child == null ? DEFAULT_TEST_BLANK_LOCALE :
child.getValueAsBoolean(DEFAULT_TEST_BLANK_LOCALE);
}
private Locale getLocale(String lang, String country, String variant) {
if (lang == null) {
return null;
}
if (country == null) {
return new Locale(lang);
}
if (variant == null) {
return new Locale(lang, country);
}
return new Locale(lang, country, variant);
}
public Map match(String pattern, Map objectModel, Parameters parameters)
throws PatternException {
Map map = new HashMap();
Request request = ObjectModelHelper.getRequest(objectModel);
Locale locale = request.getLocale();
Enumeration locales = request.getLocales();
String requestParameter = request.getParameter(requestParameterName);
String sitemapParameter = parameters.getParameter("locale", null);
String matchingUrl = null;
if (requestParameter != null && isValidResource(pattern, new
Locale(requestParameter), map)) {
return map;
}
if (sitemapParameter != null && isValidResource(pattern, new
Locale(sitemapParameter), map)) {
return map;
}
if (useLocale && !useLocales && isValidResource(pattern, locale,
map)) {
return map;
}
if (useLocales) {
for (; locales.hasMoreElements();) {
Locale l = (Locale)locales.nextElement();
if (isValidResource(pattern, l, map)) {
return map;
}
}
}
if (defaultLocale != null && isValidResource(pattern, defaultLocale,
map)) {
return map;
}
if (testBlankLocale && isValidResource(pattern, null, map)) {
return map;
}
return null;
}
private boolean isValidResource(String pattern, Locale locale, Map map) {
Locale testLocale;
if (locale == null) {
return isValidResource(pattern, null, null, map);
}
testLocale = locale;
if (isValidResource(pattern, locale, testLocale.toString(), map)) {
return true;
}
testLocale = new Locale(locale.getLanguage(), locale.getCountry());
if (isValidResource(pattern, locale, testLocale.toString(), map)) {
return true;
}
testLocale = new Locale(locale.getLanguage());
if (isValidResource(pattern, locale, testLocale.toString(), map)) {
return true;
}
return false;
}
private boolean isValidResource(String pattern, Locale locale, String
localeString, Map map) {
Source source;
String url;
if (localeString!=null) {
url = StringUtils.replace(pattern, "*", localeString);
} else {
int starPos = pattern.indexOf("*");
if (starPos< pattern.length()-1 && starPos > 1 &&
pattern.charAt(starPos-1) == pattern.charAt(starPos+1)) {
url = pattern.substring(0,starPos-1) +
pattern.substring(starPos+1);
} else {
url = StringUtils.replace(pattern, "*", "");
}
}
boolean result = false;
try {
source = resolver.resolveURI(url);
if (source.exists()) {
map.put(MAP_SOURCE, url);
map.put(MAP_MATCHED_LOCALE, localeString);
if (locale != null) {
map.put(MAP_LOCALE, locale.toString());
map.put(MAP_LANGUAGE, locale.getLanguage());
map.put(MAP_COUNTRY, locale.getCountry());
map.put(MAP_VARIANT, locale.getVariant());
}
//map.put(MAP_ENCODING, "???");
result = true;
}
resolver.release(source);
} catch (IOException e) {
return false;
}
return result;
}
}