All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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