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