View Javadoc
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.context;
20  
21  import javax.annotation.Nonnull;
22  
23  import java.io.IOException;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Optional;
27  
28  import com.google.common.collect.ImmutableMap;
29  import com.google.common.collect.Lists;
30  import org.apache.commons.lang3.builder.ToStringBuilder;
31  import org.codehaus.plexus.util.xml.Xpp3Dom;
32  import org.devacfr.maven.skins.reflow.HtmlTool;
33  import org.devacfr.maven.skins.reflow.ISkinConfig;
34  import org.devacfr.maven.skins.reflow.model.Component;
35  import org.devacfr.maven.skins.reflow.model.Footer;
36  import org.devacfr.maven.skins.reflow.model.NavSideMenu;
37  import org.devacfr.maven.skins.reflow.model.Navbar;
38  import org.devacfr.maven.skins.reflow.model.ScrollTop;
39  import org.devacfr.maven.skins.reflow.model.SideNavMenuItem;
40  import org.devacfr.maven.skins.reflow.snippet.SnippetContext;
41  import org.devacfr.maven.skins.reflow.snippet.SnippetParser;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  import static java.util.Objects.requireNonNull;
46  
47  /**
48   * The base class of all contexts depending type of page.
49   *
50   * @author Christophe Friederich
51   * @since 2.0
52   * @param <T>
53   *            type of inherrit context object.
54   */
55  public abstract class Context<T extends Context<?>> extends Component {
56  
57      /**
58       * map containing the equivalence of font awesome characters with image found in report pages.
59       */
60      private static final Map<String, String> ICON_REPLACEMENTS = ImmutableMap.<String, String> builder()
61              .put("img[src$=images/add.gif]", "<i class=\"fas fa-plus\"></i>")
62              .put("img[src$=images/remove.gif]", "<i class=\"fas fa-minus\"></i>")
63              .put("img[src$=images/fix.gif]", "<i class=\"fas fa-wrench\"></i>")
64              .put("img[src$=images/update.gif]", "<i class=\"fas fa-redo\"></i>")
65              .put("img[src$=images/icon_help_sml.gif]", "<i class=\"fas fa-question\"></i>")
66              .put("img[src$=images/icon_success_sml.gif]", "<i class=\"fas fa-check-circle\"></i>")
67              .put("img[src$=images/icon_warning_sml.gif]", "<i class=\"fas fa-exclamation-triangle\"></i>")
68              .put("img[src$=images/icon_error_sml.gif]", "<i class=\"fas fa-exclamation-circle\"></i>")
69              .put("img[src$=images/icon_info_sml.gif]", "<i class=\"fas fa-info\"></i>")
70              .build();
71  
72      /** */
73      private static final Logger LOGGER = LoggerFactory.getLogger(Context.class);
74  
75      /** */
76      private ContextType type;
77  
78      /** */
79      private final Navbar navbar;
80  
81      /** */
82      private final Footer footer;
83  
84      /** */
85      private final ScrollTop scrollTop;
86  
87      /**
88       * Build a context depending of current type of page.
89       *
90       * @param config
91       *            a config (can not be {@code null}).
92       * @return Returns a new instance of {@link Context} depending of current page.
93       */
94      @Nonnull
95      public static Context<?> buildContext(@Nonnull final ISkinConfig config) {
96          requireNonNull(config);
97  
98          ContextType type = ContextType.page;
99          final List<SideNavMenuItem> allSideNaveMenuItems = NavSideMenu.findAllSideNavMenuItems(config);
100         if (LOGGER.isTraceEnabled()) {
101             LOGGER.trace("findAllSideNavMenuItems: " + allSideNaveMenuItems);
102         }
103         final Xpp3Dom pageProperties = config.getPageProperties();
104         final String fileId = config.getFileId();
105 
106         if (pageProperties != null) {
107             if (pageProperties.getAttribute("type") != null) {
108                 type = ContextType.valueOf(pageProperties.getAttribute("type"));
109             }
110 
111             // frame type whether page associates to document page
112             if (allSideNaveMenuItems.stream().filter(item -> fileId.equals(item.getSlugName())).count() > 0) {
113                 type = ContextType.frame;
114             }
115         }
116         // if (type== null) {
117         // type = ContextType.page;
118         // }
119         Context<?> context = null;
120         switch (type) {
121             case doc:
122                 context = new DocumentContext(config);
123                 break;
124 
125             case frame:
126                 // search the parent document page
127                 final Optional<SideNavMenuItem> menuItem = allSideNaveMenuItems.stream()
128                         .filter(item -> fileId.equals(item.getSlugName()))
129                         .findFirst();
130                 final SideNavMenuItem item = menuItem.get();
131                 final String documentParent = item.getParent();
132                 context = new FrameContext(config, documentParent);
133                 break;
134             case body:
135                 context = new BodyContext(config);
136                 break;
137             case page:
138             default:
139                 context = new PageContext(config);
140                 break;
141         }
142         return context;
143     }
144 
145     /**
146      * Default constructor.
147      *
148      * @param config
149      *            a config (can not be {@code null}).
150      * @param type
151      *            the type of context (can not be {@code null}).
152      */
153     public Context(@Nonnull final ISkinConfig config, @Nonnull final ContextType type) {
154         requireNonNull(config);
155         this.withType(requireNonNull(type));
156         this.navbar = new Navbar(config);
157         this.scrollTop = new ScrollTop(config);
158         this.footer = new Footer(config);
159 
160         this.initialize(config);
161 
162         // the ordering is important for execute preRender method
163         this.addChildren(this.navbar, this.scrollTop, this.footer);
164     }
165 
166     /**
167      * Allows to initialize the context.
168      *
169      * @param config
170      *            a config (can not be {@code null}).
171      */
172     protected void initialize(@Nonnull final ISkinConfig config) {
173         // enable AnchorJS
174         if (!config.not("anchorJS")) {
175             this.addCssOptions("anchorjs-enabled");
176         }
177     }
178 
179     /**
180      * Allows to execute action before rendering of component.
181      *
182      * @param skinConfig
183      *            a config (can <b>not</b> be {@code null}).
184      * @return Returns the {@link String} representing the transformed body content.
185      * @since 2.1
186      */
187     public String preRender(@Nonnull final ISkinConfig skinConfig) {
188         return onPreRender(skinConfig, getBodyContent(skinConfig));
189     }
190 
191     @Override
192     protected String onPreRender(final @Nonnull ISkinConfig skinConfig, final @Nonnull String bodyContent) {
193         final HtmlTool htmlTool = getHtmlTool(skinConfig);
194         String content = bodyContent;
195         if (!skinConfig.not("imgLightbox")) {
196             // lightbox is enabled by default, so check for false and negate
197             content = htmlTool.setAttr(content,
198                 "a[href$=jpg], a[href$=JPG], a[href$=jpeg], a[href$=JPEG], "
199                         + "a[href$=png], a[href$=gif],a[href$=bmp]:has(img)",
200                 "data-lightbox",
201                 "page");
202         }
203 
204         if (!skinConfig.not("html5Anchor")) {
205             // HTML5-style anchors are enabled by default, so check for false and negate
206             content = htmlTool.headingAnchorToId(content);
207         }
208 
209         if (!skinConfig.not("bootstrapCss")) {
210             // Bootstrap CSS class conversion is enabled by default, so check for false and
211             // negate
212             content = htmlTool
213                     .addClass(content, "table.bodyTable", Lists.newArrayList("table", "table-striped", "table-hover"));
214             // image is responsive by default
215             content = htmlTool.addClass(content, "img", Lists.newArrayList("img-fluid"));
216             content = htmlTool.fixTableHeads(content);
217         }
218 
219         if (!skinConfig.not("bootstrapIcons")) {
220             // Bootstrap Icons are enabled by default, so check for false and negate
221             content = htmlTool.replaceAll(content, ICON_REPLACEMENTS);
222         }
223 
224         // The <tt> tag is not supported in HTML5 (see
225         // https://www.w3schools.com/tags/tag_tt.asp).
226         content = htmlTool.replaceWith(content, "tt", "<code class=\"literal\">");
227         return super.onPreRender(skinConfig, content);
228     }
229 
230     public String renderSnippets(final ISkinConfig skinConfig, final String bodyContent) throws IOException {
231         final SnippetContext snippetContext = new SnippetParser().parse(skinConfig, bodyContent);
232         return snippetContext.html();
233     }
234 
235     /**
236      * @return Returns the {@link Navbar}.
237      */
238     public Navbar getNavbar() {
239         return navbar;
240     }
241 
242     /**
243      * @return Returns the {@link ScrollTop}.
244      */
245     public ScrollTop getScrollTop() {
246         return scrollTop;
247     }
248 
249     /**
250      * @return Returns the {@link Footer}.
251      */
252     public Footer getFooter() {
253         return footer;
254     }
255 
256     /**
257      * Sets the type of context.
258      *
259      * @param type
260      *            the of context.
261      * @return Returns the fluent instance context.
262      */
263     protected T withType(final ContextType type) {
264         this.type = type;
265         return self();
266     }
267 
268     /**
269      * @return Returns the type of context of page.
270      */
271     public String getType() {
272         return type.name();
273     }
274 
275     /**
276      * @return Returns the fluent instance.
277      */
278     @SuppressWarnings("unchecked")
279     protected T self() {
280         return (T) this;
281     }
282 
283     /**
284      * {@inheritDoc}
285      */
286     @Override
287     public String toString() {
288         return ToStringBuilder.reflectionToString(this);
289     }
290 }