1. Project Clover database mar. avr. 16 2024 08:19:06 CEST
  2. Package org.devacfr.maven.skins.reflow.snippet

File ComponentResolver.java

 

Coverage histogram

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

Code metrics

36
65
13
2
236
149
37
0,57
5
6,5
2,85

Classes

Class Line # Actions
ComponentResolver 51 53 0% 25 13
0.8555555385,6%
ComponentResolver.Accumulator 195 12 0% 12 0
1.0100%
 

Contributing tests

This file is covered by 22 tests. .

Source view

1    /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10    * http://www.apache.org/licenses/LICENSE-2.0
11    *
12    * Unless required by applicable law or agreed to in writing,
13    * software distributed under the License is distributed on an
14    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15    * KIND, either express or implied. See the License for the
16    * specific language governing permissions and limitations
17    * under the License.
18    */
19    package org.devacfr.maven.skins.reflow.snippet;
20   
21    import java.util.List;
22    import java.util.Map;
23    import java.util.regex.MatchResult;
24    import java.util.regex.Matcher;
25    import java.util.regex.Pattern;
26   
27    import com.google.common.base.Strings;
28    import com.google.common.collect.Lists;
29    import com.google.common.collect.Maps;
30    import org.apache.commons.lang3.StringEscapeUtils;
31    import org.devacfr.maven.skins.reflow.snippet.ComponentToken.Tag;
32    import org.devacfr.maven.skins.reflow.snippet.ComponentToken.Type;
33    import org.jsoup.nodes.Document;
34    import org.jsoup.nodes.Element;
35    import org.jsoup.nodes.Node;
36    import org.jsoup.select.Collector;
37    import org.jsoup.select.Elements;
38    import org.jsoup.select.Evaluator;
39    import org.jsoup.select.NodeTraversor;
40    import org.jsoup.select.NodeVisitor;
41    import org.jsoup.select.QueryParser;
42    import org.slf4j.Logger;
43    import org.slf4j.LoggerFactory;
44   
45    /**
46    * Resolve the type and tag type of component.
47    *
48    * @author Christophe Friederich
49    * @version 2.4
50    */
 
51    public class ComponentResolver {
52   
53    private static final Logger LOGGER = LoggerFactory.getLogger(ComponentResolver.class);
54   
55    /** **/
56    private static final Pattern RESOLVER_PATTERN = Pattern.compile(
57    "\\{\\{(<|%) (\\/?)([\\w\\-_]*)(\\s?(?:[\\w\\-_]*)(?:=[\\u201c|\"](?:[\\s\\w\\p{Punct}]*)[\\u201d|\"])?)* (\\/?)(>|%)\\}\\}",
58    Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
59   
60    private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\s?(\\w*)=(\\\")?(\\w*)\2\\s?",
61    Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
62   
63    /**
64    * Default constructor
65    */
 
66  22 toggle public ComponentResolver() {
67  22 super();
68    }
69   
 
70  523 toggle public static boolean isSnippet(final Node node) {
71  523 return node.hasAttr("shortcode") || node.hasAttr("webcomponent");
72    }
73   
74    /**
75    * Collects all (start,end,empty) Element corresponding to a snippet component.
76    *
77    * @param document
78    * the Jsoup element to use
79    * @return Return a {@link Elements} representing all web components contained in Jsoup document.
80    */
 
81  16 toggle public Elements collect(final Element document) {
82  16 return collect(document, RESOLVER_PATTERN);
83    }
84   
85    /**
86    * @param document
87    * the Jsoup element to use
88    * @return
89    */
 
90  34 toggle public static boolean hasIncludedSnippetComponent(final Element document) {
91  34 return Collector.findFirst(QueryParser.parse("[shortcode],[webcomponent]"), document) != null;
92    }
93   
94    /**
95    * Normalise the {@link Document} to enclose inline snippet in html element.
96    *
97    * @param document
98    * the document to use
99    * @return Returns the same normalised {@link Document}.
100    */
 
101  9 toggle public Document normalize(final Document document) {
102   
103  9 final Elements elements = collect(document);
104  9 if (LOGGER.isDebugEnabled()) {
105  0 LOGGER.debug("Snippet Collected");
106  0 LOGGER.debug(elements.toString());
107    }
108    // remove all section tags
109  9 if (!elements.isEmpty()) {
110  9 final Elements sections = Collector.collect(new Evaluator.Tag("section"), document);
111  9 sections.forEach(Element::unwrap);
112    }
113   
114  9 elements.forEach(element -> {
115  55 String text = StringEscapeUtils.unescapeHtml4(element.html());
116  55 final Matcher matcher = RESOLVER_PATTERN.matcher(text);
117   
118  55 final List<MatchResult> results = Lists.newArrayList();
119   
120  110 while (matcher.find()) {
121  55 final MatchResult matchResult = matcher.toMatchResult();
122  55 if (matchResult.start() > 0 || matchResult.end() < text.length()) {
123  7 results.add(0, matcher.toMatchResult());
124    }
125    }
126  55 if (!results.isEmpty()) {
127  3 for (final MatchResult matchResult : results) {
128  7 final String snippet = text.substring(matchResult.start(), matchResult.end());
129  7 text = text.substring(0, matchResult.start()) + "<span>" + StringEscapeUtils.escapeHtml4(snippet)
130    + "</span>" + text.substring(matchResult.end());
131    }
132  3 element.html(text);
133    }
134    });
135  9 return document;
136    }
137   
138    /**
139    * Create a {@link ComponentToken} corresponding to the element.
140    *
141    * @param element
142    * the element to use.
143    * @return Return a new instance of {@link ComponentToken} representing the element.
144    */
 
145  66 toggle public ComponentToken create(final Element element) {
146  66 if (isSnippet(element)) {
147  2 Type type = null;
148  2 if (element.hasAttr("shortcode")) {
149  1 type = Type.shortcode;
150  1 } else if (element.hasAttr("webcomponent")) {
151  1 type = Type.webComponent;
152    }
153  2 return new ComponentToken(element, element.tagName(), Tag.html, type);
154    } else {
155  64 final Matcher matcher = RESOLVER_PATTERN.matcher(element.ownText());
156   
157  64 if (matcher.matches()) {
158  63 return createToken(element, matcher);
159    }
160    }
161  1 return null;
162    }
163   
 
164  63 toggle private ComponentToken createToken(final Element element, final Matcher matcher) {
165  63 if (!Strings.isNullOrEmpty(matcher.group(2)) && !Strings.isNullOrEmpty(matcher.group(5))) {
166    // can not have same time empty and end identifier.
167  1 throw new RuntimeException("malformed component");
168    }
169  62 final Type type = "<".equals(matcher.group(1)) ? Type.shortcode : Type.webComponent;
170  62 Tag tag = Tag.start;
171  62 if ("/".equals(matcher.group(2))) {
172  26 tag = Tag.end;
173  36 } else if ("/".equals(matcher.group(5))) {
174  5 tag = Tag.empty;
175    }
176   
177  62 return new ComponentToken(element, matcher.group(3), tag, type);
178    }
179   
 
180  0 toggle protected static Map<String, String> extractAttributes(final String text) {
181  0 final Map<String, String> attrs = Maps.newHashMap();
182  0 final Matcher matcher = ATTRIBUTE_PATTERN.matcher(text);
183  0 while (matcher.find()) {
184  0 attrs.put(matcher.group(1).toLowerCase(), matcher.group(3));
185    }
186  0 return attrs;
187    }
188   
 
189  16 toggle public static Elements collect(final Element root, final Pattern searchPattern) {
190  16 final Elements elements = new Elements();
191  16 NodeTraversor.traverse(new Accumulator(root, elements, searchPattern), root);
192  16 return elements;
193    }
194   
 
195    private static class Accumulator implements NodeVisitor {
196   
197    /** */
198    private final Pattern searchPattern;
199   
200    private final Element root;
201   
202    private final Elements elements;
203   
 
204  16 toggle Accumulator(final Element root, final Elements elements, final Pattern searchPattern) {
205  16 this.root = root;
206  16 this.elements = elements;
207  16 this.searchPattern = searchPattern;
208    }
209   
 
210  817 toggle @Override
211    public void head(final Node node, final int depth) {
212  817 if (node instanceof Element) {
213  293 final Element el = (Element) node;
214  293 if (matches(root, el)) {
215  102 elements.add(el);
216    }
217    }
218    }
219   
 
220  293 toggle public boolean matches(final Element root, final Element element) {
221    // exclude if in <pre> element, allowing highlight component in documentation
222  293 if ("pre".equals(element.tagName()) || "code".equals(element.tagName())
223    || element.hasParent() && "pre".equals(element.parent().tagName())) {
224  12 return false;
225    }
226  281 return searchPattern.matcher(element.ownText()).find();
227    }
228   
 
229  817 toggle @Override
230    public void tail(final Node node, final int depth) {
231  817 if (node instanceof Element && isSnippet(node)) {
232  6 elements.add((Element) node);
233    }
234    }
235    }
236    }