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