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