Created
May 30, 2013 09:24
-
-
Save ritalin/5676737 to your computer and use it in GitHub Desktop.
Component slot for Thymeleaf (Java7)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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"> | |
© 2011 The Good Thymes Virtual Grocery. | |
<span th:define-slot="test"> | |
Default span text | |
</span> | |
</div> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.thymeleaf.dom.ext; | |
public interface IteratorCallable<Arg, Result> { | |
Result call(Arg arg); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<>(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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