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