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