/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.common.impl.site;
import ch.entwine.weblounge.common.impl.language.LanguageUtils;
import ch.entwine.weblounge.common.impl.util.xml.XMLUtils;
import ch.entwine.weblounge.common.impl.util.xml.XPathHelper;
import ch.entwine.weblounge.common.language.Language;
import ch.entwine.weblounge.common.site.I18nDictionary;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
/**
* Default implementation of the site specific <code>i18n</code> dictionary.
*/
public class I18nDictionaryImpl implements I18nDictionary {
/** Logging facility */
private static final Logger logger = LoggerFactory.getLogger(I18nDictionaryImpl.class);
/** The i18n directories */
protected Map<Language, Properties> i18n = new HashMap<Language, Properties>();
/** The defaults (language neutral) */
private final Properties defaults = new Properties();
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.site.I18nDictionary#add(java.lang.String,
* java.lang.String)
*/
public void add(String key, String value) {
add(key, value, null);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.site.I18nDictionary#add(java.lang.String,
* java.lang.String, ch.entwine.weblounge.common.language.Language)
*/
public void add(String key, String value, Language language) {
if (key == null)
throw new IllegalArgumentException("I18n key cannot be null");
if (value == null)
throw new IllegalArgumentException("I18n value cannot be null");
Properties p = null;
if (language != null) {
p = i18n.get(language);
if (p == null) {
p = new Properties();
i18n.put(language, p);
}
} else {
p = defaults;
}
if (p.get(key) == null) {
p.put(key, value);
} else {
logger.warn("I18n key '{}' already defined", key);
}
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.site.I18nDictionary#get(java.lang.String)
*/
public String get(String key) {
return get(key, null);
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.site.I18nDictionary#get(java.lang.String,
* ch.entwine.weblounge.common.language.Language)
*/
public String get(String key, Language language) {
Properties p = null;
if (language != null)
p = i18n.get(language);
if (p == null)
p = defaults;
String dictEntry = null;
if (p != null)
dictEntry = p.getProperty(key);
// Either return the entry or try the default language fallback
if (dictEntry != null)
return dictEntry;
else {
dictEntry = defaults.getProperty(key);
if (dictEntry != null)
return dictEntry;
}
// Last resort: return the key itself
return key;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.site.I18nDictionary#getAsHTML(java.lang.String)
*/
public String getAsHTML(String key) {
String value = get(key);
return value != null ? StringEscapeUtils.escapeHtml(value) : null;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.site.I18nDictionary#getAsHTML(java.lang.String,
* ch.entwine.weblounge.common.language.Language)
*/
public String getAsHTML(String key, Language language) {
String value = get(key, language);
return value != null ? StringEscapeUtils.escapeHtml(value) : null;
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.site.I18nDictionary#remove(java.lang.String)
*/
public void remove(String key) {
defaults.remove(key);
for (Properties p : i18n.values()) {
p.remove(key);
}
}
/**
* {@inheritDoc}
*
* @see ch.entwine.weblounge.common.site.I18nDictionary#remove(java.lang.String,
* ch.entwine.weblounge.common.language.Language)
*/
public void remove(String key, Language language) {
Properties p = null;
if (language != null)
p = i18n.get(language);
else
p = defaults;
if (p != null)
p.remove(key);
}
/**
* Adds the the dictionary found in <code>file</code> to the current i18n
* definitions. The implementation tries to derive the language from the
* filename, which is expected to be of the form
* <code><name>_<language>.xml</code>, where <language> is
* the ISO language identifier.
*
* @param url
* the i18n dictionary
*/
public void addDictionary(URL url) {
String name = FilenameUtils.getBaseName(url.getFile());
Language language = null;
String languageId = null;
int lidstart = name.indexOf('_') + 1;
if (lidstart > 0 && lidstart < name.length()) {
languageId = name.substring(lidstart);
language = LanguageUtils.getLanguage(languageId);
}
addDictionary(url, language);
}
/**
* Adds the the dictionary found in <code>file</code> to the current i18n
* definitions.
*
* @param url
* the i18n dictionary
* @param language
* the dictionary language
*/
public void addDictionary(URL url, Language language) {
DocumentBuilder docBuilder;
try {
docBuilder = XMLUtils.getDocumentBuilder();
Document doc = docBuilder.parse(url.openStream());
if (language != null)
logger.debug("Reading i18n dictionary {} ({})", url.getFile(), language);
else
logger.debug("Reading default i18n dictionary {}", url.getFile());
// Get the target properties
Properties p = null;
if (language != null) {
p = i18n.get(language);
if (p == null) {
p = new Properties();
i18n.put(language, p);
}
} else {
p = defaults;
}
int invalidKeys = 0;
int invalidValues = 0;
// Read and store the messages
XPath path = XMLUtils.getXPath();
NodeList nodes = XPathHelper.selectList(doc, "/resources/string", path);
for (int j = 0; j < nodes.getLength(); j++) {
Node messageNode = nodes.item(j);
String key = XPathHelper.valueOf(messageNode, "@name", path);
String value = XPathHelper.valueOf(messageNode, "text()", path);
if (key == null) {
invalidKeys++;
continue;
}
if (value == null) {
logger.debug("I18n dictionary {} lacks a value for key '{}'", url, key);
invalidValues++;
continue;
}
if (p.containsKey(key)) {
logger.warn("I18n key '{}' redefined in {}", key, url);
}
p.put(key, value);
}
if (invalidKeys > 0)
logger.warn("I18n dictionary {} contains {} entries withou a key", url, invalidKeys);
if (invalidValues > 0)
logger.warn("I18n dictionary {} contains {} keys without values", url, invalidValues);
} catch (ParserConfigurationException e) {
logger.warn("Parser configuration error when reading i18n dictionary {}: {}", url, e.getMessage());
} catch (SAXException e) {
logger.warn("SAX exception while parsing i18n dictionary {}: {}", url, e.getMessage());
} catch (IOException e) {
logger.warn("IO exception while parsing i18n dictionary {}: {}", url, e.getMessage());
}
}
}