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 java.util.*; 016 017import org.apache.juneau.*; 018import org.apache.juneau.html.annotation.*; 019import org.apache.juneau.serializer.*; 020import org.apache.juneau.xml.*; 021 022/** 023 * Serializes POJO models to HTML. 024 * 025 * <h5 class='topic'>Media types</h5> 026 * 027 * Handles <code>Accept</code> types: <code><b>text/html</b></code> 028 * <p> 029 * Produces <code>Content-Type</code> types: <code><b>text/html</b></code> 030 * 031 * <h5 class='topic'>Description</h5> 032 * 033 * The conversion is as follows... 034 * <ul class='spaced-list'> 035 * <li> 036 * {@link Map Maps} (e.g. {@link HashMap}, {@link TreeMap}) and beans are converted to HTML tables with 037 * 'key' and 'value' columns. 038 * <li> 039 * {@link Collection Collections} (e.g. {@link HashSet}, {@link LinkedList}) and Java arrays are converted 040 * to HTML ordered lists. 041 * <li> 042 * {@code Collections} of {@code Maps} and beans are converted to HTML tables with keys as headers. 043 * <li> 044 * Everything else is converted to text. 045 * </ul> 046 * 047 * <p> 048 * This serializer provides several serialization options. Typically, one of the predefined <jsf>DEFAULT</jsf> 049 * serializers will be sufficient. 050 * However, custom serializers can be constructed to fine-tune behavior. 051 * 052 * <p> 053 * The {@link HtmlLink} annotation can be used on beans to add hyperlinks to the output. 054 * 055 * <h5 class='topic'>Behavior-specific subclasses</h5> 056 * 057 * The following direct subclasses are provided for convenience: 058 * <ul class='spaced-list'> 059 * <li> 060 * {@link Sq} - Default serializer, single quotes. 061 * <li> 062 * {@link SqReadable} - Default serializer, single quotes, whitespace added. 063 * </ul> 064 * 065 * <h5 class='section'>Example:</h5> 066 * <p class='bcode w800'> 067 * <jc>// Use one of the default serializers to serialize a POJO</jc> 068 * String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(someObject); 069 * 070 * <jc>// Create a custom serializer that doesn't use whitespace and newlines</jc> 071 * HtmlSerializer serializer = <jk>new</jk> HtmlSerializerBuider().ws().build(); 072 * 073 * <jc>// Same as above, except uses cloning</jc> 074 * HtmlSerializer serializer = HtmlSerializer.<jsf>DEFAULT</jsf>.builder().ws().build(); 075 * 076 * <jc>// Serialize POJOs to HTML</jc> 077 * 078 * <jc>// Produces: </jc> 079 * <jc>// <ul><li>1<li>2<li>3</ul></jc> 080 * List l = new ObjectList(1, 2, 3); 081 * String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(l); 082 * 083 * <jc>// Produces: </jc> 084 * <jc>// <table> </jc> 085 * <jc>// <tr><th>firstName</th><th>lastName</th></tr> </jc> 086 * <jc>// <tr><td>Bob</td><td>Costas</td></tr> </jc> 087 * <jc>// <tr><td>Billy</td><td>TheKid</td></tr> </jc> 088 * <jc>// <tr><td>Barney</td><td>Miller</td></tr> </jc> 089 * <jc>// </table> </jc> 090 * l = <jk>new</jk> ObjectList(); 091 * l.add(<jk>new</jk> ObjectMap(<js>"{firstName:'Bob',lastName:'Costas'}"</js>)); 092 * l.add(<jk>new</jk> ObjectMap(<js>"{firstName:'Billy',lastName:'TheKid'}"</js>)); 093 * l.add(<jk>new</jk> ObjectMap(<js>"{firstName:'Barney',lastName:'Miller'}"</js>)); 094 * String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(l); 095 * 096 * <jc>// Produces: </jc> 097 * <jc>// <table> </jc> 098 * <jc>// <tr><th>key</th><th>value</th></tr> </jc> 099 * <jc>// <tr><td>foo</td><td>bar</td></tr> </jc> 100 * <jc>// <tr><td>baz</td><td>123</td></tr> </jc> 101 * <jc>// </table> </jc> 102 * Map m = <jk>new</jk> ObjectMap(<js>"{foo:'bar',baz:123}"</js>); 103 * String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(m); 104 * 105 * <jc>// HTML elements can be nested arbitrarily deep</jc> 106 * <jc>// Produces: </jc> 107 * <jc>// <table> </jc> 108 * <jc>// <tr><th>key</th><th>value</th></tr> </jc> 109 * <jc>// <tr><td>foo</td><td>bar</td></tr> </jc> 110 * <jc>// <tr><td>baz</td><td>123</td></tr> </jc> 111 * <jc>// <tr><td>someNumbers</td><td><ul><li>1<li>2<li>3</ul></td></tr> </jc> 112 * <jc>// <tr><td>someSubMap</td><td> </jc> 113 * <jc>// <table> </jc> 114 * <jc>// <tr><th>key</th><th>value</th></tr> </jc> 115 * <jc>// <tr><td>a</td><td>b</td></tr> </jc> 116 * <jc>// </table> </jc> 117 * <jc>// </td></tr> </jc> 118 * <jc>// </table> </jc> 119 * Map m = <jk>new</jk> ObjectMap(<js>"{foo:'bar',baz:123}"</js>); 120 * m.put(<js>"someNumbers"</js>, <jk>new</jk> ObjectList(1, 2, 3)); 121 * m.put(<js>"someSubMap"</js>, <jk>new</jk> ObjectMap(<js>"{a:'b'}"</js>)); 122 * String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(m); 123 * </p> 124 */ 125public class HtmlSerializer extends XmlSerializer { 126 127 //------------------------------------------------------------------------------------------------------------------- 128 // Configurable properties 129 //------------------------------------------------------------------------------------------------------------------- 130 131 private static final String PREFIX = "HtmlSerializer."; 132 133 /** 134 * Configuration property: Add <js>"_type"</js> properties when needed. 135 * 136 * <h5 class='section'>Property:</h5> 137 * <ul> 138 * <li><b>Name:</b> <js>"HtmlSerializer.addBeanTypes.b"</js> 139 * <li><b>Data type:</b> <code>Boolean</code> 140 * <li><b>Default:</b> <jk>false</jk> 141 * <li><b>Session property:</b> <jk>false</jk> 142 * <li><b>Methods:</b> 143 * <ul> 144 * <li class='jm'>{@link HtmlSerializerBuilder#addBeanTypes(boolean)} 145 * </ul> 146 * </ul> 147 * 148 * <h5 class='section'>Description:</h5> 149 * <p> 150 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 151 * through reflection. 152 * 153 * <p> 154 * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is 155 * provided to customize the behavior of specific serializers in a {@link SerializerGroup}. 156 */ 157 public static final String HTML_addBeanTypes = PREFIX + "addBeanTypes.b"; 158 159 /** 160 * Configuration property: Add key/value headers on bean/map tables. 161 * 162 * <h5 class='section'>Property:</h5> 163 * <ul> 164 * <li><b>Name:</b> <js>"HtmlSerializer.addKeyValueTableHeaders.b"</js> 165 * <li><b>Data type:</b> <code>Boolean</code> 166 * <li><b>Default:</b> <jk>false</jk> 167 * <li><b>Session property:</b> <jk>false</jk> 168 * <li><b>Annotations:</b> 169 * <ul> 170 * <li class='ja'>{@link Html#noTableHeaders()} 171 * </ul> 172 * <li><b>Methods:</b> 173 * <ul> 174 * <li class='jm'>{@link HtmlSerializerBuilder#addKeyValueTableHeaders(boolean)} 175 * <li class='jm'>{@link HtmlSerializerBuilder#addKeyValueTableHeaders()} 176 * </ul> 177 * </ul> 178 * 179 * <p> 180 * When enabled, <code><b>key</b></code> and <code><b>value</b></code> column headers are added to tables. 181 * 182 * <h5 class='section'>Example:</h5> 183 * <p class='bcode w800'> 184 * <jc>// Our bean class.</jc> 185 * <jk>public class</jk> MyBean { 186 * <jk>public</jk> String <jf>f1</jf> = <js>"foo"</js>; 187 * <jk>public</jk> String <jf>f2</jf> = <js>"bar"</js>; 188 * } 189 * 190 * <jc>// Serializer without headers.</jc> 191 * WriterSerializer s1 = HtmlSerializer.<jsf>DEFAULT</jsf>; 192 * 193 * <jc>// Serializer with headers.</jc> 194 * WriterSerializer s2 = HtmlSerializer 195 * .<jsm>create</jsm>() 196 * .addKeyValueTableHeaders() 197 * .build(); 198 * 199 * String withoutHeaders = s1.serialize(<jk>new</jk> MyBean()); 200 * String withHeaders = s2.serialize(<jk>new</jk> MyBean()); 201 * </p> 202 * 203 * <p> 204 * The following shows the difference between the two generated outputs: 205 * 206 * <table class='styled'> 207 * <tr> 208 * <th><code>withoutHeaders</code></th> 209 * <th><code>withHeaders</code></th> 210 * </tr> 211 * <tr> 212 * <td> 213 * <table class='unstyled'> 214 * <tr><td>f1</td><td>foo</td></tr> 215 * <tr><td>f2</td><td>bar</td></tr> 216 * </table> 217 * </td> 218 * <td> 219 * <table class='unstyled'> 220 * <tr><th>key</th><th>value</th></tr> 221 * <tr><td>f1</td><td>foo</td></tr> 222 * <tr><td>f2</td><td>bar</td></tr> 223 * </table> 224 * </td> 225 * </tr> 226 * </table> 227 */ 228 public static final String HTML_addKeyValueTableHeaders = PREFIX + "addKeyValueTableHeaders.b"; 229 230 /** 231 * Configuration property: Look for URLs in {@link String Strings}. 232 * 233 * <h5 class='section'>Property:</h5> 234 * <ul> 235 * <li><b>Name:</b> <js>"HtmlSerializer.detectLinksInStrings.b"</js> 236 * <li><b>Data type:</b> <code>Boolean</code> 237 * <li><b>Default:</b> <jk>true</jk> 238 * <li><b>Session property:</b> <jk>false</jk> 239 * <li><b>Methods:</b> 240 * <ul> 241 * <li class='jm'>{@link HtmlSerializerBuilder#detectLinksInStrings(boolean)} 242 * </ul> 243 * </ul> 244 * 245 * <h5 class='section'>Description:</h5> 246 * <p> 247 * If a string looks like a URL (i.e. starts with <js>"http://"</js> or <js>"https://"</js>, then treat it like a URL 248 * and make it into a hyperlink based on the rules specified by {@link #HTML_uriAnchorText}. 249 * 250 * <h5 class='section'>Example:</h5> 251 * <p class='bcode w800'> 252 * <jc>// Our bean class with a property containing what looks like a URL.</jc> 253 * <jk>public class</jk> MyBean { 254 * <jk>public</jk> String <jf>f1</jf> = <js>"http://www.apache.org"</js>; 255 * } 256 * 257 * <jc>// Serializer with link detection.</jc> 258 * WriterSerializer s1 = HtmlSerializer 259 * .<jsm>create</jsm>() 260 * .addKeyValueTableHeaders() 261 * .build(); 262 * 263 * <jc>// Serializer without link detection.</jc> 264 * WriterSerializer s2 = HtmlSerializer 265 * .<jsm>create</jsm>() 266 * .addKeyValueTableHeaders() 267 * .detectLinksInStrings(<jk>false</jk>) 268 * .build(); 269 * 270 * String withLinks = s1.serialize(<jk>new</jk> MyBean()); 271 * String withoutLinks = s2.serialize(<jk>new</jk> MyBean()); 272 * </p> 273 * 274 * <p> 275 * The following shows the difference between the two generated outputs: 276 * 277 * <table class='styled'> 278 * <tr> 279 * <th><code>withLinks</code></th> 280 * <th><code>withoutLinks</code></th> 281 * </tr> 282 * <tr> 283 * <td> 284 * <table class='unstyled'> 285 * <tr><th>key</th><th>value</th></tr> 286 * <tr><td>f1</td><td><a href='http://www.apache.org'>http://www.apache.org</a></td></tr> 287 * </table> 288 * </td> 289 * <td> 290 * <table class='unstyled'> 291 * <tr><th>key</th><th>value</th></tr> 292 * <tr><td>f1</td><td>http://www.apache.org</td></tr> 293 * </table> 294 * </td> 295 * </tr> 296 * </table> 297 */ 298 public static final String HTML_detectLinksInStrings = PREFIX + "detectLinksInStrings.b"; 299 300 /** 301 * Configuration property: Link label parameter name. 302 * 303 * <h5 class='section'>Property:</h5> 304 * <ul> 305 * <li><b>Name:</b> <js>"HtmlSerializer.labelParameter.s"</js> 306 * <li><b>Data type:</b> <code>String</code> 307 * <li><b>Default:</b> <js>"label"</js> 308 * <li><b>Session property:</b> <jk>false</jk> 309 * <li><b>Methods:</b> 310 * <ul> 311 * <li class='jm'>{@link HtmlSerializerBuilder#labelParameter(String)} 312 * </ul> 313 * </ul> 314 * 315 * <p> 316 * The parameter name to look for when resolving link labels via {@link #HTML_detectLabelParameters}. 317 * 318 * <h5 class=''>See Also:</h5> 319 * <ul> 320 * <li class='jf'>{@link #HTML_detectLabelParameters} 321 * </ul> 322 */ 323 public static final String HTML_labelParameter = PREFIX + "labelParameter.s"; 324 325 /** 326 * Configuration property: Look for link labels in URIs. 327 * 328 * <h5 class='section'>Property:</h5> 329 * <ul> 330 * <li><b>Name:</b> <js>"HtmlSerializer.detectLabelParameters.b"</js> 331 * <li><b>Data type:</b> <code>Boolean</code> 332 * <li><b>Default:</b> <jk>true</jk> 333 * <li><b>Session property:</b> <jk>false</jk> 334 * <li><b>Methods:</b> 335 * <ul> 336 * <li class='jm'>{@link HtmlSerializerBuilder#detectLabelParameters(boolean)} 337 * </ul> 338 * </ul> 339 * 340 * <h5 class='section'>Description:</h5> 341 * <p> 342 * If the URL has a label parameter (e.g. <js>"?label=foobar"</js>), then use that as the anchor text of the link. 343 * 344 * <p> 345 * The parameter name can be changed via the {@link #HTML_labelParameter} property. 346 * 347 * <h5 class='section'>Example:</h5> 348 * <p class='bcode w800'> 349 * <jc>// Our bean class with a property containing what looks like a URL.</jc> 350 * <jk>public class</jk> MyBean { 351 * <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?label=Apache%20Foundation"</js>); 352 * } 353 * 354 * <jc>// Serializer with label detection.</jc> 355 * WriterSerializer s1 = HtmlSerializer 356 * .<jsm>create</jsm>() 357 * .addKeyValueTableHeaders() 358 * .build(); 359 * 360 * <jc>// Serializer without label detection.</jc> 361 * WriterSerializer s2 = HtmlSerializer 362 * .<jsm>create</jsm>() 363 * .addKeyValueTableHeaders() 364 * .lookForLabelParameters(<jk>false</jk>) 365 * .build(); 366 * 367 * String withLabels = s1.serialize(<jk>new</jk> MyBean()); 368 * String withoutLabels = s2.serialize(<jk>new</jk> MyBean()); 369 * </p> 370 * 371 * <p> 372 * The following shows the difference between the two generated outputs. 373 * <br>Note that they're both hyperlinks, but the anchor text differs: 374 * 375 * <table class='styled'> 376 * <tr> 377 * <th><code>withLabels</code></th> 378 * <th><code>withoutLabels</code></th> 379 * </tr> 380 * <tr> 381 * <td> 382 * <table class='unstyled'> 383 * <tr><th>key</th><th>value</th></tr> 384 * <tr><td>f1</td><td><a href='http://www.apache.org?label=Apache%20Foundation'>Apache Foundation</a></td></tr> 385 * </table> 386 * </td> 387 * <td> 388 * <table class='unstyled'> 389 * <tr><th>key</th><th>value</th></tr> 390 * <tr><td>f1</td><td><a href='http://www.apache.org?label=Apache%20Foundation'>http://www.apache.org?label=Apache%20Foundation</a></td></tr> 391 * </table> 392 * </td> 393 * </tr> 394 * </table> 395 */ 396 public static final String HTML_detectLabelParameters = PREFIX + "detectLabelParameters.b"; 397 398 /** 399 * Configuration property: Anchor text source. 400 * 401 * <h5 class='section'>Property:</h5> 402 * <ul> 403 * <li><b>Name:</b> <js>"HtmlSerializer.uriAnchorText.s"</js> 404 * <li><b>Data type:</b> <code>String</code> ({@link AnchorText}) 405 * <li><b>Default:</b> <js>"TO_STRING"</js> 406 * <li><b>Session property:</b> <jk>false</jk> 407 * <li><b>Annotations:</b> 408 * <ul> 409 * <li class='ja'>{@link Html#anchorText()} 410 * </ul> 411 * <li><b>Methods:</b> 412 * <ul> 413 * <li class='jm'>{@link HtmlSerializerBuilder#uriAnchorText(AnchorText)} 414 * <li class='jm'>{@link HtmlSerializerBuilder#uriAnchorText(String)} 415 * </ul> 416 * </ul> 417 * 418 * <h5 class='section'>Description:</h5> 419 * <p> 420 * When creating anchor tags (e.g. <code><xt><a</xt> <xa>href</xa>=<xs>'...'</xs> 421 * <xt>></xt>text<xt></a></xt></code>) in HTML, this setting defines what to set the inner text to. 422 * 423 * <p> 424 * The possible values are: 425 * <ul> 426 * <li class='jc'>{@link AnchorText} 427 * <ul> 428 * <li class='jf'>{@link AnchorText#TO_STRING TO_STRING} (default) - Set to whatever is returned by {@link #toString()} on the object. 429 * <br> 430 * <h5 class='section'>Example:</h5> 431 * <p class='bcode w800'> 432 * <jc>// Our bean class with a URI property.</jc> 433 * <jk>public class</jk> MyBean { 434 * <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?foo=bar#myAnchor"</js>); 435 * } 436 * 437 * <jc>// Serializer with TO_STRING anchor text.</jc> 438 * WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>TO_STRING</jsf>).build(); 439 * 440 * <jc>// Produces: <a href='http://www.apache.org?foo=bar#myAnchor'>http://www.apache.org?foo=bar#myAnchor</a></jc> 441 * String html = s1.serialize(<jk>new</jk> MyBean()); 442 * </p> 443 * <li class='jf'>{@link AnchorText#PROPERTY_NAME PROPERTY_NAME} - Set to the bean property name. 444 * <br> 445 * <h5 class='section'>Example:</h5> 446 * <p class='bcode w800'> 447 * <jc>// Our bean class with a URI property.</jc> 448 * <jk>public class</jk> MyBean { 449 * <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?foo=bar#myAnchor"</js>); 450 * } 451 * 452 * <jc>// Serializer with PROPERTY_NAME anchor text.</jc> 453 * WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>PROPERTY_NAME</jsf>).build(); 454 * 455 * <jc>// Produces: <a href='http://www.apache.org?foo=bar#myAnchor'>f1</a></jc> 456 * String html = s1.serialize(<jk>new</jk> MyBean()); 457 * </p> 458 * <li class='jf'>{@link AnchorText#URI URI} - Set to the URI value. 459 * <br> 460 * <h5 class='section'>Example:</h5> 461 * <p class='bcode w800'> 462 * <jc>// Our bean class with a URI property.</jc> 463 * <jk>public class</jk> MyBean { 464 * <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org?foo=bar#myAnchor"</js>); 465 * } 466 * 467 * <jc>// Serializer with URI anchor text.</jc> 468 * WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>URI</jsf>).build(); 469 * 470 * <jc>// Produces: <a href='http://www.apache.org?foo=bar#myAnchor'>http://www.apache.org?foo=bar</a></jc> 471 * String html = s1.serialize(<jk>new</jk> MyBean()); 472 * </p> 473 * <li class='jf'>{@link AnchorText#LAST_TOKEN LAST_TOKEN} - Set to the last token of the URI value. 474 * <br> 475 * <h5 class='section'>Example:</h5> 476 * <p class='bcode w800'> 477 * <jc>// Our bean class with a URI property.</jc> 478 * <jk>public class</jk> MyBean { 479 * <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org/foo/bar?baz=qux#myAnchor"</js>); 480 * } 481 * 482 * <jc>// Serializer with LAST_TOKEN anchor text.</jc> 483 * WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>LAST_TOKEN</jsf>).build(); 484 * 485 * <jc>// Produces: <a href='http://www.apache.org/foo/bar?baz=qux#myAnchor'>bar</a></jc> 486 * String html = s1.serialize(<jk>new</jk> MyBean()); 487 * </p> 488 * <li class='jf'>{@link AnchorText#URI_ANCHOR URI_ANCHOR} - Set to the anchor of the URL. 489 * <br> 490 * <h5 class='section'>Example:</h5> 491 * <p class='bcode w800'> 492 * <jc>// Our bean class with a URI property.</jc> 493 * <jk>public class</jk> MyBean { 494 * <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"http://www.apache.org/foo/bar?baz=qux#myAnchor"</js>); 495 * } 496 * 497 * <jc>// Serializer with URI_ANCHOR anchor text.</jc> 498 * WriterSerializer s1 = HtmlSerializer.<jsm>create</jsm>().anchorText(<jsf>URI_ANCHOR</jsf>).build(); 499 * 500 * <jc>// Produces: <a href='http://www.apache.org/foo/bar?baz=qux#myAnchor'>myAnchor</a></jc> 501 * String html = s1.serialize(<jk>new</jk> MyBean()); 502 * </p> 503 * <li class='jf'>{@link AnchorText#CONTEXT_RELATIVE CONTEXT_RELATIVE} - Same as {@link AnchorText#TO_STRING TO_STRING} but assumes it's a context-relative path. 504 * <br> 505 * <h5 class='section'>Example:</h5> 506 * <p class='bcode w800'> 507 * <jc>// Our bean class with a URI property.</jc> 508 * <jk>public class</jk> MyBean { 509 * <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"bar/baz"</js>); 510 * } 511 * 512 * <jc>// Serializer with CONTEXT_RELATIVE anchor text.</jc> 513 * WriterSerializer s1 = HtmlSerializer 514 * .<jsm>create</jsm>() 515 * .anchorText(<jsf>CONTEXT_RELATIVE</jsf>) 516 * .uriResolution(<jsf>ROOT_RELATIVE</jsf>) 517 * .uriRelativity(<jsf>RESOURCE</jsf>) 518 * .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>) 519 * .build(); 520 * 521 * <jc>// Produces: <a href='/myContext/myServlet/bar/baz'>myServlet/bar/baz</a></jc> 522 * String html = s1.serialize(<jk>new</jk> MyBean()); 523 * </p> 524 * <li class='jf'>{@link AnchorText#SERVLET_RELATIVE SERVLET_RELATIVE} - Same as {@link AnchorText#TO_STRING TO_STRING} but assumes it's a servlet-relative path. 525 * <br> 526 * <h5 class='section'>Example:</h5> 527 * <p class='bcode w800'> 528 * <jc>// Our bean class with a URI property.</jc> 529 * <jk>public class</jk> MyBean { 530 * <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"bar/baz"</js>); 531 * } 532 * 533 * <jc>// Serializer with SERVLET_RELATIVE anchor text.</jc> 534 * WriterSerializer s1 = HtmlSerializer 535 * .<jsm>create</jsm>() 536 * .anchorText(<jsf>SERVLET_RELATIVE</jsf>) 537 * .uriResolution(<jsf>ROOT_RELATIVE</jsf>) 538 * .uriRelativity(<jsf>RESOURCE</jsf>) 539 * .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>) 540 * .build(); 541 * 542 * <jc>// Produces: <a href='/myContext/myServlet/bar/baz'>bar/baz</a></jc> 543 * String html = s1.serialize(<jk>new</jk> MyBean()); 544 * </p> 545 * <li class='jf'>{@link AnchorText#PATH_RELATIVE PATH_RELATIVE} - Same as {@link AnchorText#TO_STRING TO_STRING} but assumes it's a path-relative path. 546 * <br> 547 * <h5 class='section'>Example:</h5> 548 * <p class='bcode w800'> 549 * <jc>// Our bean class with a URI property.</jc> 550 * <jk>public class</jk> MyBean { 551 * <jk>public</jk> URI <jf>f1</jf> = URI.<jsm>create</jsm>(<js>"bar/baz"</js>); 552 * } 553 * 554 * <jc>// Serializer with PATH_RELATIVE anchor text.</jc> 555 * WriterSerializer s1 = HtmlSerializer 556 * .<jsm>create</jsm>() 557 * .anchorText(<jsf>PATH_RELATIVE</jsf>) 558 * .uriResolution(<jsf>ROOT_RELATIVE</jsf>) 559 * .uriRelativity(<jsf>PATH_INFO</jsf>) 560 * .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>) 561 * .build(); 562 * 563 * <jc>// Produces: <a href='/myContext/myServlet/foo/bar/baz'>bar/baz</a></jc> 564 * String html = s1.serialize(<jk>new</jk> MyBean()); 565 * </p> 566 * </ul> 567 * </ul> 568 */ 569 public static final String HTML_uriAnchorText = PREFIX + "uriAnchorText.s"; 570 571 572 //------------------------------------------------------------------------------------------------------------------- 573 // Predefined instances 574 //------------------------------------------------------------------------------------------------------------------- 575 576 /** Default serializer, all default settings. */ 577 public static final HtmlSerializer DEFAULT = new HtmlSerializer(PropertyStore.DEFAULT); 578 579 /** Default serializer, single quotes. */ 580 public static final HtmlSerializer DEFAULT_SQ = new HtmlSerializer.Sq(PropertyStore.DEFAULT); 581 582 /** Default serializer, single quotes, whitespace added. */ 583 public static final HtmlSerializer DEFAULT_SQ_READABLE = new HtmlSerializer.SqReadable(PropertyStore.DEFAULT); 584 585 586 //------------------------------------------------------------------------------------------------------------------- 587 // Predefined subclasses 588 //------------------------------------------------------------------------------------------------------------------- 589 590 /** Default serializer, single quotes. */ 591 public static class Sq extends HtmlSerializer { 592 593 /** 594 * Constructor. 595 * 596 * @param ps The property store containing all the settings for this object. 597 */ 598 public Sq(PropertyStore ps) { 599 super( 600 ps.builder() 601 .set(WSERIALIZER_quoteChar, '\'') 602 .build() 603 ); 604 } 605 } 606 607 /** Default serializer, single quotes, whitespace added. */ 608 public static class SqReadable extends HtmlSerializer { 609 610 /** 611 * Constructor. 612 * 613 * @param ps The property store containing all the settings for this object. 614 */ 615 public SqReadable(PropertyStore ps) { 616 super( 617 ps.builder() 618 .set(WSERIALIZER_quoteChar, '\'') 619 .set(SERIALIZER_useWhitespace, true) 620 .build() 621 ); 622 } 623 } 624 625 626 //------------------------------------------------------------------------------------------------------------------- 627 // Instance 628 //------------------------------------------------------------------------------------------------------------------- 629 630 private final AnchorText uriAnchorText; 631 private final boolean 632 lookForLabelParameters, 633 detectLinksInStrings, 634 addKeyValueTableHeaders, 635 addBeanTypes; 636 private final String labelParameter; 637 638 private volatile HtmlSchemaSerializer schemaSerializer; 639 640 /** 641 * Constructor. 642 * 643 * @param ps 644 * The property store containing all the settings for this object. 645 */ 646 public HtmlSerializer(PropertyStore ps) { 647 this(ps, "text/html", null); 648 } 649 650 /** 651 * Constructor. 652 * 653 * @param ps 654 * The property store containing all the settings for this object. 655 * @param produces 656 * The media type that this serializer produces. 657 * @param accept 658 * The accept media types that the serializer can handle. 659 * <p> 660 * Can contain meta-characters per the <code>media-type</code> specification of 661 * {@doc RFC2616.section14.1} 662 * <p> 663 * If empty, then assumes the only media type supported is <code>produces</code>. 664 * <p> 665 * For example, if this serializer produces <js>"application/json"</js> but should handle media types of 666 * <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be: 667 * <p class='bcode w800'> 668 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>); 669 * </p> 670 * <br>...or... 671 * <p class='bcode w800'> 672 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*​/json"</js>); 673 * </p> 674 * <p> 675 * The accept value can also contain q-values. 676 */ 677 public HtmlSerializer(PropertyStore ps, String produces, String accept) { 678 super(ps, produces, accept); 679 uriAnchorText = getProperty(HTML_uriAnchorText, AnchorText.class, AnchorText.TO_STRING); 680 lookForLabelParameters = getBooleanProperty(HTML_detectLabelParameters, true); 681 detectLinksInStrings = getBooleanProperty(HTML_detectLinksInStrings, true); 682 labelParameter = getStringProperty(HTML_labelParameter, "label"); 683 addKeyValueTableHeaders = getBooleanProperty(HTML_addKeyValueTableHeaders, false); 684 addBeanTypes = getBooleanProperty(HTML_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false)); 685 } 686 687 @Override /* Context */ 688 public HtmlSerializerBuilder builder() { 689 return new HtmlSerializerBuilder(getPropertyStore()); 690 } 691 692 /** 693 * Instantiates a new clean-slate {@link HtmlSerializerBuilder} object. 694 * 695 * <p> 696 * This is equivalent to simply calling <code><jk>new</jk> HtmlSerializerBuilder()</code>. 697 * 698 * <p> 699 * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 700 * the settings of the object called on. 701 * 702 * @return A new {@link HtmlSerializerBuilder} object. 703 */ 704 public static HtmlSerializerBuilder create() { 705 return new HtmlSerializerBuilder(); 706 } 707 708 @Override /* Serializer */ 709 public WriterSerializerSession createSession(SerializerSessionArgs args) { 710 return new HtmlSerializerSession(this, args); 711 } 712 713 @Override /* XmlSerializer */ 714 public HtmlSerializer getSchemaSerializer() { 715 if (schemaSerializer == null) 716 schemaSerializer = builder().build(HtmlSchemaSerializer.class); 717 return schemaSerializer; 718 } 719 720 //----------------------------------------------------------------------------------------------------------------- 721 // Properties 722 //----------------------------------------------------------------------------------------------------------------- 723 724 /** 725 * Configuration property: Look for link labels in URIs. 726 * 727 * @see #HTML_detectLabelParameters 728 * @return 729 * <jk>true</jk> if we should look for URL label parameters (e.g. <js>"?label=foobar"</js>). 730 */ 731 protected final boolean isLookForLabelParameters() { 732 return lookForLabelParameters; 733 } 734 735 /** 736 * Configuration property: Look for URLs in {@link String Strings}. 737 * 738 * @see #HTML_detectLinksInStrings 739 * @return 740 * <jk>true</jk> if we should automatically convert strings to URLs if they look like a URL. 741 */ 742 protected final boolean isDetectLinksInStrings() { 743 return detectLinksInStrings; 744 } 745 746 /** 747 * Configuration property: Add key/value headers on bean/map tables. 748 * 749 * @see #HTML_addKeyValueTableHeaders 750 * @return 751 * <jk>true</jk> if <code><b>key</b></code> and <code><b>value</b></code> column headers are added to tables. 752 */ 753 protected final boolean isAddKeyValueTableHeaders() { 754 return addKeyValueTableHeaders; 755 } 756 757 /** 758 * Configuration property: Add <js>"_type"</js> properties when needed. 759 * 760 * @see #HTML_addBeanTypes 761 * @return 762 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 763 * through reflection. 764 */ 765 @Override 766 protected final boolean isAddBeanTypes() { 767 return addBeanTypes; 768 } 769 770 /** 771 * Configuration property: Link label parameter name. 772 * 773 * @see #HTML_labelParameter 774 * @return 775 * The parameter name to look for when resolving link labels via {@link #HTML_detectLabelParameters}. 776 */ 777 protected final String getLabelParameter() { 778 return labelParameter; 779 } 780 781 /** 782 * Configuration property: Anchor text source. 783 * 784 * @see #HTML_uriAnchorText 785 * @return 786 * When creating anchor tags (e.g. <code><xt><a</xt> <xa>href</xa>=<xs>'...'</xs> 787 * <xt>></xt>text<xt></a></xt></code>) in HTML, this setting defines what to set the inner text to. 788 */ 789 protected final AnchorText getUriAnchorText() { 790 return uriAnchorText; 791 } 792 793 @Override /* Context */ 794 public ObjectMap asMap() { 795 return super.asMap() 796 .append("HtmlSerializer", new ObjectMap() 797 .append("uriAnchorText", uriAnchorText) 798 .append("lookForLabelParameters", lookForLabelParameters) 799 .append("detectLinksInStrings", detectLinksInStrings) 800 .append("labelParameter", labelParameter) 801 .append("addKeyValueTableHeaders", addKeyValueTableHeaders) 802 .append("addBeanTypes", addBeanTypes) 803 ); 804 } 805}