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.annotation.*; 019import org.apache.juneau.html.annotation.*; 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 <c>Accept</c> types: <bc>text/html</bc> 029 * <p> 030 * Produces <c>Content-Type</c> types: <bc>text/html</bc> 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 */ 126@ConfigurableContext 127public class HtmlSerializer extends XmlSerializer { 128 129 //------------------------------------------------------------------------------------------------------------------- 130 // Configurable properties 131 //------------------------------------------------------------------------------------------------------------------- 132 133 static final String PREFIX = "HtmlSerializer"; 134 135 /** 136 * Configuration property: Add <js>"_type"</js> properties when needed. 137 * 138 * <h5 class='section'>Property:</h5> 139 * <ul> 140 * <li><b>Name:</b> <js>"HtmlSerializer.addBeanTypes.b"</js> 141 * <li><b>Data type:</b> <c>Boolean</c> 142 * <li><b>Default:</b> <jk>false</jk> 143 * <li><b>Session property:</b> <jk>false</jk> 144 * <li><b>Methods:</b> 145 * <ul> 146 * <li class='jm'>{@link HtmlSerializerBuilder#addBeanTypes(boolean)} 147 * </ul> 148 * </ul> 149 * 150 * <h5 class='section'>Description:</h5> 151 * <p> 152 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 153 * through reflection. 154 * 155 * <p> 156 * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is 157 * provided to customize the behavior of specific serializers in a {@link SerializerGroup}. 158 */ 159 public static final String HTML_addBeanTypes = PREFIX + ".addBeanTypes.b"; 160 161 /** 162 * Configuration property: Add key/value headers on bean/map tables. 163 * 164 * <h5 class='section'>Property:</h5> 165 * <ul> 166 * <li><b>Name:</b> <js>"HtmlSerializer.addKeyValueTableHeaders.b"</js> 167 * <li><b>Data type:</b> <c>Boolean</c> 168 * <li><b>Default:</b> <jk>false</jk> 169 * <li><b>Session property:</b> <jk>false</jk> 170 * <li><b>Annotations:</b> 171 * <ul> 172 * <li class='ja'>{@link Html#noTableHeaders()} 173 * </ul> 174 * <li><b>Methods:</b> 175 * <ul> 176 * <li class='jm'>{@link HtmlSerializerBuilder#addKeyValueTableHeaders(boolean)} 177 * <li class='jm'>{@link HtmlSerializerBuilder#addKeyValueTableHeaders()} 178 * </ul> 179 * </ul> 180 * 181 * <p> 182 * When enabled, <bc>key</bc> and <bc>value</bc> column headers are added to tables. 183 * 184 * <h5 class='section'>Example:</h5> 185 * <p class='bcode w800'> 186 * <jc>// Our bean class.</jc> 187 * <jk>public class</jk> MyBean { 188 * <jk>public</jk> String <jf>f1</jf> = <js>"foo"</js>; 189 * <jk>public</jk> String <jf>f2</jf> = <js>"bar"</js>; 190 * } 191 * 192 * <jc>// Serializer without headers.</jc> 193 * WriterSerializer s1 = HtmlSerializer.<jsf>DEFAULT</jsf>; 194 * 195 * <jc>// Serializer with headers.</jc> 196 * WriterSerializer s2 = HtmlSerializer 197 * .<jsm>create</jsm>() 198 * .addKeyValueTableHeaders() 199 * .build(); 200 * 201 * String withoutHeaders = s1.serialize(<jk>new</jk> MyBean()); 202 * String withHeaders = s2.serialize(<jk>new</jk> MyBean()); 203 * </p> 204 * 205 * <p> 206 * The following shows the difference between the two generated outputs: 207 * 208 * <table class='styled'> 209 * <tr> 210 * <th><c>withoutHeaders</c></th> 211 * <th><c>withHeaders</c></th> 212 * </tr> 213 * <tr> 214 * <td> 215 * <table class='unstyled'> 216 * <tr><td>f1</td><td>foo</td></tr> 217 * <tr><td>f2</td><td>bar</td></tr> 218 * </table> 219 * </td> 220 * <td> 221 * <table class='unstyled'> 222 * <tr><th>key</th><th>value</th></tr> 223 * <tr><td>f1</td><td>foo</td></tr> 224 * <tr><td>f2</td><td>bar</td></tr> 225 * </table> 226 * </td> 227 * </tr> 228 * </table> 229 */ 230 public static final String HTML_addKeyValueTableHeaders = PREFIX + ".addKeyValueTableHeaders.b"; 231 232 /** 233 * Configuration property: Look for URLs in {@link String Strings}. 234 * 235 * <h5 class='section'>Property:</h5> 236 * <ul> 237 * <li><b>Name:</b> <js>"HtmlSerializer.detectLinksInStrings.b"</js> 238 * <li><b>Data type:</b> <c>Boolean</c> 239 * <li><b>Default:</b> <jk>true</jk> 240 * <li><b>Session property:</b> <jk>false</jk> 241 * <li><b>Methods:</b> 242 * <ul> 243 * <li class='jm'>{@link HtmlSerializerBuilder#detectLinksInStrings(boolean)} 244 * </ul> 245 * </ul> 246 * 247 * <h5 class='section'>Description:</h5> 248 * <p> 249 * If a string looks like a URL (i.e. starts with <js>"http://"</js> or <js>"https://"</js>, then treat it like a URL 250 * and make it into a hyperlink based on the rules specified by {@link #HTML_uriAnchorText}. 251 * 252 * <h5 class='section'>Example:</h5> 253 * <p class='bcode w800'> 254 * <jc>// Our bean class with a property containing what looks like a URL.</jc> 255 * <jk>public class</jk> MyBean { 256 * <jk>public</jk> String <jf>f1</jf> = <js>"http://www.apache.org"</js>; 257 * } 258 * 259 * <jc>// Serializer with link detection.</jc> 260 * WriterSerializer s1 = HtmlSerializer 261 * .<jsm>create</jsm>() 262 * .addKeyValueTableHeaders() 263 * .build(); 264 * 265 * <jc>// Serializer without link detection.</jc> 266 * WriterSerializer s2 = HtmlSerializer 267 * .<jsm>create</jsm>() 268 * .addKeyValueTableHeaders() 269 * .detectLinksInStrings(<jk>false</jk>) 270 * .build(); 271 * 272 * String withLinks = s1.serialize(<jk>new</jk> MyBean()); 273 * String withoutLinks = s2.serialize(<jk>new</jk> MyBean()); 274 * </p> 275 * 276 * <p> 277 * The following shows the difference between the two generated outputs: 278 * 279 * <table class='styled'> 280 * <tr> 281 * <th><c>withLinks</c></th> 282 * <th><c>withoutLinks</c></th> 283 * </tr> 284 * <tr> 285 * <td> 286 * <table class='unstyled'> 287 * <tr><th>key</th><th>value</th></tr> 288 * <tr><td>f1</td><td><a href='http://www.apache.org'>http://www.apache.org</a></td></tr> 289 * </table> 290 * </td> 291 * <td> 292 * <table class='unstyled'> 293 * <tr><th>key</th><th>value</th></tr> 294 * <tr><td>f1</td><td>http://www.apache.org</td></tr> 295 * </table> 296 * </td> 297 * </tr> 298 * </table> 299 */ 300 public static final String HTML_detectLinksInStrings = PREFIX + ".detectLinksInStrings.b"; 301 302 /** 303 * Configuration property: Link label parameter name. 304 * 305 * <h5 class='section'>Property:</h5> 306 * <ul> 307 * <li><b>Name:</b> <js>"HtmlSerializer.labelParameter.s"</js> 308 * <li><b>Data type:</b> <c>String</c> 309 * <li><b>Default:</b> <js>"label"</js> 310 * <li><b>Session property:</b> <jk>false</jk> 311 * <li><b>Methods:</b> 312 * <ul> 313 * <li class='jm'>{@link HtmlSerializerBuilder#labelParameter(String)} 314 * </ul> 315 * </ul> 316 * 317 * <p> 318 * The parameter name to look for when resolving link labels via {@link #HTML_detectLabelParameters}. 319 * 320 * <ul class='seealso'> 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> <c>Boolean</c> 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><c>withLabels</c></th> 379 * <th><c>withoutLabels</c></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> <c>String</c> ({@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(WSERIALIZER_useWhitespace, true) 621 .build() 622 ); 623 } 624 } 625 626 627 //------------------------------------------------------------------------------------------------------------------- 628 // Instance 629 //------------------------------------------------------------------------------------------------------------------- 630 631 private final AnchorText uriAnchorText; 632 private final boolean 633 detectLabelParameters, 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 <c>media-type</c> specification of 662 * {@doc RFC2616.section14.1} 663 * <p> 664 * If empty, then assumes the only media type supported is <c>produces</c>. 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 detectLabelParameters = 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 HtmlSerializerSession createSession() { 711 return createSession(createDefaultSessionArgs()); 712 } 713 714 @Override /* Serializer */ 715 public HtmlSerializerSession createSession(SerializerSessionArgs args) { 716 return new HtmlSerializerSession(this, args); 717 } 718 719 @Override /* XmlSerializer */ 720 public HtmlSerializer getSchemaSerializer() { 721 if (schemaSerializer == null) 722 schemaSerializer = builder().build(HtmlSchemaSerializer.class); 723 return schemaSerializer; 724 } 725 726 //----------------------------------------------------------------------------------------------------------------- 727 // Properties 728 //----------------------------------------------------------------------------------------------------------------- 729 730 /** 731 * Configuration property: Add <js>"_type"</js> properties when needed. 732 * 733 * @see #HTML_addBeanTypes 734 * @return 735 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 736 * through reflection. 737 */ 738 @Override 739 protected final boolean isAddBeanTypes() { 740 return addBeanTypes; 741 } 742 743 /** 744 * Configuration property: Add key/value headers on bean/map tables. 745 * 746 * @see #HTML_addKeyValueTableHeaders 747 * @return 748 * <jk>true</jk> if <bc>key</bc> and <bc>value</bc> column headers are added to tables. 749 */ 750 protected final boolean isAddKeyValueTableHeaders() { 751 return addKeyValueTableHeaders; 752 } 753 754 /** 755 * Configuration property: Look for link labels in URIs. 756 * 757 * @see #HTML_detectLabelParameters 758 * @return 759 * <jk>true</jk> if we should look for URL label parameters (e.g. <js>"?label=foobar"</js>). 760 */ 761 protected final boolean isDetectLabelParameters() { 762 return detectLabelParameters; 763 } 764 765 /** 766 * Configuration property: Look for URLs in {@link String Strings}. 767 * 768 * @see #HTML_detectLinksInStrings 769 * @return 770 * <jk>true</jk> if we should automatically convert strings to URLs if they look like a URL. 771 */ 772 protected final boolean isDetectLinksInStrings() { 773 return detectLinksInStrings; 774 } 775 776 /** 777 * Configuration property: Link label parameter name. 778 * 779 * @see #HTML_labelParameter 780 * @return 781 * The parameter name to look for when resolving link labels via {@link #HTML_detectLabelParameters}. 782 */ 783 protected final String getLabelParameter() { 784 return labelParameter; 785 } 786 787 /** 788 * Configuration property: Anchor text source. 789 * 790 * @see #HTML_uriAnchorText 791 * @return 792 * When creating anchor tags (e.g. <code><xt><a</xt> <xa>href</xa>=<xs>'...'</xs> 793 * <xt>></xt>text<xt></a></xt></code>) in HTML, this setting defines what to set the inner text to. 794 */ 795 protected final AnchorText getUriAnchorText() { 796 return uriAnchorText; 797 } 798 799 //----------------------------------------------------------------------------------------------------------------- 800 // Other methods 801 //----------------------------------------------------------------------------------------------------------------- 802 803 @Override /* Context */ 804 public ObjectMap toMap() { 805 return super.toMap() 806 .append("HtmlSerializer", new DefaultFilteringObjectMap() 807 .append("uriAnchorText", uriAnchorText) 808 .append("detectLabelParameters", detectLabelParameters) 809 .append("detectLinksInStrings", detectLinksInStrings) 810 .append("labelParameter", labelParameter) 811 .append("addKeyValueTableHeaders", addKeyValueTableHeaders) 812 .append("addBeanTypes", addBeanTypes) 813 ); 814 } 815}