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

Java tutorial

Introduction

Here is the source code for org.obeonetwork.m2doc.generator.M2DocEvaluator.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 com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.BreakType;
import org.apache.poi.xwpf.usermodel.IBody;
import org.apache.poi.xwpf.usermodel.IRunBody;
import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFooter;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFHeaderFooter;
import org.apache.poi.xwpf.usermodel.XWPFHyperlinkRun;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
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.apache.xmlbeans.XmlException;
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.IReadOnlyQueryEnvironment;
import org.eclipse.acceleo.query.runtime.impl.QueryEvaluationEngine;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.obeonetwork.m2doc.element.MBookmark;
import org.obeonetwork.m2doc.element.MHyperLink;
import org.obeonetwork.m2doc.element.MImage;
import org.obeonetwork.m2doc.element.MPagination;
import org.obeonetwork.m2doc.element.MStyle;
import org.obeonetwork.m2doc.element.MTable;
import org.obeonetwork.m2doc.element.MTable.MCell;
import org.obeonetwork.m2doc.element.MTable.MRow;
import org.obeonetwork.m2doc.element.impl.MImageImpl;
import org.obeonetwork.m2doc.parser.TemplateValidationMessage;
import org.obeonetwork.m2doc.parser.TokenType;
import org.obeonetwork.m2doc.parser.ValidationMessageLevel;
import org.obeonetwork.m2doc.provider.AbstractDiagramProvider;
import org.obeonetwork.m2doc.provider.AbstractTableProvider;
import org.obeonetwork.m2doc.provider.IProvider;
import org.obeonetwork.m2doc.provider.OptionType;
import org.obeonetwork.m2doc.provider.ProviderConstants;
import org.obeonetwork.m2doc.provider.ProviderException;
import org.obeonetwork.m2doc.template.AbstractProviderClient;
import org.obeonetwork.m2doc.template.Block;
import org.obeonetwork.m2doc.template.Bookmark;
import org.obeonetwork.m2doc.template.Cell;
import org.obeonetwork.m2doc.template.Comment;
import org.obeonetwork.m2doc.template.Conditional;
import org.obeonetwork.m2doc.template.DocumentTemplate;
import org.obeonetwork.m2doc.template.IConstruct;
import org.obeonetwork.m2doc.template.Image;
import org.obeonetwork.m2doc.template.Let;
import org.obeonetwork.m2doc.template.Link;
import org.obeonetwork.m2doc.template.Query;
import org.obeonetwork.m2doc.template.Repetition;
import org.obeonetwork.m2doc.template.Representation;
import org.obeonetwork.m2doc.template.Row;
import org.obeonetwork.m2doc.template.StaticFragment;
import org.obeonetwork.m2doc.template.Table;
import org.obeonetwork.m2doc.template.TableClient;
import org.obeonetwork.m2doc.template.Template;
import org.obeonetwork.m2doc.template.UserContent;
import org.obeonetwork.m2doc.template.UserDoc;
import org.obeonetwork.m2doc.template.util.TemplateSwitch;
import org.obeonetwork.m2doc.util.M2DocUtils;
import org.obeonetwork.m2doc.util.PictureType;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHdrFtr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHyperlink;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;

/**
 * The {@link M2DocEvaluator} class implements a switch over template that generates the doc.
 * 
 * @author Romain Guider
 */
@SuppressWarnings("restriction")
public class M2DocEvaluator extends TemplateSwitch<IConstruct> {

    /**
     * I/O reading problem message.
     */
    private static final String AN_I_O_PROBLEM_OCCURED_WHILE_READING = "An I/O Problem occured while reading %s: %s.";
    /**
     * Error message when AQL query could not be evaluated.
     */
    private static final String QUERY_EVALERROR_MESSAGE = "Couldn't evaluate query.";
    /**
     * Error message when an AQL query contains syntax errors.
     */
    private static final String QUERY_SYNTAX_ERROR_MESSAGE = "Syntax error in AQL expression: ";

    /**
     * Invalid format picture message.
     */
    private static final String PICTURE_INVALID_FORMAT = "Picture in %s has an invalid format: %s.";

    /**
     * The {@link BookmarkManager}.
     */
    private final BookmarkManager bookmarkManager;
    /**
     * variable definition used during generation.
     */
    private final Stack<Map<String, Object>> variablesStack = new Stack<>();
    /**
     * 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;
    /**
     * The currently generated {@link XWPFTable}.
     */
    private XWPFTable currentGeneratedTable;
    /**
     * The currently generated {@link XWPFTableRow}.
     */
    private XWPFTableRow currentGeneratedRow;
    /**
     * Used to force a new paragraph in gf:for body when there's a carriage
     * return before the {m:endfor} tag.
     */
    private boolean forceNewParagraph;

    /**
     * The {@link Set} of used {@link AbstractDiagramProvider}.
     */
    private Set<AbstractDiagramProvider> usedProviders = Sets.newLinkedHashSet();

    /**
     * Last Destination UserContent Manager.
     */
    private UserContentManager userContentManager;

    /**
     * User Doc Ids list.
     * Used for uniqueness test.
     */
    private List<String> userDocIds = new ArrayList<>();

    /**
     * The {@link IQueryEvaluationEngine}.
     */
    private final IQueryEvaluationEngine evaluator;

    /**
     * The {@link GenerationResult}.
     */
    private GenerationResult result;

    /** The URI converter. */
    private URIConverter uriConverter;

    /**
     * The ResourceSet used to keep the models.
     */
    private ResourceSet resourceSetForModels;

    /**
     * The progress {@link Monitor}.
     */
    private Monitor monitor;

    /**
     * Create a new {@link M2DocEvaluator} instance given some definitions
     * and a query environment.
     * 
     * @param uriConverter
     *            the {@link URIConverter uri converter} to use.
     * @param bookmarkManager
     *            the {@link BookmarkManager}
     * @param userContentManager
     *            the {@link UserContentManager}
     * @param queryEnvironment
     *            the query environment used to evaluate queries in the
     * @param resourceSetForModels
     *            the resourceset to use for loading the models.
     * @param monitor
     *            used to track the progress will generating.
     */
    public M2DocEvaluator(URIConverter uriConverter, BookmarkManager bookmarkManager,
            UserContentManager userContentManager, IReadOnlyQueryEnvironment queryEnvironment,
            ResourceSet resourceSetForModels, Monitor monitor) {
        this.uriConverter = uriConverter;
        this.bookmarkManager = bookmarkManager;
        this.userContentManager = userContentManager;
        this.evaluator = new QueryEvaluationEngine((IQueryEnvironment) queryEnvironment);
        this.resourceSetForModels = resourceSetForModels;
        this.monitor = monitor;
    }

    /**
     * Generates the given {@link DocumentTemplate}.
     * 
     * @param documentTemplate
     *            the {@link DocumentTemplate}
     * @param variables
     *            the variables
     * @param destinationDocument
     *            the destination document.
     * @return the {@link GenerationResult}
     */
    public GenerationResult generate(DocumentTemplate documentTemplate, Map<String, Object> variables,
            IBody destinationDocument) {
        this.generatedDocument = destinationDocument;
        variablesStack.push(variables);
        result = new GenerationResult();
        result.getDuplicatedUserContentIDs().addAll(userContentManager.getDuplicatedUserContentIDs());

        doSwitch(documentTemplate);

        return result;
    }

    @Override
    public IConstruct caseDocumentTemplate(DocumentTemplate documentTemplate) {
        doSwitch(documentTemplate.getBody());

        final XWPFDocument document = (XWPFDocument) generatedDocument;
        final Iterator<XWPFFooter> footers = document.getFooterList().iterator();
        for (final Template footer : documentTemplate.getFooters()) {
            final XWPFFooter f = footers.next();
            cleanHeaderFooter(f);
            generatedDocument = f;
            doSwitch(footer);
        }
        final Iterator<XWPFHeader> headers = document.getHeaderList().iterator();
        for (final Template header : documentTemplate.getHeaders()) {
            final XWPFHeader h = headers.next();
            cleanHeaderFooter(h);
            generatedDocument = h;
            doSwitch(header);
        }

        return null;
    }

    /**
     * Clear up the header or footer from the existing paragraphs and tables.
     * 
     * @param headerFooter
     *            the header or footer to clean up.
     */
    void cleanHeaderFooter(XWPFHeaderFooter headerFooter) {
        CTHdrFtr ctHdrFtr = (CTHdrFtr) headerFooter._getHdrFtr().copy();
        ctHdrFtr.getPList().clear();
        ctHdrFtr.getTblList().clear();
        // XXX : there are many other lists in the header and footer that should
        // probably be cleaned.
        headerFooter.setHeaderFooter(ctHdrFtr);
    }

    @Override
    public IConstruct caseTemplate(Template template) {
        doSwitch(template.getBody());

        return template;
    }

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

    /**
     * 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.
     */
    private XWPFRun insertRun(XWPFRun srcRun) {
        if (srcRun.getParent() != currentTemplateParagraph || forceNewParagraph) {
            createNewParagraph((XWPFParagraph) srcRun.getParent());
            forceNewParagraph = false;
        }
        XWPFRun newRun = null;
        if (srcRun instanceof XWPFHyperlinkRun) {
            // Hyperlinks meta information is saved in the paragraph and not in the run. So we have to update the paragrapah with a copy of
            // the hyperlink to insert.
            CTHyperlink newHyperlink = currentGeneratedParagraph.getCTP().addNewHyperlink();
            newHyperlink.set(((XWPFHyperlinkRun) srcRun).getCTHyperlink());

            newRun = new XWPFHyperlinkRun(newHyperlink, srcRun.getCTR(), srcRun.getParent());
            currentGeneratedParagraph.addRun(newRun);
        } else {
            newRun = currentGeneratedParagraph.createRun();
            newRun.getCTR().set(srcRun.getCTR());
        }
        return newRun;
    }

    /**
     * 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.getParent() != currentTemplateParagraph || forceNewParagraph) {
            createNewParagraph((XWPFParagraph) srcRun.getParent());
            forceNewParagraph = false;
        }
        return insertString(srcRun, replacement);
    }

    /**
     * Insert the given text into to given {@link XWPFRun}. This take care of new lines and tabulations.
     * 
     * @param srcRun
     *            the {@link XWPFRun} to populate
     * @param text
     *            the text
     * @return the last inserted {@link XWPFRun}.
     */
    private XWPFRun insertString(XWPFRun srcRun, String text) {
        int fragmentStart = 0;

        XWPFRun inserted;
        for (int i = 0; i < text.length(); i++) {
            final char current = text.charAt(i);
            switch (current) {
            case '\n':
                inserted = insertFragment(srcRun, text.substring(fragmentStart, i));
                inserted.addBreak();
                fragmentStart = i + 1;
                break;
            case '\t':
                inserted = insertFragment(srcRun, text.substring(fragmentStart, i));
                inserted.addTab();
                fragmentStart = i + 1;
                break;
            case '\r':
                // CHECKSTYLE:OFF
                if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
                    inserted = insertFragment(srcRun, text.substring(fragmentStart, i));
                    inserted.addBreak();
                    i++;
                    fragmentStart = i + 1;
                }
                // CHECKSTYLE:ON
                break;

            default:
                break;
            }
        }

        return insertFragment(srcRun, text.substring(fragmentStart, text.length()));
    }

    /**
     * Insert a new run containing the given text.
     * 
     * @param srcRun
     *            the run to copy the style from.
     * @param fragment
     *            the text fragment to insert
     * @return the generated run.
     */
    private XWPFRun insertFragment(XWPFRun srcRun, String fragment) {
        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 IConstruct caseQuery(Query query) {
        if (query.getQuery().getDiagnostic().getSeverity() == Diagnostic.ERROR) {
            insertQuerySyntaxMessages(query, QUERY_SYNTAX_ERROR_MESSAGE);
        } else {
            final EvaluationResult queryResult = evaluator.eval(query.getQuery(), variablesStack.peek());
            if (queryResult.getDiagnostic().getSeverity() != Diagnostic.OK) {
                insertQueryEvaluationMessages(query, queryResult.getDiagnostic());
            } else {
                final Object value = queryResult.getResult();
                final XWPFRun styleRun = query.getStyleRun();
                insertObject(value, styleRun);
            }
        }

        return query;
    }

    /**
     * Inserts the given {@link Object} in the given {@link XWPFRun}.
     * 
     * @param object
     *            the {@link Object} to insert
     * @param run
     *            the {@link XWPFRun}
     */
    private void insertObject(Object object, XWPFRun run) {
        if (object instanceof Collection<?>) {
            for (Object child : (Collection<?>) object) {
                insertObject(child, run);
                // TODO insert Run ?
            }
        } else if (object instanceof MHyperLink) {
            final XWPFRun linkRun = insertFieldRunReplacement(run, "");
            insertMHyperLink(linkRun, (MHyperLink) object);
        } else if (object instanceof MBookmark) {
            insertMBookmark(run, (MBookmark) object);
        } else if (object instanceof MImage) {
            final XWPFRun imageRun = insertFieldRunReplacement(run, "");
            insertMImage(imageRun, (MImage) object);
        } else if (object instanceof MTable) {
            XWPFRun tableRun = run;
            tableRun.getCTR().getInstrTextList().clear();
            insertMTable(tableRun, (MTable) object);
        } else if (object instanceof MPagination) {
            insertMPagination(run, (MPagination) object);
        } else if (object == null) {
            insertFieldRunReplacement(run, "");
        } else {
            insertFieldRunReplacement(run, object.toString());
        }
    }

    @Override
    public IConstruct caseComment(Comment comment) {
        // nothing to do here
        return comment;
    }

    /**
     * Inserts a {@link MBookmark} in the given {@link XWPFRun}.
     * 
     * @param run
     *            the {@link XWPFRun}
     * @param bookmark
     *            the {@link MBookmark}
     */
    private void insertMBookmark(XWPFRun run, MBookmark bookmark) {
        insertFieldRunReplacement(run, "");
        if (bookmark.isReference()) {
            bookmarkManager.insertReference(currentGeneratedParagraph, bookmark.getId(), bookmark.getText());
        } else {
            final ValidationMessageLevel levelStart = bookmarkManager.startBookmark(currentGeneratedParagraph,
                    bookmark.getId());
            insertFieldRunReplacement(run, bookmark.getText());
            final ValidationMessageLevel levelEnd = bookmarkManager.endBookmark(currentGeneratedParagraph,
                    bookmark.getId());
            result.updateLevel(levelStart, levelEnd);
        }
    }

    /**
     * Inserts the given {@link MHyperLink}.
     * 
     * @param run
     *            the {@link XWPFRun}
     * @param hyperLink
     *            the {@link MHyperLink}
     */
    private void insertMHyperLink(XWPFRun run, MHyperLink hyperLink) {
        final String id = currentGeneratedParagraph.getDocument().getPackagePart()
                .addExternalRelationship(hyperLink.getUrl(), XWPFRelation.HYPERLINK.getRelation()).getId();
        final CTHyperlink cLink = currentGeneratedParagraph.getCTP().addNewHyperlink();
        cLink.setId(id);
        CTText ctText = CTText.Factory.newInstance();
        ctText.setStringValue(hyperLink.getText());

        CTR ctr = CTR.Factory.newInstance();
        ctr.setRPr((CTRPr) run.getCTR().getRPr().copy());
        ctr.setTArray(new CTText[] { ctText });
        cLink.setRArray(new CTR[] { ctr });
    }

    /**
     * Inserts the given {@link MImage}.
     * 
     * @param run
     *            the {@link XWPFRun} to insert to
     * @param image
     *            the {@link MImage} to insert
     */
    private void insertMImage(XWPFRun run, MImage image) {
        try {
            int heigth = Units.toEMU(image.getHeight());
            int width = Units.toEMU(image.getWidth());

            try (InputStream imageStream = image.getInputStream()) {
                run.addPicture(imageStream, image.getType().getPoiType(), image.getURI().toString(), width, heigth);
            }
        } catch (InvalidFormatException e) {
            insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                    String.format(PICTURE_INVALID_FORMAT, image.getURI().toString(), e.getMessage()));
        } catch (IOException e) {
            insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                    String.format(AN_I_O_PROBLEM_OCCURED_WHILE_READING, image.getURI().toString(), e.getMessage()));
        }
    }

    /**
     * Insert the given {@link MPagination}.
     * 
     * @param run
     *            the {@link XWPFRun} to insert to
     * @param mPagination
     *            the {@link MPagination}
     */
    private void insertMPagination(XWPFRun run, MPagination mPagination) {
        switch (mPagination) {
        case newColumn:
            run.addBreak(BreakType.COLUMN);
            break;

        case newParagraph:
            createNewParagraph((XWPFParagraph) run.getParent());
            break;

        case newPage:
            run.addBreak(BreakType.PAGE);
            break;

        case newTableOfContent:
            CTP ctP = currentGeneratedParagraph.getCTP();
            CTSimpleField toc = ctP.addNewFldSimple();
            toc.setInstr("TOC \\h");
            toc.setDirty(STOnOff.TRUE);
            break;

        case newTextWrapping:
            run.addBreak(BreakType.TEXT_WRAPPING);
            break;

        default:
            throw new IllegalStateException("Not supported MPagination.");
        }
    }

    /**
     * Inserts the given {@link MTable}.
     * 
     * @param run
     *            the {@link XWPFRun} to insert to
     * @param table
     *            the {@link MTable} to insert
     */
    private void insertMTable(XWPFRun run, MTable table) {
        final XWPFTable docTable;
        if (generatedDocument instanceof XWPFDocument) {
            if (table.getLabel() != null) {
                XWPFRun captionRun;
                captionRun = run;
                IRunBody runBody = captionRun.getParent();
                if (runBody instanceof XWPFParagraph) {
                    ((XWPFParagraph) runBody).setSpacingAfter(0);
                }
                captionRun.setText(table.getLabel());
                captionRun.setBold(true);
            }
            docTable = ((XWPFDocument) generatedDocument).createTable();
        } else if (generatedDocument instanceof XWPFHeaderFooter) {
            final XWPFHeaderFooter headerFooter = (XWPFHeaderFooter) generatedDocument;
            final int index = headerFooter._getHdrFtr().getTblArray().length;
            final CTTbl cttbl = headerFooter._getHdrFtr().insertNewTbl(index);
            docTable = new XWPFTable(cttbl, headerFooter);
            headerFooter.insertTable(index, docTable);
        } else if (generatedDocument instanceof XWPFTableCell) {
            XWPFTableCell tcell = (XWPFTableCell) generatedDocument;
            if (table.getLabel() != null) {
                final XWPFRun captionRun = run;
                IRunBody runBody = captionRun.getParent();
                if (runBody instanceof XWPFParagraph) {
                    ((XWPFParagraph) runBody).setSpacingAfter(0);
                }
                captionRun.setText(table.getLabel());
                captionRun.setBold(true);
            }
            CTTbl ctTbl = tcell.getCTTc().addNewTbl();
            docTable = new XWPFTable(ctTbl, tcell);
            int tableRank = tcell.getTables().size();
            tcell.insertTable(tableRank, docTable);
            // A paragraph is mandatory at the end of a cell, so let's always add one.
            tcell.addParagraph();
        } else {
            docTable = null;
            M2DocUtils.appendMessageRun((XWPFParagraph) run.getParent(), ValidationMessageLevel.ERROR,
                    "m:table can't be inserted here.");
        }

        if (docTable != null) {
            fillTable(docTable, table);
        }
    }

    /**
     * Fill a newly created word table with the data from an MTable.
     * 
     * @param table
     *            The newly created word table
     * @param mtable
     *            The MTable that describes the data and styles to insert
     */
    private void fillTable(XWPFTable table, MTable mtable) {
        List<MRow> rows = mtable.getRows();
        // Iterate over the rows
        for (int rowIdx = 0; rowIdx < rows.size(); rowIdx++) {
            MRow mRow = rows.get(rowIdx);

            // Get or create XWPF row
            XWPFTableRow xwpfRow;
            if (table.getNumberOfRows() > rowIdx) {
                xwpfRow = table.getRow(rowIdx);
            } else {
                xwpfRow = table.createRow();
            }

            // Iterate over the columns
            for (int colIdx = 0; colIdx < mtable.getColumnsCount(); colIdx++) {
                // Get or create XWPF cell
                XWPFTableCell xwpfCell;
                if (xwpfRow.getTableCells().size() > colIdx) {
                    xwpfCell = xwpfRow.getCell(colIdx);
                } else {
                    xwpfCell = xwpfRow.createCell();
                }

                // Populate cell
                XWPFParagraph xwpfCellParagraph = xwpfCell.getParagraphs().get(0);
                xwpfCellParagraph.setSpacingBefore(0);
                xwpfCellParagraph.setSpacingAfter(0);
                MCell mCell = null;
                if (colIdx < mRow.getCells().size()) {
                    mCell = mRow.getCells().get(colIdx);
                }
                setCellContent(xwpfCell, mCell);
            }
        }
    }

    /**
     * Create a new run in the cell's paragraph and set this run's text, and apply the given style to the cell and its paragraph.
     * 
     * @param cell
     *            Cell to fill in
     * @param mCell
     *            the cell to read data from
     */
    private void setCellContent(XWPFTableCell cell, MCell mCell) {
        XWPFParagraph cellParagraph = cell.getParagraphs().get(0);
        XWPFRun cellRun = cellParagraph.createRun();
        if (mCell != null) {
            String label = mCell.getLabel();
            if (label != null) {
                cellRun.setText(label);
            }
            MStyle style = mCell.getStyle();
            if (style != null) {
                if (style.getBackgroundColor() != null) {
                    cell.setColor(hexColor(style.getBackgroundColor()));
                }
                applyMStyle(cellRun, style);
            }
        }
    }

    /**
     * Apply the given style to the given run. Background color is not taken into account here since it does not apply to runs.
     * 
     * @param run
     *            The run to style
     * @param style
     *            The style to apply, can be <code>null</code>
     */
    private void applyMStyle(XWPFRun run, MStyle style) {
        if (style.getFontSize() != -1) {
            run.setFontSize(style.getFontSize());
        }
        if (style.getFontModifiers() != -1) {
            run.setBold((style.getFontModifiers() & MStyle.FONT_BOLD) != 0);
            run.setItalic((style.getFontModifiers() & MStyle.FONT_ITALIC) != 0);
            if ((style.getFontModifiers() & MStyle.FONT_UNDERLINE) != 0) {
                run.setUnderline(UnderlinePatterns.SINGLE);
            }
            run.setStrikeThrough((style.getFontModifiers() & MStyle.FONT_STRIKE_THROUGH) != 0);
        }
        if (style.getForegroundColor() != null) {
            run.setColor(hexColor(style.getForegroundColor()));
        }
    }

    /**
     * Translate a {@link Color} to the word format.
     * 
     * @param color
     *            the {@link Color}
     * @return The color as a 6-digits string.
     */
    private static String hexColor(Color color) {
        return String.format("%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
    }

    @Override
    public IConstruct caseRepetition(Repetition repetition) {
        if (repetition.getQuery().getDiagnostic().getSeverity() == Diagnostic.ERROR) {
            insertQuerySyntaxMessages(repetition, QUERY_SYNTAX_ERROR_MESSAGE);
        } else {
            final EvaluationResult queryResult = evaluator.eval(repetition.getQuery(), variablesStack.peek());
            if (queryResult.getDiagnostic().getSeverity() != Diagnostic.OK) {
                insertQueryEvaluationMessages(repetition, queryResult.getDiagnostic());
            } else {
                final List<Object> iteration = new ArrayList<>();
                if (queryResult.getResult() instanceof Collection) {
                    iteration.addAll((Collection<?>) queryResult.getResult());
                } else if (queryResult.getResult() != null) {
                    iteration.add(queryResult.getResult());
                } else {
                    insertMessage(currentGeneratedParagraph, ValidationMessageLevel.WARNING,
                            repetition.getIterationVar() + " value is null.");
                }
                final Map<String, Object> newVariables = Maps.newHashMap(variablesStack.peek());
                variablesStack.push(newVariables);
                try {
                    for (Object val : iteration) {
                        newVariables.put(repetition.getIterationVar(), val);
                        doSwitch(repetition.getBody());
                        closingRepretition(repetition);
                    }
                } finally {
                    variablesStack.pop();
                }
            }
        }

        return repetition;

    }

    @Override
    public IConstruct doSwitch(EObject eObject) {
        if (!monitor.isCanceled()) {
            return super.doSwitch(eObject);
        }
        return null;

    }

    @Override
    public IConstruct caseLet(Let let) {
        if (let.getValue().getDiagnostic().getSeverity() == Diagnostic.ERROR) {
            insertQuerySyntaxMessages(let, QUERY_SYNTAX_ERROR_MESSAGE);
        } else {
            final EvaluationResult queryResult = evaluator.eval(let.getValue(), variablesStack.peek());
            if (queryResult.getDiagnostic().getSeverity() != Diagnostic.OK) {
                insertQueryEvaluationMessages(let, queryResult.getDiagnostic());
            } else {
                if (queryResult.getResult() == null) {
                    insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                            "User doc id can't be null.");
                } else {
                    final Map<String, Object> newVariables = Maps.newHashMap(variablesStack.peek());
                    variablesStack.push(newVariables);
                    try {
                        newVariables.put(let.getName(), queryResult.getResult());
                        doSwitch(let.getBody());
                    } finally {
                        variablesStack.pop();
                    }
                }
            }
        }

        return let;
    }

    @Override
    public IConstruct caseUserDoc(UserDoc userDoc) {
        if (userDoc.getId().getDiagnostic().getSeverity() == Diagnostic.ERROR) {
            insertQuerySyntaxMessages(userDoc, QUERY_SYNTAX_ERROR_MESSAGE);
        } else {
            final EvaluationResult queryResult = evaluator.eval(userDoc.getId(), variablesStack.peek());
            if (queryResult.getDiagnostic().getSeverity() != Diagnostic.OK) {
                insertQueryEvaluationMessages(userDoc, queryResult.getDiagnostic());
            } else {
                if (queryResult.getResult() == null) {
                    insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                            "User doc id can't be null.");
                } else {
                    final String id = queryResult.getResult().toString();
                    insertUserContent(userDoc, id);
                }
            }
        }

        return userDoc;
    }

    /**
     * Inserts the {@link UserContent}.
     * 
     * @param userDoc
     *            the source {@link UserDoc}
     * @param id
     *            the ID
     */
    private void insertUserContent(UserDoc userDoc, final String id) {
        // Tag UserContent with evaluated id
        addStartUserDocField(userDoc, id);
        // manage user Doc Id Uniqueness
        manageUserDocIdUniqueness(id, userDoc);
        // Copy userdoc content
        UserContent userContent = userContentManager.consumeUserContent(id);
        boolean needNewParagraphBeforeEndTag = true;
        if (userContent == null) {
            doSwitch(userDoc.getBody());
            needNewParagraphBeforeEndTag = needNewParagraph(userDoc);
        } else {
            final UserContentRawCopy userContentRawCopy = new UserContentRawCopy();
            try {
                currentGeneratedParagraph = userContentRawCopy.copy(userContent, currentGeneratedParagraph,
                        generatedDocument);
                needNewParagraphBeforeEndTag = userContentRawCopy.needNewParagraph();
                // Affect currentTemplateParagraph after Raw copy
                if (userDoc.getClosingRuns().size() != 0) {
                    currentTemplateParagraph = (XWPFParagraph) userDoc.getClosingRuns()
                            .get(userDoc.getClosingRuns().size() - 1).getParent();
                }
            } catch (InvalidFormatException e) {
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                        UserContentManager.USERDOC_COPY_ERROR + e.getMessage());
            } catch (XmlException e) {
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                        UserContentManager.USERDOC_COPY_ERROR + e.getMessage());
            } catch (IOException e) {
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                        UserContentManager.USERDOC_COPY_ERROR + e.getMessage());
            }
        }

        // Tag m:enduserContent
        addEndUserDocField(userDoc, needNewParagraphBeforeEndTag);
    }

    /**
     * Tells if the given {@link IConstruct} need new paragraph before end tag.
     * 
     * @param construct
     *            the {@link IConstruct}
     * @return <code>true</code> if the given {@link IConstruct} need new paragraph before end tag, <code>false</code> otherwise
     */
    private boolean needNewParagraph(IConstruct construct) {
        boolean needNewParagraph = true;
        if (construct.getClosingRuns().size() != 0) {
            if (construct.getClosingRuns().get(0).getParent() == currentTemplateParagraph) {
                needNewParagraph = false;
            }
        }
        return needNewParagraph;
    }

    /**
     * userDoc Id Uniqueness test.
     * 
     * @param id
     *            id
     * @param userdoc
     *            userdoc EObject
     */
    private void manageUserDocIdUniqueness(String id, UserDoc userdoc) {
        if (userDocIds.contains(id)) {
            // insert the error message.
            String msgError = "The id '" + id
                    + "' is already used in generated document. Ids must be unique otherwise document part contained userContent could be lost at next generation.";
            insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR, msgError);

            TemplateValidationMessage templateValidationMessage = new TemplateValidationMessage(
                    ValidationMessageLevel.ERROR, msgError, userdoc.getRuns().get(userdoc.getRuns().size() - 1));
            userdoc.getValidationMessages().add(templateValidationMessage);
        } else {
            userDocIds.add(id);
        }

    }

    /**
     * Add Start UserContent word Document Field.
     * 
     * @param object
     *            AbstractConstruct where add field
     * @param id
     *            UserDoc Id
     */
    private void addStartUserDocField(IConstruct object, String id) {
        if (currentTemplateParagraph == null || object.getRuns().size() != 0
                && object.getRuns().get(0).getParent() != currentTemplateParagraph) {
            createNewParagraph((XWPFParagraph) object.getRuns().get(0).getParent());
        }
        currentGeneratedParagraph.getCTP().addNewFldSimple().setInstr(TokenType.USERCONTENT.getValue() + " " + id);
    }

    /**
     * Add End UserContent word Document Field.
     * 
     * @param object
     *            AbstractConstruct where add field
     * @param needNewParagraph
     *            need New Paragraph boolean
     */
    private void addEndUserDocField(IConstruct object, boolean needNewParagraph) {
        if (object.getClosingRuns().size() != 0) {
            if (needNewParagraph) {
                createNewParagraph((XWPFParagraph) object.getClosingRuns().get(0).getParent());
            }
            currentGeneratedParagraph.getCTP().addNewFldSimple().setInstr(TokenType.ENDUSERCONTENT.getValue());
        }
    }

    /**
     * Closes the generation of the given {@link Repetition}.
     * if the end of {@link Repetition} lies on a distinct paragraph, insert a new
     * paragraph there to take this into account.
     * 
     * @param repetition
     *            {@link Repetition} to close
     */
    private void closingRepretition(Repetition repetition) {
        int bodySize = repetition.getBody().getStatements().size();
        if (bodySize > 0 && repetition.getBody().getStatements().get(bodySize - 1).getRuns().size() > 0) {
            IConstruct lastBodyPart = repetition.getBody().getStatements().get(bodySize - 1);
            int runNumber = lastBodyPart.getRuns().size();
            XWPFRun lastRun = lastBodyPart.getRuns().get(runNumber - 1);
            int closingRunNumber = repetition.getClosingRuns().size();
            if (closingRunNumber > 0 && repetition.getClosingRuns().get(0).getParent() != lastRun.getParent()) {
                forceNewParagraph = true;
            }
        }
    }

    @Override
    public IConstruct caseBlock(Block block) {
        for (IConstruct construct : block.getStatements()) {
            doSwitch(construct);
        }

        return block;
    }

    @Override
    public IConstruct caseConditional(Conditional conditional) {
        if (conditional.getCondition().getDiagnostic().getSeverity() == Diagnostic.ERROR) {
            insertQuerySyntaxMessages(conditional, QUERY_SYNTAX_ERROR_MESSAGE);
        } else {
            final EvaluationResult evaluationResult = evaluator.eval(conditional.getCondition(),
                    variablesStack.peek());
            if (evaluationResult.getDiagnostic().getSeverity() != Diagnostic.OK) {
                insertQueryEvaluationMessages(conditional, evaluationResult.getDiagnostic());
                for (XWPFRun tagRun : conditional.getClosingRuns()) {
                    insertRun(tagRun);
                }
            } else if (evaluationResult.getResult() instanceof Boolean) {
                if ((Boolean) evaluationResult.getResult()) {
                    doSwitch(conditional.getThen());
                } else if (conditional.getElse() != null) {
                    doSwitch(conditional.getElse());
                }
            } else {
                for (XWPFRun tagRun : conditional.getRuns()) {
                    insertRun(tagRun);
                }
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                        "Condition evaluation result must be a boolean.");
                for (XWPFRun tagRun : conditional.getClosingRuns()) {
                    insertRun(tagRun);
                }
            }
        }

        return conditional;
    }

    @Override
    public IConstruct caseTable(Table table) {
        // Create the table structure in the destination document.

        CTTbl copy = (CTTbl) table.getTable().getCTTbl().copy();
        copy.getTrList().clear();
        if (generatedDocument instanceof XWPFDocument) {
            currentGeneratedTable = ((XWPFDocument) generatedDocument).createTable();
            if (currentGeneratedTable.getRows().size() > 0) {
                currentGeneratedTable.removeRow(0);
            }
            currentGeneratedTable.getCTTbl().set(copy);
        } else if (generatedDocument instanceof XWPFHeaderFooter) {
            final XWPFHeaderFooter headerFooter = (XWPFHeaderFooter) generatedDocument;
            final int index = headerFooter._getHdrFtr().getTblArray().length;
            final CTTbl cttbl = headerFooter._getHdrFtr().insertNewTbl(index);
            XWPFTable newTable = new XWPFTable(cttbl, headerFooter);
            if (newTable.getRows().size() > 0) {
                newTable.removeRow(0);
            }
            headerFooter.insertTable(index, newTable);
            currentGeneratedTable = headerFooter.getTables().get(index);
            currentGeneratedTable.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);
            currentGeneratedTable = tCell.getTables().get(tableRank);
        } else {
            throw new UnsupportedOperationException("unknown type of IBody : " + generatedDocument.getClass());
        }
        // iterate on the row
        for (Row row : table.getRows()) {
            doSwitch(row);
        }

        return table;
    }

    @Override
    public IConstruct caseRow(Row row) {
        currentGeneratedRow = currentGeneratedTable.createRow();
        final CTRow ctRow = (CTRow) row.getTableRow().getCtRow().copy();
        ctRow.getTcList().clear();
        currentGeneratedRow.getCtRow().set(ctRow);
        // iterate on cells.
        for (Cell cell : row.getCells()) {
            doSwitch(cell);
        }

        return null;
    }

    @Override
    public IConstruct caseCell(Cell cell) {
        final XWPFTableCell newCell = currentGeneratedRow.createCell();
        final CTTc ctCell = (CTTc) cell.getTableCell().getCTTc().copy();
        ctCell.getPList().clear();
        ctCell.getTblList().clear();
        newCell.getCTTc().set(ctCell);

        final IBody savedGeneratedDocument = generatedDocument;
        generatedDocument = newCell;
        try {
            doSwitch(cell.getTemplate());
        } finally {
            generatedDocument = savedGeneratedDocument;
        }

        return null;
    }

    @Override
    public IConstruct caseImage(Image image) {
        XWPFRun imageRun = insertRun(image.getStyleRun());
        imageRun.setText("");
        imageRun.getCTR().getInstrTextList().clear();
        if (image.getFileName() == null) {
            insertQuerySyntaxMessages(image, "");
        } else {
            URI imageURI = URI.createFileURI(image.getFileName());
            imageURI = imageURI.resolve(image.eResource().getURI());
            try {
                int heigth = Units.toEMU(image.getHeight());
                int width = Units.toEMU(image.getWidth());

                try (InputStream imageStream = uriConverter.createInputStream(imageURI)) {
                    imageRun.addPicture(imageStream, PictureType.toType(imageURI).getPoiType(), image.getFileName(),
                            width, heigth);
                }
            } catch (InvalidFormatException e) {
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                        String.format(PICTURE_INVALID_FORMAT, imageURI.toString(), e.getMessage()));
            } catch (IOException e) {
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                        String.format(AN_I_O_PROBLEM_OCCURED_WHILE_READING, imageURI.toString(), e.getMessage()));
            }
        }

        return image;
    }

    @Override
    public IConstruct caseRepresentation(Representation representation) {
        XWPFRun imageRun = insertRun(representation.getStyleRun());
        IProvider provider = representation.getProvider();
        if (provider == null) {
            insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                    representation.getValidationMessages().get(0).getMessage());
        } else {
            Map<String, Object> parameters;
            try {
                parameters = setupParametersMapForRepresentation(representation, provider);
                List<String> imagePaths = ((AbstractDiagramProvider) provider)
                        .getRepresentationImagePath(resourceSetForModels, parameters);
                usedProviders.add((AbstractDiagramProvider) provider);
                for (String imagePathStr : imagePaths) {
                    URI imageURI = URI.createFileURI(imagePathStr);
                    imageURI = imageURI.resolve(representation.eResource().getURI());

                    imageRun.setText("");
                    imageRun.getCTR().getInstrTextList().clear();

                    final MImage image = new MImageImpl(uriConverter, imageURI);
                    // get default image size if needed
                    image.setConserveRatio(representation.getHeight() == 0 || representation.getWidth() == 0);
                    if (representation.getHeight() != 0) {
                        image.setHeight(representation.getHeight());
                    }
                    if (representation.getWidth() != 0) {
                        image.setWidth(representation.getWidth());
                    }
                    insertMImage(imageRun, image);
                }
            } catch (IllegalArgumentException e) {
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR, e.getMessage());
            } catch (ProviderException e) {
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                        "A problem occured while creating image from an diagram provider: " + e.getMessage());
            }

        }

        return representation;

    }

    @Override
    public IConstruct caseTableClient(TableClient tableClient) {
        XWPFRun tableRun = insertRun(tableClient.getStyleRun());
        tableRun.getCTR().getInstrTextList().clear();
        AbstractTableProvider provider = (AbstractTableProvider) tableClient.getProvider();
        if (provider == null) {
            insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                    tableClient.getValidationMessages().get(0).getMessage());
        } else {
            Map<String, Object> parameters;
            try {
                parameters = setupParametersMap(tableClient, provider);
                TableClientProcessor tableProcessor = new TableClientProcessor(generatedDocument, provider,
                        parameters, resourceSetForModels);
                tableProcessor.generate(tableRun);
            } catch (IllegalArgumentException e) {
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR, e.getMessage());
            } catch (ProviderException e) {
                insertMessage(currentGeneratedParagraph, ValidationMessageLevel.ERROR,
                        "A problem occured while creating table from a table provider: " + e.getMessage());
            }
        }
        return tableClient;
    }

    @Override
    public IConstruct caseBookmark(Bookmark bookmark) {
        if (bookmark.getName().getDiagnostic().getSeverity() == Diagnostic.ERROR) {
            insertQuerySyntaxMessages(bookmark, QUERY_SYNTAX_ERROR_MESSAGE);
        } else {
            final EvaluationResult evaluationResult = evaluator.eval(bookmark.getName(), variablesStack.peek());
            if (evaluationResult.getDiagnostic().getSeverity() != Diagnostic.OK) {
                insertQueryEvaluationMessages(bookmark, evaluationResult.getDiagnostic());
            } else {
                final ValidationMessageLevel levelStart = bookmarkManager.startBookmark(currentGeneratedParagraph,
                        evaluationResult.getResult().toString());
                doSwitch(bookmark.getBody());
                final ValidationMessageLevel levelEnd = bookmarkManager.endBookmark(currentGeneratedParagraph,
                        evaluationResult.getResult().toString());
                result.updateLevel(levelStart, levelEnd);
            }
        }

        return super.caseBookmark(bookmark);
    }

    /**
     * Inserts evaluation messages for the given {@link IConstruct}.
     * 
     * @param construct
     *            the {@link IConstruct}
     * @param diagnostic
     *            the {@link Diagnostic}
     */
    protected void insertQueryEvaluationMessages(IConstruct construct, Diagnostic diagnostic) {
        for (XWPFRun tagRun : construct.getRuns()) {
            insertRun(tagRun);
        }
        result.updateLevel(M2DocUtils.appendDiagnosticMessage(currentGeneratedParagraph, diagnostic));
        for (XWPFRun tagRun : construct.getClosingRuns()) {
            insertRun(tagRun);
        }
    }

    /**
     * Inserts AQL query syntax messages for the given {@link IConstruct}.
     * 
     * @param construct
     *            the {@link IConstruct}
     * @param errorPrefix
     *            error message prefix
     */
    protected void insertQuerySyntaxMessages(IConstruct construct, String errorPrefix) {
        for (XWPFRun tagRun : construct.getRuns()) {
            insertRun(tagRun);
        }
        for (TemplateValidationMessage message : construct.getValidationMessages()) {
            insertMessage(currentGeneratedParagraph, message.getLevel(), errorPrefix + message.getMessage());
        }
        for (XWPFRun tagRun : construct.getClosingRuns()) {
            insertRun(tagRun);
        }
    }

    /**
     * Inserts the given message with the given {@link ValidationMessageLevel}.
     * 
     * @param paragraph
     *            the {@link XWPFParagraph}
     * @param level
     *            the {@link ValidationMessageLevel}
     * @param message
     *            the message
     */
    private void insertMessage(XWPFParagraph paragraph, ValidationMessageLevel level, String message) {
        M2DocUtils.appendMessageRun(paragraph, level, message);
        result.updateLevel(level);
    }

    @Override
    public IConstruct caseLink(Link link) {
        if (link.getName().getDiagnostic().getSeverity() == Diagnostic.ERROR
                || link.getText().getDiagnostic().getSeverity() == Diagnostic.ERROR) {
            insertQuerySyntaxMessages(link, QUERY_SYNTAX_ERROR_MESSAGE);
        } else {
            final EvaluationResult nameResult = evaluator.eval(link.getName(), variablesStack.peek());
            if (nameResult.getDiagnostic().getSeverity() != Diagnostic.OK) {
                insertQueryEvaluationMessages(link, nameResult.getDiagnostic());
            } else {
                final EvaluationResult textResult = evaluator.eval(link.getText(), variablesStack.peek());
                if (nameResult.getDiagnostic().getSeverity() != Diagnostic.OK) {
                    insertQueryEvaluationMessages(link, textResult.getDiagnostic());
                } else {
                    bookmarkManager.insertReference(currentGeneratedParagraph, nameResult.getResult().toString(),
                            textResult.getResult().toString());
                }
            }
        }

        return link;
    }

    /**
     * Returns a map containing all parameters coming from the table tag
     * and global variables available.
     * 
     * @param abstractProviderClient
     *            the {@link AbstractProviderClient} object from which we extracts needed parameters.
     * @param provider
     *            the provider providing information regarding tag options.
     * @return a map containing all parameters coming from the representation tag
     *         and global variables available.
     * @throws IllegalArgumentException
     *             if the evaluation fails because error were present during parse time or evaluation time.
     */
    private Map<String, Object> setupParametersMap(AbstractProviderClient abstractProviderClient,
            IProvider provider) throws IllegalArgumentException {
        Map<String, Object> parameters = new HashMap<>();
        if (variablesStack.peek().containsKey(ProviderConstants.CONF_ROOT_OBJECT_KEY)) {
            parameters.put(ProviderConstants.CONF_ROOT_OBJECT_KEY,
                    variablesStack.peek().get(ProviderConstants.CONF_ROOT_OBJECT_KEY));
        }
        if (variablesStack.peek().containsKey(ProviderConstants.REFRESH_REPRESENTATIONS_KEY)) {
            parameters.put(ProviderConstants.REFRESH_REPRESENTATIONS_KEY,
                    variablesStack.peek().get(ProviderConstants.REFRESH_REPRESENTATIONS_KEY));
        } else {
            parameters.put(ProviderConstants.REFRESH_REPRESENTATIONS_KEY, false);
        }
        setGenericParameters(abstractProviderClient, provider.getOptionTypes(), parameters);
        return parameters;
    }

    /**
     * Returns a map containing all parameters coming from the representation tag
     * and global variables available.
     * 
     * @param representation
     *            the {@link Representation} object from which we extracts needed parameters.
     * @param provider
     *            the provider providing information regarding tag options.
     * @return a map containing all parameters coming from the representation tag
     *         and global variables available.
     * @throws IllegalArgumentException
     *             if the evaluation fails because error were present during parse time or evaluation time.
     */
    private Map<String, Object> setupParametersMapForRepresentation(Representation representation,
            IProvider provider) throws IllegalArgumentException {
        Map<String, Object> parameters = new HashMap<>(setupParametersMap(representation, provider));
        parameters.put(ProviderConstants.IMAGE_HEIGHT_KEY, representation.getHeight());
        parameters.put(ProviderConstants.IMAGE_WIDTH_KEY, representation.getWidth());
        parameters.put(ProviderConstants.DIAGRAM_ACTIVATED_LAYERS_KEY, representation.getActivatedLayers());
        return parameters;
    }

    /**
     * Adds all parameters with the value evaluated if needed.
     * 
     * @param templateProvider
     *            the template element from which we set generic parameters.
     * @param optionTypes
     *            the option types provided by the provider used by the template provider model element.
     * @param parameters
     *            the map containing the parameters to pass to the provider.
     * @throws IllegalArgumentException
     *             if the evaluation fails because error were present during parse time or evaluation time.
     */
    private void setGenericParameters(AbstractProviderClient templateProvider, Map<String, OptionType> optionTypes,
            Map<String, Object> parameters) throws IllegalArgumentException {
        EMap<String, Object> optionsMap = templateProvider.getOptionValueMap();
        Set<Entry<String, Object>> optionsMapEntries = optionsMap.entrySet();
        for (Entry<String, Object> optionsMapEntry : optionsMapEntries) {
            if (optionTypes == null || optionTypes.get(optionsMapEntry.getKey()) == null) {
                parameters.put(optionsMapEntry.getKey(), optionsMapEntry.getValue());
            } else if (optionTypes != null) {
                OptionType optionType = optionTypes.get(optionsMapEntry.getKey());
                if (OptionType.AQL_EXPRESSION == optionType) {
                    evaluateAqlExpression(templateProvider, parameters, optionsMapEntry);
                } else if (OptionType.STRING == optionType) {
                    parameters.put(optionsMapEntry.getKey(), optionsMapEntry.getValue());
                } else {
                    throw new UnsupportedOperationException("All options types should be supported.");
                }
            }

        }
    }

    /**
     * Evaluate the given AQL AST tree in the given entry and put it in the options map.
     * 
     * @param templateProvider
     *            the template model element were to put AQL evaluation errors.
     * @param options
     *            the map of generic options were to put evaluated content.
     * @param aqlEntry
     *            an AQL options to evaluate and put the result in the given options map.
     */
    private void evaluateAqlExpression(AbstractProviderClient templateProvider, Map<String, Object> options,
            Entry<String, Object> aqlEntry) {
        if (aqlEntry.getValue() == null) {
            throw new IllegalArgumentException(
                    QUERY_SYNTAX_ERROR_MESSAGE + templateProvider.getValidationMessages().get(0).getMessage());
        } else {
            EvaluationResult evaluationResult = evaluator.eval((AstResult) aqlEntry.getValue(),
                    variablesStack.peek());
            if (evaluationResult == null) {
                throw new IllegalArgumentException(QUERY_EVALERROR_MESSAGE);
            } else if (evaluationResult.getResult() == null) {
                StringBuilder builder = new StringBuilder();
                getDiagnostic(evaluationResult.getDiagnostic(), builder);
                throw new IllegalArgumentException(builder.toString());
            } else {
                options.put(aqlEntry.getKey(), evaluationResult.getResult());
            }
        }
    }

    /**
     * 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;
    }

    /**
     * Should be called when the {@link M2DocEvaluator} is no longer needed so that it can cleanup temporary files used during the
     * generation.
     */
    public void clear() {
        for (AbstractDiagramProvider diagprovider : usedProviders) {
            diagprovider.clear();
        }

    }

}