You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2016/08/01 00:08:10 UTC

[21/51] [partial] incubator-juneau git commit: Initial Juno contents from IBM JazzHub repo

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParser.java
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParser.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParser.java
new file mode 100755
index 0000000..cf96164
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParser.java
@@ -0,0 +1,743 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ *******************************************************************************/
+package com.ibm.juno.core.html;
+
+import static com.ibm.juno.core.html.HtmlParser.Tag.*;
+import static com.ibm.juno.core.utils.StringUtils.*;
+import static javax.xml.stream.XMLStreamConstants.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.xml.namespace.*;
+import javax.xml.stream.*;
+import javax.xml.stream.events.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.annotation.*;
+import com.ibm.juno.core.filter.*;
+import com.ibm.juno.core.parser.*;
+
+/**
+ * Parses text generated by the {@link HtmlSerializer} class back into a POJO model.
+ *
+ *
+ * <h6 class='topic'>Media types</h6>
+ * <p>
+ * 	Handles <code>Content-Type</code> types: <code>text/html</code>
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ * 	See the {@link HtmlSerializer} class for a description of the HTML generated.
+ * <p>
+ * 	This class is used primarily for automated testing of the {@link HtmlSerializer} class.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ * <p>
+ * 	This class has the following properties associated with it:
+ * <ul>
+ * 	<li>{@link ParserProperties}
+ * 	<li>{@link BeanContextProperties}
+ * </ul>
+ *
+ *
+ * @author James Bognar (jbognar@us.ibm.com)
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+@Consumes({"text/html","text/html+stripped"})
+public final class HtmlParser extends ReaderParser {
+
+	/** Default parser, all default settings.*/
+	public static final HtmlParser DEFAULT = new HtmlParser().lock();
+
+	/** HTML specific properties currently defined on this class */
+	protected transient HtmlParserProperties hpp = new HtmlParserProperties();
+
+	/*
+	 * Reads anything starting at the current event.
+	 * <p>
+	 * 	Precondition:  Must be pointing at START_ELEMENT or CHARACTERS event.
+	 * 	Postcondition:  Pointing at next event to be processed.
+	 */
+	private <T> T parseAnything(HtmlParserContext ctx, ClassMeta<T> nt, XMLEventReader r, Object outer, Object name) throws ParseException, IOException {
+
+		try {
+			BeanContext bc = ctx.getBeanContext();
+			if (nt == null)
+				nt = (ClassMeta<T>)object();
+			PojoFilter<T,Object> filter = (PojoFilter<T,Object>)nt.getPojoFilter();
+			ClassMeta<?> ft = nt.getFilteredClassMeta();
+
+			Object o = null;
+
+			XMLEvent event = r.nextEvent();
+			while (! (event.isStartElement() || (event.isCharacters() && ! event.asCharacters().isWhiteSpace()) || event.isEndDocument()))
+				event = r.nextEvent();
+
+			if (event.isEndDocument())
+				throw new XMLStreamException("Unexpected end of stream in parseAnything for type '"+nt+"'", event.getLocation());
+
+			if (event.isCharacters()) {
+				String text = parseCharacters(event, r);
+				if (ft.isObject())
+					o = text;
+				else if (ft.isCharSequence())
+					o = text;
+				else if (ft.isNumber())
+					o = parseNumber(text, (Class<? extends Number>)nt.getInnerClass());
+				else if (ft.isChar())
+					o = text.charAt(0);
+				else if (ft.isBoolean())
+					o = Boolean.parseBoolean(text);
+				else if (ft.canCreateNewInstanceFromString(outer))
+					o = ft.newInstanceFromString(outer, text);
+				else
+					throw new XMLStreamException("Unexpected characters '"+event.asCharacters().getData()+"' for type '"+nt+"'", event.getLocation());
+
+			} else {
+				Tag tag = Tag.forString(event.asStartElement().getName().getLocalPart(), false);
+				String tableType = "object";
+				String text = "";
+
+				if (tag.isOneOf(STRING, NUMBER, BOOLEAN, BR, FF, BS, TB))
+					text = parseCharacters(event, r);
+
+				if (tag == TABLE) {
+					Map<String,String> attrs = getAttributes(event);
+					tableType = attrs.get("type");
+					String c = attrs.get("_class");
+					if (c != null)
+						ft = nt = (ClassMeta<T>)bc.getClassMetaFromString(c);
+				}
+
+				boolean isValid = true;
+
+				if (tag == NULL)
+					nextTag(r, xNULL);
+				else if (tag == A)
+					o = parseAnchor(ctx, event, r, nt);
+				else if (ft.isObject()) {
+					if (tag == STRING)
+						o = text;
+					else if (tag == NUMBER)
+						o = parseNumber(text, null);
+					else if (tag == BOOLEAN)
+						o = Boolean.parseBoolean(text);
+					else if (tag == TABLE) {
+						if (tableType.equals("object")) {
+							o = parseIntoMap(ctx, r, (Map)new ObjectMap(bc), ft.getKeyType(), ft.getValueType());
+						} else if (tableType.equals("array")) {
+							o = parseTableIntoCollection(ctx, r, (Collection)new ObjectList(bc), ft.getElementType());
+						} else
+							isValid = false;
+					}
+					else if (tag == UL)
+						o = parseIntoCollection(ctx, r, new ObjectList(bc), null);
+				}
+				else if (tag == STRING && ft.isCharSequence())
+					o = text;
+				else if (tag == STRING && ft.isChar())
+					o = text.charAt(0);
+				else if (tag == STRING && ft.canCreateNewInstanceFromString(outer))
+					o = ft.newInstanceFromString(outer, text);
+				else if (tag == NUMBER && ft.isNumber())
+					o = parseNumber(text, (Class<? extends Number>)ft.getInnerClass());
+				else if (tag == BOOLEAN && ft.isBoolean())
+					o = Boolean.parseBoolean(text);
+				else if (tag == TABLE) {
+					if (tableType.equals("object")) {
+						if (ft.isMap()) {
+							o = parseIntoMap(ctx, r, (Map)(ft.canCreateNewInstance(outer) ? ft.newInstance(outer) : new ObjectMap(bc)), ft.getKeyType(), ft.getValueType());
+						} else if (ft.canCreateNewInstanceFromObjectMap(outer)) {
+							ObjectMap m = new ObjectMap(bc);
+							parseIntoMap(ctx, r, m, string(), object());
+							o = ft.newInstanceFromObjectMap(outer, m);
+						} else if (ft.canCreateNewBean(outer)) {
+							BeanMap m = bc.newBeanMap(outer, ft.getInnerClass());
+							o = parseIntoBean(ctx, r, m).getBean();
+						}
+						else
+							isValid = false;
+					} else if (tableType.equals("array")) {
+						if (ft.isCollection())
+							o = parseTableIntoCollection(ctx, r, (Collection)(ft.canCreateNewInstance(outer) ? ft.newInstance(outer) : new ObjectList(bc)), ft.getElementType());
+						else if (ft.isArray())
+							o = bc.toArray(ft, parseTableIntoCollection(ctx, r, new ArrayList(), ft.getElementType()));
+						else
+							isValid = false;
+					} else
+						isValid = false;
+				} else if (tag == UL) {
+					if (ft.isCollection())
+						o = parseIntoCollection(ctx, r, (Collection)(ft.canCreateNewInstance(outer) ? ft.newInstance(outer) : new ObjectList(bc)), ft.getElementType());
+					else if (ft.isArray())
+						o = bc.toArray(ft, parseIntoCollection(ctx, r, new ArrayList(), ft.getElementType()));
+					else
+						isValid = false;
+				} else
+					isValid = false;
+
+				if (! isValid)
+					throw new XMLStreamException("Unexpected tag '"+tag+"' for type '"+nt+"'", event.getLocation());
+			}
+
+
+			if (filter != null && o != null)
+				o = filter.unfilter(o, nt);
+
+			if (outer != null)
+				setParent(nt, o, outer);
+
+			if (name != null)
+				setName(nt, o, name);
+
+			return (T)o;
+
+		} catch (ParseException e) {
+			throw e;
+		} catch (IOException e) {
+			throw e;
+		} catch (RuntimeException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new ParseException(e);
+		}
+	}
+
+	/*
+	 * Reads an anchor tag and converts it into a bean.
+	 */
+	private <T> T parseAnchor(HtmlParserContext ctx, XMLEvent e, XMLEventReader r, ClassMeta<T> beanType) throws XMLStreamException {
+		BeanContext bc = ctx.getBeanContext();
+		String href = e.asStartElement().getAttributeByName(new QName("href")).getValue();
+		String name = parseCharacters(e, r);
+		Class<T> beanClass = beanType.getInnerClass();
+		if (beanClass.isAnnotationPresent(HtmlLink.class)) {
+			HtmlLink h = beanClass.getAnnotation(HtmlLink.class);
+			BeanMap<T> m = bc.newBeanMap(beanClass);
+			m.put(h.hrefProperty(), href);
+			m.put(h.nameProperty(), name);
+			return m.getBean();
+		}
+		return bc.convertToType(href, beanType);
+	}
+
+	private Map<String,String> getAttributes(XMLEvent e) {
+		Map<String,String> m = new TreeMap<String,String>() ;
+		for (Iterator i = e.asStartElement().getAttributes(); i.hasNext();) {
+			Attribute a = (Attribute)i.next();
+			m.put(a.getName().getLocalPart(), a.getValue());
+		}
+		return m;
+	}
+
+	/*
+	 * Reads contents of <table> element.
+	 * Precondition:  Must be pointing at <table> event.
+	 * Postcondition:  Pointing at next START_ELEMENT or END_DOCUMENT event.
+	 */
+	private <K,V> Map<K,V> parseIntoMap(HtmlParserContext ctx, XMLEventReader r, Map<K,V> m, ClassMeta<K> keyType, ClassMeta<V> valueType) throws ParseException, IOException {
+		try {
+			Tag tag = nextTag(r, TR);
+
+			// Skip over the column headers.
+			nextTag(r, TH);
+			parseElementText(r, xTH);
+			nextTag(r, TH);
+			parseElementText(r, xTH);
+			nextTag(r, xTR);
+
+			while (true) {
+				tag = nextTag(r, TR, xTABLE);
+				if (tag == xTABLE)
+					break;
+				nextTag(r, TD);
+				K key = parseAnything(ctx, keyType, r, m, null);
+				nextTag(r, xTD);
+				nextTag(r, TD);
+				m.put(key, parseAnything(ctx, valueType, r, m, key));
+				nextTag(r, xTD);
+				nextTag(r, xTR);
+			}
+
+			return m;
+		} catch (XMLStreamException e) {
+			throw new ParseException(e);
+		}
+	}
+
+	/*
+	 * Reads contents of <ul> element.
+	 * Precondition:  Must be pointing at event following <ul> event.
+	 * Postcondition:  Pointing at next START_ELEMENT or END_DOCUMENT event.
+	 */
+	private <E> Collection<E> parseIntoCollection(HtmlParserContext ctx, XMLEventReader r, Collection<E> l, ClassMeta<E> elementType) throws ParseException, IOException {
+		try {
+			while (true) {
+				Tag tag = nextTag(r, LI, xUL);
+				if (tag == xUL)
+					break;
+				l.add(parseAnything(ctx, elementType, r, l, null));
+				nextTag(r, xLI);
+			}
+			return l;
+		} catch (XMLStreamException e) {
+			throw new ParseException(e);
+		}
+	}
+
+	/*
+	 * Reads contents of <ul> element into an Object array.
+	 * Precondition:  Must be pointing at event following <ul> event.
+	 * Postcondition:  Pointing at next START_ELEMENT or END_DOCUMENT event.
+	 */
+	private Object[] parseArgs(HtmlParserContext ctx, XMLEventReader r, ClassMeta<?>[] argTypes) throws ParseException, IOException {
+		try {
+			Object[] o = new Object[argTypes.length];
+			int i = 0;
+			while (true) {
+				Tag tag = nextTag(r, LI, xUL);
+				if (tag == xUL)
+					break;
+				o[i] = parseAnything(ctx, argTypes[i], r, ctx.getOuter(), null);
+				i++;
+				nextTag(r, xLI);
+			}
+			return o;
+		} catch (XMLStreamException e) {
+			throw new ParseException(e);
+		}
+	}
+
+	/*
+	 * Reads contents of <ul> element.
+	 * Precondition:  Must be pointing at event following <ul> event.
+	 * Postcondition:  Pointing at next START_ELEMENT or END_DOCUMENT event.
+	 */
+	private <E> Collection<E> parseTableIntoCollection(HtmlParserContext ctx, XMLEventReader r, Collection<E> l, ClassMeta<E> elementType) throws Exception {
+
+		BeanContext bc = ctx.getBeanContext();
+		if (elementType == null)
+			elementType = (ClassMeta<E>)object();
+
+		Tag tag = nextTag(r, TR);
+		List<String> keys = new ArrayList<String>();
+		while (true) {
+			tag = nextTag(r, TH, xTR);
+			if (tag == xTR)
+				break;
+			keys.add(parseElementText(r, xTH));
+		}
+
+		while (true) {
+			XMLEvent event = r.nextTag();
+			tag = Tag.forEvent(event);
+			if (tag == xTABLE)
+				break;
+			if (elementType.canCreateNewBean(l)) {
+				BeanMap m = bc.newBeanMap(l, elementType.getInnerClass());
+				for (int i = 0; i < keys.size(); i++) {
+					tag = nextTag(r, TD, NULL);
+					if (tag == NULL) {
+						m = null;
+						nextTag(r, xNULL);
+						break;
+					}
+					String key = keys.get(i);
+					BeanMapEntry e = m.getProperty(key);
+					if (e == null) {
+						//onUnknownProperty(key, m, -1, -1);
+						parseAnything(ctx, object(), r, l, null);
+					} else {
+						e.getMeta().set(m, parseAnything(ctx, e.getMeta().getClassMeta(), r, m.getBean(false), key));
+					}
+					nextTag(r, xTD);
+				}
+				l.add(m == null ? null : (E)m.getBean());
+			} else {
+				String c = getAttributes(event).get("_class");
+				Map m = (Map)(elementType.isMap() && elementType.canCreateNewInstance(l) ? elementType.newInstance(l) : new ObjectMap(bc));
+				for (int i = 0; i < keys.size(); i++) {
+					tag = nextTag(r, TD, NULL);
+					if (tag == NULL) {
+						m = null;
+						nextTag(r, xNULL);
+						break;
+					}
+					String key = keys.get(i);
+					if (m != null)
+						m.put(key, parseAnything(ctx, elementType.getElementType(), r, l, key));
+					nextTag(r, xTD);
+				}
+				if (m != null && c != null) {
+					ObjectMap m2 = (m instanceof ObjectMap ? (ObjectMap)m : new ObjectMap(m).setBeanContext(ctx.getBeanContext()));
+					m2.put("_class", c);
+					l.add((E)m2.cast());
+				} else {
+					l.add((E)m);
+				}
+			}
+			nextTag(r, xTR);
+		}
+		return l;
+	}
+
+	/*
+	 * Reads contents of <table> element.
+	 * Precondition:  Must be pointing at event following <table> event.
+	 * Postcondition:  Pointing at next START_ELEMENT or END_DOCUMENT event.
+	 */
+	private <T> BeanMap<T> parseIntoBean(HtmlParserContext ctx, XMLEventReader r, BeanMap<T> m) throws Exception {
+		Tag tag = nextTag(r, TR);
+
+		// Skip over the column headers.
+		nextTag(r, TH);
+		parseElementText(r, xTH);
+		nextTag(r, TH);
+		parseElementText(r, xTH);
+		nextTag(r, xTR);
+
+		while (true) {
+			tag = nextTag(r, TR, xTABLE);
+			if (tag == xTABLE)
+				break;
+			nextTag(r, TD);
+			String key = parseElementText(r, xTD);
+			nextTag(r, TD);
+			BeanPropertyMeta pMeta = m.getPropertyMeta(key);
+			if (pMeta == null) {
+				if (m.getMeta().isSubTyped()) {
+					m.put(key, parseAnything(ctx, object(), r, m.getBean(false), key));
+				} else {
+					onUnknownProperty(ctx, key, m, -1, -1);
+					parseAnything(ctx, object(), r, null, null);
+				}
+			} else {
+				pMeta.set(m, parseAnything(ctx, pMeta.getClassMeta(), r, m.getBean(false), key));
+			}
+			nextTag(r, xTD);
+			nextTag(r, xTR);
+		}
+		return m;
+	}
+
+	/*
+	 * Parse until the next event is an end tag.
+	 */
+	private String parseCharacters(XMLEvent e, XMLEventReader r) throws XMLStreamException {
+
+		List<String> strings = new LinkedList<String>();
+
+		while (true) {
+			int eventType = e.getEventType();
+			if (eventType == CHARACTERS) {
+				Characters c = e.asCharacters();
+				if (! c.isWhiteSpace())
+					strings.add(c.getData());
+			}
+			else if (eventType == START_ELEMENT) {
+				Tag tag = Tag.forEvent(e);
+				if (tag == BR)
+					strings.add("\n");
+				else if (tag == FF)
+					strings.add("\f");
+				else if (tag == BS)
+					strings.add("\b");
+				else if (tag == TB)
+					strings.add("\t");
+			}
+			// Ignore all other elements.
+
+			XMLEvent eNext = r.peek();
+
+			if (eNext.isStartElement() || eNext.isEndElement()) {
+				Tag tag = Tag.forEvent(eNext);
+				if (! (tag.isOneOf(A, xA, BR, xBR, FF, xFF, BS, xBS, TB, xTB, STRING, xSTRING, NUMBER, xNUMBER, BOOLEAN, xBOOLEAN)))
+					return trim(join(strings));
+			} else if (eNext.isEndDocument()) {
+				return trim(join(strings));
+			}
+
+			e = r.nextEvent();
+		}
+	}
+
+	private String trim(String s) {
+		int i2 = 0, i3;
+		for (i2 = 0; i2 < s.length(); i2++) {
+			char c = s.charAt(i2);
+			if (c != ' ')
+				break;
+		}
+		for (i3 = s.length(); i3 > i2; i3--) {
+			char c = s.charAt(i3-1);
+			if (c != ' ')
+				break;
+		}
+		return s.substring(i2, i3);
+	}
+
+	/*
+	 * Reads the element text of the current element, accounting for <a> and <br> tags. <br>
+	 * Precondition:  Must be pointing at first event AFTER the start tag.
+	 * Postcondition:  Pointing at next START_ELEMENT or END_DOCUMENT event.
+	 */
+	private String parseElementText(XMLEventReader r, Tag endTag) throws XMLStreamException {
+
+		List<String> strings = new LinkedList<String>();
+
+		XMLEvent e = r.nextEvent();
+		Tag nTag = (e.isEndElement() ? Tag.forEvent(e) : null);
+
+		while (nTag != endTag) {
+			if (e.isCharacters())
+				strings.add(parseCharacters(e, r));
+			e = r.nextEvent();
+
+			if (e.getEventType() == END_ELEMENT)
+				nTag = Tag.forEvent(e);
+
+			if (nTag == endTag)
+				return join(strings);
+		}
+
+		return "";
+	}
+
+	enum Tag {
+
+		TABLE(1,"<table>"),
+		TR(2,"<tr>"),
+		TH(3,"<th>"),
+		TD(4,"<td>"),
+		UL(5,"<ul>"),
+		LI(6,"<li>"),
+		STRING(7,"<string>"),
+		NUMBER(8,"<number>"),
+		BOOLEAN(9,"<boolean>"),
+		NULL(10,"<null>"),
+		A(11,"<a>"),
+		BR(12,"<br>"),		// newline
+		FF(13,"<ff>"),		// formfeed
+		BS(14,"<bs>"),		// backspace
+		TB(15,"<tb>"),		// tab
+		xTABLE(-1,"</table>"),
+		xTR(-2,"</tr>"),
+		xTH(-3,"</th>"),
+		xTD(-4,"</td>"),
+		xUL(-5,"</ul>"),
+		xLI(-6,"</li>"),
+		xSTRING(-7,"</string>"),
+		xNUMBER(-8,"</number>"),
+		xBOOLEAN(-9,"</boolean>"),
+		xNULL(-10,"</null>"),
+		xA(-11,"</a>"),
+		xBR(-12,"</br>"),
+		xFF(-13,"</ff>"),
+		xBS(-14,"</bs>"),
+		xTB(-15,"</tb>");
+
+		private Map<Integer,Tag> cache = new HashMap<Integer,Tag>();
+
+		int id;
+		String label;
+
+		Tag(int id, String label) {
+			this.id = id;
+			this.label = label;
+			cache.put(id, this);
+		}
+
+		static Tag forEvent(XMLEvent event) throws XMLStreamException {
+			if (event.isStartElement())
+				return forString(event.asStartElement().getName().getLocalPart(), false);
+			else if (event.isEndElement())
+				return forString(event.asEndElement().getName().getLocalPart(), true);
+			throw new XMLStreamException("Invalid call to Tag.forEvent on event of type ["+event.getEventType()+"]");
+		}
+
+		private static Tag forString(String tag, boolean end) throws XMLStreamException {
+			char c = tag.charAt(0);
+			Tag t = null;
+			if (c == 'u')
+				t = (end ? xUL : UL);
+			else if (c == 'l')
+				t = (end ? xLI : LI);
+			else if (c == 's')
+				t = (end ? xSTRING : STRING);
+			else if (c == 'b') {
+				c = tag.charAt(1);
+				if (c == 'o')
+					t = (end ? xBOOLEAN : BOOLEAN);
+				else if (c == 'r')
+					t = (end ? xBR : BR);
+				else if (c == 's')
+					t = (end ? xBS : BS);
+			}
+			else if (c == 'a')
+				t = (end ? xA : A);
+			else if (c == 'n') {
+				c = tag.charAt(2);
+				if (c == 'm')
+					t = (end ? xNUMBER : NUMBER);
+				else if (c == 'l')
+					t = (end ? xNULL : NULL);
+			}
+			else if (c == 't') {
+				c = tag.charAt(1);
+				if (c == 'a')
+					t = (end ? xTABLE : TABLE);
+				else if (c == 'r')
+					t = (end ? xTR : TR);
+				else if (c == 'h')
+					t = (end ? xTH : TH);
+				else if (c == 'd')
+					t = (end ? xTD : TD);
+				else if (c == 'b')
+					t = (end ? xTB : TB);
+			}
+			else if (c == 'f')
+				t = (end ? xFF : FF);
+			if (t == null)
+				throw new XMLStreamException("Unknown tag '"+tag+"' encountered");
+			return t;
+		}
+
+		@Override /* Object */
+		public String toString() {
+			return label;
+		}
+
+		public boolean isOneOf(Tag...tags) {
+			for (Tag tag : tags)
+				if (tag == this)
+					return true;
+			return false;
+		}
+	}
+
+	/*
+	 * Reads the current tag.  Advances past anything that's not a start or end tag.  Throws an exception if
+	 * 	it's not one of the expected tags.
+	 * Precondition:  Must be pointing before the event we want to parse.
+	 * Postcondition:  Pointing at the tag just parsed.
+	 */
+	private Tag nextTag(XMLEventReader r, Tag...expected) throws XMLStreamException {
+		XMLEvent event = r.nextTag();
+		Tag tag = Tag.forEvent(event);
+		if (expected.length == 0)
+			return tag;
+		for (Tag t : expected)
+			if (t == tag)
+				return tag;
+		throw new XMLStreamException("Unexpected tag: " + tag, event.getLocation());
+	}
+
+	private String join(List<String> s) {
+		if (s.size() == 0)
+			return "";
+		if (s.size() == 1)
+			return s.get(0);
+		StringBuilder sb = new StringBuilder();
+		for (String ss : s)
+			sb.append(ss);
+		return sb.toString();
+	}
+
+	//--------------------------------------------------------------------------------
+	// Overridden methods
+	//--------------------------------------------------------------------------------
+
+	@Override /* Parser */
+	public HtmlParserContext createContext(ObjectMap properties, Method javaMethod, Object outer) {
+		return new HtmlParserContext(getBeanContext(), pp, hpp, properties, javaMethod, outer);
+	}
+
+	@Override /* Parser */
+	protected <T> T doParse(Reader in, int estimatedSize, ClassMeta<T> type, ParserContext ctx) throws ParseException, IOException {
+		type = ctx.getBeanContext().normalizeClassMeta(type);
+		HtmlParserContext ctx2 = (HtmlParserContext)ctx;
+		return parseAnything(ctx2, type, ctx2.getReader(in, estimatedSize), ctx.getOuter(), null);
+	}
+
+	@Override /* ReaderParser */
+	protected <K,V> Map<K,V> doParseIntoMap(Reader in, int estimatedSize, Map<K,V> m, Type keyType, Type valueType, ParserContext ctx) throws ParseException, IOException {
+		HtmlParserContext hctx = (HtmlParserContext)ctx;
+		return parseIntoMap(hctx, hctx.getReader(in, estimatedSize), m, ctx.getBeanContext().getClassMeta(keyType), ctx.getBeanContext().getClassMeta(valueType));
+	}
+
+	@Override /* ReaderParser */
+	protected <E> Collection<E> doParseIntoCollection(Reader in, int estimatedSize, Collection<E> c, Type elementType, ParserContext ctx) throws ParseException, IOException {
+		HtmlParserContext hctx = (HtmlParserContext)ctx;
+		return parseIntoCollection(hctx, hctx.getReader(in, estimatedSize), c, ctx.getBeanContext().getClassMeta(elementType));
+	}
+
+	@Override /* ReaderParser */
+	protected Object[] doParseArgs(Reader in, int estimatedSize, ClassMeta<?>[] argTypes, ParserContext ctx) throws ParseException, IOException {
+		HtmlParserContext hctx = (HtmlParserContext)ctx;
+		return parseArgs(hctx, hctx.getReader(in, estimatedSize), argTypes);
+	}
+
+	@Override /* CoreApi */
+	public HtmlParser setProperty(String property, Object value) throws LockedException {
+		checkLock();
+		if (! hpp.setProperty(property, value))
+			super.setProperty(property, value);
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public HtmlParser setProperties(ObjectMap properties) throws LockedException {
+		for (Map.Entry<String,Object> e : properties.entrySet())
+			setProperty(e.getKey(), e.getValue());
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public HtmlParser addNotBeanClasses(Class<?>...classes) throws LockedException {
+		super.addNotBeanClasses(classes);
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public HtmlParser addFilters(Class<?>...classes) throws LockedException {
+		super.addFilters(classes);
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public <T> HtmlParser addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException {
+		super.addImplClass(interfaceClass, implClass);
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public HtmlParser setClassLoader(ClassLoader classLoader) throws LockedException {
+		super.setClassLoader(classLoader);
+		return this;
+	}
+
+	@Override /* Lockable */
+	public HtmlParser lock() {
+		super.lock();
+		return this;
+	}
+
+	@Override /* Lockable */
+	public HtmlParser clone() {
+		try {
+			return (HtmlParser)super.clone();
+		} catch (CloneNotSupportedException e) {
+			throw new RuntimeException(e); // Shouldn't happen
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserContext.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserContext.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserContext.class
new file mode 100755
index 0000000..00ee7b4
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserContext.class differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserContext.java
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserContext.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserContext.java
new file mode 100755
index 0000000..b23546e
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserContext.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ * Note to U.S. Government Users Restricted Rights:  Use,
+ * duplication or disclosure restricted by GSA ADP Schedule
+ * Contract with IBM Corp.
+ *******************************************************************************/
+package com.ibm.juno.core.html;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+import javax.xml.stream.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.parser.*;
+import com.ibm.juno.core.utils.*;
+
+/**
+ * Context object that lives for the duration of a single parsing of {@link HtmlParser}.
+ * <p>
+ *
+ * @author James Bognar (jbognar@us.ibm.com)
+ */
+public final class HtmlParserContext extends ParserContext {
+
+	private XMLEventReader xmlEventReader;
+
+	/**
+	 * Create a new parser context with the specified options.
+	 *
+	 * @param beanContext The bean context being used.
+	 * @param pp The default generic parser properties.
+	 * @param hpp The default HTML parser properties.
+	 * @param properties The override properties.
+	 * @param javaMethod The java method that called this parser, usually the method in a REST servlet.
+	 * @param outer The outer object for instantiating top-level non-static inner classes.
+	 */
+	public HtmlParserContext(BeanContext beanContext, ParserProperties pp, HtmlParserProperties hpp, ObjectMap properties, Method javaMethod, Object outer) {
+		super(beanContext, pp, properties, javaMethod, outer);
+	}
+
+	/**
+	 * Wraps the specified reader in an {@link XMLEventReader}.
+	 * This event reader gets closed by the {@link #close()} method.
+	 *
+	 * @param in The reader to read from.
+	 * @param estimatedSize The estimated size of the input.  If <code>-1</code>, uses a default size of <code>8196</code>.
+	 * @return A new XML event reader using a new {@link XMLInputFactory}.
+	 * @throws ParseException
+	 */
+	protected XMLEventReader getReader(Reader in, int estimatedSize) throws ParseException {
+		try {
+			in = IOUtils.getBufferedReader(in, estimatedSize);
+			XMLInputFactory factory = XMLInputFactory.newInstance();
+			factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false);
+			this.xmlEventReader = factory.createXMLEventReader(in);
+		} catch (Error e) {
+			throw new ParseException(e.getLocalizedMessage());
+		} catch (XMLStreamException e) {
+			throw new ParseException(e);
+		}
+		return xmlEventReader;
+	}
+
+	@Override /* ParserContext */
+	public void close() throws ParseException {
+		if (xmlEventReader != null) {
+			try {
+				xmlEventReader.close();
+			} catch (XMLStreamException e) {
+				throw new ParseException(e);
+			}
+		}
+		super.close();
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserProperties.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserProperties.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserProperties.class
new file mode 100755
index 0000000..9fb0222
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserProperties.class differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserProperties.java
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserProperties.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserProperties.java
new file mode 100755
index 0000000..5d2ff60
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlParserProperties.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ *******************************************************************************/
+package com.ibm.juno.core.html;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.parser.*;
+
+/**
+ * Configurable properties on the {@link HtmlParser} class.
+ * <p>
+ * 	Use the {@link HtmlParser#setProperty(String, Object)} method to set property values.
+ * <p>
+ * 	In addition to these properties, the following properties are also applicable for {@link HtmlParser}.
+ * <ul>
+ * 	<li>{@link ParserProperties}
+ * 	<li>{@link BeanContextProperties}
+ * </ul>
+ *
+ * @author James Bognar (jbognar@us.ibm.com)
+ */
+public final class HtmlParserProperties implements Cloneable {
+
+	/**
+	 * Sets the specified property value.
+	 *
+	 * @param property The property name.
+	 * @param value The property value.
+	 * @return <jk>true</jk> if property name was valid and property was set.
+	 */
+	protected boolean setProperty(String property, Object value) {
+		return false;
+	}
+
+	//--------------------------------------------------------------------------------
+	// Overridden methods
+	//--------------------------------------------------------------------------------
+
+	@Override /* Lockable */
+	public HtmlParserProperties clone() {
+		try {
+			return (HtmlParserProperties)super.clone();
+		} catch (CloneNotSupportedException e) {
+			throw new RuntimeException(e); // Shouldn't happen.
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSchemaDocSerializer.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSchemaDocSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSchemaDocSerializer.class
new file mode 100755
index 0000000..b25c1fa
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSchemaDocSerializer.class differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSchemaDocSerializer.java
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSchemaDocSerializer.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSchemaDocSerializer.java
new file mode 100755
index 0000000..2cf8d95
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSchemaDocSerializer.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ *******************************************************************************/
+package com.ibm.juno.core.html;
+
+import static com.ibm.juno.core.serializer.SerializerProperties.*;
+import static com.ibm.juno.core.utils.ClassUtils.*;
+
+import java.io.*;
+import java.util.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.annotation.*;
+import com.ibm.juno.core.filter.*;
+import com.ibm.juno.core.serializer.*;
+
+/**
+ * Serializes POJO metamodels to HTML.
+ *
+ * <h6 class='topic'>Media types</h6>
+ * <p>
+ * 	Handles <code>Accept</code> types: <code>text/html+schema</code>
+ * <p>
+ * 	Produces <code>Content-Type</code> types: <code>text/html</code>
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ * 	Essentially the same as {@link HtmlSerializer}, except serializes the POJO metamodel
+ * 		instead of the model itself.
+ * <p>
+ * 	Produces output that describes the POJO metamodel similar to an XML schema document.
+ * <p>
+ * 	The easiest way to create instances of this class is through the {@link HtmlSerializer#getSchemaSerializer()},
+ * 		which will create a schema serializer with the same settings as the originating serializer.
+ *
+ * @author James Bognar (jbognar@us.ibm.com)
+ */
+@Produces(value="text/html+schema", contentType="text/html")
+public final class HtmlSchemaDocSerializer extends HtmlDocSerializer {
+
+	/**
+	 * Constructor.
+	 */
+	public HtmlSchemaDocSerializer() {
+		setProperty(SERIALIZER_detectRecursions, true);
+		setProperty(SERIALIZER_ignoreRecursions, true);
+	}
+
+	@Override /* ISchemaSerializer */
+	protected void doSerialize(Object o, Writer out, SerializerContext ctx) throws IOException, SerializeException {
+		HtmlSerializerContext hctx = (HtmlSerializerContext)ctx;
+		ObjectMap schema = getSchema(ctx.getBeanContext().getClassMetaForObject(o), hctx, "root", null);
+		super.doSerialize(schema, out, ctx);
+	}
+
+	/*
+	 * Creates a schema representation of the specified class type.
+	 *
+	 * @param eType The class type to get the schema of.
+	 * @param ctx Serialize context used to prevent infinite loops.
+	 * @param attrName The name of the current attribute.
+	 * @return A schema representation of the specified class.
+	 * @throws SerializeException If a problem occurred trying to convert the output.
+	 */
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	private ObjectMap getSchema(ClassMeta<?> eType, HtmlSerializerContext ctx, String attrName, String[] pNames) throws SerializeException {
+		try {
+
+			ObjectMap out = new ObjectMap();
+
+			ClassMeta<?> aType;			// The actual type (will be null if recursion occurs)
+			ClassMeta<?> gType;			// The generic type
+
+			aType = ctx.push(attrName, eType, null);
+
+			gType = eType.getFilteredClassMeta();
+			String type = null;
+
+			if (gType.isEnum() || gType.isCharSequence() || gType.isChar())
+				type = "string";
+			else if (gType.isNumber())
+				type = "number";
+			else if (gType.isBoolean())
+				type = "boolean";
+			else if (gType.isBean() || gType.isMap())
+				type = "object";
+			else if (gType.isCollection() || gType.isArray())
+				type = "array";
+			else
+				type = "any";
+
+			out.put("type", type);
+			out.put("class", eType.toString());
+			PojoFilter f = eType.getPojoFilter();
+			if (f != null)
+				out.put("filter", f);
+
+			if (aType != null) {
+				if (gType.isEnum())
+					out.put("enum", getEnumStrings((Class<Enum<?>>)gType.getInnerClass()));
+				else if (gType.isCollection() || gType.isArray()) {
+					ClassMeta componentType = gType.getElementType();
+					if (gType.isCollection() && isParentClass(Set.class, gType.getInnerClass()))
+						out.put("uniqueItems", true);
+					out.put("items", getSchema(componentType, ctx, "items", pNames));
+				} else if (gType.isBean()) {
+					ObjectMap properties = new ObjectMap();
+					BeanMeta bm = ctx.getBeanContext().getBeanMeta(gType.getInnerClass());
+					if (pNames != null)
+						bm = new BeanMetaFiltered(bm, pNames);
+					for (Iterator<BeanPropertyMeta<?>> i = bm.getPropertyMetas().iterator(); i.hasNext();) {
+						BeanPropertyMeta p = i.next();
+						properties.put(p.getName(), getSchema(p.getClassMeta(), ctx, p.getName(), p.getProperties()));
+					}
+					out.put("properties", properties);
+				}
+			}
+			ctx.pop();
+			return out;
+		} catch (StackOverflowError e) {
+			throw e;
+		} catch (Throwable e) {
+			throw new SerializeException("Exception occured trying to process object of type ''{0}''", eType).initCause(e);
+		}
+	}
+
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	private List<String> getEnumStrings(Class<? extends Enum> c) {
+		List<String> l = new LinkedList<String>();
+		try {
+			for (Object e : EnumSet.allOf(c))
+				l.add(e.toString());
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		return l;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer$Sq.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer$Sq.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer$Sq.class
new file mode 100755
index 0000000..dc9c801
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer$Sq.class differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer$SqReadable.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer$SqReadable.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer$SqReadable.class
new file mode 100755
index 0000000..6e0df08
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer$SqReadable.class differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer.class
new file mode 100755
index 0000000..e182a2c
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer.class differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer.java
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer.java
new file mode 100755
index 0000000..022235d
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializer.java
@@ -0,0 +1,668 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ *******************************************************************************/
+package com.ibm.juno.core.html;
+
+import static com.ibm.juno.core.html.HtmlSerializerProperties.*;
+import static com.ibm.juno.core.serializer.SerializerProperties.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.annotation.*;
+import com.ibm.juno.core.filter.*;
+import com.ibm.juno.core.serializer.*;
+import com.ibm.juno.core.xml.*;
+import com.ibm.juno.core.xml.annotation.*;
+
+/**
+ * Serializes POJO models to HTML.
+ *
+ *
+ * <h6 class='topic'>Media types</h6>
+ * <p>
+ * 	Handles <code>Accept</code> types: <code>text/html</code>
+ * <p>
+ * 	Produces <code>Content-Type</code> types: <code>text/html</code>
+ *
+ *
+ * <h6 class='topic'>Description</h6>
+ * <p>
+ * 	The conversion is as follows...
+ * 	<ul>
+ * 		<li>{@link Map Maps} (e.g. {@link HashMap}, {@link TreeMap}) and beans are converted to HTML tables with 'key' and 'value' columns.
+ * 		<li>{@link Collection Collections} (e.g. {@link HashSet}, {@link LinkedList}) and Java arrays are converted to HTML ordered lists.
+ * 		<li>{@code Collections} of {@code Maps} and beans are converted to HTML tables with keys as headers.
+ * 		<li>Everything else is converted to text.
+ * 	</ul>
+ * <p>
+ * 	This serializer provides several serialization options.  Typically, one of the predefined <jsf>DEFAULT</jsf> serializers will be sufficient.
+ * 	However, custom serializers can be constructed to fine-tune behavior.
+ * <p>
+ * 	The {@link HtmlLink} annotation can be used on beans to add hyperlinks to the output.
+ *
+ *
+ * <h6 class='topic'>Configurable properties</h6>
+ * <p>
+ * 	This class has the following properties associated with it:
+ * <ul>
+ * 	<li>{@link HtmlSerializerProperties}
+ * 	<li>{@link SerializerProperties}
+ * 	<li>{@link BeanContextProperties}
+ * </ul>
+ *
+ *
+ * <h6 class='topic'>Behavior-specific subclasses</h6>
+ * <p>
+ * 	The following direct subclasses are provided for convenience:
+ * <ul>
+ * 	<li>{@link Sq} - Default serializer, single quotes.
+ * 	<li>{@link SqReadable} - Default serializer, single quotes, whitespace added.
+ * </ul>
+ *
+ *
+ * <h6 class='topic'>Examples</h6>
+ * <p class='bcode'>
+ * 	<jc>// Use one of the default serializers to serialize a POJO</jc>
+ * 		String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(someObject);
+ *
+ * 		<jc>// Create a custom serializer that doesn't use whitespace and newlines</jc>
+ * 		HtmlSerializer serializer = <jk>new</jk> HtmlSerializer()
+ * 			.setProperty(SerializerProperties.<jsf>SERIALIZER_useIndentation</jsf>, <jk>false</jk>);
+ *
+ * 		<jc>// Same as above, except uses cloning</jc>
+ * 		HtmlSerializer serializer = HtmlSerializer.<jsf>DEFAULT</jsf>.clone()
+ * 			.setProperty(SerializerProperties.<jsf>SERIALIZER_useIndentation</jsf>, <jk>false</jk>);
+ *
+ * 		<jc>// Serialize POJOs to HTML</jc>
+ *
+ * 		<jc>// Produces: </jc>
+ * 		<jc>// &lt;ul&gt;&lt;li&gt;1&lt;li&gt;2&lt;li&gt;3&lt;/ul&gt;</jc>
+ * 		List l = new ObjectList(1, 2, 3);
+ * 		String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(l);
+ *
+ * 		<jc>// Produces: </jc>
+ * 		<jc>//    &lt;table&gt; </jc>
+ * 		<jc>//       &lt;tr&gt;&lt;th&gt;firstName&lt;/th&gt;&lt;th&gt;lastName&lt;/th&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//       &lt;tr&gt;&lt;td&gt;Bob&lt;/td&gt;&lt;td&gt;Costas&lt;/td&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//       &lt;tr&gt;&lt;td&gt;Billy&lt;/td&gt;&lt;td&gt;TheKid&lt;/td&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//       &lt;tr&gt;&lt;td&gt;Barney&lt;/td&gt;&lt;td&gt;Miller&lt;/td&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//    &lt;/table&gt; </jc>
+ * 		l = <jk>new</jk> ObjectList();
+ * 		l.add(<jk>new</jk> ObjectMap(<js>"{firstName:'Bob',lastName:'Costas'}"</js>));
+ * 		l.add(<jk>new</jk> ObjectMap(<js>"{firstName:'Billy',lastName:'TheKid'}"</js>));
+ * 		l.add(<jk>new</jk> ObjectMap(<js>"{firstName:'Barney',lastName:'Miller'}"</js>));
+ * 		String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(l);
+ *
+ * 		<jc>// Produces: </jc>
+ * 		<jc>//    &lt;table&gt; </jc>
+ * 		<jc>//       &lt;tr&gt;&lt;th&gt;key&lt;/th&gt;&lt;th&gt;value&lt;/th&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//       &lt;tr&gt;&lt;td&gt;foo&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//       &lt;tr&gt;&lt;td&gt;baz&lt;/td&gt;&lt;td&gt;123&lt;/td&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//    &lt;/table&gt; </jc>
+ * 		Map m = <jk>new</jk> ObjectMap(<js>"{foo:'bar',baz:123}"</js>);
+ * 		String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(m);
+ *
+ * 		<jc>// HTML elements can be nested arbitrarily deep</jc>
+ * 		<jc>// Produces: </jc>
+ * 		<jc>//	&lt;table&gt; </jc>
+ * 		<jc>//		&lt;tr&gt;&lt;th&gt;key&lt;/th&gt;&lt;th&gt;value&lt;/th&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//		&lt;tr&gt;&lt;td&gt;foo&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//		&lt;tr&gt;&lt;td&gt;baz&lt;/td&gt;&lt;td&gt;123&lt;/td&gt;&lt;/tr&gt; </jc>
+ * 		<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>
+ * 		<jc>//		&lt;tr&gt;&lt;td&gt;someSubMap&lt;/td&gt;&lt;td&gt; </jc>
+ * 		<jc>//			&lt;table&gt; </jc>
+ * 		<jc>//				&lt;tr&gt;&lt;th&gt;key&lt;/th&gt;&lt;th&gt;value&lt;/th&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//				&lt;tr&gt;&lt;td&gt;a&lt;/td&gt;&lt;td&gt;b&lt;/td&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//			&lt;/table&gt; </jc>
+ * 		<jc>//		&lt;/td&gt;&lt;/tr&gt; </jc>
+ * 		<jc>//	&lt;/table&gt; </jc>
+ * 		Map m = <jk>new</jk> ObjectMap(<js>"{foo:'bar',baz:123}"</js>);
+ * 		m.put("someNumbers", new ObjectList(1, 2, 3));
+ * 		m.put(<js>"someSubMap"</js>, new ObjectMap(<js>"{a:'b'}"</js>));
+ * 		String html = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(m);
+ * </p>
+ *
+ *
+ * @author James Bognar (jbognar@us.ibm.com)
+ */
+@Produces("text/html")
+@SuppressWarnings("hiding")
+public class HtmlSerializer extends XmlSerializer {
+
+	/** Default serializer, all default settings. */
+	public static final HtmlSerializer DEFAULT = new HtmlSerializer().lock();
+
+	/** Default serializer, single quotes. */
+	public static final HtmlSerializer DEFAULT_SQ = new HtmlSerializer.Sq().lock();
+
+	/** Default serializer, single quotes, whitespace added. */
+	public static final HtmlSerializer DEFAULT_SQ_READABLE = new HtmlSerializer.SqReadable().lock();
+
+	/** Default serializer, single quotes. */
+	public static class Sq extends HtmlSerializer {
+		/** Constructor */
+		public Sq() {
+			setProperty(SERIALIZER_quoteChar, '\'');
+		}
+	}
+
+	/** Default serializer, single quotes, whitespace added. */
+	public static class SqReadable extends Sq {
+		/** Constructor */
+		public SqReadable() {
+			setProperty(SERIALIZER_useIndentation, true);
+		}
+	}
+
+
+	/** HTML serializer properties currently set on this serializer. */
+	protected transient HtmlSerializerProperties hsp = new HtmlSerializerProperties();
+
+	/**
+	 * Main serialization routine.
+	 *
+	 * @param o The object being serialized.
+	 * @param w The writer to serialize to.
+	 * @param ctx The serialization context object.
+	 *
+	 * @return The same writer passed in.
+	 * @throws IOException If a problem occurred trying to send output to the writer.
+	 */
+	private HtmlSerializerWriter doSerialize(Object o, HtmlSerializerWriter w, HtmlSerializerContext ctx) throws SerializeException, IOException {
+		serializeAnything(w, o, null, ctx, null, ctx.getInitialDepth()-1, null);
+		return w;
+	}
+
+	/**
+	 * Serialize the specified object to the specified writer.
+	 *
+	 * @param out The writer.
+	 * @param o The object to serialize.
+	 * @param eType The expected type of the object if this is a bean property.
+	 * @param ctx The context object that lives for the duration of this serialization.
+	 * @param name The attribute name of this object if this object was a field in a JSON object (i.e. key of a {@link java.util.Map.Entry} or property name of a bean).
+	 * @param indent The current indentation value.
+	 * @param pMeta The bean property being serialized, or <jk>null</jk> if we're not serializing a bean property.
+	 * @throws IOException
+	 * @throws SerializeException If a problem occurred trying to convert the output.
+	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	protected void serializeAnything(HtmlSerializerWriter out, Object o, ClassMeta<?> eType, HtmlSerializerContext ctx, String name, int indent, BeanPropertyMeta pMeta) throws IOException, SerializeException {
+
+		BeanContext bc = ctx.getBeanContext();
+		ClassMeta<?> aType = null;       // The actual type
+		ClassMeta<?> gType = object();   // The generic type
+
+		if (eType == null)
+			eType = object();
+
+		aType = ctx.push(name, o, eType);
+
+		// Handle recursion
+		if (aType == null) {
+			o = null;
+			aType = object();
+		}
+
+		ctx.indent += indent;
+		int i = ctx.indent;
+
+		// Determine the type.
+		if (o == null || (aType.isChar() && ((Character)o).charValue() == 0))
+			out.tag(i, "null").nl();
+		else {
+
+			gType = aType.getFilteredClassMeta();
+			String classAttr = null;
+			if (ctx.isAddClassAttrs() && ! eType.equals(aType))
+				classAttr = aType.toString();
+
+			// Filter if necessary
+			PojoFilter filter = aType.getPojoFilter();
+			if (filter != null) {
+				o = filter.filter(o);
+
+				// If the filter's getFilteredClass() method returns Object, we need to figure out
+				// the actual type now.
+				if (gType.isObject())
+					gType = bc.getClassMetaForObject(o);
+			}
+
+			HtmlClassMeta html = gType.getHtmlMeta();
+
+			if (html.isAsXml() || (pMeta != null && pMeta.getHtmlMeta().isAsXml()))
+				super.serializeAnything(out, o, null, ctx, null, null, false, XmlFormat.NORMAL, null);
+			else if (html.isAsPlainText() || (pMeta != null && pMeta.getHtmlMeta().isAsPlainText()))
+				out.write(o == null ? "null" : o.toString());
+			else if (o == null || (gType.isChar() && ((Character)o).charValue() == 0))
+				out.tag(i, "null").nl();
+			else if (gType.hasToObjectMapMethod())
+				serializeMap(out, gType.toObjectMap(o), eType, ctx, classAttr, pMeta);
+			else if (gType.isBean())
+				serializeBeanMap(out, bc.forBean(o), ctx, classAttr, pMeta);
+			else if (gType.isNumber())
+				out.sTag(i, "number").append(o).eTag("number").nl();
+			else if (gType.isBoolean())
+				out.sTag(i, "boolean").append(o).eTag("boolean").nl();
+			else if (gType.isMap()) {
+				if (o instanceof BeanMap)
+					serializeBeanMap(out, (BeanMap)o, ctx, classAttr, pMeta);
+				else
+					serializeMap(out, (Map)o, eType, ctx, classAttr, pMeta);
+			}
+			else if (gType.isCollection()) {
+				if (classAttr != null)
+					serializeCollection(out, (Collection)o, gType, ctx, name, classAttr, pMeta);
+				else
+					serializeCollection(out, (Collection)o, eType, ctx, name, null, pMeta);
+			}
+			else if (gType.isArray()) {
+				if (classAttr != null)
+					serializeCollection(out, toList(gType.getInnerClass(), o), gType, ctx, name, classAttr, pMeta);
+				else
+					serializeCollection(out, toList(gType.getInnerClass(), o), eType, ctx, name, null, pMeta);
+			}
+			else if (gType.isUri() || (pMeta != null && (pMeta.isUri() || pMeta.isBeanUri()))) {
+				String label = null;
+				String at = ctx.getUriAnchorText();
+				if (at != null) {
+					if (at.equals(LAST_TOKEN)) {
+						label = o.toString();
+						if (label.indexOf('/') != -1)
+							label = label.substring(label.lastIndexOf('/')+1);
+					} else if (at.equals(PROPERTY_NAME)) {
+						label = (pMeta != null ? pMeta.getName() : null);
+					} else {
+						label = o.toString();
+					}
+				}
+				if (label == null)
+					label = o.toString();
+				out.oTag(i, "a").attrUri("href", o).append('>');
+				if (at != null && at.equals(URI))
+					out.appendUri(label);
+				else
+					out.append(label);
+				out.eTag("a").nl();
+			}
+			else
+				out.sTag(i, "string").encodeText(o).eTag("string").nl();
+		}
+		ctx.pop();
+		ctx.indent -= indent;
+	}
+
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private void serializeMap(HtmlSerializerWriter out, Map m, ClassMeta<?> type, HtmlSerializerContext ctx, String classAttr, BeanPropertyMeta<?> ppMeta) throws IOException, SerializeException {
+		ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
+
+		int i = ctx.getIndent();
+		out.oTag(i, "table").attr("type", "object");
+		if (classAttr != null)
+			out.attr("class", classAttr);
+		out.appendln(">");
+		if (! (ppMeta != null && ppMeta.getHtmlMeta().isNoTableHeaders())) {
+		out.sTag(i+1, "tr").nl();
+		out.sTag(i+2, "th").nl().appendln(i+3, "<string>key</string>").eTag(i+2, "th").nl();
+		out.sTag(i+2, "th").nl().appendln(i+3, "<string>value</string>").eTag(i+2, "th").nl();
+		out.eTag(i+1, "tr").nl();
+		}
+		for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) {
+
+			Object key = generalize(ctx, e.getKey(), keyType);
+			Object value = null;
+			try {
+				value = e.getValue();
+			} catch (StackOverflowError t) {
+				throw t;
+			} catch (Throwable t) {
+				ctx.addWarning("Could not call getValue() on property ''{0}'', {1}", e.getKey(), t.getLocalizedMessage());
+			}
+
+			out.sTag(i+1, "tr").nl();
+			out.sTag(i+2, "td").nl();
+			serializeAnything(out, key, keyType, ctx, null, 2, null);
+			out.eTag(i+2, "td").nl();
+			out.sTag(i+2, "td").nl();
+			serializeAnything(out, value, valueType, ctx, (key == null ? "_x0000_" : key.toString()), 2, null);
+			out.eTag(i+2, "td").nl();
+			out.eTag(i+1, "tr").nl();
+		}
+		out.eTag(i, "table").nl();
+	}
+
+	@SuppressWarnings({ "rawtypes" })
+	private void serializeBeanMap(HtmlSerializerWriter out, BeanMap m, HtmlSerializerContext ctx, String classAttr, BeanPropertyMeta<?> ppMeta) throws IOException, SerializeException {
+		int i = ctx.getIndent();
+
+		Object o = m.getBean();
+
+		Class<?> c = o.getClass();
+		if (c.isAnnotationPresent(HtmlLink.class)) {
+			HtmlLink h = o.getClass().getAnnotation(HtmlLink.class);
+			Object urlProp = m.get(h.hrefProperty());
+			Object nameProp = m.get(h.nameProperty());
+			out.oTag(i, "a").attrUri("href", urlProp).append('>').encodeText(nameProp).eTag("a").nl();
+			return;
+		}
+
+		out.oTag(i, "table").attr("type", "object");
+		if (classAttr != null)
+			out.attr("_class", classAttr);
+		out.append('>').nl();
+		if (! (m.getClassMeta().getHtmlMeta().isNoTableHeaders() || (ppMeta != null && ppMeta.getHtmlMeta().isNoTableHeaders()))) {
+		out.sTag(i+1, "tr").nl();
+		out.sTag(i+2, "th").nl().appendln(i+3, "<string>key</string>").eTag(i+2, "th").nl();
+		out.sTag(i+2, "th").nl().appendln(i+3, "<string>value</string>").eTag(i+2, "th").nl();
+		out.eTag(i+1, "tr").nl();
+		}
+
+		Iterator mapEntries = m.entrySet().iterator();
+
+		while (mapEntries.hasNext()) {
+			BeanMapEntry p = (BeanMapEntry)mapEntries.next();
+			BeanPropertyMeta pMeta = p.getMeta();
+
+			String key = p.getKey();
+			Object value = null;
+			try {
+				value = p.getValue();
+			} catch (StackOverflowError e) {
+				throw e;
+			} catch (Throwable t) {
+				ctx.addBeanGetterWarning(pMeta, t);
+			}
+
+			if (canIgnoreValue(ctx, pMeta.getClassMeta(), key, value))
+				continue;
+
+			out.sTag(i+1, "tr").nl();
+			out.sTag(i+2, "td").nl();
+			out.sTag(i+3, "string").encodeText(key).eTag("string").nl();
+			out.eTag(i+2, "td").nl();
+			out.sTag(i+2, "td").nl();
+			try {
+				serializeAnything(out, value, p.getMeta().getClassMeta(), ctx, key, 2, pMeta);
+			} catch (SerializeException t) {
+				throw t;
+			} catch (StackOverflowError t) {
+				throw t;
+			} catch (Throwable t) {
+				ctx.addBeanGetterWarning(pMeta, t);
+			}
+			out.eTag(i+2, "td").nl();
+			out.eTag(i+1, "tr").nl();
+		}
+		out.eTag(i, "table").nl();
+	}
+
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private void serializeCollection(HtmlSerializerWriter out, Collection c, ClassMeta<?> type, HtmlSerializerContext ctx, String name, String classAttr, BeanPropertyMeta<?> ppMeta) throws IOException, SerializeException {
+
+		BeanContext bc = ctx.getBeanContext();
+		ClassMeta<?> elementType = type.getElementType();
+
+		int i = ctx.getIndent();
+		if (c.isEmpty()) {
+			out.appendln(i, "<ul></ul>");
+			return;
+		}
+
+		c = sort(ctx, c);
+
+		// Look at the objects to see how we're going to handle them.  Check the first object to see how we're going to handle this.
+		// If it's a map or bean, then we'll create a table.
+		// Otherwise, we'll create a list.
+		String[] th = getTableHeaders(ctx, c, ppMeta);
+
+		if (th != null) {
+
+			out.oTag(i, "table").attr("type", "array");
+			if (classAttr != null)
+				out.attr("_class", classAttr);
+			out.append('>').nl();
+			out.sTag(i+1, "tr").nl();
+			for (String key : th)
+				out.sTag(i+2, "th").append(key).eTag("th").nl();
+			out.eTag(i+1, "tr").nl();
+
+			for (Object o : c) {
+				ClassMeta<?> cm = bc.getClassMetaForObject(o);
+
+				if (cm != null && cm.getPojoFilter() != null) {
+					PojoFilter f = cm.getPojoFilter();
+					o = f.filter(o);
+					cm = cm.getFilteredClassMeta();
+				}
+
+				if (cm != null && ctx.isAddClassAttrs() && elementType.getInnerClass() != o.getClass())
+					out.oTag(i+1, "tr").attr("_class", o.getClass().getName()).append('>').nl();
+				else
+					out.sTag(i+1, "tr").nl();
+
+				if (cm == null) {
+					serializeAnything(out, o, null, ctx, null, 1, null);
+
+				} else if (cm.isMap() && ! (cm.isBeanMap())) {
+					Map m2 = sort(ctx, (Map)o);
+
+					Iterator mapEntries = m2.entrySet().iterator();
+					while (mapEntries.hasNext()) {
+						Map.Entry e = (Map.Entry)mapEntries.next();
+						out.sTag(i+2, "td").nl();
+						serializeAnything(out, e.getValue(), elementType, ctx, e.getKey().toString(), 2, null);
+						out.eTag(i+2, "td").nl();
+					}
+				} else {
+					BeanMap m2 = null;
+					if (o instanceof BeanMap)
+						m2 = (BeanMap)o;
+					else
+						m2 = bc.forBean(o);
+
+					Iterator mapEntries = m2.entrySet().iterator();
+					while (mapEntries.hasNext()) {
+						BeanMapEntry p = (BeanMapEntry)mapEntries.next();
+						BeanPropertyMeta pMeta = p.getMeta();
+						out.sTag(i+2, "td").nl();
+						serializeAnything(out, p.getValue(), pMeta.getClassMeta(), ctx, p.getKey().toString(), 2, pMeta);
+						out.eTag(i+2, "td").nl();
+					}
+				}
+				out.eTag(i+1, "tr").nl();
+			}
+			out.eTag(i, "table").nl();
+
+		} else {
+			out.sTag(i, "ul").nl();
+			for (Object o : c) {
+				out.sTag(i+1, "li").nl();
+				serializeAnything(out, o, elementType, ctx, name, 1, null);
+				out.eTag(i+1, "li").nl();
+			}
+			out.eTag(i, "ul").nl();
+		}
+	}
+
+	/*
+	 * Returns the table column headers for the specified collection of objects.
+	 * Returns null if collection should not be serialized as a 2-dimensional table.
+	 * 2-dimensional tables are used for collections of objects that all have the same set of property names.
+	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	private String[] getTableHeaders(SerializerContext ctx, Collection c, BeanPropertyMeta<?> pMeta) throws SerializeException {
+		BeanContext bc = ctx.getBeanContext();
+		if (c.size() == 0)
+			return null;
+		c = sort(ctx, c);
+		String[] th;
+		Set<String> s = new TreeSet<String>();
+		Set<ClassMeta> prevC = new HashSet<ClassMeta>();
+		Object o1 = null;
+		for (Object o : c)
+			if (o != null) {
+				o1 = o;
+				break;
+			}
+		if (o1 == null)
+			return null;
+		ClassMeta cm = bc.getClassMetaForObject(o1);
+		if (cm.getPojoFilter() != null) {
+			PojoFilter f = cm.getPojoFilter();
+			o1 = f.filter(o1);
+			cm = cm.getFilteredClassMeta();
+		}
+		if (cm == null || ! (cm.isMap() || cm.isBean()))
+			return null;
+		if (cm.getInnerClass().isAnnotationPresent(HtmlLink.class))
+			return null;
+		HtmlClassMeta h = cm.getHtmlMeta();
+		if (h.isNoTables() || (pMeta != null && pMeta.getHtmlMeta().isNoTables()))
+				return null;
+		if (h.isNoTableHeaders() || (pMeta != null && pMeta.getHtmlMeta().isNoTableHeaders()))
+				return new String[0];
+		if (canIgnoreValue(ctx, cm, null, o1))
+			return null;
+		if (cm.isMap() && ! cm.isBeanMap()) {
+			Map m = (Map)o1;
+			th = new String[m.size()];
+			int i = 0;
+			for (Object k : m.keySet())
+				th[i++] = (k == null ? null : k.toString());
+		} else {
+			BeanMap<?> bm = (o1 instanceof BeanMap ? (BeanMap)o1 : bc.forBean(o1));
+			List<String> l = new LinkedList<String>();
+			for (String k : bm.keySet())
+				l.add(k);
+			th = l.toArray(new String[l.size()]);
+		}
+		prevC.add(cm);
+		s.addAll(Arrays.asList(th));
+
+		for (Object o : c) {
+			if (o == null)
+				continue;
+			cm = bc.getClassMetaForObject(o);
+			if (cm != null && cm.getPojoFilter() != null) {
+				PojoFilter f = cm.getPojoFilter();
+				o = f.filter(o);
+				cm = cm.getFilteredClassMeta();
+			}
+			if (prevC.contains(cm))
+				continue;
+			if (cm == null || ! (cm.isMap() || cm.isBean()))
+				return null;
+			if (cm.getInnerClass().isAnnotationPresent(HtmlLink.class))
+				return null;
+			if (canIgnoreValue(ctx, cm, null, o))
+				return null;
+			if (cm.isMap() && ! cm.isBeanMap()) {
+				Map m = (Map)o;
+				if (th.length != m.keySet().size())
+					return null;
+				for (Object k : m.keySet())
+					if (! s.contains(k.toString()))
+						return null;
+			} else {
+				BeanMap<?> bm = (o instanceof BeanMap ? (BeanMap)o : bc.forBean(o));
+				int l = 0;
+				for (String k : bm.keySet()) {
+					if (! s.contains(k))
+						return null;
+					l++;
+				}
+				if (s.size() != l)
+					return null;
+			}
+		}
+		return th;
+	}
+
+	/**
+	 * Returns the schema serializer based on the settings of this serializer.
+	 * @return The schema serializer.
+	 */
+	@Override /* XmlSerializer */
+	public HtmlSerializer getSchemaSerializer() {
+		HtmlSchemaDocSerializer s = new HtmlSchemaDocSerializer();
+		s.beanContextFactory = this.beanContextFactory;
+		s.sp = this.sp;
+		s.xsp = this.xsp;
+		s.hsp = this.hsp;
+		return s;
+	}
+
+	//--------------------------------------------------------------------------------
+	// Overridden methods
+	//--------------------------------------------------------------------------------
+
+	@Override /* Serializer */
+	public HtmlSerializerContext createContext(ObjectMap properties, Method javaMethod) {
+		return new HtmlSerializerContext(getBeanContext(), sp, xsp, hsp, properties, javaMethod);
+	}
+
+	@Override /* Serializer */
+	protected void doSerialize(Object o, Writer out, SerializerContext ctx) throws IOException, SerializeException {
+		HtmlSerializerContext hctx = (HtmlSerializerContext)ctx;
+		doSerialize(o, hctx.getWriter(out), hctx);
+	}
+
+	@Override /* CoreApi */
+	public HtmlSerializer setProperty(String property, Object value) throws LockedException {
+		if (! hsp.setProperty(property, value))
+			super.setProperty(property, value);
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public HtmlSerializer setProperties(ObjectMap properties) throws LockedException {
+		for (Map.Entry<String,Object> e : properties.entrySet())
+			setProperty(e.getKey(), e.getValue());
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public HtmlSerializer addNotBeanClasses(Class<?>...classes) throws LockedException {
+		super.addNotBeanClasses(classes);
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public HtmlSerializer addFilters(Class<?>...classes) throws LockedException {
+		super.addFilters(classes);
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public <T> HtmlSerializer addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException {
+		super.addImplClass(interfaceClass, implClass);
+		return this;
+	}
+
+	@Override /* CoreApi */
+	public HtmlSerializer setClassLoader(ClassLoader classLoader) throws LockedException {
+		super.setClassLoader(classLoader);
+		return this;
+	}
+
+	@Override /* Lockable */
+	public HtmlSerializer lock() {
+		super.lock();
+		return this;
+	}
+
+	@Override /* Lockable */
+	public HtmlSerializer clone() {
+		HtmlSerializer c = (HtmlSerializer)super.clone();
+		c.hsp = hsp.clone();
+		return c;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerContext.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerContext.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerContext.class
new file mode 100755
index 0000000..d765a69
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerContext.class differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerContext.java
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerContext.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerContext.java
new file mode 100755
index 0000000..16c8774
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerContext.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ *******************************************************************************/
+package com.ibm.juno.core.html;
+
+import static com.ibm.juno.core.html.HtmlDocSerializerProperties.*;
+import static com.ibm.juno.core.html.HtmlSerializerProperties.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.serializer.*;
+import com.ibm.juno.core.utils.*;
+import com.ibm.juno.core.xml.*;
+
+/**
+ * Context object that lives for the duration of a single serialization of {@link HtmlSerializer} and its subclasses.
+ * <p>
+ * 	See {@link SerializerContext} for details.
+ *
+ * @author James Bognar (jbognar@us.ibm.com)
+ */
+public final class HtmlSerializerContext extends XmlSerializerContext {
+
+	private final String uriAnchorText, title, description, cssUrl;
+	private final String[] cssImports;
+	private final ObjectMap links;
+	private final boolean nowrap;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param beanContext The bean context being used by the serializer.
+	 * @param sp Default general serializer properties.
+	 * @param xsp Default XML serializer properties.
+	 * @param hsp Default HTML serializer properties.
+	 * @param op Override properties.
+	 * @param javaMethod Java method that invoked this serializer.
+	 * 	When using the REST API, this is the Java method invoked by the REST call.
+	 * 	Can be used to access annotations defined on the method or class.
+	 */
+	protected HtmlSerializerContext(BeanContext beanContext, SerializerProperties sp, XmlSerializerProperties xsp, HtmlSerializerProperties hsp, ObjectMap op, Method javaMethod) {
+		super(beanContext, sp, xsp, op, javaMethod);
+		if (op == null || op.isEmpty()) {
+			uriAnchorText = hsp.uriAnchorText;
+			title = hsp.title;
+			description = hsp.description;
+			links = hsp.links;
+			cssUrl = hsp.cssUrl;
+			cssImports = hsp.cssImports;
+			nowrap = hsp.nowrap;
+		} else {
+			uriAnchorText = op.getString(HTML_uriAnchorText, hsp.uriAnchorText);
+			title = op.getString(HTMLDOC_title, hsp.title);
+			description = op.getString(HTMLDOC_description, hsp.description);
+			ObjectMap m = op.getObjectMap(HTMLDOC_links, hsp.links);
+			if (op.containsKey(HTMLDOC_addLinks))
+				if (m == null)
+					m = op.getObjectMap(HTMLDOC_addLinks, null);
+				else
+					m.putAll(op.getObjectMap(HTMLDOC_addLinks, null));
+			links = m;
+			cssUrl = op.getString(HTMLDOC_cssUrl, hsp.cssUrl);
+			cssImports = StringUtils.split(op.getString(HTMLDOC_cssImports, null), ',');
+			nowrap = op.getBoolean(HTMLDOC_cssUrl, hsp.nowrap);
+		}
+	}
+
+	/**
+	 * Returns the {@link HtmlSerializerProperties#HTML_uriAnchorText} setting value in this context.
+	 *
+	 * @return The {@link HtmlSerializerProperties#HTML_uriAnchorText} setting value in this context.
+	 */
+	public final String getUriAnchorText() {
+		return uriAnchorText;
+	}
+
+	/**
+	 * Returns the {@link HtmlDocSerializerProperties#HTMLDOC_title} setting value in this context.
+	 *
+	 * @return The {@link HtmlDocSerializerProperties#HTMLDOC_title} setting value in this context.
+	 */
+	public final String getTitle() {
+		return title;
+	}
+
+	/**
+	 * Returns the {@link HtmlDocSerializerProperties#HTMLDOC_description} setting value in this context.
+	 *
+	 * @return The {@link HtmlDocSerializerProperties#HTMLDOC_description} setting value in this context.
+	 */
+	public final String getDescription() {
+		return description;
+	}
+
+	/**
+	 * Returns the {@link HtmlDocSerializerProperties#HTMLDOC_links} setting value in this context.
+	 *
+	 * @return The {@link HtmlDocSerializerProperties#HTMLDOC_links} setting value in this context.
+	 */
+	public final ObjectMap getLinks() {
+		return links;
+	}
+
+	/**
+	 * Returns the {@link HtmlDocSerializerProperties#HTMLDOC_cssUrl} setting value in this context.
+	 *
+	 * @return The {@link HtmlDocSerializerProperties#HTMLDOC_cssUrl} setting value in this context.
+	 */
+	public final String getCssUrl() {
+		return cssUrl;
+	}
+
+	/**
+	 * Returns the {@link HtmlDocSerializerProperties#HTMLDOC_cssImports} setting value in this context.
+	 *
+	 * @return The {@link HtmlDocSerializerProperties#HTMLDOC_cssImports} setting value in this context.
+	 */
+	public final String[] getCssImports() {
+		return cssImports;
+	}
+
+	/**
+	 * Returns the {@link HtmlDocSerializerProperties#HTMLDOC_nowrap} setting value in this context.
+	 *
+	 * @return The {@link HtmlDocSerializerProperties#HTMLDOC_nowrap} setting value in this context.
+	 */
+	public final boolean isNoWrap() {
+		return nowrap;
+	}
+
+	/**
+	 * Wraps the specified writer in a {@link HtmlSerializerWriter}.
+	 */
+	@Override /* XmlSerializerContext */
+	public HtmlSerializerWriter getWriter(Writer w) {
+		if (w instanceof HtmlSerializerWriter)
+			return (HtmlSerializerWriter)w;
+		return new HtmlSerializerWriter(w, isUseIndentation(), getQuoteChar(), getRelativeUriBase(), getAbsolutePathUriBase());
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerProperties.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerProperties.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerProperties.class
new file mode 100755
index 0000000..6857e05
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerProperties.class differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerProperties.java
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerProperties.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerProperties.java
new file mode 100755
index 0000000..8b6068d
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerProperties.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ *******************************************************************************/
+package com.ibm.juno.core.html;
+
+import static com.ibm.juno.core.html.HtmlDocSerializerProperties.*;
+
+import com.ibm.juno.core.*;
+import com.ibm.juno.core.parser.*;
+import com.ibm.juno.core.serializer.*;
+import com.ibm.juno.core.utils.*;
+import com.ibm.juno.core.xml.*;
+
+/**
+ * Configurable properties on the {@link HtmlSerializer} class.
+ * <p>
+ * 	Use the {@link HtmlSerializer#setProperty(String, Object)} method to set property values.
+ * <p>
+ * 	In addition to these properties, the following properties are also applicable for {@link HtmlSerializer}.
+ * <ul>
+ * 	<li>{@link XmlSerializerProperties}
+ * 	<li>{@link SerializerProperties}
+ * 	<li>{@link BeanContextProperties}
+ * </ul>
+ *
+ * @author James Bognar (jbognar@us.ibm.com)
+ */
+public final class HtmlSerializerProperties implements Cloneable {
+
+	/**
+	 * Anchor text source ({@link String}, default={@link #TO_STRING}).
+	 * <p>
+	 * When creating anchor tags (e.g. <code><xt>&lt;a</xt> <xa>href</xa>=<xs>'...'</xs><xt>&gt;</xt>text<xt>&lt;/a&gt;</xt></code>)
+	 * 	in HTML, this setting defines what to set the inner text to.
+	 * <p>
+	 * Possible values:
+	 * <ul>
+	 * 	<li>{@link #TO_STRING} / <js>"toString"</js> - Set to whatever is returned by {@link #toString()} on the object.
+	 * 	<li>{@link #URI} / <js>"uri"</js> - Set to the URI value.
+	 * 	<li>{@link #LAST_TOKEN} / <js>"lastToken"</js> - Set to the last token of the URI value.
+	 * 	<li>{@link #PROPERTY_NAME} / <js>"propertyName"</js> - Set to the bean property name.
+	 * </ul>
+	 */
+	public static final String HTML_uriAnchorText = "HtmlSerializer.uriAnchorText";
+
+	/** Constant for {@link HtmlSerializerProperties#HTML_uriAnchorText} property. */
+	public static final String PROPERTY_NAME = "propertyName";
+	/** Constant for {@link HtmlSerializerProperties#HTML_uriAnchorText} property. */
+	public static final String TO_STRING = "toString";
+	/** Constant for {@link HtmlSerializerProperties#HTML_uriAnchorText} property. */
+	public static final String URI = "uri";
+	/** Constant for {@link HtmlSerializerProperties#HTML_uriAnchorText} property. */
+	public static final String LAST_TOKEN = "lastToken";
+
+	String uriAnchorText = TO_STRING, title, description, cssUrl;
+	String[] cssImports;
+	ObjectMap links;
+	boolean nowrap;
+
+	/**
+	 * Sets the specified property value.
+	 *
+	 * @param property The property name.
+	 * @param value The property value.
+	 * @return <jk>true</jk> if property name was valid and property was set.
+	 */
+	public boolean setProperty(String property, Object value) {
+		if (property.equals(HTML_uriAnchorText))
+			uriAnchorText = (value == null ? null : value.toString());
+		else if (property.equals(HTMLDOC_title))
+			title = (value == null ? null : value.toString());
+		else if (property.equals(HTMLDOC_description))
+			description = (value == null ? null : value.toString());
+		else if (property.equals(HTMLDOC_nowrap))
+			nowrap = Boolean.valueOf(value.toString());
+		else if (property.equals(HTMLDOC_links))
+			try {
+				links = new ObjectMap(value.toString());
+			} catch (ParseException e) {
+				e.printStackTrace();
+			}
+		else if (property.equals(HTMLDOC_addLinks))
+			try {
+				if (links == null)
+					links = new ObjectMap(value.toString());
+				else
+					links.putAll(new ObjectMap(value.toString()));
+			} catch (ParseException e) {
+				e.printStackTrace();
+			}
+		else if (property.equals(HTMLDOC_cssUrl))
+			cssUrl = (value == null ? null : value.toString());
+		else if (property.equals(HTMLDOC_cssImports))
+			cssImports = StringUtils.split(value == null ? null : value.toString(), ',');
+		else
+			return false;
+		return true;
+	}
+
+	@Override /* Cloneable */
+	public HtmlSerializerProperties clone() {
+		try {
+			return (HtmlSerializerProperties)super.clone();
+		} catch (CloneNotSupportedException e) {
+			throw new RuntimeException(e); // Shouldn't happen
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerWriter.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerWriter.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerWriter.class
new file mode 100755
index 0000000..52d07fe
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerWriter.class differ

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerWriter.java
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerWriter.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerWriter.java
new file mode 100755
index 0000000..28e2eb4
--- /dev/null
+++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlSerializerWriter.java
@@ -0,0 +1,328 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved.
+ *
+ *  The source code for this program is not published or otherwise
+ *  divested of its trade secrets, irrespective of what has been
+ *  deposited with the U.S. Copyright Office.
+ *******************************************************************************/
+package com.ibm.juno.core.html;
+
+import java.io.*;
+
+import com.ibm.juno.core.xml.*;
+
+/**
+ * Specialized writer for serializing HTML.
+ *
+ * @author James Bognar (jbognar@us.ibm.com)
+ */
+public class HtmlSerializerWriter extends XmlSerializerWriter {
+
+	/**
+	 * Constructor.
+	 *
+	 * @param out The writer being wrapped.
+	 * @param useIndentation If <jk>true</jk>, tabs will be used in output.
+	 * @param quoteChar The quote character to use (i.e. <js>'\''</js> or <js>'"'</js>)
+	 * @param uriContext The web application context path (e.g. "/contextRoot").
+	 * @param uriAuthority The web application URI authority (e.g. "http://hostname:9080")
+	 */
+	public HtmlSerializerWriter(Writer out, boolean useIndentation, char quoteChar, String uriContext, String uriAuthority) {
+		super(out, useIndentation, quoteChar, uriContext, uriAuthority, false, null);
+	}
+
+	/**
+	 * Append an attribute with a URI value.
+	 *
+	 * @param name The attribute name.
+	 * @param value The attribute value.  Can be any object whose <code>toString()</code> method returns a URI.
+	 * @return This object (for method chaining);
+	 * @throws IOException If a problem occurred.
+	 */
+	public HtmlSerializerWriter attrUri(String name, Object value) throws IOException {
+		super.attrUri((String)null, name, value);
+		return this;
+	}
+
+
+	//--------------------------------------------------------------------------------
+	// Overridden methods
+	//--------------------------------------------------------------------------------
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter encodeText(Object o) throws IOException {
+
+		String s = o.toString();
+		for (int i = 0; i < s.length(); i++) {
+			char test = s.charAt(i);
+			if (test == '&')
+				append("&amp;");
+			else if (test == '<')
+				append("&lt;");
+			else if (test == '>')
+				append("&gt;");
+			else if (test == '\n')
+				append("<br/>");
+			else if (test == '\f')
+				append("<ff/>");
+			else if (test == '\b')
+				append("<bs/>");
+			else if (test == '\t')
+				append("<tb/>");
+			else if (Character.isISOControl(test))
+				append("&#" + (int) test + ";");
+			else
+				append(test);
+		}
+
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter oTag(String ns, String name, boolean needsEncoding) throws IOException {
+		super.oTag(ns, name, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter oTag(String ns, String name) throws IOException {
+		super.oTag(ns, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter oTag(String name) throws IOException {
+		super.oTag(name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter oTag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
+		super.oTag(indent, ns, name, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter oTag(int indent, String ns, String name) throws IOException {
+		super.oTag(indent, ns, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter oTag(int indent, String name) throws IOException {
+		super.oTag(indent, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter tag(String ns, String name, boolean needsEncoding) throws IOException {
+		super.tag(ns, name, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter tag(String ns, String name) throws IOException {
+		super.tag(ns, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter tag(String name) throws IOException {
+		super.tag(name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter tag(int indent, String name) throws IOException {
+		super.tag(indent, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter tag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
+		super.tag(indent, ns, name, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter tag(int indent, String ns, String name) throws IOException {
+		super.tag(indent, ns, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter sTag(String ns, String name) throws IOException {
+		super.sTag(ns, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter sTag(String ns, String name, boolean needsEncoding) throws IOException {
+		super.sTag(ns, name, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter sTag(int indent, String ns, String name) throws IOException {
+		super.sTag(indent, ns, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter sTag(int indent, String name) throws IOException {
+		super.sTag(indent, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter sTag(String name) throws IOException {
+		super.sTag(name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter sTag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
+		super.sTag(indent, ns, name, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter eTag(String ns, String name) throws IOException {
+		super.eTag(ns, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter eTag(String ns, String name, boolean needsEncoding) throws IOException {
+		super.eTag(ns, name, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter eTag(int indent, String ns, String name) throws IOException {
+		super.eTag(indent, ns, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter eTag(int indent, String name) throws IOException {
+		super.eTag(indent, name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter eTag(String name) throws IOException {
+		super.eTag(name);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter eTag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
+		super.eTag(indent, ns, name, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter attr(String name, Object value) throws IOException {
+		super.attr(name, value);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter attr(String ns, String name, Object value) throws IOException {
+		super.attr(ns, name, value);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter attr(String ns, String name, Object value, boolean needsEncoding) throws IOException {
+		super.attr(ns, name, value, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter attr(String name, Object value, boolean needsEncoding) throws IOException {
+		super.attr(null, name, value, needsEncoding);
+		return this;
+	}
+
+	@Override /* XmlSerializerWriter */
+	public HtmlSerializerWriter oAttr(String ns, String name) throws IOException {
+		super.oAttr(ns, name);
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter cr(int depth) throws IOException {
+		super.cr(depth);
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter appendln(int indent, String text) throws IOException {
+		super.appendln(indent, text);
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter appendln(String text) throws IOException {
+		super.appendln(text);
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter append(int indent, String text) throws IOException {
+		super.append(indent, text);
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter append(int indent, char c) throws IOException {
+		super.append(indent, c);
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter s() throws IOException {
+		super.s();
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter q() throws IOException {
+		super.q();
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter i(int indent) throws IOException {
+		super.i(indent);
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter nl() throws IOException {
+		super.nl();
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter append(Object text) throws IOException {
+		super.append(text);
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter append(String text) throws IOException {
+		super.append(text);
+		return this;
+	}
+
+	@Override /* SerializerWriter */
+	public HtmlSerializerWriter append(char c) throws IOException {
+		super.append(c);
+		return this;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlStrippedDocSerializer.class
----------------------------------------------------------------------
diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlStrippedDocSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlStrippedDocSerializer.class
new file mode 100755
index 0000000..5e453b7
Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/html/HtmlStrippedDocSerializer.class differ