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