Search in sources :

Example 1 with ITemplateEvent

use of org.thymeleaf.model.ITemplateEvent in project thymeleaf-tests by thymeleaf.

the class ModelAttributeTagProcessor method doProcess.

@Override
protected void doProcess(final ITemplateContext context, final IModel model, final AttributeName attributeName, final String attributeValue, final IElementModelStructureHandler structureHandler) {
    final IModelFactory modelFactory = context.getModelFactory();
    final IProcessableElementTag firstEvent = (IProcessableElementTag) model.get(0);
    final Map<String, String> attributes = firstEvent.getAttributeMap();
    final ITemplateEvent lastEvent = model.get(model.size() - 1);
    if (firstEvent == lastEvent) {
        final IStandaloneElementTag newFirstEvent = modelFactory.createStandaloneElementTag("ctx", attributes, AttributeValueQuotes.DOUBLE, false, false);
        model.replace(0, newFirstEvent);
    } else {
        final IOpenElementTag newFirstEvent = modelFactory.createOpenElementTag("ctx", attributes, AttributeValueQuotes.DOUBLE, false);
        final ICloseElementTag newLastEvent = modelFactory.createCloseElementTag("ctx", false, false);
        model.replace(0, newFirstEvent);
        model.replace(model.size() - 1, newLastEvent);
    }
}
Also used : ITemplateEvent(org.thymeleaf.model.ITemplateEvent) IProcessableElementTag(org.thymeleaf.model.IProcessableElementTag) IOpenElementTag(org.thymeleaf.model.IOpenElementTag) IModelFactory(org.thymeleaf.model.IModelFactory) ICloseElementTag(org.thymeleaf.model.ICloseElementTag) IStandaloneElementTag(org.thymeleaf.model.IStandaloneElementTag)

Example 2 with ITemplateEvent

use of org.thymeleaf.model.ITemplateEvent in project thymeleaf by thymeleaf.

the class IteratedGatheringModelProcessable method computeIterationModels.

/*
     * Internal-only method, meant to reshape the gathered model so that white space is adequately handled
     * during iteration. As a result, this method will produce different Model object for the first, the middle
     * and the last iterations.
     */
private IterationModels computeIterationModels(final IterationWhiteSpaceHandling iterationWhiteSpaceHandling) {
    /*
         * Nothing to iterate
         */
    if (iterationWhiteSpaceHandling == IterationWhiteSpaceHandling.ZERO_ITER) {
        return IterationModels.EMPTY;
    }
    /*
         * Get the originally gathered model. This will serve as a base for any needed modifications
         */
    final Model innerModel = getInnerModel();
    final int gatheredModelSize = innerModel.size();
    /*
         * If there is only one iteration, we need to perform no modifications at all, whichever the template mode
         */
    if (iterationWhiteSpaceHandling == IterationWhiteSpaceHandling.SINGLE_ITER) {
        return new IterationModels(innerModel, innerModel, innerModel);
    }
    /*
         * If template mode is a markup one, we will only need to take care of the existence of a preceding white space
         */
    if (!this.templateMode.isText()) {
        if (this.precedingWhitespace != null) {
            final Model modelWithWhiteSpace = new Model(innerModel);
            modelWithWhiteSpace.insert(0, this.precedingWhitespace);
            return new IterationModels(innerModel, modelWithWhiteSpace, modelWithWhiteSpace);
        }
        return new IterationModels(innerModel, innerModel, innerModel);
    }
    if (innerModel.size() <= 2) {
        // This does only contain the template open + close events -- nothing to be done
        return new IterationModels(innerModel, innerModel, innerModel);
    }
    int firstBodyEventCutPoint = -1;
    int lastBodyEventCutPoint = -1;
    // we know there is at least one body event
    final ITemplateEvent firstBodyEvent = innerModel.get(1);
    Text firstTextBodyEvent = null;
    if (innerModel.get(0) instanceof OpenElementTag && firstBodyEvent instanceof IText) {
        firstTextBodyEvent = Text.asEngineText((IText) firstBodyEvent);
        final int firstTextEventLen = firstTextBodyEvent.length();
        int i = 0;
        char c;
        while (i < firstTextEventLen && firstBodyEventCutPoint < 0) {
            c = firstTextBodyEvent.charAt(i);
            if (c == '\n') {
                firstBodyEventCutPoint = i + 1;
                // we've already assigned the value we were looking for
                break;
            } else if (Character.isWhitespace(c)) {
                i++;
                continue;
            } else {
                // We will not be able to perform any whitespace reduction here
                break;
            }
        }
    }
    final ITemplateEvent lastBodyEvent = innerModel.get(gatheredModelSize - 2);
    Text lastTextBodyEvent = null;
    if (firstBodyEventCutPoint >= 0 && innerModel.get(gatheredModelSize - 1) instanceof CloseElementTag && lastBodyEvent instanceof IText) {
        lastTextBodyEvent = Text.asEngineText((IText) lastBodyEvent);
        final int lastTextEventLen = lastTextBodyEvent.length();
        int i = lastTextEventLen - 1;
        char c;
        while (i >= 0 && lastBodyEventCutPoint < 0) {
            c = lastTextBodyEvent.charAt(i);
            if (c == '\n') {
                lastBodyEventCutPoint = i + 1;
                // we've already assigned the value we were looking for
                break;
            } else if (Character.isWhitespace(c)) {
                i--;
                continue;
            } else {
                // We will not be able to perform any whitespace reduction here
                break;
            }
        }
    }
    /*
         * If there is no reason to perform any modifications, just use the gathered model
         */
    if (firstBodyEventCutPoint < 0 || lastBodyEventCutPoint < 0) {
        // We don't have the scenario required for performing the needed whitespace collapsing operation
        return new IterationModels(innerModel, innerModel, innerModel);
    }
    if (firstBodyEvent == lastBodyEvent) {
        // If the first and the last event are actually the same, we need to take better care of how we manage whitespace
        final Text textForFirst = new Text(firstTextBodyEvent.subSequence(0, lastBodyEventCutPoint));
        final Text textForMiddle = new Text(firstTextBodyEvent.subSequence(firstBodyEventCutPoint, lastBodyEventCutPoint));
        final Text textForLast = new Text(firstTextBodyEvent.subSequence(firstBodyEventCutPoint, firstTextBodyEvent.length()));
        final Model modelFirst = new Model(innerModel);
        modelFirst.replace(1, textForFirst);
        final Model modelMiddle = new Model(innerModel);
        modelMiddle.replace(1, textForMiddle);
        final Model modelLast = new Model(innerModel);
        modelLast.replace(1, textForLast);
        return new IterationModels(modelFirst, modelMiddle, modelLast);
    }
    // At this point, we know the first and last body events are different objects
    final Model modelFirst = new Model(innerModel);
    final Model modelMiddle = new Model(innerModel);
    final Model modelLast = new Model(innerModel);
    if (firstBodyEventCutPoint > 0) {
        final Text headTextForMiddleAndMax = new Text(firstTextBodyEvent.subSequence(firstBodyEventCutPoint, firstTextBodyEvent.length()));
        modelMiddle.replace(1, headTextForMiddleAndMax);
        modelLast.replace(1, headTextForMiddleAndMax);
    }
    if (lastBodyEventCutPoint < lastTextBodyEvent.length()) {
        final Text tailTextForFirstAndMiddle = new Text(lastTextBodyEvent.subSequence(0, lastBodyEventCutPoint));
        modelFirst.replace(gatheredModelSize - 2, tailTextForFirstAndMiddle);
        modelMiddle.replace(gatheredModelSize - 2, tailTextForFirstAndMiddle);
    }
    return new IterationModels(modelFirst, modelMiddle, modelLast);
}
Also used : ITemplateEvent(org.thymeleaf.model.ITemplateEvent) IText(org.thymeleaf.model.IText) IText(org.thymeleaf.model.IText)

Example 3 with ITemplateEvent

use of org.thymeleaf.model.ITemplateEvent in project thymeleaf by thymeleaf.

the class AbstractElementModelProcessor method process.

public final void process(final ITemplateContext context, final IModel model, final IElementModelStructureHandler structureHandler) {
    ITemplateEvent firstEvent = null;
    try {
        firstEvent = model.get(0);
        doProcess(context, model, structureHandler);
    } catch (final TemplateProcessingException e) {
        if (firstEvent != null) {
            String modelTemplateName = firstEvent.getTemplateName();
            int modelLine = firstEvent.getLine();
            int modelCol = firstEvent.getCol();
            if (modelTemplateName != null) {
                if (!e.hasTemplateName()) {
                    e.setTemplateName(modelTemplateName);
                }
            }
            if (modelLine != -1 && modelCol != -1) {
                if (!e.hasLineAndCol()) {
                    e.setLineAndCol(modelLine, modelCol);
                }
            }
        }
        throw e;
    } catch (final Exception e) {
        // We will try to add all information possible to the exception report (template name, line, col)
        String modelTemplateName = null;
        int modelLine = -1;
        int modelCol = -1;
        if (firstEvent != null) {
            modelTemplateName = firstEvent.getTemplateName();
            modelLine = firstEvent.getLine();
            modelCol = firstEvent.getCol();
        }
        throw new TemplateProcessingException("Error during execution of processor '" + this.getClass().getName() + "'", modelTemplateName, modelLine, modelCol, e);
    }
}
Also used : ITemplateEvent(org.thymeleaf.model.ITemplateEvent) TemplateProcessingException(org.thymeleaf.exceptions.TemplateProcessingException) TemplateProcessingException(org.thymeleaf.exceptions.TemplateProcessingException)

Example 4 with ITemplateEvent

use of org.thymeleaf.model.ITemplateEvent in project thymeleaf by thymeleaf.

the class AbstractStandardFragmentInsertionTagProcessor method doProcess.

@Override
protected void doProcess(final ITemplateContext context, final IProcessableElementTag tag, final AttributeName attributeName, final String attributeValue, final IElementTagStructureHandler structureHandler) {
    if (StringUtils.isEmptyOrWhitespace(attributeValue)) {
        throw new TemplateProcessingException("Fragment specifications cannot be empty");
    }
    final IEngineConfiguration configuration = context.getConfiguration();
    /*
         * PARSE AND PROCESS THE FRAGMENT
         */
    final Object fragmentObj = computeFragment(context, attributeValue);
    if (fragmentObj == null) {
        throw new TemplateInputException("Error resolving fragment: \"" + attributeValue + "\": " + "template or fragment could not be resolved");
    } else if (fragmentObj == NoOpToken.VALUE) {
        // If the Fragment result is NO-OP, we will just do nothing (apart from deleting the th:* attribute)
        return;
    } else if (fragmentObj == Fragment.EMPTY_FRAGMENT) {
        // tag (th:insert) or remove it completely, tag included (th:replace)
        if (this.replaceHost) {
            structureHandler.removeElement();
        } else {
            structureHandler.removeBody();
        }
        return;
    }
    final Fragment fragment = (Fragment) fragmentObj;
    final TemplateModel fragmentModel = fragment.getTemplateModel();
    Map<String, Object> fragmentParameters = fragment.getParameters();
    /*
         * ONCE WE HAVE THE FRAGMENT MODEL (its events, in fact), CHECK THE FRAGMENT SIGNATURE
         * Fragment signature is important because it might affect the way we apply the parameters to the fragment.
         *
         * Note this works whatever the template mode of the inserted fragment, given we are looking for an
         * element containing a "th:fragment/data-th-fragment" in a generic, non-template-dependent way.
         */
    // We will check types first instead of events in order to (many times) avoid creating an immutably-wrapped
    // event object when calling "model.get(pos)"
    boolean signatureApplied = false;
    final ITemplateEvent firstEvent = (fragmentModel.size() > 2 ? fragmentModel.get(1) : null);
    if (firstEvent != null && IProcessableElementTag.class.isAssignableFrom(firstEvent.getClass())) {
        final String dialectPrefix = attributeName.getPrefix();
        final IProcessableElementTag fragmentHolderEvent = (IProcessableElementTag) firstEvent;
        if (fragmentHolderEvent.hasAttribute(dialectPrefix, FRAGMENT_ATTR_NAME)) {
            // The selected fragment actually has a "th:fragment" attribute, so we should process its signature
            final String fragmentSignatureSpec = EscapedAttributeUtils.unescapeAttribute(fragmentModel.getTemplateMode(), fragmentHolderEvent.getAttributeValue(dialectPrefix, FRAGMENT_ATTR_NAME));
            if (!StringUtils.isEmptyOrWhitespace(fragmentSignatureSpec)) {
                final FragmentSignature fragmentSignature = FragmentSignatureUtils.parseFragmentSignature(configuration, fragmentSignatureSpec);
                if (fragmentSignature != null) {
                    // Reshape the fragment parameters into the ones that we will actually use, according to the signature
                    fragmentParameters = FragmentSignatureUtils.processParameters(fragmentSignature, fragmentParameters, fragment.hasSyntheticParameters());
                    signatureApplied = true;
                }
            }
        }
    }
    // not being applied, maybe not realising there was no signature assignation involved.
    if (!signatureApplied && fragment.hasSyntheticParameters()) {
        throw new TemplateProcessingException("Fragment '" + attributeValue + "' specifies synthetic (unnamed) parameters, but the resolved fragment " + "does not match a fragment signature (th:fragment,data-th-fragment) which could apply names to " + "the specified parameters.");
    }
    /*
         * CHECK WHETHER THIS IS A CROSS-TEMPLATE-MODE INSERTION. Only TemplateModels for the same template mode
         * can be safely inserted into the template being executed and processed just like any other sequences of
         * events. If the inserted template has a different template mode, we will need to process it aside and
         * obtain a String result for it, then insert such String as mere text.
         *
         * Note inserting large templates with a different template mode could therefore have a negative effect
         * on performance and memory usage, as their result needs to be completely stored in memory at some point
         * before being handled to the following phases of template processing. It is therefore recommended that
         * cross-template-mode fragment insertion is done only for small fragments, in which case it will work
         * almost the same as inlining (with the exception that the content to be inlined will be retrieved from
         * somewhere else by means of template resolution).
         */
    if (context.getTemplateMode() != fragmentModel.getTemplateMode()) {
        // Check if this is a th:include. If so, just don't allow
        if (this.insertOnlyContents) {
            throw new TemplateProcessingException("Template being processed uses template mode " + context.getTemplateMode() + ", " + "inserted fragment \"" + attributeValue + "\" uses template mode " + fragmentModel.getTemplateMode() + ". Cross-template-mode fragment insertion is not " + "allowed using the " + attributeName + " attribute, which is no longer recommended for use as " + "of Thymeleaf 3.0. Use {th:insert,data-th-insert} or {th:replace,data-th-replace} " + "instead, which do not remove the container element from the fragment being inserted.");
        }
        // doing it through the structure handler (we are going to perform a nested template processing operation)
        if (fragmentParameters != null && fragmentParameters.size() > 0) {
            if (!(context instanceof IEngineContext)) {
                throw new TemplateProcessingException("Parameterized fragment insertion is not supported because local variable support is DISABLED. This is due to " + "the use of an implementation of the " + ITemplateContext.class.getName() + " interface that does " + "not provide local-variable support. In order to have local-variable support, the variables map " + "implementation should also implement the " + IEngineContext.class.getName() + " interface");
            }
            // NOTE this IEngineContext interface is internal and should not be used in users' code
            ((IEngineContext) context).setVariables(fragmentParameters);
        }
        // Once parameters are in order, just process the template in a nested template engine execution
        final Writer stringWriter = new FastStringWriter(200);
        configuration.getTemplateManager().process(fragmentModel, context, stringWriter);
        // We will insert the result as NON-PROCESSABLE text (it's already been processed!)
        if (this.replaceHost) {
            structureHandler.replaceWith(stringWriter.toString(), false);
        } else {
            structureHandler.setBody(stringWriter.toString(), false);
        }
        return;
    }
    /*
         * APPLY THE FRAGMENT'S TEMPLATE RESOLUTION so that all code inside the fragment is executed with its own
         * template resolution info (working as if it were a local variable)
         */
    final TemplateData fragmentTemplateData = fragmentModel.getTemplateData();
    structureHandler.setTemplateData(fragmentTemplateData);
    /*
         * APPLY THE FRAGMENT PARAMETERS AS LOCAL VARIABLES, perhaps after reshaping it according to the fragment signature
         */
    if (fragmentParameters != null && fragmentParameters.size() > 0) {
        for (final Map.Entry<String, Object> fragmentParameterEntry : fragmentParameters.entrySet()) {
            structureHandler.setLocalVariable(fragmentParameterEntry.getKey(), fragmentParameterEntry.getValue());
        }
    }
    /*
         * IF WE ARE ASKING ONLY FOR CONTENTS (th:include), THEN REMOVE THE CONTAINER BLOCK
         */
    if (this.insertOnlyContents && fragmentTemplateData.hasTemplateSelectors()) {
        /*
             * In the case of th:include, things get a bit complicated because we need to remove the "element envelopes"
             * that contain what we really want to include (these envelopes' contents). So we will need to traverse
             * the entire returned model detecting those envelopes (open+close tags at model level == 0) and remove
             * them, along with anything else that is also at that level 0.
             */
        final IModel model = fragmentModel.cloneModel();
        int modelLevel = 0;
        int n = model.size();
        while (n-- != 0) {
            // We traverse backwards so that we can modify at the same time
            final ITemplateEvent event = model.get(n);
            if (event instanceof ICloseElementTag) {
                if (((ICloseElementTag) event).isUnmatched()) {
                    // This is an unmatched close tag (no corresponding open), therefore should not affect our count
                    continue;
                }
                if (modelLevel <= 0) {
                    model.remove(n);
                }
                modelLevel++;
                continue;
            }
            if (event instanceof IOpenElementTag) {
                modelLevel--;
                if (modelLevel <= 0) {
                    model.remove(n);
                }
                continue;
            }
            if (modelLevel <= 0) {
                model.remove(n);
            }
        }
        if (this.replaceHost) {
            structureHandler.replaceWith(model, true);
        } else {
            structureHandler.setBody(model, true);
        }
        return;
    }
    if (this.replaceHost) {
        structureHandler.replaceWith(fragmentModel, true);
    } else {
        structureHandler.setBody(fragmentModel, true);
    }
}
Also used : ITemplateEvent(org.thymeleaf.model.ITemplateEvent) IModel(org.thymeleaf.model.IModel) IEngineConfiguration(org.thymeleaf.IEngineConfiguration) IOpenElementTag(org.thymeleaf.model.IOpenElementTag) FragmentSignature(org.thymeleaf.standard.expression.FragmentSignature) TemplateModel(org.thymeleaf.engine.TemplateModel) Fragment(org.thymeleaf.standard.expression.Fragment) TemplateInputException(org.thymeleaf.exceptions.TemplateInputException) IEngineContext(org.thymeleaf.context.IEngineContext) TemplateData(org.thymeleaf.engine.TemplateData) IProcessableElementTag(org.thymeleaf.model.IProcessableElementTag) FastStringWriter(org.thymeleaf.util.FastStringWriter) TemplateProcessingException(org.thymeleaf.exceptions.TemplateProcessingException) ITemplateContext(org.thymeleaf.context.ITemplateContext) Map(java.util.Map) FastStringWriter(org.thymeleaf.util.FastStringWriter) Writer(java.io.Writer) ICloseElementTag(org.thymeleaf.model.ICloseElementTag)

Aggregations

ITemplateEvent (org.thymeleaf.model.ITemplateEvent)4 TemplateProcessingException (org.thymeleaf.exceptions.TemplateProcessingException)2 ICloseElementTag (org.thymeleaf.model.ICloseElementTag)2 IOpenElementTag (org.thymeleaf.model.IOpenElementTag)2 IProcessableElementTag (org.thymeleaf.model.IProcessableElementTag)2 Writer (java.io.Writer)1 Map (java.util.Map)1 IEngineConfiguration (org.thymeleaf.IEngineConfiguration)1 IEngineContext (org.thymeleaf.context.IEngineContext)1 ITemplateContext (org.thymeleaf.context.ITemplateContext)1 TemplateData (org.thymeleaf.engine.TemplateData)1 TemplateModel (org.thymeleaf.engine.TemplateModel)1 TemplateInputException (org.thymeleaf.exceptions.TemplateInputException)1 IModel (org.thymeleaf.model.IModel)1 IModelFactory (org.thymeleaf.model.IModelFactory)1 IStandaloneElementTag (org.thymeleaf.model.IStandaloneElementTag)1 IText (org.thymeleaf.model.IText)1 Fragment (org.thymeleaf.standard.expression.Fragment)1 FragmentSignature (org.thymeleaf.standard.expression.FragmentSignature)1 FastStringWriter (org.thymeleaf.util.FastStringWriter)1