1. Project Clover database mer. févr. 4 2026 12:48:28 CET
  2. Package org.devacfr.maven.skins.reflow.snippet

File SnippetContext.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart9.png
20% of files have more coverage

Code metrics

24
84
23
2
409
221
37
0,44
3,65
11,5
1,61

Classes

Class Line # Actions
SnippetContext 63 79 0% 33 6
0.950819795,1%
SnippetContext.SnippetResource 369 5 0% 4 6
0.3333333433,3%
 

Contributing tests

This file is covered by 66 tests. .

Source view

1    /*
2    * Copyright 2012-2025 Christophe Friederich
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10    * Unless required by applicable law or agreed to in writing, software
11    * distributed under the License is distributed on an "AS IS" BASIS,
12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    * See the License for the specific language governing permissions and
14    * limitations under the License.
15    */
16    package org.devacfr.maven.skins.reflow.snippet;
17   
18    import static java.util.Objects.requireNonNull;
19   
20    import java.io.StringWriter;
21    import java.io.Writer;
22    import java.util.UUID;
23    import java.util.function.Consumer;
24    import javax.annotation.Nonnull;
25    import javax.annotation.Nullable;
26    import org.apache.commons.io.IOUtils;
27    import org.apache.velocity.VelocityContext;
28    import org.apache.velocity.app.Velocity;
29    import org.apache.velocity.context.Context;
30    import org.apache.velocity.runtime.RuntimeConstants;
31    import org.apache.velocity.runtime.RuntimeSingleton;
32    import org.apache.velocity.tools.Scope;
33    import org.apache.velocity.tools.ToolManager;
34    import org.apache.velocity.tools.config.EasyFactoryConfiguration;
35    import org.apache.velocity.tools.generic.ClassTool;
36    import org.apache.velocity.tools.generic.ComparisonDateTool;
37    import org.apache.velocity.tools.generic.ContextTool;
38    import org.apache.velocity.tools.generic.DisplayTool;
39    import org.apache.velocity.tools.generic.EscapeTool;
40    import org.apache.velocity.tools.generic.FieldTool;
41    import org.apache.velocity.tools.generic.LinkTool;
42    import org.apache.velocity.tools.generic.LoopTool;
43    import org.apache.velocity.tools.generic.MathTool;
44    import org.apache.velocity.tools.generic.NumberTool;
45    import org.apache.velocity.tools.generic.RenderTool;
46    import org.apache.velocity.tools.generic.ResourceTool;
47    import org.apache.velocity.tools.generic.XmlTool;
48    import org.devacfr.maven.skins.reflow.HtmlTool;
49    import org.devacfr.maven.skins.reflow.ISkinConfig;
50    import org.devacfr.maven.skins.reflow.JsoupUtils;
51    import org.devacfr.maven.skins.reflow.URITool;
52    import org.devacfr.maven.skins.reflow.snippet.SnippetComponent.Type;
53    import org.jsoup.nodes.Element;
54    import org.jsoup.nodes.Node;
55    import org.jsoup.nodes.TextNode;
56    import org.slf4j.Logger;
57    import org.slf4j.LoggerFactory;
58   
59    /**
60    * @author Christophe Friederich
61    * @version 2.4
62    */
 
63    public class SnippetContext {
64   
65    /** */
66    private static final Logger LOGGER = LoggerFactory.getLogger(SnippetContext.class);
67   
68    /** **/
69    private String htmlSource;
70   
71    /** */
72    private ISkinConfig config;
73   
74    /** */
75    private final SnippetParser parser;
76   
77    /**
78    * Constructor.
79    *
80    * @param parser
81    */
 
82  68 toggle public SnippetContext(@Nonnull final SnippetParser parser) {
83  68 this.parser = requireNonNull(parser);
84    }
85   
86    /**
87    * Reset the context.
88    */
 
89  34 toggle public void reset() {
90  34 this.htmlSource = null;
91  34 this.config = null;
92    }
93   
94    /**
95    * Generate a unique snippet identifier.
96    *
97    * @return a unique snippet identifier
98    */
 
99  0 toggle public String generateSnippetIdentifier() {
100  0 return "snippet-placement-" + UUID.randomUUID().toString();
101    }
102   
103    /**
104    * Returns the current parser.
105    *
106    * @return the current parser
107    */
 
108  104 toggle public SnippetParser getParser() {
109  104 return this.parser;
110    }
111   
112    /**
113    * Returns the skin configuration.
114    *
115    * @return the skin configuration
116    */
 
117  108 toggle public ISkinConfig getConfig() {
118  108 return config;
119    }
120   
121    /**
122    * Create a child parser.
123    *
124    * @return a new SnippetParser
125    */
 
126  2 toggle public SnippetParser createChildParser() {
127  2 final SnippetParser parser = new SnippetParser();
128  2 return parser;
129    }
130   
131    /**
132    * Sets the skin configuration.
133    *
134    * @param config
135    * the skin configuration
136    */
 
137  36 toggle public void setConfig(final ISkinConfig config) {
138  36 this.config = config;
139    }
140   
141    /**
142    * Sets the html source.
143    *
144    * @param htmlSource
145    * the html source
146    */
 
147  68 toggle void setHtmlSource(final String htmlSource) {
148  68 this.htmlSource = htmlSource;
149    }
150   
151    /**
152    * Returns the html source.
153    *
154    * @return the html source
155    */
 
156  34 toggle public String html() {
157  34 return htmlSource;
158    }
159   
160    /**
161    * Returns the html document.
162    *
163    * @return the html document
164    */
 
165  2 toggle public Element document() {
166  2 return JsoupUtils.createHtmlDocument(html());
167    }
168   
169    /**
170    * Create a component from element and tokens.
171    *
172    * @param element
173    * the html element to use.
174    * @param startToken
175    * the start token
176    * @param endToken
177    * the end token.
178    * @return Returns a new {@link Component} representing the snippet.
179    */
 
180  102 toggle @Nonnull
181    public Component<?> create(@Nonnull final Element element,
182    @Nonnull final ComponentToken startToken,
183    @Nullable final ComponentToken endToken) {
184  102 requireNonNull(element);
185  102 requireNonNull(startToken);
186  102 Component<?> component = null;
187  102 if (parser.isSnippet(startToken.name())) {
188    // create snippet component
189  100 component = SnippetComponent.createSnippet(element, null, startToken.type());
190  100 recurciveCreateComponent(element, component);
191    } else {
192    // create generic component
193  2 component = Component.createComponent(element, null);
194  2 recurciveCreateComponent(element, component);
195    }
196  102 return component;
197    }
198   
199    /**
200    * Create a component from element and component.
201    *
202    * @param element
203    * the html element to use.
204    * @param parent
205    * the parent component.
206    * @return Returns a new {@link SnippetComponent} representing the snippet.
207    */
 
208  418 toggle @Nonnull
209    public Component<?> create(@Nonnull final Element element, final Component<?> parent) {
210  418 requireNonNull(element);
211  418 Component<?> component = null;
212  418 if (parser.isSnippet(element.tagName())) {
213  6 component = SnippetComponent.createSnippet(element, parent, Type.webComponent);
214    } else {
215  412 component = Component.createComponent(element, parent);
216    }
217  418 recurciveCreateComponent(element, component);
218  418 return component;
219    }
220   
221    /**
222    * Recursively create components from element.
223    *
224    * @param element
225    * the html element to use.
226    * @param parent
227    * the parent component.
228    */
 
229  520 toggle private void recurciveCreateComponent(@Nonnull final Node element, final Component<?> parent) {
230  520 element.childNodes().forEach(child -> {
231  1228 Component<?> component = null;
232    // accept textnode not empty as component.
233  1228 if (child instanceof TextNode && ((TextNode) child).text().trim().length() > 0) {
234  252 component = Component.createComponent(child, parent);
235  976 } else if ("p".equals(child.nodeName()) && child.outerHtml().length() == 7) {
236    // skip empty <p> tags
237  976 } else if (child instanceof Element) {
238  416 final Element el = (Element) child;
239  416 component = create(el, parent);
240    }
241  1228 if (component != null) {
242  668 parent.addChild(component);
243    }
244    });
245    }
246   
247    /**
248    * Render the component.
249    *
250    * @param component
251    * the component to render.
252    */
 
253  102 toggle protected void render(final Component<?> component) {
254  102 traverseTee(component, c -> {
255  668 if (c instanceof SnippetComponent) {
256  4 ((SnippetComponent<?>) c).render(this);
257    }
258    });
259  102 if (component instanceof SnippetComponent) {
260  100 ((SnippetComponent<?>) component).render(this);
261    }
262    }
263   
 
264  770 toggle private void traverseTee(final Component<?> component, final Consumer<Component<?>> consumer) {
265  770 final Consumer<Component<?>> traverse = c -> traverseTee(c, consumer);
266  770 component.getChildren().forEach(consumer.andThen(traverse));
267    }
268   
 
269  106 toggle protected String renderComponent(final SnippetComponent<?> component, final Context context) {
270  106 final StringWriter writer = new StringWriter();
271  106 try {
272  106 mergeTemplate(component, context, writer);
273  106 return writer.toString();
274    } finally {
275  106 IOUtils.closeQuietly(writer);
276    }
277    }
278   
279    /**
280    * Merges the template for the given component.
281    *
282    * @param component
283    * the snippet component
284    * @param contextParent
285    * the velocity context parent
286    * @param writer
287    * the writer to use.
288    */
 
289  106 toggle protected void mergeTemplate(final SnippetComponent<?> component, final Context contextParent, final Writer writer) {
290  106 boolean found = false;
291  106 for (final String path : this.parser.getSnippetPaths()) {
292  292 final String filePath = path + '/' + component.getName() + ".vm";
293  292 if (Velocity.resourceExists(filePath)) {
294  106 found = true;
295  106 final Context context = createVelocityContext(contextParent);
296  106 context.put("snippet", component);
297  106 context.put("snippetPath", filePath);
298  106 context.put("config", this.config);
299  106 if (this.config.getContext() != null) {
300  38 context.put("pageContext", this.config.getContext());
301  38 context.put("pageType", this.config.getContext().getType());
302    }
303  106 context.put("velocity", Velocity.class);
304  106 context.put("site", this.config.getSiteModel());
305   
306  106 Velocity.mergeTemplate("META-INF/skin/snippets/_snippet.vm",
307    RuntimeSingleton.getString(RuntimeConstants.INPUT_ENCODING, RuntimeConstants.ENCODING_DEFAULT),
308    context,
309    writer);
310  106 break;
311    } else {
312  186 if (LOGGER.isDebugEnabled()) {
313  186 LOGGER.debug("Template for component '{}' not found in path:{} ", component, filePath);
314    }
315    }
316    }
317  106 if (!found) {
318  0 LOGGER.warn("The snippet '{}' template doesn't exist", component.getName());
319    }
320    }
321   
322    /**
323    * Creates a Velocity Context with all generic tools configured wit the site rendering context.
324    *
325    * @param contextParent
326    * velocity context parent
327    * @return a Velocity tools managed context
328    */
 
329  106 toggle protected Context createVelocityContext(final Context contextParent) {
330  106 final VelocityContext context = new VelocityContext(contextParent);
331  106 return context;
332    }
333   
334    /**
335    * Creates a ToolManager with all generic tools configured.
336    *
337    * @return a Velocity tools managed
338    */
 
339  34 toggle protected static ToolManager createToolManaged() {
340   
341  34 final EasyFactoryConfiguration config = new EasyFactoryConfiguration(false);
342  34 config.property("safeMode", Boolean.FALSE);
343  34 config.toolbox(Scope.REQUEST)
344    .tool(ContextTool.class)
345    .tool(LinkTool.class)
346    .tool(LoopTool.class)
347    .tool(RenderTool.class);
348  34 config.toolbox(Scope.APPLICATION)
349    .tool(ClassTool.class)
350    .tool(ComparisonDateTool.class)
351    .tool(DisplayTool.class)
352    .tool(EscapeTool.class)
353    .tool(FieldTool.class)
354    .tool(MathTool.class)
355    .tool(NumberTool.class)
356    .tool(ResourceTool.class)
357    .tool(XmlTool.class)
358    .tool(URITool.class)
359    .tool(HtmlTool.class);
360   
361  34 final ToolManager manager = new ToolManager(false, false);
362  34 manager.configure(config);
363  34 return manager;
364    }
365   
366    /**
367    * Represents a snippet resource.
368    */
 
369    public static class SnippetResource {
370   
371    /** */
372    private final String name;
373   
374    /** */
375    private final String path;
376   
377    /**
378    * Constructor.
379    *
380    * @param name
381    * the resource name
382    * @param path
383    * the resource path
384    */
 
385  4776 toggle public SnippetResource(final String name, final String path) {
386  4776 this.name = name;
387  4776 this.path = path;
388    }
389   
390    /** */
 
391  0 toggle public String getName() {
392  0 return name;
393    }
394   
395    /** */
 
396  0 toggle public String getPath() {
397  0 return path;
398    }
399   
400    /**
401    * {@inheritDoc}
402    */
 
403  0 toggle @Override
404    public String toString() {
405  0 return "SnippetResource [name=" + name + ", path=" + path + "]";
406    }
407    }
408   
409    }