Skip to content

Commit

Permalink
Move experimental title handling to a separate processor
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraq committed May 12, 2024
1 parent 6008579 commit 03ae2a9
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package nz.net.ultraq.thymeleaf.layoutdialect
import nz.net.ultraq.thymeleaf.layoutdialect.decorators.DecorateProcessor
import nz.net.ultraq.thymeleaf.layoutdialect.decorators.SortingStrategy
import nz.net.ultraq.thymeleaf.layoutdialect.decorators.TitlePatternProcessor
import nz.net.ultraq.thymeleaf.layoutdialect.decorators.TitleProcessor
import nz.net.ultraq.thymeleaf.layoutdialect.decorators.strategies.AppendingStrategy
import nz.net.ultraq.thymeleaf.layoutdialect.fragments.CollectFragmentProcessor
import nz.net.ultraq.thymeleaf.layoutdialect.fragments.FragmentProcessor
Expand Down Expand Up @@ -86,7 +87,8 @@ class LayoutDialect extends AbstractProcessorDialect {
new ReplaceProcessor(TemplateMode.HTML, dialectPrefix),
new FragmentProcessor(TemplateMode.HTML, dialectPrefix),
new CollectFragmentProcessor(TemplateMode.HTML, dialectPrefix),
new TitlePatternProcessor(TemplateMode.HTML, dialectPrefix, newTitleTokens),
new TitlePatternProcessor(TemplateMode.HTML, dialectPrefix),
new TitleProcessor(TemplateMode.HTML, dialectPrefix),

// Processors available in the XML template mode
new StandardXmlNsTagProcessor(TemplateMode.XML, dialectPrefix),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import nz.net.ultraq.thymeleaf.layoutdialect.decorators.html.HtmlDocumentDecorat
import nz.net.ultraq.thymeleaf.layoutdialect.decorators.xml.XmlDocumentDecorator
import nz.net.ultraq.thymeleaf.layoutdialect.fragments.FragmentFinder
import nz.net.ultraq.thymeleaf.layoutdialect.models.TemplateModelFinder
import nz.net.ultraq.thymeleaf.layoutdialect.models.TitleExtractor

import org.thymeleaf.context.ITemplateContext
import org.thymeleaf.engine.AttributeName
Expand Down Expand Up @@ -72,12 +71,6 @@ class DecorateProcessor extends AbstractAttributeModelProcessor {
/**
* Locates the template to decorate and, once decorated, inserts it into the
* processing chain.
*
* @param context
* @param model
* @param attributeName
* @param attributeValue
* @param structureHandler
*/
@Override
protected void doProcess(ITemplateContext context, IModel model, AttributeName attributeName,
Expand Down Expand Up @@ -110,19 +103,21 @@ class DecorateProcessor extends AbstractAttributeModelProcessor {
decorateTemplate = decorateTemplate.cloneModel()

// Extract titles from content and layout templates and save to the template context
def titleExtractor = new TitleExtractor(context, newTitleTokens)
titleExtractor.extract(contentTemplate, TitlePatternProcessor.CONTENT_TITLE_KEY)
titleExtractor.extract(decorateTemplate, TitlePatternProcessor.LAYOUT_TITLE_KEY)
// def titleExtractor = new TitleExtractor(context, newTitleTokens)
// titleExtractor.extract(contentTemplate, TitlePatternProcessor.CONTENT_TITLE_KEY)
// titleExtractor.extract(decorateTemplate, TitlePatternProcessor.LAYOUT_TITLE_KEY)

// Gather all fragment parts from this page to apply to the new document
// after decoration has taken place
def pageFragments = new FragmentFinder(dialectPrefix).findFragments(model)

// Choose the decorator to use based on template mode, then apply it
def decorator =
templateMode == TemplateMode.HTML ? new HtmlDocumentDecorator(context, sortingStrategy, autoHeadMerging) :
templateMode == TemplateMode.XML ? new XmlDocumentDecorator(context) :
null
templateMode == TemplateMode.HTML ?
new HtmlDocumentDecorator(context, sortingStrategy, autoHeadMerging, newTitleTokens) :
templateMode == TemplateMode.XML ?
new XmlDocumentDecorator(context) :
null
if (!decorator) {
throw new IllegalArgumentException(
"Layout dialect cannot be applied to the ${templateMode} template mode, only HTML and XML template modes are currently supported"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package nz.net.ultraq.thymeleaf.layoutdialect.decorators

import nz.net.ultraq.thymeleaf.expressionprocessor.ExpressionProcessor

import org.thymeleaf.context.ITemplateContext
import org.thymeleaf.engine.AttributeName
import org.thymeleaf.model.IProcessableElementTag
Expand All @@ -38,44 +36,28 @@ import java.util.regex.Pattern
class TitlePatternProcessor extends AbstractAttributeTagProcessor {

// private static final String TOKEN_CONTENT_TITLE = '$CONTENT_TITLE'
private static final String TOKEN_LAYOUT_TITLE = '$LAYOUT_TITLE'
private static final String TOKEN_LAYOUT_TITLE = '$LAYOUT_TITLE'
private static final Pattern TOKEN_PATTERN = ~/(\$(LAYOUT|CONTENT)_TITLE)/

static final String PROCESSOR_NAME = 'title-pattern'
static final int PROCESSOR_PRECEDENCE = 1

static final String CONTENT_TITLE_KEY = 'layoutDialectContentTitle'
static final String LAYOUT_TITLE_KEY = 'layoutDialectLayoutTitle'
static final String CONTENT_TITLE_KEY_OLD = 'LayoutDialect::ContentTitle'
static final String LAYOUT_TITLE_KEY_OLD = 'LayoutDialect::LayoutTitle'

final boolean newTitleTokens
static final String CONTENT_TITLE_KEY = 'LayoutDialect::ContentTitle'
static final String LAYOUT_TITLE_KEY = 'LayoutDialect::LayoutTitle'

/**
* Constructor, sets this processor to work on the 'title-pattern' attribute.
*
* @param templateMode
* @param dialectPrefix
*/
TitlePatternProcessor(TemplateMode templateMode, String dialectPrefix, boolean newTitleTokens) {
TitlePatternProcessor(TemplateMode templateMode, String dialectPrefix) {

super(templateMode, dialectPrefix, null, false, PROCESSOR_NAME, true, PROCESSOR_PRECEDENCE, true)

this.newTitleTokens = newTitleTokens
}

/**
* Process the {@code layout:title-pattern} directive, replaces the title text
* with the titles from the content and layout pages.
*
* @param context
* @param tag
* @param attributeName
* @param attributeValue
* @param structureHandler
*/
@Override
@SuppressWarnings('AssignmentToStaticFieldFromInstanceMethod')
protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
AttributeName attributeName, String attributeValue, IElementTagStructureHandler structureHandler) {

Expand All @@ -86,15 +68,8 @@ class TitlePatternProcessor extends AbstractAttributeTagProcessor {

def modelFactory = context.modelFactory
def titleModel = modelFactory.createModel()

// TODO: Experimental title tokens branch
if (newTitleTokens) {
structureHandler.setBody(new ExpressionProcessor(context).processAsString(attributeValue), false)
return
}

def contentTitle = context[CONTENT_TITLE_KEY_OLD]
def layoutTitle = context[LAYOUT_TITLE_KEY_OLD]
def contentTitle = context[CONTENT_TITLE_KEY]
def layoutTitle = context[LAYOUT_TITLE_KEY]

// Break the title pattern up into tokens to map to their respective models
if (layoutTitle && contentTitle) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2024, Emanuel Rabina (http://www.ultraq.net.nz/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package nz.net.ultraq.thymeleaf.layoutdialect.decorators

import nz.net.ultraq.thymeleaf.expressionprocessor.ExpressionProcessor

import org.thymeleaf.context.ITemplateContext
import org.thymeleaf.engine.AttributeName
import org.thymeleaf.model.IProcessableElementTag
import org.thymeleaf.processor.element.AbstractAttributeTagProcessor
import org.thymeleaf.processor.element.IElementTagStructureHandler
import org.thymeleaf.templatemode.TemplateMode

/**
* A processor for the experimental feature of using standard Thymeleaf
* expression syntax when creating the {@code <title>} element, as opposed to
* the special syntax required of {@code layout:title-pattern}.
*
* @author Emanuel Rabina
*/
class TitleProcessor extends AbstractAttributeTagProcessor {

static final String PROCESSOR_NAME = 'title'
static final int PROCESSOR_PRECEDENCE = 1

static final String CONTENT_TITLE_KEY = 'layoutDialectContentTitle'
static final String LAYOUT_TITLE_KEY = 'layoutDialectLayoutTitle'

/**
* Constructor, sets this processor to work on the 'title' attribute.
*/
TitleProcessor(TemplateMode templateMode, String dialectPrefix) {

super(templateMode, dialectPrefix, null, false, PROCESSOR_NAME, true, PROCESSOR_PRECEDENCE, true)
}

/**
* Process the {@code layout:title} directive, replaces the title text using
* values extracted from the layout and content templates and emitted with
* standard Thymeleaf expression syntax.
*/
@Override
protected void doProcess(ITemplateContext context, IProcessableElementTag tag, AttributeName attributeName,
String attributeValue, IElementTagStructureHandler structureHandler) {

// Ensure this attribute is only on the <title> element
if (tag.elementCompleteName != 'title') {
throw new IllegalArgumentException("${attributeName} processor should only appear in a <title> element")
}

structureHandler.setBody(new ExpressionProcessor(context).processAsString(attributeValue), false)
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
/*
* Copyright 2013, Emanuel Rabina (http://www.ultraq.net.nz/)
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -28,35 +28,27 @@ import org.thymeleaf.model.IOpenElementTag
* A decorator made to work over an HTML document. Decoration for a document
* involves 2 sub-decorators: a special one for the {@code <head>} element, and
* a standard one for the {@code <body>} element.
*
*
* @author Emanuel Rabina
*/
class HtmlDocumentDecorator extends XmlDocumentDecorator {

final SortingStrategy sortingStrategy
final boolean autoHeadMerging
final boolean newTitleTokens

/**
* Constructor, builds a decorator with the given configuration.
*
* @param context
* @param sortingStrategy
* @param autoHeadMerging
*/
HtmlDocumentDecorator(ITemplateContext context, SortingStrategy sortingStrategy, boolean autoHeadMerging) {
HtmlDocumentDecorator(ITemplateContext context, SortingStrategy sortingStrategy, boolean autoHeadMerging,
boolean newTitleTokens) {

super(context)
this.sortingStrategy = sortingStrategy
this.autoHeadMerging = autoHeadMerging
this.newTitleTokens = newTitleTokens
}

/**
* Decorate an entire HTML page.
*
* @param targetDocumentModel
* @param sourceDocumentModel
* @return Result of the decoration.
*/
@Override
IModel decorate(IModel targetDocumentModel, IModel sourceDocumentModel) {

Expand All @@ -67,7 +59,7 @@ class HtmlDocumentDecorator extends XmlDocumentDecorator {
def headModelFinder = { event -> event.isOpeningElementOf('head') }
if (autoHeadMerging) {
def targetHeadModel = resultDocumentModel.findModel(headModelFinder)
def resultHeadModel = new HtmlHeadDecorator(context, sortingStrategy)
def resultHeadModel = new HtmlHeadDecorator(context, sortingStrategy, newTitleTokens)
.decorate(targetHeadModel, sourceDocumentModel.findModel(headModelFinder))
if (resultHeadModel) {
if (targetHeadModel) {
Expand All @@ -76,7 +68,7 @@ class HtmlDocumentDecorator extends XmlDocumentDecorator {
else {
resultDocumentModel.insertModelWithWhitespace(resultDocumentModel.findIndexOf { event ->
return (event instanceof IOpenElementTag && event.elementCompleteName == 'body') ||
(event instanceof ICloseElementTag && event.elementCompleteName == 'html')
(event instanceof ICloseElementTag && event.elementCompleteName == 'html')
} - 1, resultHeadModel, modelFactory)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
/*
* Copyright 2013, Emanuel Rabina (http://www.ultraq.net.nz/)
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -27,22 +27,16 @@ import groovy.transform.TupleConstructor

/**
* A decorator specific to processing an HTML {@code <head>} element.
*
*
* @author Emanuel Rabina
*/
@TupleConstructor(defaults = false)
class HtmlHeadDecorator implements Decorator {

final ITemplateContext context
final SortingStrategy sortingStrategy
final boolean newTitleTokens

/**
* Decorate the {@code <head>} part.
*
* @param targetHeadModel
* @param sourceHeadModel
* @return Result of the decoration.
*/
@Override
IModel decorate(IModel targetHeadModel, IModel sourceHeadModel) {

Expand All @@ -69,7 +63,7 @@ class HtmlHeadDecorator implements Decorator {
def indexOfTitle = resultHeadModel.findIndexOf(titleFinder)
if (indexOfTitle != -1) {
resultHeadModel.removeAllModels(titleFinder)
def resultTitle = new HtmlTitleDecorator(context).decorate(
def resultTitle = new HtmlTitleDecorator(context, newTitleTokens).decorate(
targetHeadModel?.findModel(titleFinder),
sourceHeadModel?.findModel(titleFinder)
)
Expand Down
Loading

0 comments on commit 03ae2a9

Please sign in to comment.