Skip to content

Commit

Permalink
fix #200: redeclaration of layout fragments in nested decoration hier…
Browse files Browse the repository at this point in the history
…archies must produce the expected result
  • Loading branch information
silkentrance committed Mar 7, 2020
1 parent 75c51d1 commit 9db661e
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 37 deletions.
25 changes: 25 additions & 0 deletions source/nz/net/ultraq/thymeleaf/fragments/FragmentMap.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,43 @@ class FragmentMap extends HashMap<String,List<IModel>> {
/**
* Set the fragment collection to contain whatever it initially had, plus the
* given fragments, just for the scope of the current node.
*
* This must be used when processing non include/insert/replace related directives.
*
* @param context
* @param structureHandler
* @param fragments The new fragments to add to the map.
*/
static void setForNode(IContext context, IElementModelStructureHandler structureHandler,
Map<String,List<IModel>> fragments) {
setForNodeInternal(context, structureHandler, fragments, false)
}

/**
* Set the fragment collection to contain whatever it initially had, plus the
* given fragments, just for the scope of the current node.
*
* This must be used when processing include/insert/replace related directives.
*
* @param context
* @param structureHandler
* @param fragments The new fragments to add to the map.
*/
static void setForNodeIncludeProcessing(IContext context, IElementModelStructureHandler structureHandler,
Map<String,List<IModel>> fragments) {
setForNodeInternal(context, structureHandler, fragments, true)
}

private static void setForNodeInternal(IContext context, IElementModelStructureHandler structureHandler,
Map<String,List<IModel>> fragments, boolean isIncludeProcessing) {

structureHandler.setLocalVariable(FRAGMENT_COLLECTION_KEY,
get(context).inject(fragments.clone()) { accumulator, fragmentName, fragmentList ->
if (accumulator[fragmentName]) {
accumulator[fragmentName] += fragmentList
if (isIncludeProcessing) {
accumulator[fragmentName] = ((List) accumulator[fragmentName]).reverse()
}
}
else {
accumulator[fragmentName] = fragmentList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class FragmentProcessor extends AbstractAttributeTagProcessor {

// Replace the tag body with the fragment
if (fragments) {
def fragment = fragments.first()
def fragment = fragments.size() == 1 ? fragments.first() : fragments.last()
def modelFactory = context.modelFactory
def replacementModel = new ElementMerger(context).merge(modelFactory.createModel(tag), fragment)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class IncludeProcessor extends AbstractAttributeModelProcessor {

// Gather all fragment parts within the include element, scoping them to this element
def includeFragments = new FragmentFinder(dialectPrefix).findFragments(model)
FragmentMap.setForNode(context, structureHandler, includeFragments)
FragmentMap.setForNodeIncludeProcessing(context, structureHandler, includeFragments)

// Keep track of what template is being processed? Thymeleaf does this for
// its include processor, so I'm just doing the same here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class InsertProcessor extends AbstractAttributeModelProcessor {

// Gather all fragment parts within this element, scoping them to this element
def includeFragments = new FragmentFinder(dialectPrefix).findFragments(model)
FragmentMap.setForNode(context, structureHandler, includeFragments)
FragmentMap.setForNodeIncludeProcessing(context, structureHandler, includeFragments)

// Keep track of what template is being processed? Thymeleaf does this for
// its include processor, so I'm just doing the same here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class ReplaceProcessor extends AbstractAttributeModelProcessor {

// Gather all fragment parts within the include element, scoping them to this element
def includeFragments = new FragmentFinder(dialectPrefix).findFragments(model)
FragmentMap.setForNode(context, structureHandler, includeFragments)
FragmentMap.setForNodeIncludeProcessing(context, structureHandler, includeFragments)

// Keep track of what template is being processed? Thymeleaf does this for
// its include processor, so I'm just doing the same here.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@

# Test a deep layout hierarchy (3 levels).

%TEMPLATE_MODE HTML


%INPUT
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{Parent}">
<head>
<title>Page title</title>
<script src="child-script.js"></script>
</head>
<body>
<div layout:fragment="content">
<p>Page content</p>
</div>
<footer layout:fragment="footer">
<p>Page footer</p>
</footer>
</body>
</html>


%INPUT[Parent]
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{Grandparent}">
<head>
<script src="parent-script.js"></script>
</head>
<body>
<section layout:fragment="section">
<header>
<h1>My website</h1>
</header>
<div layout:fragment="content">
<p>Parent content</p>
</div>
</section>
<!-- parent redeclares footer -->
<footer layout:fragment="footer">
<p>Parent footer</p>
</footer>
</body>
</html>


%INPUT[Grandparent]
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<script src="grandparent-script.js"></script>
</head>
<body>
<section layout:fragment="section">
<p>Grandparent section</p>
</section>
<footer layout:fragment="footer">
<p>Grandparent footer</p>
</footer>
</body>
</html>


%OUTPUT
<!DOCTYPE html>
<html>
<head>
<title>Page title</title>
<script src="grandparent-script.js"></script>
<script src="parent-script.js"></script>
<script src="child-script.js"></script>
</head>
<body>
<section>
<header>
<h1>My website</h1>
</header>
<div>
<p>Page content</p>
</div>
</section>
<footer>
<p>Page footer</p>
</footer>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,26 @@
%INPUT
<html>
<body>
<div layout:fragment="comment">
Comment structure
<div layout:fragment="comment-details">Default comment details</div>
</div>

<div layout:include="~{::comment}">
<div layout:include="~{comments :: comment}">
<div layout:fragment="comment-details">
Top level comment
<div layout:include="~{::comment}">
<div layout:include="~{comments :: comment}">
<div layout:fragment="comment-details">Nested comment</div>
</div>
</div>
</div>
</body>
</html>

%INPUT[comments]
<div layout:fragment="comment">
Comment structure
<div layout:fragment="comment-details">Default comment details</div>
</div>

%OUTPUT
<html>
<body>
<div>
Comment structure
<div>Default comment details</div>
</div>
<div>
Comment structure
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,26 @@
%INPUT
<html>
<body>
<div layout:fragment="comment">
Comment structure
<div layout:fragment="comment-details">Default comment details</div>
</div>

<div layout:insert="~{::comment}">
<div layout:insert="~{comments :: comment}">
<div layout:fragment="comment-details">
Top level comment
<div layout:insert="~{::comment}">
<div layout:insert="~{comments :: comment}">
<div layout:fragment="comment-details">Nested comment</div>
</div>
</div>
</div>
</body>
</html>

%INPUT[comments]
<div layout:fragment="comment">
Comment structure
<div layout:fragment="comment-details">Default comment details</div>
</div>

%OUTPUT
<html>
<body>
<div>
Comment structure
<div>Default comment details</div>
</div>
<div>
<div>
Comment structure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,26 @@
%INPUT
<html>
<body>
<div layout:fragment="comment">
Comment structure
<div layout:fragment="comment-details">Default comment details</div>
</div>

<div layout:replace="~{::comment}">
<div layout:replace="~{comments :: comment}">
<div layout:fragment="comment-details">
Top level comment
<div layout:replace="~{::comment}">
<div layout:replace="~{comments :: comment}">
<div layout:fragment="comment-details">Nested comment</div>
</div>
</div>
</div>
</body>
</html>

%INPUT[comments]
<div layout:fragment="comment">
Comment structure
<div layout:fragment="comment-details">Default comment details</div>
</div>

%OUTPUT
<html>
<body>
<div>
Comment structure
<div>Default comment details</div>
</div>
<div>
Comment structure
<div>
Expand Down

0 comments on commit 9db661e

Please sign in to comment.