Skip to content

Instantly share code, notes, and snippets.

@ritalin
Created May 30, 2013 09:24
Show Gist options
  • Save ritalin/5676737 to your computer and use it in GitHub Desktop.
Save ritalin/5676737 to your computer and use it in GitHub Desktop.
Component slot for Thymeleaf (Java7)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="utf-8" />
<title>Insert title here</title>
</head>
<body>
<div th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery.
<span th:define-slot="test">
Default span text
</span>
</div>
</body>
</html>
package org.thymeleaf.processor.ext;
import java.util.List;
import java.util.Map;
import org.thymeleaf.Arguments;
import org.thymeleaf.dom.Element;
import org.thymeleaf.dom.Node;
import org.thymeleaf.dom.ext.ElementGrouping;
import org.thymeleaf.dom.ext.ElementWalker;
import org.thymeleaf.dom.ext.IteratorCallable;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.fragment.FragmentAndTarget;
import org.thymeleaf.processor.IAttributeNameProcessorMatcher;
import org.thymeleaf.processor.ProcessorResult;
import org.thymeleaf.processor.attr.AbstractAttrProcessor;
import org.thymeleaf.standard.fragment.StandardFragmentProcessor;
import org.thymeleaf.standard.processor.attr.StandardFragmentAttrProcessor;
import org.thymeleaf.util.PrefixUtils;
public final class ApplyLayoutAttrbuteProcessor extends AbstractAttrProcessor {
public static final String FRAGMENT_ATTR_NAME = StandardFragmentAttrProcessor.ATTR_NAME;
public static int ATTR_PRECEDENCE = 100;
public ApplyLayoutAttrbuteProcessor() {
super("apply-layout");
}
public ApplyLayoutAttrbuteProcessor(IAttributeNameProcessorMatcher matcher) {
super(matcher);
}
@Override
public final ProcessorResult processAttribute(final Arguments arguments, final Element element, final String attributeName) {
final String attributeValue = element.getAttributeValue(attributeName);
final boolean substituteInclusionNode =
getSubstituteInclusionNode(arguments, element, attributeName, attributeValue);
final FragmentAndTarget fragmentAndTarget =
getFragmentAndTarget(arguments, element, attributeName, attributeValue, substituteInclusionNode);
final List<Node> fragmentNodes =
fragmentAndTarget.extractFragment(
arguments.getConfiguration(), arguments, arguments.getTemplateRepository());
if (fragmentNodes == null) {
throw new TemplateProcessingException(
"Cannot correctly process \"" + attributeName + "\" attribute. " +
"Fragment specification \"" + attributeValue + "\" matched null.");
}
List<Node> children = element.getChildren();
final String prefix = PrefixUtils.getPrefix(attributeName);
final String sourceSlotName = prefix + ":use-slot";
final String targetSlotName = prefix + ":define-slot";
Map<String, List<Element>> slots = this.extracSourceSlot(children, sourceSlotName);
for (Element target : this.extracTargetSlot(fragmentNodes, targetSlotName)) {
String attr = target.getAttributeValue(targetSlotName);
if (slots.containsKey(attr)) {
target.clearChildren();
for (Element slot : slots.get(attr)) {
target.addChild(slot.cloneNode(null, true));
}
target.getParent().extractChild(target);
}
}
element.setChildren(fragmentNodes);
element.getParent().extractChild(element);
return ProcessorResult.OK;
}
private Map<String, List<Element>> extracSourceSlot(List<Node> root, final String attrName) {
return this.extracSlot(root, attrName)
.groupBy(new IteratorCallable<Element, String>() {
@Override
public String call(Element element) {
return element.getAttributeValue(attrName);
}
})
.toMap(
new IteratorCallable<ElementGrouping<String,Element>, String>() {
@Override
public String call(ElementGrouping<String, Element> g) {
return g.getKey();
}
},
new IteratorCallable<ElementGrouping<String, Element>, List<Element>>() {
@Override
public List<Element> call(ElementGrouping<String, Element> g) {
return g.getValues();
}
}
)
;
}
private ElementWalker<Element> extracTargetSlot(List<Node> root, final String attrName) {
return extracSlot(root, attrName);
}
private ElementWalker<Element> extracSlot(List<Node> root, final String attrName) {
return ElementWalker.create(root)
.collect(new IteratorCallable<Element, Boolean>() {
@Override
public Boolean call(Element element) {
return element.hasAttribute(attrName);
}
})
;
}
protected final FragmentAndTarget getFragmentAndTarget(final Arguments arguments,
final Element element, final String attributeName, final String attributeValue,
final boolean substituteInclusionNode) {
final String targetAttributeName =
getTargetAttributeName(arguments, element, attributeName, attributeValue);
return StandardFragmentProcessor.computeStandardFragmentSpec(
arguments.getConfiguration(), arguments, attributeValue, null, targetAttributeName,
!substituteInclusionNode
);
}
protected String getTargetAttributeName(Arguments arguments, Element element, String attributeName,
String attributeValue)
{
if (attributeName != null) {
final String prefix = PrefixUtils.getPrefix(attributeName);
if (prefix != null) {
return prefix + ":" + FRAGMENT_ATTR_NAME;
}
}
return FRAGMENT_ATTR_NAME;
}
protected boolean getSubstituteInclusionNode(Arguments arguments, Element element, String attributeName,
String attributeValue) {
return true;
}
@Override
public int getPrecedence() {
return ATTR_PRECEDENCE ;
}
}
package org.thymeleaf.dom.ext;
import java.util.List;
public class ElementGrouping<Key, Source> {
private Key key;
private List<Source> sources;
public ElementGrouping(Key key, List<Source> elements) {
this.key = key;
this.sources = elements;
}
public Key getKey() {
return this.key;
}
public List<Source> getValues() {
return this.sources;
}
}
package org.thymeleaf.dom.ext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.thymeleaf.dom.Element;
import org.thymeleaf.dom.Node;
public class ElementWalker<Source> implements Iterable<Source> {
private final Iterator<Source> baseIterator;
public static ElementWalker<Element> create(Element root) {
return new ElementWalker<>(new ElementWalkerIteraror(root));
}
public static ElementWalker<Element> create(List<Node> children) {
return new ElementWalker<>(new ElementChildrenIterator(children));
}
private ElementWalker(Iterator<Source> baseIterator) {
assert baseIterator != null;
this.baseIterator = baseIterator;
}
public ElementWalker<Source> collect(IteratorCallable<Source, Boolean> callback) {
return new ElementWalker<>(new ElementCollectIterator<>(this.baseIterator, callback));
}
public <Key> ElementWalker<ElementGrouping<Key, Source>> groupBy(IteratorCallable<Source, Key> keySelector) {
return new ElementWalker<>(new ElementGroupingIterator<>(this.baseIterator, keySelector));
}
public List<Source> toList() {
List<Source> result = new ArrayList<>();
for (Source e : this) {
result.add(e);
}
return result;
}
public <Key, Value> Map<Key, Value> toMap(IteratorCallable<Source, Key> keySelector, IteratorCallable<Source, Value> valueSelector) {
assert keySelector != null;
assert valueSelector != null;
Map<Key, Value> result = new HashMap<>();
for (Source s : this) {
result.put(keySelector.call(s), valueSelector.call(s));
}
return result;
}
@Override
public Iterator<Source> iterator() {
return this.baseIterator;
}
}
class ElementWalkerIteraror implements Iterator<Element> {
private List<Iterator<Element>> internalIters;
private Iterator<Element> current;
private int index;
public ElementWalkerIteraror(final Element root) {
assert root != null;
this.internalIters = new ArrayList<Iterator<Element>>() {{
add(Arrays.asList(root).iterator());
add(new ElementChildrenIterator(root.getChildren()));
}};
}
@Override
public boolean hasNext() {
if ((this.current != null) && this.current.hasNext()) return true;
return this.getNextIterator() != null ? this.hasNext() : false;
}
private Iterator<Element> getNextIterator() {
if (this.index < internalIters.size()) {
this.current = internalIters.get(this.index++);
}
else {
this.current = null;
}
return this.current;
}
@Override
public Element next() {
return this.current != null ? this.current.next() : null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
class ElementChildrenIterator implements Iterator<Element> {
private List<Node> children;
private Iterator<Element> current;
private int index;
public ElementChildrenIterator(List<Node> children) {
this.children = children;
}
@Override
public boolean hasNext() {
if ((this.current != null) && this.current.hasNext()) return true;
return this.getNextIterator() != null ? this.hasNext() : false;
}
private Iterator<Element> getNextIterator() {
if (this.children != null) {
Element nextElement = this.getNextElement();
if (nextElement != null) {
this.current = new ElementWalkerIteraror(nextElement);
}
else {
this.current = null;
}
}
return this.current;
}
private Element getNextElement() {
while (this.index < this.children.size()) {
Node node = this.children.get(this.index++);
if (node instanceof Element) {
return (Element)node;
}
}
return null;
}
@Override
public Element next() {
return this.current != null ? this.current.next() : null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
class ElementCollectIterator<Source> implements Iterator<Source> {
private Iterator<Source> baseIterator;
private IteratorCallable<Source, Boolean> callback;
private Source current;
public ElementCollectIterator(Iterator<Source> baseIterator, IteratorCallable<Source, Boolean> callback) {
assert baseIterator != null;
assert callback != null;
this.baseIterator = baseIterator;
this.callback = callback;
}
@Override
public boolean hasNext() {
while (baseIterator.hasNext()) {
this.current = baseIterator.next();
if ((this.current != null) && (this.callback.call(this.current))) return true;
}
return false;
}
@Override
public Source next() {
return this.current;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
class ElementGroupingIterator<Key, Source> implements Iterator<ElementGrouping<Key, Source>> {
private Iterator<Entry<Key, List<Source>>> baseIterator;
private IteratorCallable<Boolean, Iterator<Entry<Key, List<Source>>>> factory;
public ElementGroupingIterator(final Iterator<Source> it, final IteratorCallable<Source, Key> keySelector) {
assert it != null;
assert keySelector != null;
this.factory = new IteratorCallable<Boolean, Iterator<Entry<Key, List<Source>>>>() {
@Override
public Iterator<Entry<Key, List<Source>>> call(Boolean arg) {
Map<Key, List<Source>> lookup = new HashMap<>();
while (it.hasNext()) {
final Source e = it.next();
Key key = keySelector.call(e);
if (lookup.containsKey(key)) {
lookup.get(key).add(e);
}
else {
lookup.put(key, new ArrayList<Source>() {{
add(e);
}});
}
}
return lookup.entrySet().iterator();
}
};
}
@Override
public boolean hasNext() {
if (this.baseIterator == null) {
this.baseIterator = this.factory.call(true);
}
return this.baseIterator.hasNext();
}
@Override
public ElementGrouping<Key, Source> next() {
Entry<Key, List<Source>> e = this.baseIterator.next();
return new ElementGrouping<Key, Source>(e.getKey(), e.getValue());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
<!DOCTYPE html>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="utf-8" />
<title>Insert title here</title>
</head>
<body>
<div th:apply-layout="_layout :: copy">
<div>
<div>なんとかかんとか・・・</div>
<div th:use-slot="test">置き換えられた ?</div>
</div>
</div>
</body>
</html>
package sample;
import static spark.Spark.*;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.processor.ext.LayoutDialect;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
import spark.Request;
import spark.Response;
import spark.Route;
public class HelloWorld {
public static void main(String[] args) {
get(new Route("/hello") {
@Override
public Object handle(Request request, Response response) {
TemplateEngine engine = createEngine();
Context ctx = new Context();
return engine.process("hello", ctx);
}
});
}
private static TemplateEngine createEngine() {
TemplateResolver r = new ClassLoaderTemplateResolver();
r.setTemplateMode("HTML5");
r.setSuffix(".html");
r.setPrefix("resources/templates/");
TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(r);
engine.addDialect(new LayoutDialect());
return engine;
}
}
package org.thymeleaf.dom.ext;
public interface IteratorCallable<Arg, Result> {
Result call(Arg arg);
}
package org.thymeleaf.processor.ext;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.doctype.resolution.IDocTypeResolutionEntry;
import org.thymeleaf.doctype.translation.IDocTypeTranslation;
import org.thymeleaf.processor.IProcessor;
public final class LayoutDialect implements IDialect {
@Override
public String getPrefix() {
return "th";
}
@Override
public boolean isLenient() {
return false;
}
@Override
public Set<IProcessor> getProcessors() {
return new HashSet<IProcessor>() {{
add(new LayoutSlotAttributeProcessor("define-slot"));
add(new LayoutSlotAttributeProcessor("use-slot"));
add(new ApplyLayoutAttrbuteProcessor());
}};
}
@Override
public Map<String, Object> getExecutionAttributes() {
return new HashMap<>();
}
@Override
public Set<IDocTypeTranslation> getDocTypeTranslations() {
return new HashSet<>();
}
@Override
public Set<IDocTypeResolutionEntry> getDocTypeResolutionEntries() {
return new HashSet<>();
}
}
package org.thymeleaf.processor.ext;
import org.thymeleaf.Arguments;
import org.thymeleaf.dom.Element;
import org.thymeleaf.processor.ProcessorResult;
import org.thymeleaf.processor.attr.AbstractAttrProcessor;
public final class LayoutSlotAttributeProcessor extends AbstractAttrProcessor {
public static int ATTR_PRECEDENCE = 100;
public LayoutSlotAttributeProcessor(String attrName) {
super(attrName);
}
@Override
protected ProcessorResult processAttribute(Arguments arguments, Element element, String attributeName) {
element.removeAttribute(attributeName);
return ProcessorResult.OK;
}
@Override
public int getPrecedence() {
return ATTR_PRECEDENCE;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment