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