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