1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
39
40
41
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
52
53
54
55
56 public Processor(final SnippetParser parser) {
57 this.parser = parser;
58 }
59
60
61
62
63
64
65
66 public void parse(final ComponentToken token) {
67 if (LOGGER.isDebugEnabled()) {
68 LOGGER.debug("Parse Token: {}", token);
69 }
70 switch (token.tag()) {
71 case empty:
72 parser.getSnippetContext().render(createSnippetComponent(token, null));
73 break;
74
75 case start:
76 parser.push(token);
77 parser.parse();
78 break;
79
80 case end:
81 final ComponentToken startToken = parser.pop();
82 if (!token.isCloseTagOf(startToken)) {
83 throw new RuntimeException(
84 "start token " + startToken + " should be closed, but next token is " + token);
85 }
86 parser.getSnippetContext().render(createSnippetComponent(startToken, token));
87 break;
88
89 case html:
90 parser.getSnippetContext().render(createSnippetComponent(token, null));
91 break;
92
93 default:
94 throw new SnippetParseException("unknown token tag " + token.tag());
95 }
96 }
97
98
99
100
101
102
103
104
105
106
107
108 protected abstract void appendChildrenToHtml(Node node, Appendable writer) throws IOException;
109
110
111
112
113
114
115
116
117
118
119 protected Element convertToHtml(@Nonnull final ComponentToken startToken, @Nullable final ComponentToken endToken) {
120 final Element startElement = startToken.getElement();
121 final Node previousElement = startElement.previousSibling();
122 Element endElement = null;
123 if (endToken != null) {
124 endElement = endToken.getElement();
125 }
126 final Element tmp = new Element("component");
127 final Node parent = startElement.parentNode();
128
129 final StringBuilder html = new StringBuilder(convertElementToHtml(startElement));
130 final List<Node> nodesToRemove = Lists.newArrayList();
131 nodesToRemove.add(startElement);
132 if (endElement != null) {
133 boolean startCopy = false;
134
135 for (final Node n : parent.childNodes()) {
136 if (n.equals(endElement)) {
137 nodesToRemove.add(n);
138 break;
139 }
140 if (startCopy) {
141 try {
142 appendChildrenToHtml(n, html);
143 } catch (final IOException e) {
144 throw new RuntimeException(e.getMessage(), e);
145 }
146 nodesToRemove.add(n);
147 }
148 if (n.equals(startElement)) {
149 startCopy = true;
150 }
151 }
152 html.append(convertElementToHtml(endElement));
153 }
154 tmp.append(html.toString());
155 final Element component = tmp.children().first();
156 final Element parentElement = startElement.parent();
157
158 if (previousElement != null) {
159 previousElement.after(component);
160 } else {
161 if (parentElement.children().first() != null) {
162 parentElement.children().first().before(component);
163 } else {
164 parentElement.children().add(component);
165 }
166 }
167 nodesToRemove.forEach(Node::remove);
168 return component;
169 }
170
171
172
173
174
175
176
177
178
179
180
181 protected SnippetComponent<?> createSnippetComponent(final ComponentToken startToken,
182 final ComponentToken endToken) {
183 final SnippetContext snippetContext = parser.getSnippetContext();
184 Element componentElement = null;
185 if (Tag.html.equals(startToken.tag())) {
186 componentElement = startToken.getElement();
187 } else {
188 componentElement = convertToHtml(startToken, endToken);
189 }
190 return snippetContext.create(componentElement, startToken, endToken);
191 }
192
193
194
195
196
197
198
199
200 protected String convertElementToHtml(final Element element) {
201 return element.text()
202 .replace("{{< ", "<")
203 .replace(" />}}", "/>")
204 .replace(" /%}}", "/>")
205 .replace(" >}}", ">")
206 .replace("{{% ", "<")
207 .replace(" %}}", ">")
208 .replaceAll("\\u201c|\\u201d", "\"");
209 }
210
211
212
213
214
215
216
217 public static class WebComponentProcessor extends Processor {
218
219
220
221
222
223
224
225 public WebComponentProcessor(final SnippetParser parser) {
226 super(parser);
227 }
228
229
230
231
232 @Override
233 protected void appendChildrenToHtml(final Node node, final Appendable writer) throws IOException {
234 if (node instanceof Element) {
235 final Element el = (Element) node;
236
237 if ("div".equals(el.tagName()) && el.hasClass("source")) {
238 writer.append(el.text());
239 } else {
240
241 if (Iterables.tryFind(el.childNodes(), n -> n instanceof Comment).isPresent()) {
242 writer.append(el.data());
243 } else {
244 writer.append(el.outerHtml());
245 }
246 }
247 } else if (node instanceof Comment) {
248 writer.append(((Comment) node).getData());
249 } else if (node instanceof TextNode) {
250 writer.append(((TextNode) node).text());
251 }
252 }
253 }
254
255
256
257
258
259
260
261 public static class ShortcodeProcessor extends Processor {
262
263
264
265
266
267
268
269 public ShortcodeProcessor(final SnippetParser parser) {
270 super(parser);
271 }
272
273 @Override
274 protected void appendChildrenToHtml(final Node node, final Appendable writer) throws IOException {
275 writer.append(node.outerHtml());
276 }
277 }
278 }