org.obeonetwork.m2doc.generator.TemplateProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.obeonetwork.m2doc.generator.TemplateProcessor.java

Source

/*******************************************************************************
 *  Copyright (c) 2016 Obeo. 
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  which accompanies this distribution, and is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *   
 *   Contributors:
 *       Obeo - initial API and implementation
 *  
 *******************************************************************************/
package org.obeonetwork.m2doc.generator;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.Document;
import org.apache.poi.xwpf.usermodel.IBody;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFHeaderFooter;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.eclipse.acceleo.query.runtime.EvaluationResult;
import org.eclipse.acceleo.query.runtime.IQueryBuilderEngine.AstResult;
import org.eclipse.acceleo.query.runtime.IQueryEnvironment;
import org.eclipse.acceleo.query.runtime.IQueryEvaluationEngine;
import org.eclipse.acceleo.query.runtime.impl.QueryEvaluationEngine;
import org.eclipse.emf.common.util.Diagnostic;
import org.obeonetwork.m2doc.template.AbstractConstruct;
import org.obeonetwork.m2doc.template.Cell;
import org.obeonetwork.m2doc.template.Conditionnal;
import org.obeonetwork.m2doc.template.Image;
import org.obeonetwork.m2doc.template.Query;
import org.obeonetwork.m2doc.template.Repetition;
import org.obeonetwork.m2doc.template.Row;
import org.obeonetwork.m2doc.template.StaticFragment;
import org.obeonetwork.m2doc.template.Table;
import org.obeonetwork.m2doc.template.Template;
import org.obeonetwork.m2doc.template.util.TemplateSwitch;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;

/**
 * The {@link TemplateProcessor} class implements a switch over template that generates the doc.
 * 
 * @author Romain Guider
 */
public class TemplateProcessor extends TemplateSwitch<AbstractConstruct> {
    /**
     * constant defining the color of error messages.
     */
    private static final String ERROR_COLOR = "FF0000";
    /**
     * The path to the root project where the template is located.
     */
    private String rootProjectPath;
    /**
     * The {@link IQueryEnvironment} instance used for evaluating all the AQL
     * queries found in the template.
     */
    private IQueryEnvironment queryEnvironment;

    /**
     * variable definition used during generation.
     */
    private GenerationEnvironment definitions;
    /**
     * The generated document.
     */
    private IBody generatedDocument;
    /**
     * The currently read template paragraph used to detect paragraph changes.
     */
    private XWPFParagraph currentTemplateParagraph;
    /**
     * The currently generated paragraph where runs are actually inserted.
     */
    private XWPFParagraph currentGeneratedParagraph;
    /**
     * Used to force a new paragraph in gf:for body when there's a carriage
     * return before the {gd:endfor} tag.
     */
    private boolean forceNewParagraph;

    /**
     * Create a new {@link TemplateProcessor} instance given some definitions
     * and a query environment.
     * 
     * @param initialDefs
     *            the definitions used in queries and variable tags
     * @param projectPath
     *            the path to the project where the template is located.
     * @param queryEnvironment
     *            the query environment used to evaluate queries in the
     * @param destinationDocument
     *            the path to the destination document.
     */
    public TemplateProcessor(Map<String, Object> initialDefs, String projectPath,
            IQueryEnvironment queryEnvironment, IBody destinationDocument) {
        this.rootProjectPath = projectPath;
        this.definitions = new GenerationEnvironment(initialDefs);
        this.queryEnvironment = queryEnvironment;
        this.generatedDocument = destinationDocument;
    }

    /**
     * Create a new {@link TemplateProcessor} instance given some definitions
     * and a query environment.
     * 
     * @param defs
     *            the definitions used in queries and variable tags
     * @param queryEnvironment
     *            the query environment used to evaluate queries in the
     *            template.
     * @param destinationDocument
     *            the path to the destination document.
     */
    public TemplateProcessor(GenerationEnvironment defs, IQueryEnvironment queryEnvironment,
            IBody destinationDocument) {
        this.definitions = defs;
        this.queryEnvironment = queryEnvironment;
        this.generatedDocument = destinationDocument;
    }

    /**
     * returns the diagnostic associated to the {@link Diagnostic} instance and its children.
     * 
     * @param diagnostic
     *            the {@link Diagnostic} in which searching
     * @param builder
     *            a string builder that aggregate the messages
     * @return the diagnostic status of the specified diagnostic tree.
     */
    private int getDiagnostic(Diagnostic diagnostic, StringBuilder builder) {
        String message;
        int code;
        if (diagnostic.getCode() == Diagnostic.ERROR) {
            message = diagnostic.getMessage();
            code = Diagnostic.ERROR;
        } else {
            message = diagnostic.getMessage();
            code = diagnostic.getCode();
            for (Diagnostic child : diagnostic.getChildren()) {
                int childrenCode = getDiagnostic(child, builder);
                if (childrenCode > code) {
                    code = childrenCode;
                }
            }
        }
        if (message != null) {
            if (builder.length() > 0) {
                builder.append('\n');
            }
            builder.append(message);
        }
        return code;
    }

    @Override
    public AbstractConstruct caseTemplate(Template object) {
        List<AbstractConstruct> subConstructs = object.getSubConstructs();
        for (int i = 0; i < subConstructs.size(); i++) {
            doSwitch(subConstructs.get(i));
        }
        return object;
    }

    @Override
    public AbstractConstruct caseStaticFragment(StaticFragment object) {
        for (XWPFRun run : object.getRuns()) {
            insertRun(run);
        }
        return object;
    }

    /**
     * Inserts a run in the generated document. The new run is a copy from the specified run.
     * 
     * @param srcRun
     *            the run to copy
     * @return the inserted run.
     */
    @SuppressWarnings("deprecation")
    private XWPFRun insertRun(XWPFRun srcRun) {
        if (srcRun.getParagraph() != currentTemplateParagraph || forceNewParagraph) {
            createNewParagraph(srcRun.getParagraph());
            forceNewParagraph = false;
        }
        XWPFRun generatedRun = currentGeneratedParagraph.createRun();
        generatedRun.getCTR().set(srcRun.getCTR());
        return generatedRun;
    }

    /**
     * Inserts a run in the generated document and set it's text to the specified replacement. The new run is a copy from the specified run.
     * 
     * @param srcRun
     *            the run to copy
     * @param replacement
     *            the text to set
     * @return the inserted run
     */
    private XWPFRun insertFieldRunReplacement(XWPFRun srcRun, String replacement) {
        if (srcRun.getParagraph() != currentTemplateParagraph || forceNewParagraph) {
            createNewParagraph(srcRun.getParagraph());
            forceNewParagraph = false;
        }
        // creates as many paragraphs as there are '\n's in the string.
        String[] fragments = replacement.split("\n");
        XWPFRun result = null;
        int size = fragments.length;
        for (int i = 0; i < size - 1; i++) {
            XWPFRun generatedRun = insertFragment(fragments[i], srcRun);
            if (result == null) {
                result = generatedRun;
            }
            createNewParagraph(srcRun.getParagraph());
        }
        if (size > 0) {
            XWPFRun generatedRun = insertFragment(fragments[size - 1], srcRun);
            if (result == null) {
                result = generatedRun;
            }
        }
        return result;
    }

    /**
     * Insert a new run for a fragment of text following a '\n' character.
     * 
     * @param fragment
     *            the text fragment to insert
     * @param srcRun
     *            the run to copy the style from.
     * @return the generated run.
     */
    private XWPFRun insertFragment(String fragment, XWPFRun srcRun) {
        XWPFRun generatedRun = currentGeneratedParagraph.createRun();
        generatedRun.getCTR().set(srcRun.getCTR().copy());
        generatedRun.getCTR().getInstrTextList().clear();
        generatedRun.setText(fragment);
        return generatedRun;
    }

    /**
     * Creates a new paragraph and replaces the currentParagrap variable.
     * 
     * @param srcParagraph
     *            the origin paragraph to copy the style from.
     */
    private void createNewParagraph(XWPFParagraph srcParagraph) {
        // create a new paragraph.
        XWPFParagraph newParagraph;
        if (generatedDocument instanceof XWPFTableCell) {
            XWPFTableCell cell = (XWPFTableCell) generatedDocument;
            newParagraph = cell.addParagraph();
        } else if (generatedDocument instanceof XWPFDocument) {
            newParagraph = ((XWPFDocument) generatedDocument).createParagraph();
        } else if (generatedDocument instanceof XWPFHeaderFooter) {
            newParagraph = ((XWPFHeaderFooter) generatedDocument).createParagraph();
        } else {
            throw new UnsupportedOperationException("unkown IBody type :" + generatedDocument.getClass());
        }
        CTP ctp = (CTP) srcParagraph.getCTP().copy();
        ctp.getRList().clear();
        ctp.getFldSimpleList().clear();
        ctp.getHyperlinkList().clear();
        newParagraph.getCTP().set(ctp);
        int runNb = newParagraph.getRuns().size();
        for (int i = 0; i < runNb; i++) {
            newParagraph.removeRun(i);
        }
        currentTemplateParagraph = srcParagraph;
        currentGeneratedParagraph = newParagraph;
    }

    @Override
    public AbstractConstruct caseQuery(Query object) {
        // first evaluate the query.
        @SuppressWarnings("restriction")
        IQueryEvaluationEngine evaluator = new QueryEvaluationEngine(queryEnvironment);
        EvaluationResult result = evaluator.eval((AstResult) object.getQuery(),
                definitions.getCurrentDefinitions());
        String strResult;
        if (result == null) {
            strResult = "Couldn't parse query.";
        } else if (result.getResult() == null) {
            StringBuilder builder = new StringBuilder();
            getDiagnostic(result.getDiagnostic(), builder);
            strResult = builder.toString();
        } else {
            strResult = result.getResult().toString();
        }
        XWPFRun run = insertFieldRunReplacement(object.getStyleRun(), strResult);
        if (result == null || result.getResult() == null) {
            run.setBold(true);
            run.setColor(ERROR_COLOR);
        }
        return object;
    }

    @SuppressWarnings("unchecked")
    @Override
    public AbstractConstruct caseRepetition(Repetition object) {
        // first evaluate the query
        @SuppressWarnings("restriction")
        EvaluationResult result = new QueryEvaluationEngine(queryEnvironment).eval(object.getQuery(),
                definitions.getCurrentDefinitions());
        if (result == null || result.getDiagnostic().getCode() == Diagnostic.ERROR) {
            // insert the tag runs as is.
            for (XWPFRun tagRun : object.getRuns()) {
                insertRun(tagRun);
            }
            // insert the error message.
            XWPFRun run = currentGeneratedParagraph.createRun();
            if (result != null) {
                run.setText(result.getDiagnostic().getMessage());
            } else {
                run.setText("couldn't evaluate expression");
            }
            if (result == null || result.getDiagnostic().getCode() == Diagnostic.ERROR) {
                run.setBold(true);
                run.setColor(ERROR_COLOR);
            }

            for (XWPFRun tagRun : object.getClosingRuns()) {
                insertRun(tagRun);
            }
        } else {
            List<Object> iteration = new ArrayList<Object>();
            if (result.getResult() instanceof Collection) {
                iteration.addAll((Collection<? extends Object>) result.getResult());
            } else {
                iteration.add(result.getResult());
            }
            for (Object val : iteration) {
                this.definitions.setValue(object.getIterationVar(), val);
                for (AbstractConstruct construct : object.getSubConstructs()) {
                    doSwitch(construct);
                }
                // if the {gd:endfor} lies on a distinct paragraph, insert a new
                // paragraph there to take this into acount.
                int bodySize = object.getSubConstructs().size();
                if (bodySize > 0 && object.getSubConstructs().get(bodySize - 1).getRuns().size() > 0) {
                    AbstractConstruct lastBodyPart = object.getSubConstructs().get(bodySize - 1);
                    int runNumber = lastBodyPart.getRuns().size();
                    XWPFRun lastRun = lastBodyPart.getRuns().get(runNumber - 1);
                    int closingRunNumber = object.getClosingRuns().size();
                    if (closingRunNumber > 0 && object.getClosingRuns().get(0).getParent() != lastRun.getParent()) {
                        forceNewParagraph = true;
                    }
                }
            }
        }
        return object;

    }

    @Override
    public AbstractConstruct caseConditionnal(Conditionnal object) {
        @SuppressWarnings("restriction")
        EvaluationResult result = new QueryEvaluationEngine(queryEnvironment).eval(object.getQuery(),
                definitions.getCurrentDefinitions());
        // XXX : result can be null here : check that before using it.
        if (result.getResult() instanceof Boolean) {
            if ((Boolean) result.getResult()) {
                for (AbstractConstruct construct : object.getSubConstructs()) {
                    doSwitch(construct);
                }
            } else if (object.getAlternative() != null) {
                doSwitch(object.getAlternative());
            } else if (object.getElse() != null) {
                for (AbstractConstruct construct : object.getElse().getSubConstructs()) {
                    doSwitch(construct);
                }
            }
        } else {
            for (XWPFRun tagRun : object.getRuns()) {
                insertRun(tagRun);
            }
            XWPFRun run = currentGeneratedParagraph.createRun();
            run.setText(result.getDiagnostic().getMessage());
            if (result.getDiagnostic().getCode() == Diagnostic.ERROR) {
                run.setBold(true);
                run.setColor(ERROR_COLOR);
            }
            for (XWPFRun tagRun : object.getClosingRuns()) {
                insertRun(tagRun);
            }
        }
        return object;
    }

    @Override
    public AbstractConstruct caseTable(Table object) {
        // Create the table structure in the destination document.
        XWPFTable generatedTable;
        CTTbl copy = (CTTbl) object.getTable().getCTTbl().copy();
        copy.getTrList().clear();
        if (generatedDocument instanceof XWPFDocument) {
            generatedTable = ((XWPFDocument) generatedDocument).createTable();
            if (generatedTable.getRows().size() > 0) {
                generatedTable.removeRow(0);
            }
            generatedTable.getCTTbl().set(copy);
        } else if (generatedDocument instanceof XWPFTableCell) {
            XWPFTableCell tCell = (XWPFTableCell) generatedDocument;
            int tableRank = tCell.getTables().size();
            XWPFTable newTable = new XWPFTable(copy, tCell, 0, 0);
            if (newTable.getRows().size() > 0) {
                newTable.removeRow(0);
            }
            tCell.insertTable(tableRank, newTable);
            generatedTable = tCell.getTables().get(tableRank);
        } else {
            throw new UnsupportedOperationException("unknown type of IBody : " + generatedDocument.getClass());
        }
        // iterate on the row
        for (Row row : object.getRows()) {
            XWPFTableRow newRow = generatedTable.createRow();
            CTRow ctRow = (CTRow) row.getTableRow().getCtRow().copy();
            ctRow.getTcList().clear();
            newRow.getCtRow().set(ctRow);
            // iterate on cells.
            for (Cell cell : row.getCells()) {
                XWPFTableCell newCell = newRow.createCell();
                CTTc ctCell = (CTTc) cell.getTableCell().getCTTc().copy();
                ctCell.getPList().clear();
                ctCell.getTblList().clear();
                newCell.getCTTc().set(ctCell);
                // process the cell :
                TemplateProcessor processor = new TemplateProcessor(definitions, queryEnvironment, newCell);
                processor.doSwitch(cell.getTemplate());
            }
        }
        return super.caseTable(object);
    }

    /**
     * Returns the picture type based on the filename's extension.
     * 
     * @param fileName
     *            the picture file
     * @return the picture's file extension
     */
    private int getPictureType(String fileName) {
        String[] segments = fileName.split("\\.");
        int result;
        if (segments.length > 1) {
            String extension = segments[segments.length - 1].trim();
            if ("jpg".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_JPEG;
            } else if ("gif".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_GIF;
            } else if ("png".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_PNG;
            } else if ("emf".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_EMF;
            } else if ("wmf".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_WMF;
            } else if ("pict".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_PICT;
            } else if ("dib".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_DIB;
            } else if ("tiff".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_TIFF;
            } else if ("eps".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_EPS;
            } else if ("bmp".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_BMP;
            } else if ("wpg".equalsIgnoreCase(extension)) {
                result = Document.PICTURE_TYPE_WPG;
            } else {
                result = 0;
            }
        } else {
            result = 0;
        }
        return result;
    }

    @Override
    public AbstractConstruct caseImage(Image object) {
        XWPFRun imageRun = insertRun(object.getStyleRun());
        imageRun.setText("");
        imageRun.getCTR().getInstrTextList().clear();
        String filePath;
        if ("".equals(this.rootProjectPath) || this.rootProjectPath == null) {
            filePath = object.getFileName();
        } else {
            filePath = this.rootProjectPath + "/" + object.getFileName();
        }
        try {
            int heigth = Units.toEMU(object.getHeight());
            int width = Units.toEMU(object.getWidth());
            imageRun.addPicture(new FileInputStream(filePath), getPictureType(object.getFileName()),
                    object.getFileName(), width, heigth);
        } catch (InvalidFormatException e) {
            imageRun.setText("Picture in " + object.getFileName() + " has an invalid format.");
            imageRun.setBold(true);
            imageRun.setColor(ERROR_COLOR);
        } catch (FileNotFoundException e) {
            imageRun.setText("File " + filePath + " cannot be found.");
            imageRun.setBold(true);
            imageRun.setColor(ERROR_COLOR);
        } catch (IOException e) {
            imageRun.setText("An I/O Problem occured while reading file ");
            imageRun.setBold(true);
            imageRun.setColor(ERROR_COLOR);
        }
        return super.caseImage(object);
    }
}