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