org.texconverter.TexConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.texconverter.TexConverter.java

Source

/**
 * $Revision: 1.3 $
 * $Date: 2006/09/26 09:31:36 $
 *
 * ====================================================================
 * TexConverter
 * Copyright (C) 2006 - NEUSTA GmbH Bremen, Germany
 *
 * 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.
 *
 * created: 02.08.2006 tfrana
 */
package org.texconverter;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.texconverter.dom.Node;
import org.texconverter.dom.NodeWithChilds;
import org.texconverter.dom.Picture;
import org.texconverter.dom.TexDocument;
import org.texconverter.reader.tex.builder.TeXDocumentBuilder;
import org.texconverter.reader.tex.builder.impl.BuilderFactory;
import org.texconverter.reader.tex.exceptions.ConfigurationException;
import org.texconverter.reader.tex.exceptions.RendererException;
import org.texconverter.reader.tex.exceptions.TexSyntaxException;
import org.texconverter.renderer.Renderer;
import org.texconverter.renderer.velocity.VelocityRenderer;
import org.texconverter.renderer.velocity.WikiWickedVelocityRenderer;
import org.texconverter.resource.ResourceLoader;
import org.texconverter.resource.ResourceManager;

/**
 * @author sgodau, tfrana
 */
public abstract class TexConverter {

    /**
     * Apache commons logging reference.
     */
    private final static Logger LOGGER = LoggerFactory.getLogger(TexConverter.class);

    private static final String SUBFOLDER_IMAGES = "images";

    public static final String PROP_PROJECTS = "texconverter.projects";

    public static final String PROP_INPUT_FILE = "inputFileName";

    public static final String PROP_OUTPUT_FILE = "outputFileName";

    public static final String PROP_OUTPUT_FORMAT = "outputFormatName";

    public static final String PROP_LOCALECODE = "localeCode";

    public static final String COMMANDDEFS_FILE = "cmdDefs.xml";

    public static String RESOURCES_PATH = "resources/";

    /**
     * @author tfrana
     */
    public static enum OUTPUTFORMATS {
        /** HTML output. */
        HTML(VelocityRenderer.class, new String[] { "JPG", "JPEG", "PNG", "GIF", "BMP" }),
        /** Xdoc output. */
        XDOC(VelocityRenderer.class, new String[] { "JPG", "JPEG", "PNG", "GIF", "BMP" }),
        /** Wicked Wiki output format. */
        WIKI_WICKED(WikiWickedVelocityRenderer.class, new String[] { "JPG", "JPEG", "PNG", "GIF", "BMP" });

        private final Class rendererClass;

        private final String[] validPictureFormats;

        private OUTPUTFORMATS(final Class rendererClass, final String[] validPictureFormats) {
            assert (Renderer.class.isAssignableFrom(rendererClass));
            this.rendererClass = rendererClass;
            this.validPictureFormats = validPictureFormats;
        }

        /**
         * @return a Class implementing {@link Renderer} to render this output
         *         format
         */
        public Class getRendererClass() {
            return rendererClass;
        }

        /**
         * @return An array with the file extensions of picture formats that are
         *         usable by the output document format. Preferred picture
         *         formats are first in the array.
         */
        public String[] getValidPictureFormats() {
            return validPictureFormats;
        }
    }

    private String resourceDir = "";

    protected boolean isInitialized = false;

    /**
     * Standard constructor.
     */
    public TexConverter() {
        ResourceManager.getInstance().addResourceLoader(getResourceLoader());
    }

    protected void init(final String baseDir, final boolean verbose) {
        String basDir = baseDir;

        if (!isInitialized) {
            if (basDir == null) {
                basDir = RESOURCES_PATH;
            } else if (basDir.length() > 0) {
                basDir = convertSlashes(basDir);
                if (!basDir.endsWith("/")) {

                    basDir += "/";
                }
            }

            this.resourceDir = basDir;

            isInitialized = true;
        }
    }

    /**
     * Start conversion. This will simply call
     * {@link #startSingleConversion(String, String, String, Locale)} if the
     * propertiesFile parameter is <code>null</code>, otherwise
     * {@link #startConversionFromProperties(String)} will be called.
     * 
     * @param propertiesFile
     *            Path to the properties file containing the conversion
     *            properties. If <code>null</code>, the other paramaters will
     *            be used.
     * @param inputFilePath
     *            Path of the input TeX file. Only used when propertiesFile is
     *            <code>null</code>.
     * @param outputFilePath
     *            Path of the output file. Only used when propertiesFile is
     *            <code>null</code>.
     * @param outputFormatName
     *            To what output format to render. Only used when propertiesFile
     *            is <code>null</code>.
     * @param localeCode
     *            the ISO-3-letter language code, for localized resources
     */
    public void startConversion(final String propertiesFile, final String inputFilePath,
            final String outputFilePath, final String outputFormatName, final String localeCode) {

        try {
            if (propertiesFile != null) {
                startConversionFromProperties(propertiesFile);
            } else {

                startSingleConversion(inputFilePath, outputFilePath, outputFormatName, getLocale(localeCode));
            }
        } catch (final IOException ex) {
            LOGGER.error(ex.getMessage(), ex);
        }
        LOGGER.info("Finished all conversions.");
    }

    /**
     * Read one or several conversion projects from a properties file, and
     * execute each one.
     * 
     * @param propertiesFilePath
     *            the filepath
     * @param verbose
     * @param locale
     * @throws IOException
     * @throws FileNotFoundException
     */
    private void startConversionFromProperties(final String propertiesFilePath)
            throws FileNotFoundException, IOException {

        final Properties props = new Properties();
        final InputStream istream = new FileInputStream(propertiesFilePath);
        props.load(istream);
        istream.close();

        String[] projects = null;
        if (!props.containsKey(PROP_PROJECTS)) {
            LOGGER.error("No property " + PROP_PROJECTS + " found!");
        } else {
            projects = props.getProperty(PROP_PROJECTS).split(",");
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("" + projects.length + " conversion projects found.");
            }

            boolean usePropsDirAsCwd = false;
            File inputFile, outputFile;
            String inputFilePath, outputFilePath;
            String outputFormatName, localeCode;
            File propsDir = new File(propertiesFilePath).getParentFile();

            for (int i = 0; i < projects.length; i++) {
                inputFilePath = props.getProperty(projects[i] + "." + PROP_INPUT_FILE);
                if (inputFilePath != null) {
                    inputFile = new File(inputFilePath);
                    if (!inputFile.isAbsolute() && !inputFile.exists()) {
                        // input file not found, try relative to props dir
                        inputFile = new File(propsDir, inputFile.getPath());
                        if (inputFile.exists()) {
                            usePropsDirAsCwd = true;
                            inputFilePath = inputFile.getAbsolutePath();
                        }
                    }
                }

                outputFilePath = props.getProperty(projects[i] + "." + PROP_OUTPUT_FILE);
                if (outputFilePath != null) {
                    outputFile = new File(props.getProperty(projects[i] + "." + PROP_OUTPUT_FILE));
                    if (!outputFile.isAbsolute() && usePropsDirAsCwd) {
                        outputFile = new File(propsDir, outputFile.getPath());
                    }
                }

                outputFormatName = props.getProperty(projects[i] + "." + PROP_OUTPUT_FORMAT);

                localeCode = props.getProperty(projects[i] + "." + PROP_LOCALECODE);

                startSingleConversion(inputFilePath, outputFilePath, outputFormatName, getLocale(localeCode));
            }
        }
    }

    private boolean startSingleConversion(final String inputFilePath, final String outputFilePath,
            final String outputFormatType, final Locale locale) {
        boolean success = false;
        String outputFormatName = outputFormatType;
        if (outputFormatName == null) {
            outputFormatName = "html";
        } else {
            outputFormatName = outputFormatName.trim();
        }

        if (inputFilePath == null) {
            LOGGER.error("no inputFileName specified");
        } else if (outputFilePath == null) {
            LOGGER.error("no outputFileName specified");
        } else {

            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Starting conversion: Input file " + inputFilePath + ", output file " + outputFilePath
                        + ", using format " + outputFormatName);
            }

            File inputFile = new File(inputFilePath);
            File outputFile = new File(outputFilePath);

            try {
                OUTPUTFORMATS outputFormat = null;
                try {
                    outputFormat = OUTPUTFORMATS.valueOf(outputFormatName.toUpperCase());
                } catch (final IllegalArgumentException e) {
                    LOGGER.error("Unknown output format name \"" + outputFormatName + "\"");
                    throw new RendererException(e);
                }

                TeXDocumentBuilder builder;
                try {
                    builder = BuilderFactory.getInstance().getTeXDocumentBuilder(resourceDir + COMMANDDEFS_FILE,
                            locale);
                } catch (final Exception e) {
                    LOGGER.error("Exception while accessing command definitions!", e);
                    throw new RendererException(e);
                }

                // creating TexDocument from input
                TexDocument texdoc = null;
                try {
                    if (LOGGER.isInfoEnabled()) {
                        LOGGER.info("Parsing file " + inputFilePath);
                    }
                    texdoc = builder.build(new File(inputFilePath));
                } catch (final TexSyntaxException e) {
                    LOGGER.error("Exception while parsing input file!", e);
                    throw new RendererException(e);
                } catch (final IOException e) {
                    LOGGER.error("Exception while accessing input file!", e);
                    throw new RendererException(e);
                } catch (final Exception e) {
                    LOGGER.error("Exception while building tex dom!", e);
                    throw new RendererException(e);
                }

                // rendering TexDocument
                LOGGER.debug("Creating new instance of Renderer class " + outputFormat.getRendererClass());

                Renderer renderer;
                try {
                    renderer = (Renderer) outputFormat.getRendererClass().newInstance();
                } catch (final InstantiationException e) {
                    final String msg = "Renderer class " + outputFormat.getRendererClass()
                            + " could not be instantiated";
                    LOGGER.error(msg, e);
                    throw new RendererException(e);
                } catch (final IllegalAccessException e) {
                    final String msg = "Renderer class " + outputFormat.getRendererClass()
                            + " could not be accessed";
                    LOGGER.error(msg, e);
                    throw new RendererException(e);
                }

                checkAndCopyReferencedImages(texdoc, inputFile, outputFile, outputFormat);

                String result = null;
                try {
                    if (LOGGER.isInfoEnabled()) {
                        LOGGER.info("Rendering text dom to output format " + outputFormatName);
                    }

                    result = renderer.render(outputFormat, texdoc, resourceDir);
                } catch (final RendererException e) {
                    LOGGER.error("RendererException while rendering tex dom!", e);
                    throw new RendererException(e);
                } catch (final ConfigurationException e) {
                    LOGGER.error("Error on configuring Renderer", e);
                    throw new RendererException(e);
                }

                // writing output
                try {
                    if (LOGGER.isInfoEnabled()) {
                        LOGGER.info("Writing output to file " + outputFilePath);
                    }
                    outputFile.getParentFile().mkdirs();
                    final FileWriter tmpWriter = new FileWriter(outputFile, false);
                    tmpWriter.write(result);
                    tmpWriter.flush();
                    tmpWriter.close();
                } catch (final IOException e) {
                    LOGGER.error("Exception while accessing output file!", e);
                    throw new RendererException(e);
                }

                success = true;

            } catch (final RendererException e) {
                // using this exception type for flow control above, already
                // logged and handled
            }
        }

        if (LOGGER.isInfoEnabled()) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Conversion finished.");
            }
        }

        return success;
    }

    /**
     * @return the {@link ResourceLoader} used for accessing application
     *         resources
     */
    protected abstract ResourceLoader getResourceLoader();

    /**
     * Checks if node is a picture or has any childs which are pictures
     * (recursive). If yes, check where the file resides, choose correct
     * extension (log error if no fitting one is available), copy it to the
     * target directory, and adjust the path in the picture node accordingly.
     * 
     * @param aNode
     *            the node
     * @param inputFile
     *            used to find picture files
     * @param outputFile
     *            used to determine target location
     * @param outputFormat
     *            used to determine valid/preferred extensions
     */
    private static void checkAndCopyReferencedImages(final Node aNode, final File inputFile, final File outputFile,
            final OUTPUTFORMATS outputFormat) {
        if (aNode instanceof Picture) {

            File imageFile = new File(((Picture) aNode).getPath());
            if (!imageFile.isAbsolute()) {
                imageFile = new File(inputFile.getParentFile(), imageFile.getPath());
            }

            // find extension if none is specified
            if (imageFile.getName().indexOf('.') == -1) {
                String chosenExtension = null;

                // find available extensions
                final Set<String> extensions = getAvailableExtensions(imageFile);
                // is there a format usable for the current output format
                final String[] formats = outputFormat.getValidPictureFormats();
                for (int i = 0; i < formats.length; i++) {
                    if (extensions.contains(formats[i])) {
                        chosenExtension = formats[i];
                        break;
                    }
                }

                if (chosenExtension == null) {
                    // no valid extension found
                    LOGGER.error("No valid format found for picture " + imageFile.getName()
                            + " (accepted extensions for output format " + outputFormat + " are: "
                            + Arrays.toString(outputFormat.getValidPictureFormats()) + ")");
                    imageFile = null;
                } else {
                    imageFile = new File(imageFile.getPath() + "." + chosenExtension);
                }
            }

            // copy image file and update picture node with new path
            if (imageFile != null) {
                try {
                    FileUtils.copyFileToDirectory(imageFile,
                            new File(outputFile.getParentFile(), SUBFOLDER_IMAGES));
                    ((Picture) aNode).setPath(SUBFOLDER_IMAGES + "/" + imageFile.getName());

                } catch (final IOException e) {
                    LOGGER.warn("Exception while copying image " + imageFile.getName() + "!", e);
                }
            }
        } else if (aNode instanceof NodeWithChilds) {

            // check child nodes
            final NodeWithChilds tmpNode = (NodeWithChilds) aNode;
            for (int i = 0; tmpNode.getChildren() != null && i < tmpNode.getNumChildren(); i++) {
                checkAndCopyReferencedImages(tmpNode.getChild(i), inputFile, outputFile, outputFormat);
            }
        }
    }

    private static Set<String> getAvailableExtensions(final File pic) {

        final Set<String> extensions = new HashSet<String>();

        if (!pic.isDirectory()) {
            File dir = pic.getParentFile();
            if (dir == null) {
                dir = new File(".");
            }

            dir.list(new FilenameFilter() {

                public boolean accept(final File dir, final String name) {

                    final int extoffset = name.lastIndexOf('.');
                    if (extoffset != -1 && pic.getName().equals(name.substring(0, extoffset))) {
                        extensions.add(name.substring(extoffset + 1).toUpperCase());
                    }
                    return false;
                }
            });
        }

        return extensions;
    }

    private static String convertSlashes(final String path) {
        String tmpPath = path;
        if (tmpPath != null) {
            int tmpIndex = tmpPath.indexOf('\\');
            while (tmpIndex != -1) {
                tmpPath = tmpPath.substring(0, tmpIndex) + "/" + tmpPath.substring(tmpIndex + 1, tmpPath.length());
                tmpIndex = tmpPath.indexOf('\\');
            }
        }
        return tmpPath;
    }

    private static Locale getLocale(final String localeCode) {
        Locale locale = null;

        if (localeCode != null) {
            Locale[] locales = Locale.getAvailableLocales();
            for (int i = 0; i < locales.length; i++) {
                if (localeCode.equalsIgnoreCase(locales[i].getISO3Language())) {
                    locale = locales[i];
                    break;
                }
            }
            if (locale == null) {
                locale = Locale.getDefault();
                LOGGER.warn("getLocale -> unknown locale code " + localeCode + ", using system locale "
                        + locale.getISO3Language() + " instead.");
            }
        } else {
            locale = Locale.getDefault();
        }

        return locale;
    }
}