Search in sources :

Example 6 with IOpenElementTag

use of org.thymeleaf.model.IOpenElementTag 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

IOpenElementTag (org.thymeleaf.model.IOpenElementTag)6 Test (org.junit.Test)2 TemplateProcessingException (org.thymeleaf.exceptions.TemplateProcessingException)2 ICloseElementTag (org.thymeleaf.model.ICloseElementTag)2 IProcessableElementTag (org.thymeleaf.model.IProcessableElementTag)2 ITemplateEvent (org.thymeleaf.model.ITemplateEvent)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 IAttribute (org.thymeleaf.model.IAttribute)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 IElementModelProcessor (org.thymeleaf.processor.element.IElementModelProcessor)1