org.apache.juneau.html.HtmlSerializer Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.html;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.html.annotation.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.xml.*;
/**
* Serializes POJO models to HTML.
*
* Media types
*
* Handles Accept
types: text/html
*
* Produces Content-Type
types: text/html
*
*
Description
*
* The conversion is as follows...
*
* -
* {@link Map Maps} (e.g. {@link HashMap}, {@link TreeMap}) and beans are converted to HTML tables with
* 'key' and 'value' columns.
*
-
* {@link Collection Collections} (e.g. {@link HashSet}, {@link LinkedList}) and Java arrays are converted
* to HTML ordered lists.
*
-
* {@code Collections} of {@code Maps} and beans are converted to HTML tables with keys as headers.
*
-
* Everything else is converted to text.
*
*
*
* This serializer provides several serialization options. Typically, one of the predefined DEFAULT
* serializers will be sufficient.
* However, custom serializers can be constructed to fine-tune behavior.
*
*
* The {@link HtmlLink} annotation can be used on beans to add hyperlinks to the output.
*
*
Behavior-specific subclasses
*
* The following direct subclasses are provided for convenience:
*
* -
* {@link Sq} - Default serializer, single quotes.
*
-
* {@link SqReadable} - Default serializer, single quotes, whitespace added.
*
*
* Example:
*
* // Use one of the default serializers to serialize a POJO
* String html = HtmlSerializer.DEFAULT .serialize(someObject);
*
* // Create a custom serializer that doesn't use whitespace and newlines
* HtmlSerializer serializer = new HtmlSerializerBuider().ws().build();
*
* // Same as above, except uses cloning
* HtmlSerializer serializer = HtmlSerializer.DEFAULT .builder().ws().build();
*
* // Serialize POJOs to HTML
*
* // Produces:
* // <ul><li>1<li>2<li>3</ul>
* List l = new ObjectList(1, 2, 3);
* String html = HtmlSerializer.DEFAULT .serialize(l);
*
* // Produces:
* // <table>
* // <tr><th>firstName</th><th>lastName</th></tr>
* // <tr><td>Bob</td><td>Costas</td></tr>
* // <tr><td>Billy</td><td>TheKid</td></tr>
* // <tr><td>Barney</td><td>Miller</td></tr>
* // </table>
* l = new ObjectList();
* l.add(new ObjectMap("{firstName:'Bob',lastName:'Costas'}" ));
* l.add(new ObjectMap("{firstName:'Billy',lastName:'TheKid'}" ));
* l.add(new ObjectMap("{firstName:'Barney',lastName:'Miller'}" ));
* String html = HtmlSerializer.DEFAULT .serialize(l);
*
* // Produces:
* // <table>
* // <tr><th>key</th><th>value</th></tr>
* // <tr><td>foo</td><td>bar</td></tr>
* // <tr><td>baz</td><td>123</td></tr>
* // </table>
* Map m = new ObjectMap("{foo:'bar',baz:123}" );
* String html = HtmlSerializer.DEFAULT .serialize(m);
*
* // HTML elements can be nested arbitrarily deep
* // Produces:
* // <table>
* // <tr><th>key</th><th>value</th></tr>
* // <tr><td>foo</td><td>bar</td></tr>
* // <tr><td>baz</td><td>123</td></tr>
* // <tr><td>someNumbers</td><td><ul><li>1<li>2<li>3</ul></td></tr>
* // <tr><td>someSubMap</td><td>
* // <table>
* // <tr><th>key</th><th>value</th></tr>
* // <tr><td>a</td><td>b</td></tr>
* // </table>
* // </td></tr>
* // </table>
* Map m = new ObjectMap("{foo:'bar',baz:123}" );
* m.put("someNumbers" , new ObjectList(1, 2, 3));
* m.put("someSubMap" , new ObjectMap("{a:'b'}" ));
* String html = HtmlSerializer.DEFAULT .serialize(m);
*
*/
public class HtmlSerializer extends XmlSerializer {
//-------------------------------------------------------------------------------------------------------------------
// Configurable properties
//-------------------------------------------------------------------------------------------------------------------
private static final String PREFIX = "HtmlSerializer.";
/**
* Configuration property: Add "_type" properties when needed.
*
* Property:
*
* - Name:
"HtmlSerializer.addBeanTypes.b"
* - Data type:
Boolean
* - Default:
false
* - Session property:
false
* - Methods:
*
* - {@link HtmlSerializerBuilder#addBeanTypes(boolean)}
*
*
*
* Description:
*
* If true , then "_type" properties will be added to beans if their type cannot be inferred
* through reflection.
*
*
* When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is
* provided to customize the behavior of specific serializers in a {@link SerializerGroup}.
*/
public static final String HTML_addBeanTypes = PREFIX + "addBeanTypes.b";
/**
* Configuration property: Add key/value headers on bean/map tables.
*
*
Property:
*
* - Name:
"HtmlSerializer.addKeyValueTableHeaders.b"
* - Data type:
Boolean
* - Default:
false
* - Session property:
false
* - Annotations:
*
* - {@link Html#noTableHeaders()}
*
* - Methods:
*
* - {@link HtmlSerializerBuilder#addKeyValueTableHeaders(boolean)}
*
- {@link HtmlSerializerBuilder#addKeyValueTableHeaders()}
*
*
*
*
* When enabled, key
and value
column headers are added to tables.
*
*
Example:
*
* // Our bean class.
* public class MyBean {
* public String f1 = "foo" ;
* public String f2 = "bar" ;
* }
*
* // Serializer without headers.
* WriterSerializer s1 = HtmlSerializer.DEFAULT ;
*
* // Serializer with headers.
* WriterSerializer s2 = HtmlSerializer
* .create ()
* .addKeyValueTableHeaders()
* .build();
*
* String withoutHeaders = s1.serialize(new MyBean());
* String withHeaders = s2.serialize(new MyBean());
*
*
*
* The following shows the difference between the two generated outputs:
*
*
*
* withoutHeaders
* withHeaders
*
*
*
*
* f1 foo
* f2 bar
*
*
*
*
* key value
* f1 foo
* f2 bar
*
*
*
*
*/
public static final String HTML_addKeyValueTableHeaders = PREFIX + "addKeyValueTableHeaders.b";
/**
* Configuration property: Look for URLs in {@link String Strings}.
*
* Property:
*
* - Name:
"HtmlSerializer.detectLinksInStrings.b"
* - Data type:
Boolean
* - Default:
true
* - Session property:
false
* - Methods:
*
* - {@link HtmlSerializerBuilder#detectLinksInStrings(boolean)}
*
*
*
* Description:
*
* If a string looks like a URL (i.e. starts with "http://" or "https://" , then treat it like a URL
* and make it into a hyperlink based on the rules specified by {@link #HTML_uriAnchorText}.
*
*
Example:
*
* // Our bean class with a property containing what looks like a URL.
* public class MyBean {
* public String f1 = "http://www.apache.org" ;
* }
*
* // Serializer with link detection.
* WriterSerializer s1 = HtmlSerializer
* .create ()
* .addKeyValueTableHeaders()
* .build();
*
* // Serializer without link detection.
* WriterSerializer s2 = HtmlSerializer
* .create ()
* .addKeyValueTableHeaders()
* .detectLinksInStrings(false )
* .build();
*
* String withLinks = s1.serialize(new MyBean());
* String withoutLinks = s2.serialize(new MyBean());
*
*
*
* The following shows the difference between the two generated outputs:
*
*
*
* withLinks
* withoutLinks
*
*
*
*
* key value
* f1 http://www.apache.org
*
*
*
*
* key value
* f1 http://www.apache.org
*
*
*
*
*/
public static final String HTML_detectLinksInStrings = PREFIX + "detectLinksInStrings.b";
/**
* Configuration property: Link label parameter name.
*
* Property:
*
* - Name:
"HtmlSerializer.labelParameter.s"
* - Data type:
String
* - Default:
"label"
* - Session property:
false
* - Methods:
*
* - {@link HtmlSerializerBuilder#labelParameter(String)}
*
*
*
*
* The parameter name to look for when resolving link labels via {@link #HTML_detectLabelParameters}.
*
*
See Also:
*
* - {@link #HTML_detectLabelParameters}
*
*/
public static final String HTML_labelParameter = PREFIX + "labelParameter.s";
/**
* Configuration property: Look for link labels in URIs.
*
* Property:
*
* - Name:
"HtmlSerializer.detectLabelParameters.b"
* - Data type:
Boolean
* - Default:
true
* - Session property:
false
* - Methods:
*
* - {@link HtmlSerializerBuilder#detectLabelParameters(boolean)}
*
*
*
* Description:
*
* If the URL has a label parameter (e.g. "?label=foobar" ), then use that as the anchor text of the link.
*
*
* The parameter name can be changed via the {@link #HTML_labelParameter} property.
*
*
Example:
*
* // Our bean class with a property containing what looks like a URL.
* public class MyBean {
* public URI f1 = URI.create ("http://www.apache.org?label=Apache%20Foundation" );
* }
*
* // Serializer with label detection.
* WriterSerializer s1 = HtmlSerializer
* .create ()
* .addKeyValueTableHeaders()
* .build();
*
* // Serializer without label detection.
* WriterSerializer s2 = HtmlSerializer
* .create ()
* .addKeyValueTableHeaders()
* .lookForLabelParameters(false )
* .build();
*
* String withLabels = s1.serialize(new MyBean());
* String withoutLabels = s2.serialize(new MyBean());
*
*
*
* The following shows the difference between the two generated outputs.
*
Note that they're both hyperlinks, but the anchor text differs:
*
*
*
* withLabels
* withoutLabels
*
*
*
*
* key value
* f1 Apache Foundation
*
*
*
*
* key value
* f1 http://www.apache.org?label=Apache%20Foundation
*
*
*
*
*/
public static final String HTML_detectLabelParameters = PREFIX + "detectLabelParameters.b";
/**
* Configuration property: Anchor text source.
*
* Property:
*
* - Name:
"HtmlSerializer.uriAnchorText.s"
* - Data type:
String
({@link AnchorText})
* - Default:
"TO_STRING"
* - Session property:
false
* - Annotations:
*
* - {@link Html#anchorText()}
*
* - Methods:
*
* - {@link HtmlSerializerBuilder#uriAnchorText(AnchorText)}
*
- {@link HtmlSerializerBuilder#uriAnchorText(String)}
*
*
*
* Description:
*
* When creating anchor tags (e.g. <a href ='...'
* > text</a>
) in HTML, this setting defines what to set the inner text to.
*
*
* The possible values are:
*
* - {@link AnchorText}
*
* - {@link AnchorText#TO_STRING TO_STRING} (default) - Set to whatever is returned by {@link #toString()} on the object.
*
* Example:
*
* // Our bean class with a URI property.
* public class MyBean {
* public URI f1 = URI.create ("http://www.apache.org?foo=bar#myAnchor" );
* }
*
* // Serializer with TO_STRING anchor text.
* WriterSerializer s1 = HtmlSerializer.create ().anchorText(TO_STRING ).build();
*
* // Produces: <a href='http://www.apache.org?foo=bar#myAnchor'>http://www.apache.org?foo=bar#myAnchor</a>
* String html = s1.serialize(new MyBean());
*
* - {@link AnchorText#PROPERTY_NAME PROPERTY_NAME} - Set to the bean property name.
*
* Example:
*
* // Our bean class with a URI property.
* public class MyBean {
* public URI f1 = URI.create ("http://www.apache.org?foo=bar#myAnchor" );
* }
*
* // Serializer with PROPERTY_NAME anchor text.
* WriterSerializer s1 = HtmlSerializer.create ().anchorText(PROPERTY_NAME ).build();
*
* // Produces: <a href='http://www.apache.org?foo=bar#myAnchor'>f1</a>
* String html = s1.serialize(new MyBean());
*
* - {@link AnchorText#URI URI} - Set to the URI value.
*
* Example:
*
* // Our bean class with a URI property.
* public class MyBean {
* public URI f1 = URI.create ("http://www.apache.org?foo=bar#myAnchor" );
* }
*
* // Serializer with URI anchor text.
* WriterSerializer s1 = HtmlSerializer.create ().anchorText(URI ).build();
*
* // Produces: <a href='http://www.apache.org?foo=bar#myAnchor'>http://www.apache.org?foo=bar</a>
* String html = s1.serialize(new MyBean());
*
* - {@link AnchorText#LAST_TOKEN LAST_TOKEN} - Set to the last token of the URI value.
*
* Example:
*
* // Our bean class with a URI property.
* public class MyBean {
* public URI f1 = URI.create ("http://www.apache.org/foo/bar?baz=qux#myAnchor" );
* }
*
* // Serializer with LAST_TOKEN anchor text.
* WriterSerializer s1 = HtmlSerializer.create ().anchorText(LAST_TOKEN ).build();
*
* // Produces: <a href='http://www.apache.org/foo/bar?baz=qux#myAnchor'>bar</a>
* String html = s1.serialize(new MyBean());
*
* - {@link AnchorText#URI_ANCHOR URI_ANCHOR} - Set to the anchor of the URL.
*
* Example:
*
* // Our bean class with a URI property.
* public class MyBean {
* public URI f1 = URI.create ("http://www.apache.org/foo/bar?baz=qux#myAnchor" );
* }
*
* // Serializer with URI_ANCHOR anchor text.
* WriterSerializer s1 = HtmlSerializer.create ().anchorText(URI_ANCHOR ).build();
*
* // Produces: <a href='http://www.apache.org/foo/bar?baz=qux#myAnchor'>myAnchor</a>
* String html = s1.serialize(new MyBean());
*
* - {@link AnchorText#CONTEXT_RELATIVE CONTEXT_RELATIVE} - Same as {@link AnchorText#TO_STRING TO_STRING} but assumes it's a context-relative path.
*
* Example:
*
* // Our bean class with a URI property.
* public class MyBean {
* public URI f1 = URI.create ("bar/baz" );
* }
*
* // Serializer with CONTEXT_RELATIVE anchor text.
* WriterSerializer s1 = HtmlSerializer
* .create ()
* .anchorText(CONTEXT_RELATIVE )
* .uriResolution(ROOT_RELATIVE )
* .uriRelativity(RESOURCE )
* .uriContext("{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}" )
* .build();
*
* // Produces: <a href='/myContext/myServlet/bar/baz'>myServlet/bar/baz</a>
* String html = s1.serialize(new MyBean());
*
* - {@link AnchorText#SERVLET_RELATIVE SERVLET_RELATIVE} - Same as {@link AnchorText#TO_STRING TO_STRING} but assumes it's a servlet-relative path.
*
* Example:
*
* // Our bean class with a URI property.
* public class MyBean {
* public URI f1 = URI.create ("bar/baz" );
* }
*
* // Serializer with SERVLET_RELATIVE anchor text.
* WriterSerializer s1 = HtmlSerializer
* .create ()
* .anchorText(SERVLET_RELATIVE )
* .uriResolution(ROOT_RELATIVE )
* .uriRelativity(RESOURCE )
* .uriContext("{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}" )
* .build();
*
* // Produces: <a href='/myContext/myServlet/bar/baz'>bar/baz</a>
* String html = s1.serialize(new MyBean());
*
* - {@link AnchorText#PATH_RELATIVE PATH_RELATIVE} - Same as {@link AnchorText#TO_STRING TO_STRING} but assumes it's a path-relative path.
*
* Example:
*
* // Our bean class with a URI property.
* public class MyBean {
* public URI f1 = URI.create ("bar/baz" );
* }
*
* // Serializer with PATH_RELATIVE anchor text.
* WriterSerializer s1 = HtmlSerializer
* .create ()
* .anchorText(PATH_RELATIVE )
* .uriResolution(ROOT_RELATIVE )
* .uriRelativity(PATH_INFO )
* .uriContext("{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}" )
* .build();
*
* // Produces: <a href='/myContext/myServlet/foo/bar/baz'>bar/baz</a>
* String html = s1.serialize(new MyBean());
*
*
*
*/
public static final String HTML_uriAnchorText = PREFIX + "uriAnchorText.s";
//-------------------------------------------------------------------------------------------------------------------
// Predefined instances
//-------------------------------------------------------------------------------------------------------------------
/** Default serializer, all default settings. */
public static final HtmlSerializer DEFAULT = new HtmlSerializer(PropertyStore.DEFAULT);
/** Default serializer, single quotes. */
public static final HtmlSerializer DEFAULT_SQ = new HtmlSerializer.Sq(PropertyStore.DEFAULT);
/** Default serializer, single quotes, whitespace added. */
public static final HtmlSerializer DEFAULT_SQ_READABLE = new HtmlSerializer.SqReadable(PropertyStore.DEFAULT);
//-------------------------------------------------------------------------------------------------------------------
// Predefined subclasses
//-------------------------------------------------------------------------------------------------------------------
/** Default serializer, single quotes. */
public static class Sq extends HtmlSerializer {
/**
* Constructor.
*
* @param ps The property store containing all the settings for this object.
*/
public Sq(PropertyStore ps) {
super(
ps.builder()
.set(WSERIALIZER_quoteChar, '\'')
.build()
);
}
}
/** Default serializer, single quotes, whitespace added. */
public static class SqReadable extends HtmlSerializer {
/**
* Constructor.
*
* @param ps The property store containing all the settings for this object.
*/
public SqReadable(PropertyStore ps) {
super(
ps.builder()
.set(WSERIALIZER_quoteChar, '\'')
.set(SERIALIZER_useWhitespace, true)
.build()
);
}
}
//-------------------------------------------------------------------------------------------------------------------
// Instance
//-------------------------------------------------------------------------------------------------------------------
private final AnchorText uriAnchorText;
private final boolean
lookForLabelParameters,
detectLinksInStrings,
addKeyValueTableHeaders,
addBeanTypes;
private final String labelParameter;
private volatile HtmlSchemaSerializer schemaSerializer;
/**
* Constructor.
*
* @param ps
* The property store containing all the settings for this object.
*/
public HtmlSerializer(PropertyStore ps) {
this(ps, "text/html", null);
}
/**
* Constructor.
*
* @param ps
* The property store containing all the settings for this object.
* @param produces
* The media type that this serializer produces.
* @param accept
* The accept media types that the serializer can handle.
*
* Can contain meta-characters per the media-type
specification of
* {@doc RFC2616.section14.1}
*
* If empty, then assumes the only media type supported is produces
.
*
* For example, if this serializer produces "application/json" but should handle media types of
* "application/json" and "text/json" , then the arguments should be:
*
* super (ps, "application/json" , "application/json,text/json" );
*
*
...or...
*
* super (ps, "application/json" , "*/json" );
*
*
* The accept value can also contain q-values.
*/
public HtmlSerializer(PropertyStore ps, String produces, String accept) {
super(ps, produces, accept);
uriAnchorText = getProperty(HTML_uriAnchorText, AnchorText.class, AnchorText.TO_STRING);
lookForLabelParameters = getBooleanProperty(HTML_detectLabelParameters, true);
detectLinksInStrings = getBooleanProperty(HTML_detectLinksInStrings, true);
labelParameter = getStringProperty(HTML_labelParameter, "label");
addKeyValueTableHeaders = getBooleanProperty(HTML_addKeyValueTableHeaders, false);
addBeanTypes = getBooleanProperty(HTML_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false));
}
@Override /* Context */
public HtmlSerializerBuilder builder() {
return new HtmlSerializerBuilder(getPropertyStore());
}
/**
* Instantiates a new clean-slate {@link HtmlSerializerBuilder} object.
*
*
* This is equivalent to simply calling new HtmlSerializerBuilder()
.
*
*
* Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
* the settings of the object called on.
*
* @return A new {@link HtmlSerializerBuilder} object.
*/
public static HtmlSerializerBuilder create() {
return new HtmlSerializerBuilder();
}
@Override /* Serializer */
public WriterSerializerSession createSession(SerializerSessionArgs args) {
return new HtmlSerializerSession(this, args);
}
@Override /* XmlSerializer */
public HtmlSerializer getSchemaSerializer() {
if (schemaSerializer == null)
schemaSerializer = builder().build(HtmlSchemaSerializer.class);
return schemaSerializer;
}
//-----------------------------------------------------------------------------------------------------------------
// Properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Configuration property: Look for link labels in URIs.
*
* @see #HTML_detectLabelParameters
* @return
* true if we should look for URL label parameters (e.g. "?label=foobar" ).
*/
protected final boolean isLookForLabelParameters() {
return lookForLabelParameters;
}
/**
* Configuration property: Look for URLs in {@link String Strings}.
*
* @see #HTML_detectLinksInStrings
* @return
* true if we should automatically convert strings to URLs if they look like a URL.
*/
protected final boolean isDetectLinksInStrings() {
return detectLinksInStrings;
}
/**
* Configuration property: Add key/value headers on bean/map tables.
*
* @see #HTML_addKeyValueTableHeaders
* @return
* true if key
and value
column headers are added to tables.
*/
protected final boolean isAddKeyValueTableHeaders() {
return addKeyValueTableHeaders;
}
/**
* Configuration property: Add "_type" properties when needed.
*
* @see #HTML_addBeanTypes
* @return
* true if "_type" properties will be added to beans if their type cannot be inferred
* through reflection.
*/
@Override
protected final boolean isAddBeanTypes() {
return addBeanTypes;
}
/**
* Configuration property: Link label parameter name.
*
* @see #HTML_labelParameter
* @return
* The parameter name to look for when resolving link labels via {@link #HTML_detectLabelParameters}.
*/
protected final String getLabelParameter() {
return labelParameter;
}
/**
* Configuration property: Anchor text source.
*
* @see #HTML_uriAnchorText
* @return
* When creating anchor tags (e.g. <a href ='...'
* > text</a>
) in HTML, this setting defines what to set the inner text to.
*/
protected final AnchorText getUriAnchorText() {
return uriAnchorText;
}
@Override /* Context */
public ObjectMap asMap() {
return super.asMap()
.append("HtmlSerializer", new ObjectMap()
.append("uriAnchorText", uriAnchorText)
.append("lookForLabelParameters", lookForLabelParameters)
.append("detectLinksInStrings", detectLinksInStrings)
.append("labelParameter", labelParameter)
.append("addKeyValueTableHeaders", addKeyValueTableHeaders)
.append("addBeanTypes", addBeanTypes)
);
}
}