io.apiman.tools.i18n.TemplateScanner.java Source code

Java tutorial

Introduction

Here is the source code for io.apiman.tools.i18n.TemplateScanner.java

Source

/*
 * Copyright 2015 JBoss Inc
 *
 * 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 io.apiman.tools.i18n;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.apache.commons.io.FileUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;

/**
 * Used to scan all of the UI templates looking for strings that require
 * translation. The output from this scanner is a messages.properties file with
 * all of the strings that require translation. This should then be compared
 * with the messages.properties file included in the API Manager UI project to
 * see if anything is missing.
 *
 * TODO: integrate this into a maven plugin of some kind to automatically detect
 * missing strings during the build
 */
@SuppressWarnings("nls")
public class TemplateScanner {

    private static final Set<String> TRANSLATABLE_ATTRIBUTES = new HashSet<>();
    static {
        TRANSLATABLE_ATTRIBUTES.add("title");
        TRANSLATABLE_ATTRIBUTES.add("placeholder");
    }

    public static void main(String[] args) throws IOException {
        if (args == null || args.length != 1) {
            System.out.println("Template directory not provided (no path provided).");
            System.exit(1);
        }
        File templateDir = new File(args[0]);
        if (!templateDir.isDirectory()) {
            System.out.println("Template directory not provided (provided path is not a directory).");
            System.exit(1);
        }

        if (!new File(templateDir, "dash.html").isFile()) {
            System.out.println("Template directory not provided (dash.html not found).");
            System.exit(1);
        }

        File outputDir = new File(templateDir, "../../../../../../tools/i18n/target");
        if (!outputDir.isDirectory()) {
            System.out.println("Output directory not found: " + outputDir);
            System.exit(1);
        }
        File outputFile = new File(outputDir, "scanner-messages.properties");
        if (outputFile.isFile() && !outputFile.delete()) {
            System.out.println("Couldn't delete the old messages.properties: " + outputFile);
            System.exit(1);
        }

        System.out.println("Starting scan.");
        System.out.println("Scanning template directory: " + templateDir.getAbsolutePath());

        String[] extensions = { "html", "include" };
        Collection<File> files = FileUtils.listFiles(templateDir, extensions, true);

        TreeMap<String, String> strings = new TreeMap<>();

        for (File file : files) {
            System.out.println("\tScanning file: " + file);
            scanFile(file, strings);
        }

        outputMessages(strings, outputFile);

        System.out.println("Scan complete.  Scanned " + files.size() + " files and discovered " + strings.size()
                + " translation strings.");
    }

    /**
     * Scan the given html template using jsoup and find all strings that require translation.  This is
     * done by finding all elements with a "apiman-i18n-key" attribute.
     * @param file
     * @param strings
     * @throws IOException
     */
    private static void scanFile(File file, TreeMap<String, String> strings) throws IOException {
        Document doc = Jsoup.parse(file, "UTF-8");

        // First, scan for elements with the 'apiman-i18n-key' attribute.  These require translating.
        Elements elements = doc.select("*[apiman-i18n-key]");
        for (Element element : elements) {
            String i18nKey = element.attr("apiman-i18n-key");
            boolean isNecessary = false;

            // Process the element text (if the element has no children)
            if (strings.containsKey(i18nKey)) {
                if (hasNoChildren(element)) {
                    isNecessary = true;
                    String elementVal = element.text();
                    if (elementVal.trim().length() > 0 && !elementVal.contains("{{")) {
                        String currentValue = strings.get(i18nKey);
                        if (!currentValue.equals(elementVal)) {
                            throw new IOException("Duplicate i18n key found with different default values.  Key="
                                    + i18nKey + "  Value1=" + elementVal + "  Value2=" + currentValue);
                        }
                    }
                }
            } else {
                if (hasNoChildren(element)) {
                    String elementVal = element.text();
                    if (elementVal.trim().length() > 0 && !elementVal.contains("{{")) {
                        isNecessary = true;
                        strings.put(i18nKey, elementVal);
                    }
                }
            }

            // Process the translatable attributes
            for (String tattr : TRANSLATABLE_ATTRIBUTES) {
                if (element.hasAttr(tattr)) {
                    String attrValue = element.attr(tattr);
                    if (attrValue.contains("{{")) {
                        continue;
                    }
                    String attrI18nKey = i18nKey + '.' + tattr;
                    String currentAttrValue = strings.get(attrI18nKey);
                    if (currentAttrValue == null) {
                        isNecessary = true;
                        strings.put(attrI18nKey, attrValue);
                    } else if (!currentAttrValue.equals(attrValue)) {
                        throw new IOException(
                                "Duplicate i18n key found with different default values (for attribute '" + tattr
                                        + "').  Key=" + attrI18nKey + "  Value1=" + attrValue + "  Value2="
                                        + currentAttrValue);
                    } else {
                        isNecessary = true;
                    }
                }
            }

            if (!isNecessary) {
                throw new IOException("Detected an unnecessary apiman-i18n-key attribute in file '" + file.getName()
                        + "' on element: " + element);
            }
        }

        // Next, scan all elements to see if the element *should* be marked for translation
        elements = doc.select("*");
        for (Element element : elements) {
            if (element.hasAttr("apiman-i18n-key") || element.hasAttr("apiman-i18n-skip")) {
                continue;
            }
            if (hasNoChildren(element)) {
                String value = element.text();
                if (value != null && value.trim().length() > 0) {
                    if (!value.contains("{{")) {
                        throw new IOException("Found an element in '" + file.getName()
                                + "' that should be translated:  " + element);
                    }
                }
            }
        }

        // Next scan elements with a translatable attribute and fail if any of those elements
        // are missing the apiman-i18n-key attribute.
        for (String tattr : TRANSLATABLE_ATTRIBUTES) {
            elements = doc.select("*[" + tattr + "]");
            for (Element element : elements) {
                if (element.hasAttr("apiman-i18n-key") || element.hasAttr("apiman-i18n-skip")
                        || element.attr(tattr).contains("{{")) {
                    continue;
                } else {
                    throw new IOException("In template '" + file.getName() + "', found an element with a '" + tattr
                            + "' attribute but missing 'apiman-i18n-key': " + element);
                }
            }
        }

    }

    /**
     * @param element
     * @return true if the element doesn't have any child elements
     */
    private static boolean hasNoChildren(Element element) {
        List<Node> childNodes = element.childNodes();
        for (Node node : childNodes) {
            if (node instanceof Element) {
                return false;
            }
        }
        return true;
    }

    /**
     * Output the sorted map of strings to the specified output file.
     * @param strings
     * @param outputFile
     * @throws FileNotFoundException
     */
    private static void outputMessages(TreeMap<String, String> strings, File outputFile)
            throws FileNotFoundException {
        PrintWriter writer = new PrintWriter(new FileOutputStream(outputFile));
        for (Entry<String, String> entry : strings.entrySet()) {
            String key = entry.getKey();
            String val = entry.getValue();
            writer.append(key);
            writer.append('=');
            writer.append(val);
            writer.append("\n");
        }
        writer.flush();
        writer.close();
    }

}