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

File Processor.java

 

Coverage histogram

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

Code metrics

28
83
13
2
297
157
33
0,4
6,38
6,5
2,54

Classes

Class Line # Actions
Processor 39 57 0% 20 14
0.82582,5%
Processor.WebComponentProcessor 212 26 0% 13 5
0.886363688,6%
 

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 com.google.common.collect.Iterables;
19    import com.google.common.collect.Lists;
20    import java.io.IOException;
21    import java.util.List;
22    import javax.annotation.Nonnull;
23    import javax.annotation.Nullable;
24    import org.devacfr.maven.skins.reflow.JsoupUtils;
25    import org.jsoup.nodes.Comment;
26    import org.jsoup.nodes.Document;
27    import org.jsoup.nodes.Element;
28    import org.jsoup.nodes.Node;
29    import org.jsoup.nodes.TextNode;
30    import org.slf4j.Logger;
31    import org.slf4j.LoggerFactory;
32   
33    /**
34    * Specific process for each type of snippet component.
35    *
36    * @author Christophe Friederich
37    * @version 2.4
38    */
 
39    public abstract class Processor {
40   
41    private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class);
42   
43    /** */
44    protected final SnippetParser parser;
45   
46    /**
47    * Default constructor
48    *
49    * @param parser
50    * current parser.
51    */
 
52  68 toggle public Processor(final SnippetParser parser) {
53  68 this.parser = parser;
54    }
55   
56    /**
57    * Specific parsing for each {@link ComponentToken}.
58    *
59    * @param token
60    * the current token.
61    */
 
62  182 toggle public void parse(final ComponentToken token) {
63  182 if (LOGGER.isDebugEnabled()) {
64  182 LOGGER.debug("Parse Token: {}", token);
65    }
66  182 switch (token.tag()) {
67  18 case empty:
68  18 this.handleEmptyTag(token);
69  18 break;
70   
71  80 case start:
72  80 this.handleStartTag(token);
73  80 parser.push(token);
74  80 parser.parse();
75  80 break;
76   
77  80 case end:
78  80 final ComponentToken startToken = parser.pop();
79  80 final ComponentToken endToken = token;
80  80 this.handleCloseTag(startToken, endToken);
81  80 break;
82  4 case html:
83  4 this.handleHtmlTag(token);
84  4 break;
85  0 default:
86  0 throw new SnippetParseException("unknown token tag " + token.tag());
87    }
88    }
89   
90    /**
91    * Append child {@link Node} in html rendering.
92    *
93    * @param node
94    * the node to use.
95    * @param writer
96    * the html writer
97    * @throws IOException
98    * If an I/O error occurs.
99    */
100    protected abstract void appendChildrenToHtml(Node node, Appendable writer) throws IOException;
101   
102    /**
103    * Handle start tag mismatch.
104    *
105    * @param token
106    * the token
107    */
 
108  0 toggle protected void handleStartTag(final ComponentToken token) {
109    // do nothing
110    }
111   
112    /**
113    * Handle close tag mismatch.
114    *
115    * @param startToken
116    * the start token
117    * @param endToken
118    * the end token.
119    */
 
120  0 toggle protected void handleCloseTag(final ComponentToken startToken, final ComponentToken endToken) {
121    // do nothing
122    }
123   
124    /**
125    * Handle empty tag.
126    *
127    * @param token
128    * the token
129    */
 
130  0 toggle protected void handleEmptyTag(final ComponentToken token) {
131    // do nothing
132    }
133   
134    /**
135    * Handle html tag.
136    *
137    * @param token
138    * the token
139    */
 
140  0 toggle protected void handleHtmlTag(final ComponentToken token) {
141    // do nothing
142    }
143   
144    /**
145    * Convert the snippet to html.
146    *
147    * @param startToken
148    * the start token
149    * @param endToken
150    * the end token.
151    * @return Returns a new {@link Element} representing html represention of snippet.
152    */
 
153  98 toggle protected Element convertToHtml(@Nonnull final ComponentToken startToken, @Nullable final ComponentToken endToken) {
154  98 final Element startElement = startToken.getElement();
155  98 final Node previousElement = startElement.previousSibling();
156  98 Element endElement = null;
157  98 if (endToken != null) {
158  80 endElement = endToken.getElement();
159    }
160  98 final Document doc = JsoupUtils.createHtmlDocument("");
161  98 final Element tmp = doc.body();
162  98 final Node parent = startElement.parentNode();
163   
164  98 final StringBuilder html = new StringBuilder(ComponentResolver.convertElementTextToHtml(startElement));
165  98 final List<Node> nodesToRemove = Lists.newArrayList();
166  98 nodesToRemove.add(startElement);
167  98 if (endElement != null) {
168  80 boolean startCopy = false;
169   
170  80 for (final Node n : parent.childNodes()) {
171  1040 if (n.equals(endElement)) {
172  80 nodesToRemove.add(n);
173  80 break;
174    }
175  960 if (startCopy) {
176  272 try {
177  272 appendChildrenToHtml(n, html);
178    } catch (final IOException e) {
179  0 throw new RuntimeException(e.getMessage(), e);
180    }
181  272 nodesToRemove.add(n);
182    }
183  960 if (n.equals(startElement)) {
184  80 startCopy = true;
185    }
186    }
187  80 html.append(ComponentResolver.convertElementTextToHtml(endElement));
188    }
189  98 tmp.html(html.toString());
190  98 final Element component = tmp.children().first();
191  98 final Element parentElement = startElement.parent();
192   
193  98 if (previousElement != null) {
194  98 previousElement.after(component);
195    } else {
196  0 if (parentElement.children().first() != null) {
197  0 parentElement.children().first().before(component);
198    } else {
199  0 parentElement.children().add(component);
200    }
201    }
202  98 nodesToRemove.forEach(Node::remove);
203  98 return component;
204    }
205   
206    /**
207    * Specific process for snippet web component.
208    *
209    * @author Christophe Friederich
210    * @version 2.4
211    */
 
212    public static class WebComponentProcessor extends Processor {
213   
214    /**
215    * Default constructor
216    *
217    * @param parser
218    * current parser.
219    */
 
220  68 toggle public WebComponentProcessor(final SnippetParser parser) {
221  68 super(parser);
222    }
223   
224    /**
225    * {@inheritDoc}
226    */
 
227  272 toggle @Override
228    protected void appendChildrenToHtml(final Node node, final Appendable writer) throws IOException {
229  272 if (node instanceof Element) {
230  74 final Element el = (Element) node;
231    // when use code highlighting
232  74 if ("div".equals(el.tagName()) && el.hasClass("source")) {
233  8 writer.append(el.text());
234    } else {
235    // comment can be enclose in <p> element.
236  66 if (Iterables.tryFind(el.childNodes(), n -> n instanceof Comment).isPresent()) {
237  0 writer.append(el.data());
238    } else {
239  66 writer.append(el.outerHtml());
240    }
241    }
242  198 } else if (node instanceof Comment) {
243  22 writer.append(((Comment) node).getData());
244  176 } else if (node instanceof TextNode) {
245  176 writer.append(((TextNode) node).text());
246    }
247    }
248   
 
249  80 toggle @Override
250    protected void handleStartTag(ComponentToken token) {
251   
252    }
253   
254    /**
255    * {@inheritDoc}
256    */
 
257  18 toggle @Override
258    protected void handleEmptyTag(final ComponentToken token) {
259  18 SnippetContext snippetContext = parser.getSnippetContext();
260  18 Element componentElement = convertToHtml(token, null);
261  18 Component<?> component = snippetContext.create(componentElement, token, null);
262  18 snippetContext.render(component);
263    }
264   
265    /**
266    * {@inheritDoc}
267    */
 
268  80 toggle @Override
269    protected void handleCloseTag(final ComponentToken startToken, final ComponentToken endToken) {
270  80 SnippetContext snippetContext = parser.getSnippetContext();
271    // if (snippetContext.getCurrentWebComponentToken() != null) {
272    // return;
273    // }
274  80 if (!endToken.isCloseTagOf(startToken)) {
275  0 throw new RuntimeException("start token " + startToken + " should be closed by " + startToken.getCloseTag()
276    + ", but found " + endToken);
277    }
278   
279  80 Element componentElement = convertToHtml(startToken, endToken);
280  80 Component<?> component = snippetContext.create(componentElement, startToken, endToken);
281  80 snippetContext.render(component);
282    }
283   
284    /**
285    * {@inheritDoc}
286    */
 
287  4 toggle @Override
288    protected void handleHtmlTag(final ComponentToken token) {
289  4 SnippetContext snippetContext = parser.getSnippetContext();
290  4 Element componentElement = token.getElement();
291  4 Component<?> component = snippetContext.create(componentElement, token, null);
292  4 snippetContext.render(component);
293    }
294   
295    }
296   
297    }