001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.html;
014
015import static org.apache.juneau.html.AsideFloat.*;
016
017import org.apache.juneau.internal.*;
018
019/**
020 * A basic template for the HTML doc serializer.
021 *
022 * <p>
023 * This class can be subclassed to customize page rendering.
024 *
025 * <h5 class='section'>See Also:</h5><ul>
026 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.HtmlDetails">HTML Details</a>
027 * </ul>
028 */
029public class BasicHtmlDocTemplate implements HtmlDocTemplate {
030
031   @Override /* HtmlDocTemplate */
032   public void writeTo(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
033      w.sTag("html").nl(0);
034      w.sTag(1, "head").nl(1);
035      head(session, w, o);
036      w.eTag(1, "head").nl(1);
037      w.sTag(1, "body").nl(1);
038      body(session, w, o);
039      w.eTag(1, "body").nl(1);
040      w.eTag("html").nl(0);
041   }
042
043   /**
044    * Renders the contents of the <code><xt>&lt;head&gt;</xt></code> element.
045    *
046    * @param session The current serializer session.
047    * @param w The writer being written to.
048    * @param o The object being serialized.
049    * @throws Exception Any exception can be thrown.
050    */
051   protected void head(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
052
053      String[] head = session.getHead();
054      for (int i = 0; i < head.length; i++)
055         w.sIf(i > 0).appendln(2, session.resolve(head[i]));
056
057      if (hasStyle(session)) {
058         w.sTag(2, "style").nl(2);
059         style(session, w, o);
060         w.ie(2).eTag("style").nl(2);
061      }
062      if (hasScript(session)) {
063         w.sTag(2, "script").nl(2);
064         script(session, w, o);
065         w.ie(2).eTag("script").nl(2);
066      }
067   }
068
069   /**
070    * Renders the contents of the <code><xt>&lt;head&gt;</xt>/<xt>&lt;style&gt;</xt></code> element.
071    *
072    * @param session The current serializer session.
073    * @param w The writer being written to.
074    * @param o The object being serialized.
075    * @throws Exception Any exception can be thrown.
076    */
077   protected void style(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
078      Flag addSpace = Flag.create();
079      for (String s : session.getStylesheet())
080         w.sIf(addSpace.getAndSet()).append(3, "@import ").q().append(session.resolveUri(session.resolve(s))).q().appendln(";");
081      if (session.isNowrap())
082         w.appendln(3, "div.data * {white-space:nowrap;} ");
083      for (String s : session.getStyle())
084         w.sIf(addSpace.getAndSet()).appendln(3, session.resolve(s));
085      session.forEachWidget(x -> {
086         w.sIf(addSpace.getAndSet()).appendln(3, session.resolve(x.getStyle(session.getVarResolver())));
087      });
088   }
089
090   /**
091    * Renders the contents of the <code><xt>&lt;head&gt;</xt>/<xt>&lt;script&gt;</xt></code> element.
092    *
093    * @param session The current serializer session.
094    * @param w The writer being written to.
095    * @param o The object being serialized.
096    * @throws Exception Any exception can be thrown.
097    */
098   protected void script(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
099      Flag addSpace = Flag.create();
100      for (String s : session.getScript())
101         w.sIf(addSpace.getAndSet()).append(3, session.resolve(s)).append('\n'); // Must always append a newline even if whitespace disabled!
102      session.forEachWidget(x -> {
103         w.sIf(addSpace.getAndSet()).append(3, session.resolve(x.getScript(session.getVarResolver()))).w('\n'); // Must always append a newline even if whitespace disabled!
104      });
105   }
106
107   /**
108    * Renders the contents of the <code><xt>&lt;body&gt;</xt></code> element.
109    *
110    * @param session The current serializer session.
111    * @param w The writer being written to.
112    * @param o The object being serialized.
113    * @throws Exception Any exception can be thrown.
114    */
115   protected void body(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
116
117      AsideFloat asideFloat = session.getAsideFloat();
118      boolean hasAside = hasAside(session);
119
120      if (hasHeader(session)) {
121         w.sTag(2, "header").nl(2);
122         header(session, w, o);
123         w.ie(2).eTag("header").nl(2);
124      }
125
126      if (hasNav(session)) {
127         w.sTag(2, "nav").nl(2);
128         nav(session, w, o);
129         w.ie(2).eTag("nav").nl(2);
130      }
131
132      if (hasAside && asideFloat.is(TOP)) {
133         w.sTag(2, "section").nl(2);
134         w.sTag(3, "aside").nl(3);
135         aside(session, w, o);
136         w.ie(3).eTag("aside").nl(3);
137         w.ie(2).eTag("section").nl(2);
138      }
139
140      w.sTag(2, "section").nl(2);
141
142      if (hasAside && asideFloat.is(LEFT)) {
143         w.sTag(3, "aside").nl(3);
144         aside(session, w, o);
145         w.ie(3).eTag("aside").nl(3);
146      }
147
148      w.sTag(3, "article").nl(3);
149      article(session, w, o);
150      w.ie(3).eTag("article").nl(3);
151
152      if (hasAside && asideFloat.isAny(RIGHT, DEFAULT)) {
153         w.sTag(3, "aside").nl(3);
154         aside(session, w, o);
155         w.ie(3).eTag("aside").nl(3);
156      }
157
158      w.ie(2).eTag("section").nl(2);
159
160      if (hasAside && asideFloat.is(BOTTOM)) {
161         w.sTag(2, "section").nl(2);
162         w.sTag(3, "aside").nl(3);
163         aside(session, w, o);
164         w.ie(3).eTag("aside").nl(3);
165         w.ie(2).eTag("section").nl(2);
166      }
167
168      if (hasFooter(session)) {
169         w.sTag(2, "footer").nl(2);
170         footer(session, w, o);
171         w.ie(2).eTag("footer").nl(2);
172      }
173   }
174
175   /**
176    * Renders the contents of the <code><xt>&lt;body&gt;</xt>/<xt>&lt;header&gt;</xt></code> element.
177    *
178    * @param session The current serializer session.
179    * @param w The writer being written to.
180    * @param o The object being serialized.
181    * @throws Exception Any exception can be thrown.
182    */
183   protected void header(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
184      // Write the title of the page.
185      String[] header = session.getHeader();
186      for (int i = 0; i < header.length; i++)
187         w.sIf(i > 0).appendln(3, session.resolve(header[i]));
188   }
189
190   /**
191    * Renders the contents of the <code><xt>&lt;body&gt;</xt>/<xt>&lt;nav&gt;</xt></code> element.
192    *
193    * @param session The current serializer session.
194    * @param w The writer being written to.
195    * @param o The object being serialized.
196    * @throws Exception Any exception can be thrown.
197    */
198   protected void nav(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
199      String[] links = session.getNavLinks();
200      if (links.length > 0 && ! ArrayUtils.contains("NONE", links)) {
201         w.sTag(3, "ol").nl(3);
202         for (String l : links) {
203            w.sTag(4, "li");
204            l = session.resolve(l);
205            if (l.matches("(?s)\\S+\\:.*")) {
206               int i = l.indexOf(':');
207               String key = l.substring(0, i);
208               String val = l.substring(i+1).trim();
209               if (val.startsWith("<"))
210                  w.nl(4).appendln(5, val);
211               else
212                  w.oTag("a").attr("href", session.resolveUri(val), true).cTag().text(key, true).eTag("a");
213               w.eTag("li").nl(4);
214            } else {
215               w.nl(4).appendln(5, l);
216               w.eTag(4, "li").nl(4);
217            }
218         }
219         w.eTag(3, "ol").nl(3);
220      }
221      String[] nav = session.getNav();
222      if (nav.length > 0) {
223         for (int i = 0; i < nav.length; i++)
224            w.sIf(i > 0).appendln(3, session.resolve(nav[i]));
225      }
226   }
227
228   /**
229    * Renders the contents of the <code><xt>&lt;body&gt;</xt>/<xt>&lt;aside&gt;</xt></code> element.
230    *
231    * @param session The current serializer session.
232    * @param w The writer being written to.
233    * @param o The object being serialized.
234    * @throws Exception Any exception can be thrown.
235    */
236   protected void aside(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
237      String[] aside = session.getAside();
238      for (int i = 0; i < aside.length; i++)
239         w.sIf(i > 0).appendln(4, session.resolve(aside[i]));
240   }
241
242   /**
243    * Renders the contents of the <code><xt>&lt;body&gt;</xt>/<xt>&lt;article&gt;</xt></code> element.
244    *
245    * @param session The current serializer session.
246    * @param w The writer being written to.
247    * @param o The object being serialized.
248    * @throws Exception Any exception can be thrown.
249    */
250   protected void article(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
251      // To allow for page formatting using CSS, we encapsulate the data inside two div tags:
252      // <div class='outerdata'><div class='data' id='data'>...</div></div>
253      w.oTag(4, "div").attr("class","outerdata").append('>').nl(4);
254      w.oTag(5, "div").attr("class","data").attr("id", "data").append('>').nl(5);
255
256      if (o == null) {
257         w.append(6, "<null/>").nl(6);
258      } else if (ObjectUtils.isEmpty(o)){
259         String m = session.getNoResultsMessage();
260         if (exists(m))
261            w.append(6, session.resolve(m)).nl(6);
262      } else {
263         session.indent = 6;
264         w.flush();
265         session.parentSerialize(w, o);
266      }
267
268      w.ie(5).eTag("div").nl(5);
269      w.ie(4).eTag("div").nl(4);
270   }
271
272   /**
273    * Renders the contents of the <code><xt>&lt;body&gt;</xt>/<xt>&lt;footer&gt;</xt></code> element.
274    *
275    * @param session The current serializer session.
276    * @param w The writer being written to.
277    * @param o The object being serialized.
278    * @throws Exception Any exception can be thrown.
279    */
280   protected void footer(HtmlDocSerializerSession session, HtmlWriter w, Object o) throws Exception {
281      String[] footer = session.getFooter();
282      for (int i = 0; i < footer.length; i++)
283         w.sIf(i > 0).appendln(3, session.resolve(footer[i]));
284   }
285
286   /**
287    * Returns <jk>true</jk> if this page should render a <code><xt>&lt;head&gt;</xt>/<xt>&lt;style&gt;</xt></code> element.
288    *
289    * @param session The current serializer session.
290    * @return A boolean flag.
291    */
292   protected boolean hasStyle(HtmlDocSerializerSession session) {
293      return true;
294   }
295
296   /**
297    * Returns <jk>true</jk> if this page should render a <code><xt>&lt;head&gt;</xt>/<xt>&lt;script&gt;</xt></code> element.
298    *
299    * @param session The current serializer session.
300    * @return A boolean flag.
301    */
302   protected boolean hasScript(HtmlDocSerializerSession session) {
303      return true;
304   }
305
306   /**
307    * Returns <jk>true</jk> if this page should render a <code><xt>&lt;body&gt;</xt>/<xt>&lt;header&gt;</xt></code>
308    * element.
309    *
310    * @param session The current serializer session.
311    * @return A boolean flag.
312    */
313   protected boolean hasHeader(HtmlDocSerializerSession session) {
314      return session.getHeader().length > 0;
315   }
316
317   /**
318    * Returns <jk>true</jk> if this page should render a <code><xt>&lt;body&gt;</xt>/<xt>&lt;nav&gt;</xt></code>
319    * element.
320    *
321    * @param session The current serializer session.
322    * @return A boolean flag.
323    */
324   protected boolean hasNav(HtmlDocSerializerSession session) {
325      return session.getNav().length > 0 || session.getNavLinks().length > 0;
326   }
327
328   /**
329    * Returns <jk>true</jk> if this page should render a <code><xt>&lt;body&gt;</xt>/<xt>&lt;aside&gt;</xt></code>
330    * element.
331    *
332    * @param session The current serializer session.
333    * @return A boolean flag.
334    */
335   protected boolean hasAside(HtmlDocSerializerSession session) {
336      return session.getAside().length > 0;
337   }
338
339   /**
340    * Returns <jk>true</jk> if this page should render a <code><xt>&lt;body&gt;</xt>/<xt>&lt;footer&gt;</xt></code>
341    * element.
342    *
343    * @param session The current serializer session.
344    * @return A boolean flag.
345    */
346   protected boolean hasFooter(HtmlDocSerializerSession session) {
347      return session.getFooter().length > 0;
348   }
349
350   private static boolean exists(String s) {
351      return s != null && ! "NONE".equals(s);
352   }
353}