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><head></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><head></xt>/<xt><style></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><head></xt>/<xt><script></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><body></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><body></xt>/<xt><header></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><body></xt>/<xt><nav></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><body></xt>/<xt><aside></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><body></xt>/<xt><article></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><body></xt>/<xt><footer></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><head></xt>/<xt><style></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><head></xt>/<xt><script></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><body></xt>/<xt><header></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><body></xt>/<xt><nav></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><body></xt>/<xt><aside></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><body></xt>/<xt><footer></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}