View Javadoc
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 static java.util.Objects.requireNonNull;
19  
20  import javax.annotation.Nonnull;
21  import org.apache.commons.lang3.builder.ToStringBuilder;
22  import org.apache.velocity.context.Context;
23  import org.devacfr.maven.skins.reflow.JsoupUtils;
24  import org.jsoup.nodes.Document;
25  import org.jsoup.nodes.Element;
26  import org.jsoup.select.Elements;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  /**
31   * @author Christophe Friederich
32   * @version 2.4
33   * @param <T>
34   *          type of component
35   */
36  public class SnippetComponent<T extends SnippetComponent<T>> extends Component<T> {
37  
38    /**
39     * type of snippet component
40     *
41     * @author Christophe Friederich
42     * @version 2.4
43     */
44    public enum Type {
45      /** web component */
46      webComponent,
47      /** shortcode component */
48      shortcode
49    }
50  
51    private static Logger LOGGER = LoggerFactory.getLogger(SnippetComponent.class);
52  
53    /** */
54    private final Type type;
55  
56    @Nonnull
57    public static SnippetComponent<?> createSnippet(@Nonnull final Element element,
58      final Component<?> parent,
59      final Type type) {
60      requireNonNull(element);
61      return new SnippetComponent<>(element, type).withParent(parent);
62    }
63  
64    /**
65     * Constructor.
66     *
67     * @param element
68     *          the element
69     */
70    protected SnippetComponent(@Nonnull final Element element, @Nonnull final Type type) {
71      super(element);
72      this.type = requireNonNull(type);
73      this.addAttributes(element.attributes());
74    }
75  
76    /**
77     * Get the type of snippet component.
78     */
79    @Nonnull
80    public Type getType() {
81      return type;
82    }
83  
84    /**
85     * Get the root parent {@link SnippetComponent}.
86     */
87    @Override
88    protected @Nonnull SnippetComponent<?> getRootParent() {
89      return this;
90    }
91  
92    /**
93     * Render the {@link SnippetComponent} between the {@code startElement} and {@code endElement} include.
94     *
95     * @param context
96     *          the snippet context to use
97     */
98    public void render(final SnippetContext context) {
99      final Context renderContext = context.getConfig().getVelocityContext();
100     final Element element = getElement();
101     if (element == null) {
102       LOGGER.warn("Element is null for snippet component: {}, file:{}", this, context.getConfig().getFileId());
103       return;
104     }
105     try {
106       final String html = context.renderComponent(this, renderContext);
107 
108       final Document doc = JsoupUtils.createHtmlDocument(html);
109       final Elements root = doc.body().children();
110       if (root.isEmpty()) {
111         element.remove();
112         return;
113       }
114       // normally, when debug trace is activated
115       if (root.size() > 1 && root.first().tagName().equals("pre")) {
116         final Element div = new Element("div");
117         root.forEach((e) -> div.appendChild(e));
118         element.replaceWith(div);
119       } else {
120         for (final Element el : root) {
121           // if snippet contains rendered snippet.
122           if (context.getParser().hasIncludedSnippetComponent(el)) {
123             final SnippetParser parser = context.createChildParser();
124             final Element childDoc = parser.parse(context.getConfig(), el.outerHtml()).document();
125             childDoc.children().forEach((e) -> element.before(e));
126           } else {
127             element.before(el);
128           }
129         }
130         element.remove();
131       }
132     } catch (final Exception e) {
133       LOGGER.error("Rendering Snippet component failed:{}, file:{}", this, context.getConfig().getFileId());
134       throw new RuntimeException(e.getMessage(), e);
135     }
136   }
137 
138   @Override
139   public String toString() {
140     return ToStringBuilder.reflectionToString(this);
141   }
142 
143 }