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