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