/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * Free SoftwareFoundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.xml; import com.caucho.java.LineMap; import com.caucho.util.CharBuffer; import com.caucho.util.IntMap; import com.caucho.util.L10N; import com.caucho.vfs.EnclosedWriteStream; import com.caucho.vfs.Path; import com.caucho.vfs.Vfs; import com.caucho.vfs.WriteStream; import org.w3c.dom.*; import org.xml.sax.Locator; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; import java.util.logging.Logger; /** * Controls printing of XML documents. * * Typical use: * <code><pre> * Node node = ...; * * OutputStream os = Vfs.openWrite("test.xml"); * XmlPrinter printer = new XmlPrinter(os); * * printer.printXml(node); * </pre></code> */ public class XmlPrinter implements XMLWriter { static final Logger log = Logger.getLogger(XmlPrinter.class.getName()); static final L10N L = new L10N(XmlPrinter.class); private static final int NO_PRETTY = 0; private static final int INLINE = 1; private static final int NO_INDENT = 2; private static final int PRE = 3; private static final char OMITTED_SPACE = 0; private static final char OMITTED_NEWLINE = 1; private static final char OMITTED = 2; private static final char NULL_SPACE = 3; private static final char SPACE = 4; private static final char NEWLINE = 5; private static final char WHITESPACE = 6; private static final int ALWAYS_EMPTY = 1; private static final int EMPTY_IF_EMPTY = 2; private static IntMap _empties; private static HashMap<String,String> _booleanAttrs; private static HashMap<String,String> _verbatimTags; private static IntMap _prettyMap; private WriteStream _os; private char []_buffer = new char[256]; private int _capacity = _buffer.length; private int _length; boolean _isAutomaticMethod = true; boolean _isTop = true; boolean _isJsp = false; String _encoding; String _method; boolean _isText; boolean _isHtml; boolean _inHead; String _version; boolean _isAutomaticPretty = true; boolean _isPretty; int _indent; int _preCount; int _lastTextChar = NULL_SPACE; boolean _hasMetaContentType = false; boolean _includeContentType = true; boolean _printDeclaration; String _standalone; String _systemId; String _publicId; private ExtendedLocator _locator; boolean _escapeText = true; boolean _inVerbatim = false; private HashMap<String,String> _namespace; private HashMap<String,String> _cdataElements; private Entities _entities; private String _mimeType; private ArrayList<String> _prefixList; private ArrayList<String> _attributeNames = new ArrayList<String>(); private ArrayList<String> _attributeValues = new ArrayList<String>(); private char []_cbuf = new char[256]; private char []_abuf = new char[256]; private LineMap _lineMap; private int _line; private String _srcFilename; private int _srcLine; private String _currentElement; private Document _currentDocument; private boolean _isEnclosedStream; /** * Create an XmlPrinter. Using this API, you'll need to use * printer.init(os) to assign an output stream. */ public XmlPrinter() { } /** * Creates a new XmlPrinter writing to an output stream. * * @param os output stream serving as the destination */ public XmlPrinter(OutputStream os) { if (os instanceof WriteStream) init((WriteStream) os); else if (os instanceof EnclosedWriteStream) init(((EnclosedWriteStream) os).getWriteStream()); else { _isEnclosedStream = true; WriteStream ws = Vfs.openWrite(os); try { ws.setEncoding("UTF-8"); } catch (UnsupportedEncodingException e) { } init(ws); } } /** * Creates a new XmlPrinter writing to a writer. * * @param writer destination of the serialized node */ public XmlPrinter(Writer writer) { if (writer instanceof EnclosedWriteStream) init(((EnclosedWriteStream) writer).getWriteStream()); else { _isEnclosedStream = true; WriteStream ws = Vfs.openWrite(writer); init(ws); } } /** * Initialize the XmlPrinter with the write stream. * * @param os WriteStream containing the results. */ public void init(WriteStream os) { _os = os; init(); } /** * Initialize the XmlWriter in preparation for serializing a new XML. */ void init() { String encoding = null; if (_os != null) encoding = _os.getEncoding(); _length = 0; if (encoding == null || encoding.equals("US-ASCII") || encoding.equals("ISO-8859-1")) _entities = XmlLatin1Entities.create(); else _entities = XmlEntities.create(); _encoding = encoding; _namespace = new HashMap<String,String>(); _line = 1; _isTop = true; _hasMetaContentType = false; _attributeNames.clear(); _attributeValues.clear(); } /** * Prints the node as XML. The destination stream has already been * set using init() or in the constructor. * * @param node source DOM node */ public static void print(Path path, Node node) throws IOException { WriteStream os = path.openWrite(); try { new XmlPrinter(os).printXml(node); } finally { os.close(); } } /** * Prints the node as XML. The destination stream has already been * set using init() or in the constructor. * * @param node source DOM node */ public void printXml(Node node) throws IOException { _isAutomaticMethod = false; ((QAbstractNode) node).print(this); flush(); } /** * Prints the node and children as HTML * * @param node the top node to print */ public void printHtml(Node node) throws IOException { setMethod("html"); setVersion("4.0"); ((QAbstractNode) node).print(this); flush(); } /** * Prints the node and children as XML, automatically indending * * @param node the top node to print */ public void printPrettyXml(Node node) throws IOException { _isAutomaticMethod = false; setPretty(true); ((QAbstractNode) node).print(this); flush(); } /** * Prints the node as XML to a string. * * @param node the source node * @return a string containing the XML. */ public String printString(Node node) throws IOException { CharBuffer cb = CharBuffer.allocate(); _os = Vfs.openWrite(cb); init(_os); try { ((QAbstractNode) node).print(this); } finally { flush(); _os.close(); } return cb.close(); } /** * Sets to true if XML entities like < should be escaped as &lt;. * The default is true. * * @param escapeText set to true if entities should be escaped. */ public void setEscaping(boolean escapeText) { if (! _isText) _escapeText = escapeText; } /** * Returns the current XML escaping. If true, entities like < * will be escaped as &lt;. * * @return true if entities should be escaped. */ public boolean getEscaping() { return _escapeText; } /** * Sets the output methods, like the XSL <xsl:output method='method'/>. */ public void setMethod(String method) { _method = method; if (method == null) { _isAutomaticMethod = true; _isHtml = false; } else if (method.equals("html")) { _isAutomaticMethod = false; _isHtml = true; if (_isAutomaticPretty) _isPretty = true; } else if (method.equals("text")) { _isAutomaticMethod = false; _isText = true; _escapeText = false; } else { _isAutomaticMethod = false; _isHtml = false; } } /** * Sets the XML/HTML version of the output file. * * @param version the output version */ public void setVersion(String version) { _version = version; } /** * Sets the character set encoding for the output file. * * @param encoding the mime name of the output encoding */ public void setEncoding(String encoding) { _encoding = encoding; try { if (encoding != null) { _os.setEncoding(encoding); if (encoding.equals("US-ASCII") || encoding.equals("ISO-8859-1")) _entities = XmlLatin1Entities.create(); else _entities = XmlEntities.create(); } } catch (Exception e) { } } public void setMimeType(String mimeType) { _mimeType = mimeType; if (_method == null && mimeType != null && mimeType.equals("text/html")) setMethod("html"); } /** * Set true if this is JSP special cased. */ public void setJSP(boolean isJsp) { _isJsp = isJsp; } /** * True if this is JSP special cased. */ public boolean isJSP() { return _isJsp; } /** * Returns true if the printer is printing HTML. */ boolean isHtml() { return _isHtml; } /** * Set to true if the printer should add whitespace to 'pretty-print' * the output. * * @param isPretty if true, add spaces for printing */ public void setPretty(boolean isPretty) { _isPretty = isPretty; _isAutomaticPretty = false; } /** * Returns true if the printer is currently pretty-printing the output. */ public boolean isPretty() { return _isPretty; } public void setPrintDeclaration(boolean printDeclaration) { _printDeclaration = printDeclaration; } boolean getPrintDeclaration() { return _printDeclaration; } public void setStandalone(String standalone) { _standalone = standalone; } String getStandalone() { return _standalone; } public void setSystemId(String id) { _systemId = id; } String getSystemId() { return _systemId; } /** * Set true if the printer should automatically add the * <meta content-type> to HTML. */ public void setIncludeContentType(boolean include) { _includeContentType = include; } /** * Return true if the printer should automatically add the * <meta content-type> to HTML. */ public boolean getIncludeContentType() { return _includeContentType; } public void setPublicId(String id) { _publicId = id; } String getPublicId() { return _publicId; } public Path getPath() { if (_os instanceof WriteStream) return ((WriteStream) _os).getPath(); else return null; } /** * Creates a new line map. */ public void setLineMap(String filename) { _lineMap = new LineMap(filename); } public LineMap getLineMap() { return _lineMap; } public void addCdataElement(String elt) { if (_cdataElements == null) _cdataElements = new HashMap<String,String>(); _cdataElements.put(elt, ""); } public void print(Node node) throws IOException { if (node instanceof QAbstractNode) ((QAbstractNode) node).print(this); else { printNode(node); } if (_isEnclosedStream) _os.flush(); } public void printNode(Node node) throws IOException { if (node == null) return; switch (node.getNodeType()) { case Node.DOCUMENT_NODE: startDocument((Document) node); for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) printNode(child); endDocument(); break; case Node.ELEMENT_NODE: { Element elt = (Element) node; startElement(elt.getNamespaceURI(), elt.getLocalName(), elt.getNodeName()); NamedNodeMap attrs = elt.getAttributes(); int len = attrs.getLength(); for (int i = 0; i < len; i++) { Attr attr = (Attr) attrs.item(i); attribute(attr.getNamespaceURI(), attr.getLocalName(), attr.getNodeName(), attr.getNodeValue()); } for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { printNode(child); } endElement(elt.getNamespaceURI(), elt.getLocalName(), elt.getNodeName()); break; } case Node.TEXT_NODE: case Node.CDATA_SECTION_NODE: { CharacterData text = (CharacterData) node; text(text.getData()); break; } case Node.COMMENT_NODE: { Comment comment = (Comment) node; comment(comment.getData()); break; } case Node.PROCESSING_INSTRUCTION_NODE: { ProcessingInstruction pi = (ProcessingInstruction) node; processingInstruction(pi.getNodeName(), pi.getData()); break; } } } WriteStream getStream() { return _os; } public void startDocument(Document document) throws IOException { _currentDocument = document; startDocument(); } /** * Callback when the document starts printing. */ public void startDocument() throws IOException { _isTop = true; } /** * Callback when the document completes */ public void endDocument() throws IOException { if (_isPretty && _lastTextChar < SPACE) println(); flush(); } /** * Sets the locator. */ public void setDocumentLocator(Locator locator) { _locator = (ExtendedLocator) locator; } /** * Sets the current location. * * @param filename the source filename * @param line the source line * @param column the source column */ public void setLocation(String filename, int line, int column) { _srcFilename = filename; _srcLine = line; } /** * Called at the start of a new element. * * @param url the namespace url * @param localName the local name * @param qName the qualified name */ public void startElement(String url, String localName, String qName) throws IOException { if (_isText) return; if (_isAutomaticMethod) { _isHtml = (qName.equalsIgnoreCase("html") && (url == null || url.equals(""))); if (_isAutomaticPretty) _isPretty = _isHtml; _isAutomaticMethod = false; } if (_isTop) printHeader(qName); if (_currentElement != null) completeOpenTag(); _attributeNames.clear(); _attributeValues.clear(); if (_isHtml && _verbatimTags.get(qName.toLowerCase(Locale.ENGLISH)) != null) _inVerbatim = true; if (_isPretty && _preCount <= 0) printPrettyStart(qName); if (_lineMap == null) { } else if (_locator != null) { _lineMap.add(_locator.getFilename(), _locator.getLineNumber(), _line); } else if (_srcFilename != null) _lineMap.add(_srcFilename, _srcLine, _line); print('<'); print(qName); _currentElement = qName; _lastTextChar = NULL_SPACE; } /** * Prints the header, if necessary. * * @param top name of the top element */ public void printHeader(String top) throws IOException { if (! _isTop) return; _isTop = false; String encoding = _encoding; if (encoding != null && encoding.equalsIgnoreCase("UTF-16")) print('\ufeff'); if (_isHtml) { double dVersion = 4.0; if (_version == null || _version.compareTo("4.0") >= 0) { } else { dVersion = 3.2; } if (_systemId != null || _publicId != null) printDoctype("html"); if (encoding == null || encoding.equalsIgnoreCase("ISO-8859-1")) // _entities = Latin1Entities.create(dVersion); _entities = HtmlEntities.create(dVersion); else if (encoding.equalsIgnoreCase("US-ASCII")) _entities = HtmlEntities.create(dVersion); else _entities = OtherEntities.create(dVersion); } else { if (_printDeclaration) { String version = _version; if (version == null) version = "1.0"; print("<?xml version=\""); print(version); print("\""); if (encoding == null || encoding.equals("") || encoding.equalsIgnoreCase("US-ASCII")) { } else print(" encoding=\"" + encoding + "\""); if (_standalone != null && (_standalone.equals("true") || _standalone.equals("yes"))) print(" standalone=\"yes\""); println("?>"); } printDoctype(top); if (encoding == null || encoding.equalsIgnoreCase("US-ASCII") || encoding.equalsIgnoreCase("ISO-8859-1")) _entities = XmlLatin1Entities.create(); else _entities = XmlEntities.create(); } _lastTextChar = NEWLINE; } /** * Prints the doctype declaration * * @param topElt name of the top element */ private void printDoctype(String topElt) throws IOException { if (_publicId != null && _systemId != null) println("<!DOCTYPE " + topElt + " PUBLIC \"" + _publicId + "\" \"" + _systemId + "\">"); else if (_publicId != null) println("<!DOCTYPE " + topElt + " PUBLIC \"" + _publicId + "\">"); else if (_systemId != null) println("<!DOCTYPE " + topElt + " SYSTEM \"" + _systemId + "\">"); else if (_currentDocument instanceof QDocument) { QDocumentType dtd = (QDocumentType) _currentDocument.getDoctype(); if (dtd != null && dtd.getName() != null && dtd.getParentNode() == null) { dtd.print(this); println(); } } } public void startPrefixMapping(String prefix, String uri) throws IOException { } public void endPrefixMapping(String prefix) throws IOException { } /** * Pretty printing for a start tag. * * @param qName the name of the element */ private void printPrettyStart(String qName) throws IOException { int code = _isHtml ? _prettyMap.get(qName.toLowerCase(Locale.ENGLISH)) : -1; if (code == NO_PRETTY) { if (_lastTextChar == OMITTED_NEWLINE) println(); else if (_lastTextChar == OMITTED_SPACE) print(' '); } else if (code != INLINE && _lastTextChar < WHITESPACE) { if (_lastTextChar != NEWLINE) println(); for (int i = 0; i < _indent; i++) print(' '); } else if (code == INLINE && _lastTextChar < WHITESPACE) { if (_lastTextChar == OMITTED_NEWLINE) println(); else if (_lastTextChar == OMITTED_SPACE) print(' '); } if (! _isHtml || code < 0) { _indent += 2; } if (code == PRE) { _preCount++; _lastTextChar = 'a'; } else if (code == NO_PRETTY || code == INLINE) _lastTextChar = 'a'; else _lastTextChar = NULL_SPACE; } /** * Prints an attribute * * @param uri namespace uri * @param localName localname of the attribute * @param qName qualified name of the attribute * @param value value of the attribute. */ public void attribute(String uri, String localName, String qName, String value) throws IOException { if (_isText) return; if (_currentElement != null) { } else if (qName.equals("encoding")) { _encoding = value; return; } else if (qName.startsWith("xmlns")) { } else throw new IOException(L.l("attribute `{0}' outside element.", qName)); qName = qName.intern(); if (qName.startsWith("xmlns")) { if (localName == null) localName = ""; if (_isHtml && localName.equals("") && value.equals("")) return; _namespace.put(localName, value); if (_prefixList == null) _prefixList = new ArrayList<String>(); if (! _prefixList.contains(localName)) _prefixList.add(localName); return; } else if (qName.equals("xtp:jsp-attribute")) { _attributeNames.add("<%= " + value + "%>"); _attributeValues.add(null); return; } if (_isHtml && ! _hasMetaContentType && _currentElement.equals("meta") && qName.equalsIgnoreCase("http-equiv") && value.equalsIgnoreCase("content-type")) { _hasMetaContentType = true; } for (int i = 0; i < _attributeNames.size(); i++) { String oldName = _attributeNames.get(i); if (oldName == qName) { _attributeValues.set(i, value); return; } } if (qName == null || qName.equals("")) throw new NullPointerException(); _attributeNames.add(qName); _attributeValues.add(value); } /** * Complete printing of the attributes when the open tag completes. */ public boolean finishAttributes() throws IOException { if (_currentElement == null) return false; for (int i = 0; i < _attributeNames.size(); i++) { String qName = _attributeNames.get(i); String value = _attributeValues.get(i); if (_isHtml && _booleanAttrs.get(qName.toLowerCase(Locale.ENGLISH)) != null && (value == null || value.equals("") || value.equals(qName))) { print(' '); print(qName); } else { print(' '); print(qName); if (value != null) { print("=\""); if (! _escapeText || _inVerbatim) print(value); /* else if (isHtml && isURIAttribute(currentElement, qName)) { int len = value.length(); int offset = 0; while (len > abuf.length) { value.getChars(offset, offset + abuf.length, abuf, 0); entities.printURIAttr(this, abuf, 0, abuf.length); len -= abuf.length; offset += abuf.length; } value.getChars(offset, offset + len, abuf, 0); entities.printURIAttr(this, abuf, 0, len); } */ else { int len = value.length(); int offset = 0; while (len > _abuf.length) { value.getChars(offset, offset + _abuf.length, _abuf, 0); _entities.printText(this, _abuf, 0, _abuf.length, true); len -= _abuf.length; offset += _abuf.length; } value.getChars(offset, offset + len, _abuf, 0); _entities.printText(this, _abuf, 0, len, true); } print('\"'); } else if (! _isHtml) { print("=\"\""); } } } if (_prefixList != null && _prefixList.size() > 0) { for (int i = 0; i < _prefixList.size(); i++) { String prefix = _prefixList.get(i); String url = _namespace.get(prefix); if (prefix.equals("")) { print(" xmlns=\""); print(url); print('\"'); } else if (prefix.startsWith("xmlns")) { print(" "); print(prefix); print("=\""); print(url); print('\"'); } else { print(" xmlns:"); print(prefix); print("=\""); print(url); print('\"'); } } _prefixList.clear(); _namespace.clear(); } _currentElement = null; // lastTextChar = NULL_SPACE; return true; } /** * Prints the end tag of an element * * @param uri the namespace uri of the element * @param localName the localname of the element tag * @param qName qualified name of the element */ public void endElement(String uri, String localName, String qName) throws IOException { if (_isText) return; String normalName = _isHtml ? qName.toLowerCase(Locale.ENGLISH) : qName; if (_isHtml && _verbatimTags.get(normalName) != null) _inVerbatim = false; int prevTextChar = _lastTextChar; boolean isEmpty = _currentElement != null; if (_currentElement != null) finishAttributes(); if (! _isHtml || _hasMetaContentType) { } else if (normalName.equals("head")) { if (isEmpty) print(">"); isEmpty = false; printMetaContentType(); _currentElement = null; } if (isEmpty) { if (_isHtml && _empties.get(normalName) >= 0) print(">"); else if (prevTextChar <= OMITTED) { print(">"); printPrettyEnd(qName); print("</"); print(qName); print(">"); return; } else if (_isHtml) { print("></"); print(qName); print(">"); } else { print("/>"); } if (_isPretty) closePretty(qName); } else if (_isHtml && _empties.get(normalName) >= 0 && ! normalName.equals("p")) { if (_isPretty) closePretty(qName); } else if (_isPretty) { printPrettyEnd(qName); print("</"); print(qName); print(">"); } else { print("</"); print(qName); print(">"); } _currentElement = null; } /** * Handle pretty printing at an end tag. */ private void printPrettyEnd(String qName) throws IOException { int code = _isHtml ? _prettyMap.get(qName.toLowerCase(Locale.ENGLISH)) : -1; if (code == PRE) { _preCount--; _lastTextChar = NULL_SPACE; return; } else if (_preCount > 0) { return; } else if (code == NO_PRETTY) { if (_lastTextChar <= OMITTED) println(); _lastTextChar = 'a'; // indent -= 2; return; } else if (code == INLINE) { _lastTextChar = NULL_SPACE; return; } if (! _isHtml || code < 0) { _indent -= 2; } if (_lastTextChar <= WHITESPACE) { if (_lastTextChar != NEWLINE) println(); for (int i = 0; i < _indent; i++) print(' '); } _lastTextChar = NULL_SPACE; } /** * Handle the pretty printing after the closing of a tag. */ private void closePretty(String qName) { int code = _isHtml ? _prettyMap.get(qName.toLowerCase(Locale.ENGLISH)) : -1; if (code == PRE) { _preCount--; _lastTextChar = NULL_SPACE; return; } if (_preCount > 0) return; if (! _isHtml || code < 0) { _indent -= 2; } if (code != NO_PRETTY) _lastTextChar = NULL_SPACE; else _lastTextChar = 'a'; } /** * Prints a processing instruction * * @param name the name of the processing instruction * @param data the processing instruction data */ public void processingInstruction(String name, String data) throws IOException { if (_isText) return; if (_currentElement != null) completeOpenTag(); if (_isTop && ! _isHtml && ! _isAutomaticMethod) { printHeader(null); _isTop = false; } print("<?"); print(name); if (data != null && data.length() > 0) { print(" "); print(data); } if (isHtml()) print(">"); else print("?>"); _lastTextChar = NULL_SPACE; } /** * Prints a comment * * @param data the comment data */ public void comment(String data) throws IOException { if (_isText) return; int textChar = _lastTextChar; if (_currentElement != null) completeOpenTag(); if (_isPretty && _preCount <= 0 && (textChar == OMITTED_NEWLINE || textChar == NULL_SPACE)) { println(); for (int i = 0; i < _indent; i++) print(' '); } print("<!--"); print(data); print("-->"); _lastTextChar = NULL_SPACE; } /** * Returns true if the text is currently being escaped */ public boolean getEscapeText() { return _escapeText; } /** * Sets true if the text should be escaped, else it will be printed * verbatim. */ public void setEscapeText(boolean isEscaped) { _escapeText = isEscaped; } /** * Prints text. If the text is escaped, codes like < will be printed as * &lt;. */ public void text(String text) throws IOException { int length = text.length(); for (int offset = 0; offset < length; offset += _cbuf.length) { int sublen = length - offset; if (sublen > _cbuf.length) sublen = _cbuf.length; text.getChars(offset, offset + sublen, _cbuf, 0); text(_cbuf, 0, sublen); } } /** * Prints text. If the text is escaped, codes like < will be printed as * &lt;. */ public void text(char []buffer, int offset, int length) throws IOException { if (length == 0) return; int prevTextChar = _lastTextChar; if ((_isPretty && _preCount <= 0 || _isTop) && ! _isText && trimPrettyWhitespace(buffer, offset, length)) { if (prevTextChar <= WHITESPACE) return; if (_lastTextChar == OMITTED_SPACE) _lastTextChar = SPACE; if (_lastTextChar == OMITTED_NEWLINE) _lastTextChar = NEWLINE; } int nextTextChar = _lastTextChar; if (_currentElement != null) { completeOpenTag(); if (_isPretty && _preCount <= 0 && prevTextChar <= OMITTED) println(); } _lastTextChar = nextTextChar; if (! _isTop) { } else if (! _isJsp) { _isTop = false; } else if (_isAutomaticMethod) { } else if (! _isHtml) { printHeader(null); _isTop = false; } if (_isHtml && ! _hasMetaContentType && ! _inHead) { int textChar = _lastTextChar; if (_isPretty && _preCount <= 0 && prevTextChar <= OMITTED) println(); // printHeadContentType(); _lastTextChar = textChar; prevTextChar = 'a'; } if (! _isPretty || _preCount > 0) { } else if (prevTextChar == OMITTED_NEWLINE) { if (buffer[offset] != '\n') println(); } else if (prevTextChar == OMITTED_SPACE) { char ch = buffer[offset]; if (ch != ' ' && ch != '\n') print(' '); } if (_lineMap == null) { } else if (_locator != null) { _lineMap.add(_locator.getFilename(), _locator.getLineNumber(), _line); } else if (_srcFilename != null) _lineMap.add(_srcFilename, _srcLine, _line); if (! _escapeText || _inVerbatim || _entities == null) print(buffer, offset, length); else _entities.printText(this, buffer, offset, length, false); } /** * If the text is completely whitespace, skip it. */ boolean trimPrettyWhitespace(char []buffer, int offset, int length) { char textChar = 'a'; int i = length - 1; for (; i >= 0; i--) { char ch = buffer[offset + i]; if (ch == '\r' || ch == '\n') { if (textChar != NEWLINE) textChar = OMITTED_NEWLINE; } else if (ch == ' ' || ch == '\t') { if (textChar == 'a' || textChar == NULL_SPACE) textChar = OMITTED_SPACE; } else if (textChar == OMITTED_NEWLINE) { textChar = NEWLINE; break; } else if (textChar == OMITTED_SPACE) { textChar = SPACE; break; } else break; } _lastTextChar = textChar; return (i < 0 && textChar <= WHITESPACE); } public void cdata(String text) throws IOException { if (text.length() == 0) return; _isTop = false; if (_currentElement != null) completeOpenTag(); if (_lineMap != null && _srcFilename != null) _lineMap.add(_srcFilename, _srcLine, _line); print("<![CDATA["); print(text); print("]]>"); _lastTextChar = NEWLINE; } private void completeOpenTag() throws IOException { boolean isHead = (_isHtml && ! _hasMetaContentType && _currentElement.equalsIgnoreCase("head")); finishAttributes(); print(">"); if (isHead) printHeadContentType(); } public void cdata(char []buffer, int offset, int length) throws IOException { cdata(new String(buffer, offset, length)); } private void printHeadContentType() throws IOException { printMetaContentType(); } private void printMetaContentType() throws IOException { if (! _includeContentType) return; _hasMetaContentType = true; if (_lastTextChar != NEWLINE) println(); if (_encoding == null || _encoding.equals("US-ASCII")) _encoding = "ISO-8859-1"; String mimeType = _mimeType; if (mimeType == null) mimeType = "text/html"; println(" <meta http-equiv=\"Content-Type\" content=\"" + mimeType + "; charset=" + _encoding + "\">"); _lastTextChar = NEWLINE; } void printDecl(String text) throws IOException { for (int i = 0; i < text.length(); i++) { char ch = text.charAt(i); switch (ch) { case '&': if (i + 1 < text.length() && text.charAt(i + 1) == '#') print("&"); else print(ch); break; case '"': print("""); break; case '\'': print("'"); break; case '\n': print("\n"); break; default: print(ch); } } } /** * Prints a newline to the output stream. */ void println() throws IOException { print('\n'); } void println(String text) throws IOException { print(text); println(); } /** * Prints a char buffer. */ void print(char []buf) throws IOException { print(buf, 0, buf.length); } /** * Prints a char buffer. */ void print(char []buf, int off, int len) throws IOException { for (int i = 0; i < len; i++) print(buf[off + i]); } /** * Prints a chunk of text. */ void print(String text) throws IOException { int len = text.length(); for (int i = 0; i < len; i++) { char ch = text.charAt(i); print(ch); } } /** * Prints a character. */ void print(char ch) throws IOException { if (_capacity <= _length) { _os.print(_buffer, 0, _length); _length = 0; } _buffer[_length++] = ch; if (ch == '\n') _line++; } /** * Prints an integer to the output stream. */ void print(int i) throws IOException { if (i < 0) { } else if (i < 10) { print((char) ('0' + i)); return; } else if (i < 100) { print((char) ('0' + i / 10)); print((char) ('0' + i % 10)); return; } if (_length >= 0) { _os.print(_buffer, 0, _length); _length = 0; } _os.print(i); } private void flush() throws IOException { if (_length >= 0) { _os.print(_buffer, 0, _length); _length = 0; } if (_isEnclosedStream) _os.flush(); } private void close() throws IOException { flush(); if (_isEnclosedStream) _os.close(); } static void add(IntMap map, String name, int value) { map.put(name, value); map.put(name.toUpperCase(Locale.ENGLISH), value); } static void add(HashMap<String,String> map, String name) { map.put(name, name); map.put(name.toUpperCase(Locale.ENGLISH), name); } static { _empties = new IntMap(); add(_empties, "basefont", ALWAYS_EMPTY); add(_empties, "br", ALWAYS_EMPTY); add(_empties, "area", ALWAYS_EMPTY); add(_empties, "link", ALWAYS_EMPTY); add(_empties, "img", ALWAYS_EMPTY); add(_empties, "param", ALWAYS_EMPTY); add(_empties, "hr", ALWAYS_EMPTY); add(_empties, "input", ALWAYS_EMPTY); add(_empties, "col", ALWAYS_EMPTY); add(_empties, "frame", ALWAYS_EMPTY); add(_empties, "isindex", ALWAYS_EMPTY); add(_empties, "base", ALWAYS_EMPTY); add(_empties, "meta", ALWAYS_EMPTY); add(_empties, "p", ALWAYS_EMPTY); add(_empties, "li", ALWAYS_EMPTY); add(_empties, "option", EMPTY_IF_EMPTY); _booleanAttrs = new HashMap<String,String>(); // input add(_booleanAttrs, "checked"); // dir, menu, dl, ol, ul add(_booleanAttrs, "compact"); // object add(_booleanAttrs, "declare"); // script add(_booleanAttrs, "defer"); // button, input, optgroup, option, select, textarea add(_booleanAttrs, "disabled"); // img add(_booleanAttrs, "ismap"); // select add(_booleanAttrs, "multiple"); // area add(_booleanAttrs, "nohref"); // frame add(_booleanAttrs, "noresize"); // hr add(_booleanAttrs, "noshade"); // td, th add(_booleanAttrs, "nowrap"); // textarea, input add(_booleanAttrs, "readonly"); // option add(_booleanAttrs, "selected"); _prettyMap = new IntMap(); // next two break browsers add(_prettyMap, "img", NO_PRETTY); add(_prettyMap, "a", NO_PRETTY); add(_prettyMap, "embed", NO_PRETTY); add(_prettyMap, "th", NO_PRETTY); add(_prettyMap, "td", NO_PRETTY); // inline tags look better without the indent add(_prettyMap, "tt", INLINE); add(_prettyMap, "i", INLINE); add(_prettyMap, "b", INLINE); add(_prettyMap, "big", INLINE); add(_prettyMap, "em", INLINE); add(_prettyMap, "string", INLINE); add(_prettyMap, "dfn", INLINE); add(_prettyMap, "code", INLINE); add(_prettyMap, "samp", INLINE); add(_prettyMap, "kbd", INLINE); add(_prettyMap, "var", INLINE); add(_prettyMap, "cite", INLINE); add(_prettyMap, "abbr", INLINE); add(_prettyMap, "acronym", INLINE); add(_prettyMap, "object", INLINE); add(_prettyMap, "q", INLINE); add(_prettyMap, "sub", INLINE); add(_prettyMap, "sup", INLINE); add(_prettyMap, "font", INLINE); add(_prettyMap, "small", INLINE); add(_prettyMap, "span", INLINE); add(_prettyMap, "bdo", INLINE); add(_prettyMap, "jsp:expression", INLINE); add(_prettyMap, "textarea", PRE); add(_prettyMap, "pre", PRE); add(_prettyMap, "html", NO_INDENT); add(_prettyMap, "body", NO_INDENT); add(_prettyMap, "ul", NO_INDENT); add(_prettyMap, "table", NO_INDENT); add(_prettyMap, "frameset", NO_INDENT); _verbatimTags = new HashMap<String,String>(); add(_verbatimTags, "script"); add(_verbatimTags, "style"); } }