1. Project Clover database mar. janv. 20 2026 12:32:22 CET
  2. Package org.devacfr.maven.skins.reflow.snippet

File SnippetParser.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart7.png
60% of files have more coverage

Code metrics

54
120
21
1
338
246
54
0,45
5,71
21
2,57

Classes

Class Line # Actions
SnippetParser 55 120 0% 54 57
0.707692370,8%
 

Contributing tests

This file is covered by 32 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 com.google.common.collect.Lists;
19    import com.google.common.collect.Maps;
20    import java.io.File;
21    import java.io.IOException;
22    import java.net.URL;
23    import java.net.URLDecoder;
24    import java.nio.charset.StandardCharsets;
25    import java.nio.file.Paths;
26    import java.util.ArrayList;
27    import java.util.Collections;
28    import java.util.Enumeration;
29    import java.util.Iterator;
30    import java.util.List;
31    import java.util.Map;
32    import java.util.jar.JarEntry;
33    import java.util.jar.JarFile;
34    import java.util.stream.Collectors;
35    import javax.annotation.Nonnull;
36    import javax.annotation.Nullable;
37    import org.apache.commons.io.FilenameUtils;
38    import org.apache.velocity.context.Context;
39    import org.devacfr.maven.skins.reflow.ISkinConfig;
40    import org.devacfr.maven.skins.reflow.JsoupUtils;
41    import org.devacfr.maven.skins.reflow.snippet.Processor.WebComponentProcessor;
42    import org.devacfr.maven.skins.reflow.snippet.SnippetContext.SnippetResource;
43    import org.jsoup.nodes.Element;
44    import org.jsoup.nodes.Node;
45    import org.jsoup.select.Collector;
46    import org.jsoup.select.Elements;
47    import org.jsoup.select.QueryParser;
48    import org.slf4j.Logger;
49    import org.slf4j.LoggerFactory;
50   
51    /**
52    * @author Christophe Friederich
53    * @version 2.4
54    */
 
55    public class SnippetParser {
56   
57    /** */
58    private static final Logger LOGGER = LoggerFactory.getLogger(SnippetParser.class);
59   
60    /** */
61    private static final List<String> DEFAULT_PATHS = Lists.newArrayList("src/site/layouts/snippets",
62    "META-INF/skin/snippets");
63   
64    /** */
65    private final ComponentResolver resolver;
66   
67    /** */
68    private final ArrayList<ComponentToken> stack;
69   
70    /** */
71    private Iterator<Element> it;
72   
73    /** */
74    private Processor state = null;
75   
76    /** */
77    private final Processor processor;
78   
79    /** */
80    private final SnippetContext snippetContext;
81   
82    /** */
83    private ComponentToken currentToken;
84   
85    /** */
86    private final List<String> snippetPaths = Lists.newArrayList(DEFAULT_PATHS);
87   
88    /** */
89    private final Map<String, SnippetResource> snippetResources = Maps.newHashMap();
90   
 
91  33 toggle public SnippetParser() {
92  33 stack = Lists.newArrayListWithCapacity(32);
93  33 snippetContext = new SnippetContext(this);
94  33 resolver = new ComponentResolver(this);
95  33 processor = new WebComponentProcessor(this);
96  33 snippetResources.putAll(loadSnippetResources(getSnippetPaths()));
97  33 if (LOGGER.isTraceEnabled()) {
98  0 LOGGER.trace("Loaded {} snippet resources: {}", snippetResources.size(), getSnippets());
99    }
100    }
101   
 
102  0 toggle @Nonnull
103    protected Map<String, SnippetResource> getSnippetResources() {
104  0 return Collections.unmodifiableMap(snippetResources);
105    }
106   
 
107  816 toggle @Nonnull
108    protected List<String> getSnippets() {
109  816 return snippetResources.keySet().stream().collect(Collectors.toList());
110    }
111   
 
112  86 toggle @Nonnull
113    public List<String> getSnippetPaths() {
114  86 return Collections.unmodifiableList(snippetPaths);
115    }
116   
 
117  16 toggle public SnippetParser insertResourcePath(final @Nonnull String path, final int index) {
118  16 this.snippetPaths.add(index, path);
119  16 return this;
120    }
121   
 
122  1 toggle public SnippetParser addResourcePath(final @Nonnull String path) {
123  1 this.snippetPaths.add(path);
124  1 return this;
125    }
126   
 
127  1 toggle public SnippetParser refreshParser() {
128  1 snippetResources.clear();
129  1 snippetResources.putAll(loadSnippetResources(getSnippetPaths()));
130  1 if (LOGGER.isTraceEnabled()) {
131  0 LOGGER.trace("Reloaded {} snippet resources: {}", snippetResources.size(), getSnippets());
132    }
133  1 return this;
134    }
135   
 
136  762 toggle public boolean isSnippet(final Node node) {
137  762 return getSnippets().stream().anyMatch(snippet -> snippet.equalsIgnoreCase(JsoupUtils.getNodeName(node)));
138    }
139   
140    /**
141    * @param document
142    * the Jsoup element to use
143    * @return Returns {@code true} if the document contains at least one snippet component.
144    */
 
145  54 toggle public boolean hasIncludedSnippetComponent(final Element document) {
146  54 final String tags = getSnippets().stream().collect(Collectors.joining(","));
147  54 if (LOGGER.isDebugEnabled()) {
148  54 LOGGER.debug("Check document for snippet components: {}", tags);
149    }
150  54 return Collector.findFirst(QueryParser.parse(tags), document) != null;
151    }
152   
 
153  16 toggle public SnippetContext parse(final ISkinConfig config, @Nullable String htmlSource) throws IOException {
154  16 if (htmlSource == null) {
155  0 htmlSource = "";
156    }
157   
158  16 snippetContext.reset();
159  16 snippetContext.setConfig(config);
160  16 snippetContext.setHtmlSource(htmlSource);
161   
162  16 if (LOGGER.isDebugEnabled()) {
163  16 LOGGER.debug("Parse Snippet");
164  16 LOGGER.debug(htmlSource);
165    }
166   
167    // find all snippets
168  16 final Element doc = resolver.normalize(JsoupUtils.createHtmlDocument(htmlSource));
169   
170  16 if (LOGGER.isDebugEnabled()) {
171  16 LOGGER.debug("Normalized HTML source:");
172  16 LOGGER.debug(doc.html());
173    }
174   
175  16 final Elements elements = resolver.collect(doc);
176   
177  65 for (it = elements.iterator(); it.hasNext();) {
178  49 try {
179  49 parse();
180    } catch (final Exception ex) {
181  0 throw new SnippetParseException(
182    "error on parse token " + currentToken + " when generate file " + config.getFileId(), ex);
183    }
184    }
185  16 snippetContext.setHtmlSource(doc.html());
186  16 return snippetContext;
187    }
188   
 
189  87 toggle protected void parse() {
190  87 if (!it.hasNext()) {
191  0 throw new SnippetParseException("EOF");
192    }
193  87 final Element element = it.next();
194  87 currentToken = resolver.create(element);
195  87 if (currentToken == null) {
196  0 throw new SnippetParseException("unknown component: " + element);
197    }
198  87 state = processor;
199  87 parse(currentToken);
200  87 currentToken = null;
201    }
202   
 
203  87 toggle protected void parse(final ComponentToken token) {
204  87 state.parse(token);
205    }
206   
 
207  0 toggle protected ComponentToken currentToken() {
208  0 final int size = stack.size();
209  0 return size > 0 ? stack.get(size - 1) : null;
210    }
211   
 
212  38 toggle protected ComponentToken pop() {
213  38 final int size = stack.size();
214  38 if (LOGGER.isDebugEnabled()) {
215  38 LOGGER.debug("Stack size befor pop: {}", size);
216    }
217   
218  38 if (size == 0) {
219  0 throw new SnippetParseException("Cannot pop from empty stack");
220    }
221  38 try {
222   
223  38 final ComponentToken element = stack.remove(size - 1);
224  38 if (LOGGER.isDebugEnabled()) {
225  38 LOGGER.debug("Remove component from stack: {}", element);
226    }
227  38 return element;
228    } catch (final Exception e) {
229  0 throw new SnippetParseException("Error while popping from stack", e);
230    }
231    }
232   
 
233  38 toggle protected void push(final ComponentToken element) {
234  38 stack.add(element);
235  38 if (LOGGER.isDebugEnabled()) {
236  38 LOGGER.debug("Add component to stack: {}", element);
237    }
238    }
239   
 
240  88 toggle public SnippetContext getSnippetContext() {
241  88 return snippetContext;
242    }
243   
 
244  0 toggle public Context getVelocityContext() {
245  0 return this.snippetContext.getConfig().getVelocityContext();
246    }
247   
 
248  34 toggle private Map<String, SnippetResource> loadSnippetResources(final List<String> snippetPaths) {
249  34 final Map<String, SnippetResource> resources = Maps.newHashMap();
250  34 final List<String> paths = Lists.newArrayList(snippetPaths);
251  34 Collections.reverse(paths);
252  34 for (final String path : paths) {
253  70 try {
254  70 final List<String> files = getResources(path);
255  70 for (final String file : files) {
256  966 final SnippetResource resource = new SnippetResource(file, path + '/' + file);
257  966 final String name = FilenameUtils.getBaseName(file);
258  966 final String ext = FilenameUtils.getExtension(file);
259  966 if (!ext.equalsIgnoreCase("vm")) {
260  110 if (LOGGER.isDebugEnabled()) {
261  110 LOGGER.debug("Ignore snippet resource with unsupported extension: {} (allowed: vm)", file);
262    }
263  110 continue;
264    }
265  856 if (!name.startsWith("_")) {
266  822 resources.put(name, resource);
267    }
268    }
269    } catch (final Exception e) {
270  0 LOGGER.warn("Cannot load snippet resources from path: {}", path, e);
271    }
272    }
273  34 return resources;
274    }
275   
276    /**
277    * Get resource as stream.
278    *
279    * @param resource
280    * the resource
281    * @return the resource as stream
282    */
 
283  70 toggle private static List<String> getResources(final String path) throws Exception {
284  70 final List<String> filenames = Lists.newArrayList();
285   
286  70 final URL url = getResource(path);
287  70 if (LOGGER.isTraceEnabled()) {
288  0 LOGGER.trace("Loading resources from path: {} (url={})", path, url);
289    }
290  70 if (url != null) {
291  36 if (url.getProtocol().equals("file")) {
292  36 final File file = Paths.get(url.toURI()).toFile();
293  36 if (file != null) {
294  36 final File[] files = file.listFiles();
295  36 if (files != null) {
296  36 for (final File filename : files) {
297  966 filenames.add(filename.toString());
298    }
299    }
300    }
301  0 } else if (url.getProtocol().equals("jar")) {
302  0 final String dirname = path + "/";
303  0 final String p = url.getPath();
304  0 final String jarPath = p.substring(5, p.indexOf("!"));
305  0 try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, StandardCharsets.UTF_8.name()))) {
306  0 final Enumeration<JarEntry> entries = jar.entries();
307  0 while (entries.hasMoreElements()) {
308  0 final JarEntry entry = entries.nextElement();
309  0 final String name = entry.getName();
310  0 if (name.startsWith(dirname) && !dirname.equals(name)) {
311  0 final URL resource = getResource(name);
312  0 filenames.add(resource.toString());
313    }
314    }
315    }
316    }
317    }
318  70 if (LOGGER.isTraceEnabled()) {
319  0 LOGGER.trace("Found {} resources ({}) in path: {}", filenames.size(), filenames, path);
320    }
321  70 return filenames;
322    }
323   
 
324  70 toggle private static URL getResource(final String resource) {
325  70 final URL url = getContextClassLoader().getResource(resource);
326  70 return url == null ? SnippetContext.class.getClassLoader().getResource(resource) : url;
327    }
328   
329    /**
330    * Get the context class loader.
331    *
332    * @return the context class loader
333    */
 
334  70 toggle private static ClassLoader getContextClassLoader() {
335  70 return Thread.currentThread().getContextClassLoader();
336    }
337   
338    }