Skip to content

Commit

Permalink
Merge pull request #166 from Vineg/master
Browse files Browse the repository at this point in the history
Added support for multiple fragments definition
  • Loading branch information
ultraq authored Jan 31, 2018
2 parents cfafa2b + 2fc178b commit 3a7f79e
Show file tree
Hide file tree
Showing 10 changed files with 342 additions and 22 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/Classes
/node_modules
npm-debug.log
bin

# IDEs
/.idea
Expand Down
15 changes: 9 additions & 6 deletions source/nz/net/ultraq/thymeleaf/LayoutDialect.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@

package nz.net.ultraq.thymeleaf

import org.thymeleaf.dialect.AbstractProcessorDialect
import org.thymeleaf.processor.IProcessor
import org.thymeleaf.standard.processor.StandardXmlNsTagProcessor
import org.thymeleaf.templatemode.TemplateMode

import nz.net.ultraq.thymeleaf.context.extensions.IContextExtensions
import nz.net.ultraq.thymeleaf.decorators.DecorateProcessor
import nz.net.ultraq.thymeleaf.decorators.DecoratorProcessor
import nz.net.ultraq.thymeleaf.decorators.SortingStrategy
import nz.net.ultraq.thymeleaf.decorators.TitlePatternProcessor
import nz.net.ultraq.thymeleaf.decorators.strategies.AppendingStrategy
import nz.net.ultraq.thymeleaf.fragments.CollectFragmentProcessor
import nz.net.ultraq.thymeleaf.fragments.FragmentProcessor
import nz.net.ultraq.thymeleaf.includes.IncludeProcessor
import nz.net.ultraq.thymeleaf.includes.InsertProcessor
Expand All @@ -35,11 +41,6 @@ import nz.net.ultraq.thymeleaf.models.extensions.ITemplateEventExtensions
import nz.net.ultraq.thymeleaf.models.extensions.ITextExtensions
import nz.net.ultraq.thymeleaf.models.extensions.TemplateModelExtensions

import org.thymeleaf.dialect.AbstractProcessorDialect
import org.thymeleaf.processor.IProcessor
import org.thymeleaf.standard.processor.StandardXmlNsTagProcessor
import org.thymeleaf.templatemode.TemplateMode

/**
* A dialect for Thymeleaf that lets you build layouts and reusable templates in
* order to improve code reuse
Expand Down Expand Up @@ -104,6 +105,7 @@ class LayoutDialect extends AbstractProcessorDialect {
new InsertProcessor(TemplateMode.HTML, dialectPrefix),
new ReplaceProcessor(TemplateMode.HTML, dialectPrefix),
new FragmentProcessor(TemplateMode.HTML, dialectPrefix),
new CollectFragmentProcessor(TemplateMode.HTML, dialectPrefix),
new TitlePatternProcessor(TemplateMode.HTML, dialectPrefix),

// Processors available in the XML template mode
Expand All @@ -113,7 +115,8 @@ class LayoutDialect extends AbstractProcessorDialect {
new IncludeProcessor(TemplateMode.XML, dialectPrefix),
new InsertProcessor(TemplateMode.XML, dialectPrefix),
new ReplaceProcessor(TemplateMode.XML, dialectPrefix),
new FragmentProcessor(TemplateMode.XML, dialectPrefix)
new FragmentProcessor(TemplateMode.XML, dialectPrefix),
new CollectFragmentProcessor(TemplateMode.XML, dialectPrefix)
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2012, 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.fragments

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

import nz.net.ultraq.thymeleaf.models.ElementMerger

/**
* Processor produced from FragmentProcessor in order to separate include and define logic to avoid ambiguity
*
* @authors Emanuel Rabina, George Vinokhodov
*/
class CollectFragmentProcessor extends AbstractAttributeTagProcessor {

private static final Logger logger = LoggerFactory.getLogger(CollectFragmentProcessor)

private static boolean warned = false

static final String PROCESSOR_DEFINE = 'define'
static final String PROCESSOR_COLLECT = 'collect'
static final int PROCESSOR_PRECEDENCE = 1

/**
* Constructor, sets this processor to work on the 'collect' attribute.
*
* @param templateMode
* @param dialectPrefix
*/
CollectFragmentProcessor(TemplateMode templateMode, String dialectPrefix) {

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

/**
* Inserts the content of <code>:define</code> fragments into the encountered collect placeholder.
*
* @param context
* @param model
* @param attributeName
* @param attributeValue
* @param structureHandler
*/
@Override
@SuppressWarnings('AssignmentToStaticFieldFromInstanceMethod')
protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
AttributeName attributeName, String attributeValue, IElementTagStructureHandler structureHandler) {

// Emit a warning if found in the <head> section
if (templateMode == TemplateMode.HTML &&
context.elementStack.any { element -> element.elementCompleteName == 'head' }) {
if (!warned) {
logger.warn(
'You don\'t need to put the layout:fragment/data-layout-fragment attribute into the <head> section - ' +
'the decoration process will automatically copy the <head> section of your content templates into your layout page.'
)
warned = true
}
}

// All :define fragments we collected, :collect fragments included to determine where to stop.
// Fragments after :collect are preserved for the next :collect event
Queue fragments = FragmentMap.get(context)[(attributeValue)]

// Replace the tag body with the fragment
if (fragments) {
def modelFactory = context.modelFactory
def merger = new ElementMerger(context)
def replacementModel = modelFactory.createModel(tag)
def first = true
while (!fragments.empty) {
def fragment = fragments.poll()
if (fragment.get(0).getAttributeValue(dialectPrefix, CollectFragmentProcessor.PROCESSOR_COLLECT)) {
break
}
if (first) {
replacementModel = merger.merge(replacementModel, fragment)
first = false
} else {
def firstEvent = true
fragment.each {
event ->
if (firstEvent) {
firstEvent = false
replacementModel.add(new Text('\n'))
replacementModel.add(modelFactory.removeAttribute(event,
dialectPrefix, PROCESSOR_DEFINE))
} else {
replacementModel.add(event)
}
}
}
}

// Remove the layout:collect attribute - Thymeleaf won't do it for us
// when using StructureHandler.replaceWith(...)
replacementModel.replace(0, modelFactory.removeAttribute(replacementModel.first(),
dialectPrefix, PROCESSOR_COLLECT))

structureHandler.replaceWith(replacementModel, true)
}
}
}
19 changes: 14 additions & 5 deletions source/nz/net/ultraq/thymeleaf/fragments/FragmentFinder.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,32 @@ class FragmentFinder {
*/
Map<String,IModel> findFragments(IModel model) {

def fragments = [:]
def fragmentsMap = [:]

def eventIndex = 0
while (eventIndex < model.size()) {
def event = model.get(eventIndex)
if (event instanceof IOpenElementTag) {
def fragmentName = event.getAttributeValue(dialectPrefix, FragmentProcessor.PROCESSOR_NAME)
def collect = false
if (!fragmentName) {
collect = true
fragmentName = event.getAttributeValue(dialectPrefix, CollectFragmentProcessor.PROCESSOR_DEFINE) ?:
event.getAttributeValue(dialectPrefix, CollectFragmentProcessor.PROCESSOR_COLLECT)
}
if (fragmentName) {
def fragment = model.getModel(eventIndex)
fragments << [(fragmentName): fragment]
eventIndex += fragment.size()
continue
fragmentsMap[fragmentName] = fragmentsMap[fragmentName] ?: [] as Queue
fragmentsMap[fragmentName] << fragment
if (!collect) {
eventIndex += fragment.size()
continue
}
}
}
eventIndex++
}

return fragments
return fragmentsMap
}
}
15 changes: 12 additions & 3 deletions source/nz/net/ultraq/thymeleaf/fragments/FragmentMap.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,17 @@ class FragmentMap extends HashMap<String,IModel> {
* @param fragments The new fragments to add to the map.
*/
static void setForNode(IContext context, IElementModelStructureHandler structureHandler,
Map<String,IModel> fragments) {

structureHandler.setLocalVariable(FRAGMENT_COLLECTION_KEY, get(context) + fragments)
Map<String,List> fragments) {
def res = fragments
def appned = get(context)
appned.each {
k, v ->
if (res[k]) {
res[k] += v
} else {
res[k] = v
}
}
structureHandler.setLocalVariable(FRAGMENT_COLLECTION_KEY, res)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package nz.net.ultraq.thymeleaf.fragments

import nz.net.ultraq.thymeleaf.models.ElementMerger

import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.thymeleaf.context.ITemplateContext
Expand All @@ -27,6 +25,8 @@ import org.thymeleaf.processor.element.AbstractAttributeTagProcessor
import org.thymeleaf.processor.element.IElementTagStructureHandler
import org.thymeleaf.templatemode.TemplateMode

import nz.net.ultraq.thymeleaf.models.ElementMerger

/**
* This processor serves a dual purpose: to mark sections of the template that
* can be replaced, and to do the replacing when they're encountered.
Expand Down Expand Up @@ -80,10 +80,11 @@ class FragmentProcessor extends AbstractAttributeTagProcessor {
}

// Locate the fragment that corresponds to this decorator/include fragment
def fragment = FragmentMap.get(context)[(attributeValue)]
def fragments = FragmentMap.get(context)[(attributeValue)]

// Replace the tag body with the fragment
if (fragment) {
if (fragments) {
def fragment = fragments[-1]
def modelFactory = context.modelFactory
def replacementModel = new ElementMerger(context).merge(modelFactory.createModel(tag), fragment)

Expand Down
10 changes: 6 additions & 4 deletions source/nz/net/ultraq/thymeleaf/models/AttributeMerger.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@

package nz.net.ultraq.thymeleaf.models

import nz.net.ultraq.thymeleaf.LayoutDialect
import nz.net.ultraq.thymeleaf.fragments.FragmentProcessor

import org.thymeleaf.context.ITemplateContext
import org.thymeleaf.model.IModel
import org.thymeleaf.standard.StandardDialect
import org.thymeleaf.standard.processor.StandardWithTagProcessor

import nz.net.ultraq.thymeleaf.LayoutDialect
import nz.net.ultraq.thymeleaf.fragments.CollectFragmentProcessor
import nz.net.ultraq.thymeleaf.fragments.FragmentProcessor

/**
* Merges attributes from one element into another.
*
Expand Down Expand Up @@ -72,7 +73,8 @@ class AttributeMerger implements ModelMerger {

// Don't include layout:fragment processors
.findAll { sourceAttribute ->
return !sourceAttribute.equalsName(layoutDialectPrefix, FragmentProcessor.PROCESSOR_NAME)
return !sourceAttribute.equalsName(layoutDialectPrefix, FragmentProcessor.PROCESSOR_NAME) &&
!sourceAttribute.equalsName(layoutDialectPrefix, CollectFragmentProcessor.PROCESSOR_DEFINE)
}

.each { sourceAttribute ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

# Test that standard decoration can occur on XML documents.

%TEMPLATE_MODE XML


%INPUT
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="http://www.example.org/"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{Layout}">
<item layout:define="item">
<name>Tomatoes</name>
<price>3.99</price>
</item>
</root>

%INPUT[Layout]
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="http://www.example.org/"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{Common}">
<item layout:define="item">
<name>Apples</name>
<price>2.75</price>
</item>
</root>

%INPUT[Common]
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="http://www.example.org/"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<list>
<item layout:collect="item"></item>
<item>
<name>Potatoes</name>
<price>4.99</price>
</item>
</list>
</root>

%OUTPUT
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="http://www.example.org/">
<list>
<item>
<name>Apples</name>
<price>2.75</price>
</item>
<item>
<name>Tomatoes</name>
<price>3.99</price>
</item>
<item>
<name>Potatoes</name>
<price>4.99</price>
</item>
</list>
</root>
Loading

0 comments on commit 3a7f79e

Please sign in to comment.