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