net.java.hulp.i18n.buildtools.I18nXmlTask Maven / Gradle / Ivy
The newest version!
// Copyright (c) 2007 Sun Microsystems
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
package net.java.hulp.i18n.buildtools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import java.util.Vector;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Generates internationalized versions of XML files.
* @author Kevan Simpson
*/
public class I18nXmlTask extends Task {
private static final String COMMENTS = "Default I18n properties bundle";
private static final String PROP_EXT = ".properties";
private static final String XML_EXT = ".xml";
private static DocumentBuilderFactory mFactory = DocumentBuilderFactory.newInstance();
private static XPathFactory mXPathFactory = XPathFactory.newInstance();
private static TransformerFactory mTransFactory = TransformerFactory.newInstance();
private File mFile, mProperties, mOutputDir;
private FileSet mFileSet;
private Vector mNamespaces = new Vector();
private Vector mXPathExprs = new Vector();
private DocumentBuilder mBuilder;
/** Default void constructor. */
public I18nXmlTask() {
mFactory.setNamespaceAware(true);
}
/** @see org.apache.tools.ant.Task#execute() */
public void execute() throws BuildException {
// validate task attributes
validateTaskAttrs(); // don't catch possible BuildException
try {
// initialize DocumentBuilder
mBuilder = mFactory.newDocumentBuilder();
// parse file to acquire i18n'able properties
Properties xmlFile = parseXmlFileToProperties(mFile),
i18n = new Properties();
if (mProperties.exists()) {
i18n.load(new FileInputStream(mProperties));
// compare against existing version
if (xmlFile.equals(i18n)) { // property values are current, generate i18n files
i18nFiles(mFileSet);
return;
}
}
// write jbi to mProperties
xmlFile.store(new FileOutputStream(mProperties), COMMENTS);
}
catch (Exception e) {
throw new BuildException("Failed to i18n XML files: "+
e.getMessage(), e, getLocation());
}
}
/**
* Returns an XPath
instance configured with user-defined
* namespaces.
* @return an XPath
instance.
*/
XPath newXPath() {
XPath xpath = mXPathFactory.newXPath();
SimpleNSContext ctx = new SimpleNSContext();
// add namespaces configured on task
for (Namespace ns : mNamespaces) {
ctx.addNS(ns.getPrefix(), ns.getUri());
}
xpath.setNamespaceContext(ctx);
return xpath;
}
/**
* Generates properties for i18n attributes.
*
* @param xmlFile The xml file to parse for default locale values.
* @return A set of i18n properties
* @throws Exception if an error occurs parsing file or building properties.
*/
Properties parseXmlFileToProperties(File xmlFile) throws Exception {
Properties props = new Properties();
Element elem = readFile(xmlFile);
if (elem != null) {
XPath xpath = newXPath();
// for every 'expr', get all nodes matching query
for (Expr expr : mXPathExprs) {
Object result = xpath.evaluate(expr.getQuery(),
elem,
XPathConstants.NODESET);
if (result instanceof NodeList) {
NodeList list = (NodeList) result;
// for every node matching query, add property
for (int i = 0, n = list.getLength(); i < n; i++) {
createPropertyFromElement((Element) list.item(i), props,
expr, xpath.getNamespaceContext());
}
}
else if (result instanceof Node) {
createPropertyFromElement((Element) result, props,
expr, xpath.getNamespaceContext());
}
}
}
return props;
}
/**
* Adds a property, the value of which will be localized.
* @param elem The element containing the default value to be localized.
* @param props The localizable properties.
* @param expr The XPath expression identifying the element.
* @param ctx The namespace context of the XPath engine.
* @throws Exception if an error occurs creating the property.
*/
void createPropertyFromElement(Element elem, Properties props,
Expr expr, NamespaceContext ctx)
throws Exception {
String i18nAttr = expr.getI18nAttr();
if (i18nAttr == null || i18nAttr.length() == 0) {
props.put(buildExpr(elem, expr, ctx),
elem.getTextContent().trim()); // does this work?
}
else {
props.put(buildExpr(elem, expr, ctx) +"/@"+ expr.getI18nAttr(),
elem.getAttribute(expr.getI18nAttr()));
}
}
/**
* Walks XML tree upwards to root, building XPath query.
*
* @param elem The leaf element, from which walking tree begins.
* @param expr An Expr
defined in task.
* @param ctx A map of namespaces defined in task.
* @return An XPath query which will resolve to specified element.
* @throws Exception if an error occurs building query.
*/
String buildExpr(Element elem, Expr expr, NamespaceContext ctx) throws Exception {
// walk up tree to root element
Stack stack = new Stack();
Node node = elem;
while (node instanceof Element) {
String nodeName = node.getNodeName();
String prefix = ctx.getPrefix(node.getNamespaceURI());
int ix = nodeName.indexOf(":");
if (ix > 0) { // already qualified with prefix
// remove prefix and substitute with ns defined on task
nodeName = prefix + nodeName.substring(ix);
}
else {
nodeName = prefix +":"+ nodeName;
}
// push properly prefixed node name on stack
stack.push(nodeName);
node = node.getParentNode();
}
// build element steps
StringBuffer buff = new StringBuffer();
while (!stack.isEmpty()) {
buff.append("/").append(stack.pop());
}
// append key predicate
String keyAttr = expr.getKeyAttr();
if (keyAttr != null && keyAttr.length() > 0) {
buff.append("[@").append(keyAttr).append("='")
.append(elem.getAttribute(keyAttr)).append("']");
}
return buff.toString();
}
/**
* Generates localized xml files based on the localized
* properties files described by specified FileSet
.
*
* @param fileSet Set of localized properties files.
* @throws Exception if an error occurs generating localized xml files.
*/
void i18nFiles(FileSet fileSet) throws Exception {
DirectoryScanner ds = fileSet.getDirectoryScanner(getProject());
File dir = ds.getBasedir();
// read in local copy of document, since we're changing it
Element jbi = readFile(mFile);
XPath xpath = newXPath();
for (String filename : ds.getIncludedFiles()) {
File file = new File(dir, filename);
if (!file.equals(mProperties)) {
// read in i18n properties
Properties p = new Properties();
p.load(new FileInputStream(file));
// set values on nodes = property key
for (Object obj : p.keySet()) {
String key = String.valueOf(obj);
Object result = xpath.evaluate(key, jbi, XPathConstants.NODE);
if (result instanceof Attr) {
Attr attr = (Attr) result;//xpath.evaluate(key, jbi, XPathConstants.NODE);
if (attr == null) {
System.err.println("Failed to set value for attribute: "+ key);
}
else {
attr.setNodeValue(p.getProperty(key));
}
}
else if (result instanceof Element) {
Element elem = (Element) result;
elem.setTextContent(p.getProperty(key)); // does this work?
}
}
// generate xml to write
String xml = toXml(jbi);
// determine generated filename
String prop = mProperties.getName();
String base = prop.substring(0, prop.indexOf(PROP_EXT));
String genFilename = getFile().getName();
genFilename = genFilename.substring(0, genFilename.indexOf(XML_EXT));
String suffix = filename.substring(base.length(), filename.lastIndexOf(PROP_EXT));
// write file
FileOutputStream out = null;
try {
out = new FileOutputStream(new File(mOutputDir, genFilename + suffix + XML_EXT));
out.write(xml.getBytes("UTF-8"));
}
finally {
I18NTask.safeClose(out);
}
}
}
}
private Element readFile(File descriptor) throws Exception {
Document doc = mBuilder.parse(descriptor);
return (doc != null) ? doc.getDocumentElement() : null;
}
/* ANT TASK ACCESSORS + MUTATORS */
public void addConfiguredExpr(Expr expr) {
mXPathExprs.add(expr);
}
public void addConfiguredNamespace(Namespace ns) {
mNamespaces.add(ns);
}
public void addFileSet(FileSet fileset) {
mFileSet = fileset;
}
public FileSet getFileSet() {
return mFileSet;
}
/**
* @return the file
*/
public File getFile() {
return mFile;
}
/**
* @param file the file to set
*/
public void setFile(File file) {
mFile = file;
}
/**
* @return the properties
*/
public File getProperties() {
return mProperties;
}
/**
* @param properties the properties to set
*/
public void setProperties(File properties) {
mProperties = properties;
}
/**
* @return the outputDir
*/
public File getOutputDir() {
return mOutputDir;
}
/**
* @param outputDir the outputDir to set
*/
public void setOutputDir(File outputDir) {
mOutputDir = outputDir;
}
void validateTaskAttrs() throws BuildException {
if (mFile == null) {
throw new BuildException("XML file must be specified!");
}
else if (!mFile.exists()) {
throw new BuildException("XML file does not exist at: "+
mFile.getAbsolutePath());
}
if (mFileSet == null) {
throw new BuildException("Task must include FileSet!");
}
if (mProperties == null) { // default properties file
throw new BuildException("Properties file must be specified!");
}
if (mOutputDir == null) {
throw new BuildException("Output Directory must be specified!");
}
else if (!mOutputDir.exists()) {
throw new BuildException("Output Directory does not exist: "+
mOutputDir.getAbsolutePath());
}
else if (!mOutputDir.exists()) {
throw new BuildException("Output Directory must be a directory: "+
mOutputDir.getAbsolutePath());
}
}
static String toXml(Element elem) throws Exception {
Transformer transformer = null;
synchronized(mTransFactory) {
transformer = mTransFactory.newTransformer();
}
StringWriter writer = new StringWriter();
DOMSource src = new DOMSource(elem);
StreamResult dest = new StreamResult(writer);
transformer.transform(src, dest);
return writer.toString();
}
/** Factory method for creating nested 'namespace's. */
public Expr createExpr() {
Expr xp = new Expr();
mXPathExprs.add(xp);
return xp;
}
/** Factory method for creating nested 'namespace's. */
public Namespace createNamespace() {
Namespace ns = new Namespace();
mNamespaces.add(ns);
return ns;
}
/** A nested 'namespace'. */
public class Namespace {
private String mPrefix, mUri;
// Bean constructor
public Namespace() {}
public String getPrefix() { return mPrefix; }
public String getUri() { return mUri; }
public void setPrefix(String prefix) { mPrefix = prefix; }
public void setUri(String uri) { mUri = uri; }
public String toString() { return "NS["+ getPrefix() +"="+ getUri() +"]"; }
}
/** A nested XPath expr. */
public class Expr {
private String mQuery, mI18nAttr, mKeyAttr;
// Bean constructor
public Expr() {}
public String getQuery() {
return mQuery;
}
public void setQuery(String query) {
mQuery = query;
}
public String getI18nAttr() {
return mI18nAttr;
}
public void setI18nAttr(String attr) {
mI18nAttr = attr;
}
public String getKeyAttr() {
return mKeyAttr;
}
public void setKeyAttr(String keyAttr) {
mKeyAttr = keyAttr;
}
public String toString() { return "Expr["+ getQuery() +"]"; }
}
private static class SimpleNSContext implements NamespaceContext {
private Map mNsMap;
public SimpleNSContext() {
mNsMap = new HashMap();
}
public void addNS(String prefix, String uri) {
mNsMap.put(prefix, uri);
}
/** @see javax.xml.namespace.NamespaceContext#getNamespaceURI(java.lang.String) */
public String getNamespaceURI(String prefix) {
return mNsMap.get(prefix);
}
/** @see javax.xml.namespace.NamespaceContext#getPrefix(java.lang.String) */
public String getPrefix(String namespaceURI) {
if (namespaceURI != null) {
for (String p : mNsMap.keySet()) {
String ns = mNsMap.get(p);
if (namespaceURI.equals(ns)) {
return p;
}
}
}
return null;
}
/** @see javax.xml.namespace.NamespaceContext#getPrefixes(java.lang.String) */
public Iterator getPrefixes(String namespaceURI) {
List prefixes = new ArrayList();
if (namespaceURI != null) {
for (String p : mNsMap.keySet()) {
String ns = mNsMap.get(p);
if (namespaceURI.equals(ns)) {
prefixes.add(p);
}
}
}
return prefixes.iterator();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy