0001: /*
0002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
0003: *
0004: * This file is part of Resin(R) Open Source
0005: *
0006: * Each copy or derived work must preserve the copyright notice and this
0007: * notice unmodified.
0008: *
0009: * Resin Open Source is free software; you can redistribute it and/or modify
0010: * it under the terms of the GNU General Public License as published by
0011: * the Free Software Foundation; either version 2 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * Resin Open Source is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
0017: * of NON-INFRINGEMENT. See the GNU General Public License for more
0018: * details.
0019: *
0020: * You should have received a copy of the GNU General Public License
0021: * along with Resin Open Source; if not, write to the
0022: * Free SoftwareFoundation, Inc.
0023: * 59 Temple Place, Suite 330
0024: * Boston, MA 02111-1307 USA
0025: *
0026: * @author Scott Ferguson
0027: */
0028:
0029: package com.caucho.xml;
0030:
0031: import com.caucho.java.LineMap;
0032: import com.caucho.log.Log;
0033: import com.caucho.util.CharBuffer;
0034: import com.caucho.util.IntMap;
0035: import com.caucho.util.L10N;
0036: import com.caucho.vfs.EnclosedWriteStream;
0037: import com.caucho.vfs.Path;
0038: import com.caucho.vfs.Vfs;
0039: import com.caucho.vfs.WriteStream;
0040:
0041: import org.w3c.dom.*;
0042: import org.xml.sax.Locator;
0043:
0044: import java.io.IOException;
0045: import java.io.OutputStream;
0046: import java.io.UnsupportedEncodingException;
0047: import java.io.Writer;
0048: import java.util.ArrayList;
0049: import java.util.HashMap;
0050: import java.util.logging.Logger;
0051:
0052: /**
0053: * Controls printing of XML documents.
0054: *
0055: * Typical use:
0056: * <code><pre>
0057: * Node node = ...;
0058: *
0059: * OutputStream os = Vfs.openWrite("test.xml");
0060: * XmlPrinter printer = new XmlPrinter(os);
0061: *
0062: * printer.printXml(node);
0063: * </pre></code>
0064: */
0065: public class XmlPrinter implements XMLWriter {
0066: static final Logger log = Log.open(XmlPrinter.class);
0067: static final L10N L = new L10N(XmlPrinter.class);
0068:
0069: private static final int NO_PRETTY = 0;
0070: private static final int INLINE = 1;
0071: private static final int NO_INDENT = 2;
0072: private static final int PRE = 3;
0073:
0074: private static final char OMITTED_SPACE = 0;
0075: private static final char OMITTED_NEWLINE = 1;
0076: private static final char OMITTED = 2;
0077: private static final char NULL_SPACE = 3;
0078: private static final char SPACE = 4;
0079: private static final char NEWLINE = 5;
0080: private static final char WHITESPACE = 6;
0081:
0082: private static final int ALWAYS_EMPTY = 1;
0083: private static final int EMPTY_IF_EMPTY = 2;
0084:
0085: private static IntMap _empties;
0086: private static HashMap<String, String> _booleanAttrs;
0087: private static HashMap<String, String> _verbatimTags;
0088: private static IntMap _prettyMap;
0089:
0090: private WriteStream _os;
0091: private char[] _buffer = new char[256];
0092: private int _capacity = _buffer.length;
0093: private int _length;
0094:
0095: boolean _isAutomaticMethod = true;
0096: boolean _isTop = true;
0097: boolean _isJsp = false;
0098: String _encoding;
0099: String _method;
0100: boolean _isText;
0101: boolean _isHtml;
0102: boolean _inHead;
0103: String _version;
0104:
0105: boolean _isAutomaticPretty = true;
0106: boolean _isPretty;
0107: int _indent;
0108: int _preCount;
0109: int _lastTextChar = NULL_SPACE;
0110: boolean _hasMetaContentType = false;
0111: boolean _includeContentType = true;
0112:
0113: boolean _printDeclaration;
0114:
0115: String _standalone;
0116: String _systemId;
0117: String _publicId;
0118: private ExtendedLocator _locator;
0119:
0120: boolean _escapeText = true;
0121: boolean _inVerbatim = false;
0122:
0123: private HashMap<String, String> _namespace;
0124: private HashMap<String, String> _cdataElements;
0125: private Entities _entities;
0126: private String _mimeType;
0127:
0128: private ArrayList<String> _prefixList;
0129:
0130: private ArrayList<String> _attributeNames = new ArrayList<String>();
0131: private ArrayList<String> _attributeValues = new ArrayList<String>();
0132:
0133: private char[] _cbuf = new char[256];
0134: private char[] _abuf = new char[256];
0135:
0136: private LineMap _lineMap;
0137: private int _line;
0138: private String _srcFilename;
0139: private int _srcLine;
0140:
0141: private String _currentElement;
0142: private Document _currentDocument;
0143:
0144: private boolean _isEnclosedStream;
0145:
0146: /**
0147: * Create an XmlPrinter. Using this API, you'll need to use
0148: * printer.init(os) to assign an output stream.
0149: */
0150: public XmlPrinter() {
0151: }
0152:
0153: /**
0154: * Creates a new XmlPrinter writing to an output stream.
0155: *
0156: * @param os output stream serving as the destination
0157: */
0158: public XmlPrinter(OutputStream os) {
0159: if (os instanceof WriteStream)
0160: init((WriteStream) os);
0161: else if (os instanceof EnclosedWriteStream)
0162: init(((EnclosedWriteStream) os).getWriteStream());
0163: else {
0164: _isEnclosedStream = true;
0165: WriteStream ws = Vfs.openWrite(os);
0166: try {
0167: ws.setEncoding("UTF-8");
0168: } catch (UnsupportedEncodingException e) {
0169: }
0170: init(ws);
0171: }
0172: }
0173:
0174: /**
0175: * Creates a new XmlPrinter writing to a writer.
0176: *
0177: * @param writer destination of the serialized node
0178: */
0179: public XmlPrinter(Writer writer) {
0180: if (writer instanceof EnclosedWriteStream)
0181: init(((EnclosedWriteStream) writer).getWriteStream());
0182: else {
0183: _isEnclosedStream = true;
0184: WriteStream ws = Vfs.openWrite(writer);
0185: init(ws);
0186: }
0187: }
0188:
0189: /**
0190: * Initialize the XmlPrinter with the write stream.
0191: *
0192: * @param os WriteStream containing the results.
0193: */
0194: public void init(WriteStream os) {
0195: _os = os;
0196: init();
0197: }
0198:
0199: /**
0200: * Initialize the XmlWriter in preparation for serializing a new XML.
0201: */
0202: void init() {
0203: String encoding = null;
0204:
0205: if (_os != null)
0206: encoding = _os.getEncoding();
0207:
0208: _length = 0;
0209:
0210: if (encoding == null || encoding.equals("US-ASCII")
0211: || encoding.equals("ISO-8859-1"))
0212: _entities = XmlLatin1Entities.create();
0213: else
0214: _entities = XmlEntities.create();
0215: _encoding = encoding;
0216: _namespace = new HashMap<String, String>();
0217: _line = 1;
0218: _isTop = true;
0219: _hasMetaContentType = false;
0220: _attributeNames.clear();
0221: _attributeValues.clear();
0222: }
0223:
0224: /**
0225: * Prints the node as XML. The destination stream has already been
0226: * set using init() or in the constructor.
0227: *
0228: * @param node source DOM node
0229: */
0230: public static void print(Path path, Node node) throws IOException {
0231: WriteStream os = path.openWrite();
0232:
0233: try {
0234: new XmlPrinter(os).printXml(node);
0235: } finally {
0236: os.close();
0237: }
0238: }
0239:
0240: /**
0241: * Prints the node as XML. The destination stream has already been
0242: * set using init() or in the constructor.
0243: *
0244: * @param node source DOM node
0245: */
0246: public void printXml(Node node) throws IOException {
0247: _isAutomaticMethod = false;
0248:
0249: ((QAbstractNode) node).print(this );
0250:
0251: flush();
0252: }
0253:
0254: /**
0255: * Prints the node and children as HTML
0256: *
0257: * @param node the top node to print
0258: */
0259: public void printHtml(Node node) throws IOException {
0260: setMethod("html");
0261: setVersion("4.0");
0262:
0263: ((QAbstractNode) node).print(this );
0264:
0265: flush();
0266: }
0267:
0268: /**
0269: * Prints the node and children as XML, automatically indending
0270: *
0271: * @param node the top node to print
0272: */
0273: public void printPrettyXml(Node node) throws IOException {
0274: _isAutomaticMethod = false;
0275: setPretty(true);
0276:
0277: ((QAbstractNode) node).print(this );
0278:
0279: flush();
0280: }
0281:
0282: /**
0283: * Prints the node as XML to a string.
0284: *
0285: * @param node the source node
0286: * @return a string containing the XML.
0287: */
0288: public String printString(Node node) throws IOException {
0289: CharBuffer cb = CharBuffer.allocate();
0290:
0291: _os = Vfs.openWrite(cb);
0292: init(_os);
0293: try {
0294: ((QAbstractNode) node).print(this );
0295: } finally {
0296: flush();
0297: _os.close();
0298: }
0299:
0300: return cb.close();
0301: }
0302:
0303: /**
0304: * Sets to true if XML entities like < should be escaped as &lt;.
0305: * The default is true.
0306: *
0307: * @param escapeText set to true if entities should be escaped.
0308: */
0309: public void setEscaping(boolean escapeText) {
0310: if (!_isText)
0311: _escapeText = escapeText;
0312: }
0313:
0314: /**
0315: * Returns the current XML escaping. If true, entities like <
0316: * will be escaped as &lt;.
0317: *
0318: * @return true if entities should be escaped.
0319: */
0320: public boolean getEscaping() {
0321: return _escapeText;
0322: }
0323:
0324: /**
0325: * Sets the output methods, like the XSL <xsl:output method='method'/>.
0326: */
0327: public void setMethod(String method) {
0328: _method = method;
0329:
0330: if (method == null) {
0331: _isAutomaticMethod = true;
0332: _isHtml = false;
0333: } else if (method.equals("html")) {
0334: _isAutomaticMethod = false;
0335: _isHtml = true;
0336: if (_isAutomaticPretty)
0337: _isPretty = true;
0338: } else if (method.equals("text")) {
0339: _isAutomaticMethod = false;
0340: _isText = true;
0341: _escapeText = false;
0342: } else {
0343: _isAutomaticMethod = false;
0344: _isHtml = false;
0345: }
0346: }
0347:
0348: /**
0349: * Sets the XML/HTML version of the output file.
0350: *
0351: * @param version the output version
0352: */
0353: public void setVersion(String version) {
0354: _version = version;
0355: }
0356:
0357: /**
0358: * Sets the character set encoding for the output file.
0359: *
0360: * @param encoding the mime name of the output encoding
0361: */
0362: public void setEncoding(String encoding) {
0363: _encoding = encoding;
0364: try {
0365: if (encoding != null) {
0366: _os.setEncoding(encoding);
0367:
0368: if (encoding.equals("US-ASCII")
0369: || encoding.equals("ISO-8859-1"))
0370: _entities = XmlLatin1Entities.create();
0371: else
0372: _entities = XmlEntities.create();
0373: }
0374: } catch (Exception e) {
0375: }
0376: }
0377:
0378: public void setMimeType(String mimeType) {
0379: _mimeType = mimeType;
0380: if (_method == null && mimeType != null
0381: && mimeType.equals("text/html"))
0382: setMethod("html");
0383: }
0384:
0385: /**
0386: * Set true if this is JSP special cased.
0387: */
0388: public void setJSP(boolean isJsp) {
0389: _isJsp = isJsp;
0390: }
0391:
0392: /**
0393: * True if this is JSP special cased.
0394: */
0395: public boolean isJSP() {
0396: return _isJsp;
0397: }
0398:
0399: /**
0400: * Returns true if the printer is printing HTML.
0401: */
0402: boolean isHtml() {
0403: return _isHtml;
0404: }
0405:
0406: /**
0407: * Set to true if the printer should add whitespace to 'pretty-print'
0408: * the output.
0409: *
0410: * @param isPretty if true, add spaces for printing
0411: */
0412: public void setPretty(boolean isPretty) {
0413: _isPretty = isPretty;
0414: _isAutomaticPretty = false;
0415: }
0416:
0417: /**
0418: * Returns true if the printer is currently pretty-printing the output.
0419: */
0420: public boolean isPretty() {
0421: return _isPretty;
0422: }
0423:
0424: public void setPrintDeclaration(boolean printDeclaration) {
0425: _printDeclaration = printDeclaration;
0426: }
0427:
0428: boolean getPrintDeclaration() {
0429: return _printDeclaration;
0430: }
0431:
0432: public void setStandalone(String standalone) {
0433: _standalone = standalone;
0434: }
0435:
0436: String getStandalone() {
0437: return _standalone;
0438: }
0439:
0440: public void setSystemId(String id) {
0441: _systemId = id;
0442: }
0443:
0444: String getSystemId() {
0445: return _systemId;
0446: }
0447:
0448: /**
0449: * Set true if the printer should automatically add the
0450: * <meta content-type> to HTML.
0451: */
0452: public void setIncludeContentType(boolean include) {
0453: _includeContentType = include;
0454: }
0455:
0456: /**
0457: * Return true if the printer should automatically add the
0458: * <meta content-type> to HTML.
0459: */
0460: public boolean getIncludeContentType() {
0461: return _includeContentType;
0462: }
0463:
0464: public void setPublicId(String id) {
0465: _publicId = id;
0466: }
0467:
0468: String getPublicId() {
0469: return _publicId;
0470: }
0471:
0472: public Path getPath() {
0473: if (_os instanceof WriteStream)
0474: return ((WriteStream) _os).getPath();
0475: else
0476: return null;
0477: }
0478:
0479: /**
0480: * Creates a new line map.
0481: */
0482: public void setLineMap(String filename) {
0483: _lineMap = new LineMap(filename);
0484: }
0485:
0486: public LineMap getLineMap() {
0487: return _lineMap;
0488: }
0489:
0490: public void addCdataElement(String elt) {
0491: if (_cdataElements == null)
0492: _cdataElements = new HashMap<String, String>();
0493: _cdataElements.put(elt, "");
0494: }
0495:
0496: public void print(Node node) throws IOException {
0497: if (node instanceof QAbstractNode)
0498: ((QAbstractNode) node).print(this );
0499: else {
0500: printNode(node);
0501: }
0502:
0503: if (_isEnclosedStream)
0504: _os.flush();
0505: }
0506:
0507: public void printNode(Node node) throws IOException {
0508: if (node == null)
0509: return;
0510:
0511: switch (node.getNodeType()) {
0512: case Node.DOCUMENT_NODE:
0513: startDocument((Document) node);
0514: for (Node child = node.getFirstChild(); child != null; child = child
0515: .getNextSibling())
0516: printNode(child);
0517: endDocument();
0518: break;
0519:
0520: case Node.ELEMENT_NODE: {
0521: Element elt = (Element) node;
0522:
0523: startElement(elt.getNamespaceURI(), elt.getLocalName(), elt
0524: .getNodeName());
0525:
0526: NamedNodeMap attrs = elt.getAttributes();
0527: int len = attrs.getLength();
0528: for (int i = 0; i < len; i++) {
0529: Attr attr = (Attr) attrs.item(i);
0530:
0531: attribute(attr.getNamespaceURI(), attr.getLocalName(),
0532: attr.getNodeName(), attr.getNodeValue());
0533: }
0534:
0535: for (Node child = node.getFirstChild(); child != null; child = child
0536: .getNextSibling()) {
0537: printNode(child);
0538: }
0539: endElement(elt.getNamespaceURI(), elt.getLocalName(), elt
0540: .getNodeName());
0541: break;
0542: }
0543:
0544: case Node.TEXT_NODE:
0545: case Node.CDATA_SECTION_NODE: {
0546: CharacterData text = (CharacterData) node;
0547: text(text.getData());
0548: break;
0549: }
0550:
0551: case Node.COMMENT_NODE: {
0552: Comment comment = (Comment) node;
0553: comment(comment.getData());
0554: break;
0555: }
0556:
0557: case Node.PROCESSING_INSTRUCTION_NODE: {
0558: ProcessingInstruction pi = (ProcessingInstruction) node;
0559: processingInstruction(pi.getNodeName(), pi.getData());
0560: break;
0561: }
0562: }
0563: }
0564:
0565: WriteStream getStream() {
0566: return _os;
0567: }
0568:
0569: public void startDocument(Document document) throws IOException {
0570: _currentDocument = document;
0571:
0572: startDocument();
0573: }
0574:
0575: /**
0576: * Callback when the document starts printing.
0577: */
0578: public void startDocument() throws IOException {
0579: _isTop = true;
0580: }
0581:
0582: /**
0583: * Callback when the document completes
0584: */
0585: public void endDocument() throws IOException {
0586: if (_isPretty && _lastTextChar < SPACE)
0587: println();
0588:
0589: flush();
0590: }
0591:
0592: /**
0593: * Sets the locator.
0594: */
0595: public void setDocumentLocator(Locator locator) {
0596: _locator = (ExtendedLocator) locator;
0597: }
0598:
0599: /**
0600: * Sets the current location.
0601: *
0602: * @param filename the source filename
0603: * @param line the source line
0604: * @param column the source column
0605: */
0606: public void setLocation(String filename, int line, int column) {
0607: _srcFilename = filename;
0608: _srcLine = line;
0609: }
0610:
0611: /**
0612: * Called at the start of a new element.
0613: *
0614: * @param url the namespace url
0615: * @param localName the local name
0616: * @param qName the qualified name
0617: */
0618: public void startElement(String url, String localName, String qName)
0619: throws IOException {
0620: if (_isText)
0621: return;
0622:
0623: if (_isAutomaticMethod) {
0624: _isHtml = (qName.equalsIgnoreCase("html") && (url == null || url
0625: .equals("")));
0626:
0627: if (_isAutomaticPretty)
0628: _isPretty = _isHtml;
0629:
0630: _isAutomaticMethod = false;
0631: }
0632:
0633: if (_isTop)
0634: printHeader(qName);
0635:
0636: if (_currentElement != null)
0637: completeOpenTag();
0638:
0639: _attributeNames.clear();
0640: _attributeValues.clear();
0641:
0642: if (_isHtml && _verbatimTags.get(qName.toLowerCase()) != null)
0643: _inVerbatim = true;
0644:
0645: if (_isPretty && _preCount <= 0)
0646: printPrettyStart(qName);
0647:
0648: if (_lineMap == null) {
0649: } else if (_locator != null) {
0650: _lineMap.add(_locator.getFilename(), _locator
0651: .getLineNumber(), _line);
0652: } else if (_srcFilename != null)
0653: _lineMap.add(_srcFilename, _srcLine, _line);
0654:
0655: print('<');
0656: print(qName);
0657: _currentElement = qName;
0658: _lastTextChar = NULL_SPACE;
0659: }
0660:
0661: /**
0662: * Prints the header, if necessary.
0663: *
0664: * @param top name of the top element
0665: */
0666: public void printHeader(String top) throws IOException {
0667: if (!_isTop)
0668: return;
0669:
0670: _isTop = false;
0671:
0672: String encoding = _encoding;
0673:
0674: if (encoding != null && encoding.equalsIgnoreCase("UTF-16"))
0675: print('\ufeff');
0676:
0677: if (_isHtml) {
0678: double dVersion = 4.0;
0679:
0680: if (_version == null || _version.compareTo("4.0") >= 0) {
0681: } else {
0682: dVersion = 3.2;
0683: }
0684:
0685: if (_systemId != null || _publicId != null)
0686: printDoctype("html");
0687:
0688: if (encoding == null
0689: || encoding.equalsIgnoreCase("ISO-8859-1"))
0690: // _entities = Latin1Entities.create(dVersion);
0691: _entities = HtmlEntities.create(dVersion);
0692: else if (encoding.equalsIgnoreCase("US-ASCII"))
0693: _entities = HtmlEntities.create(dVersion);
0694: else
0695: _entities = OtherEntities.create(dVersion);
0696: } else {
0697: if (_printDeclaration) {
0698: String version = _version;
0699:
0700: if (version == null)
0701: version = "1.0";
0702:
0703: print("<?xml version=\"");
0704: print(version);
0705: print("\"");
0706:
0707: if (encoding == null || encoding.equals("")
0708: || encoding.equalsIgnoreCase("US-ASCII")) {
0709: } else
0710: print(" encoding=\"" + encoding + "\"");
0711:
0712: if (_standalone != null
0713: && (_standalone.equals("true") || _standalone
0714: .equals("yes")))
0715: print(" standalone=\"yes\"");
0716:
0717: println("?>");
0718: }
0719:
0720: printDoctype(top);
0721:
0722: if (encoding == null
0723: || encoding.equalsIgnoreCase("US-ASCII")
0724: || encoding.equalsIgnoreCase("ISO-8859-1"))
0725: _entities = XmlLatin1Entities.create();
0726: else
0727: _entities = XmlEntities.create();
0728: }
0729:
0730: _lastTextChar = NEWLINE;
0731: }
0732:
0733: /**
0734: * Prints the doctype declaration
0735: *
0736: * @param topElt name of the top element
0737: */
0738: private void printDoctype(String topElt) throws IOException {
0739: if (_publicId != null && _systemId != null)
0740: println("<!DOCTYPE " + topElt + " PUBLIC \"" + _publicId
0741: + "\" \"" + _systemId + "\">");
0742: else if (_publicId != null)
0743: println("<!DOCTYPE " + topElt + " PUBLIC \"" + _publicId
0744: + "\">");
0745: else if (_systemId != null)
0746: println("<!DOCTYPE " + topElt + " SYSTEM \"" + _systemId
0747: + "\">");
0748: else if (_currentDocument instanceof QDocument) {
0749: QDocumentType dtd = (QDocumentType) _currentDocument
0750: .getDoctype();
0751:
0752: if (dtd != null && dtd.getName() != null
0753: && dtd.getParentNode() == null) {
0754: dtd.print(this );
0755: println();
0756: }
0757: }
0758: }
0759:
0760: public void startPrefixMapping(String prefix, String uri)
0761: throws IOException {
0762: }
0763:
0764: public void endPrefixMapping(String prefix) throws IOException {
0765: }
0766:
0767: /**
0768: * Pretty printing for a start tag.
0769: *
0770: * @param qName the name of the element
0771: */
0772: private void printPrettyStart(String qName) throws IOException {
0773: int code = _isHtml ? _prettyMap.get(qName.toLowerCase()) : -1;
0774:
0775: if (code == NO_PRETTY) {
0776: if (_lastTextChar == OMITTED_NEWLINE)
0777: println();
0778: else if (_lastTextChar == OMITTED_SPACE)
0779: print(' ');
0780: } else if (code != INLINE && _lastTextChar < WHITESPACE) {
0781: if (_lastTextChar != NEWLINE)
0782: println();
0783: for (int i = 0; i < _indent; i++)
0784: print(' ');
0785: } else if (code == INLINE && _lastTextChar < WHITESPACE) {
0786: if (_lastTextChar == OMITTED_NEWLINE)
0787: println();
0788: else if (_lastTextChar == OMITTED_SPACE)
0789: print(' ');
0790: }
0791:
0792: if (!_isHtml || code < 0) {
0793: _indent += 2;
0794: }
0795:
0796: if (code == PRE) {
0797: _preCount++;
0798: _lastTextChar = 'a';
0799: } else if (code == NO_PRETTY || code == INLINE)
0800: _lastTextChar = 'a';
0801: else
0802: _lastTextChar = NULL_SPACE;
0803: }
0804:
0805: /**
0806: * Prints an attribute
0807: *
0808: * @param uri namespace uri
0809: * @param localName localname of the attribute
0810: * @param qName qualified name of the attribute
0811: * @param value value of the attribute.
0812: */
0813: public void attribute(String uri, String localName, String qName,
0814: String value) throws IOException {
0815: if (_isText)
0816: return;
0817:
0818: if (_currentElement != null) {
0819: } else if (qName.equals("encoding")) {
0820: _encoding = value;
0821: return;
0822: } else if (qName.startsWith("xmlns")) {
0823: } else
0824: throw new IOException(L.l(
0825: "attribute `{0}' outside element.", qName));
0826:
0827: qName = qName.intern();
0828:
0829: if (qName.startsWith("xmlns")) {
0830: if (localName == null)
0831: localName = "";
0832:
0833: if (_isHtml && localName.equals("") && value.equals(""))
0834: return;
0835:
0836: _namespace.put(localName, value);
0837: if (_prefixList == null)
0838: _prefixList = new ArrayList<String>();
0839: if (!_prefixList.contains(localName))
0840: _prefixList.add(localName);
0841: return;
0842: } else if (qName.equals("xtp:jsp-attribute")) {
0843: _attributeNames.add("<%= " + value + "%>");
0844: _attributeValues.add(null);
0845: return;
0846: }
0847:
0848: if (_isHtml && !_hasMetaContentType
0849: && _currentElement.equals("meta")
0850: && qName.equalsIgnoreCase("http-equiv")
0851: && value.equalsIgnoreCase("content-type")) {
0852: _hasMetaContentType = true;
0853: }
0854:
0855: for (int i = 0; i < _attributeNames.size(); i++) {
0856: String oldName = _attributeNames.get(i);
0857:
0858: if (oldName == qName) {
0859: _attributeValues.set(i, value);
0860: return;
0861: }
0862: }
0863:
0864: if (qName == null || qName.equals(""))
0865: throw new NullPointerException();
0866:
0867: _attributeNames.add(qName);
0868: _attributeValues.add(value);
0869: }
0870:
0871: /**
0872: * Complete printing of the attributes when the open tag completes.
0873: */
0874: public boolean finishAttributes() throws IOException {
0875: if (_currentElement == null)
0876: return false;
0877:
0878: for (int i = 0; i < _attributeNames.size(); i++) {
0879: String qName = _attributeNames.get(i);
0880: String value = _attributeValues.get(i);
0881:
0882: if (_isHtml
0883: && _booleanAttrs.get(qName.toLowerCase()) != null
0884: && (value == null || value.equals("") || value
0885: .equals(qName))) {
0886: print(' ');
0887: print(qName);
0888: } else {
0889: print(' ');
0890: print(qName);
0891:
0892: if (value != null) {
0893: print("=\"");
0894:
0895: if (!_escapeText || _inVerbatim)
0896: print(value);
0897: /*
0898: else if (isHtml && isURIAttribute(currentElement, qName)) {
0899: int len = value.length();
0900: int offset = 0;
0901:
0902: while (len > abuf.length) {
0903: value.getChars(offset, offset + abuf.length, abuf, 0);
0904: entities.printURIAttr(this, abuf, 0, abuf.length);
0905: len -= abuf.length;
0906: offset += abuf.length;
0907: }
0908:
0909: value.getChars(offset, offset + len, abuf, 0);
0910: entities.printURIAttr(this, abuf, 0, len);
0911: }
0912: */
0913: else {
0914: int len = value.length();
0915: int offset = 0;
0916:
0917: while (len > _abuf.length) {
0918: value.getChars(offset, offset
0919: + _abuf.length, _abuf, 0);
0920: _entities.printText(this , _abuf, 0,
0921: _abuf.length, true);
0922: len -= _abuf.length;
0923: offset += _abuf.length;
0924: }
0925:
0926: value.getChars(offset, offset + len, _abuf, 0);
0927: _entities.printText(this , _abuf, 0, len, true);
0928: }
0929: print('\"');
0930: } else if (!_isHtml) {
0931: print("=\"\"");
0932: }
0933: }
0934: }
0935:
0936: if (_prefixList != null && _prefixList.size() > 0) {
0937: for (int i = 0; i < _prefixList.size(); i++) {
0938: String prefix = _prefixList.get(i);
0939: String url = _namespace.get(prefix);
0940:
0941: if (prefix.equals("")) {
0942: print(" xmlns=\"");
0943: print(url);
0944: print('\"');
0945: } else if (prefix.startsWith("xmlns")) {
0946: print(" ");
0947: print(prefix);
0948: print("=\"");
0949: print(url);
0950: print('\"');
0951: } else {
0952: print(" xmlns:");
0953: print(prefix);
0954: print("=\"");
0955: print(url);
0956: print('\"');
0957: }
0958: }
0959:
0960: _prefixList.clear();
0961: _namespace.clear();
0962: }
0963:
0964: _currentElement = null;
0965: // lastTextChar = NULL_SPACE;
0966:
0967: return true;
0968: }
0969:
0970: /**
0971: * Prints the end tag of an element
0972: *
0973: * @param uri the namespace uri of the element
0974: * @param localName the localname of the element tag
0975: * @param qName qualified name of the element
0976: */
0977: public void endElement(String uri, String localName, String qName)
0978: throws IOException {
0979: if (_isText)
0980: return;
0981:
0982: String normalName = _isHtml ? qName.toLowerCase() : qName;
0983:
0984: if (_isHtml && _verbatimTags.get(normalName) != null)
0985: _inVerbatim = false;
0986:
0987: int prevTextChar = _lastTextChar;
0988: boolean isEmpty = _currentElement != null;
0989: if (_currentElement != null)
0990: finishAttributes();
0991:
0992: if (!_isHtml || _hasMetaContentType) {
0993: } else if (normalName.equals("head")) {
0994: if (isEmpty)
0995: print(">");
0996: isEmpty = false;
0997: printMetaContentType();
0998: _currentElement = null;
0999: }
1000:
1001: if (isEmpty) {
1002: if (_isHtml && _empties.get(normalName) >= 0)
1003: print(">");
1004: else if (prevTextChar <= OMITTED) {
1005: print(">");
1006: printPrettyEnd(qName);
1007: print("</");
1008: print(qName);
1009: print(">");
1010: return;
1011: } else if (_isHtml) {
1012: print("></");
1013: print(qName);
1014: print(">");
1015: } else {
1016: print("/>");
1017: }
1018:
1019: if (_isPretty)
1020: closePretty(qName);
1021: } else if (_isHtml && _empties.get(normalName) >= 0
1022: && !normalName.equals("p")) {
1023: if (_isPretty)
1024: closePretty(qName);
1025: } else if (_isPretty) {
1026: printPrettyEnd(qName);
1027: print("</");
1028: print(qName);
1029: print(">");
1030: } else {
1031: print("</");
1032: print(qName);
1033: print(">");
1034: }
1035:
1036: _currentElement = null;
1037: }
1038:
1039: /**
1040: * Handle pretty printing at an end tag.
1041: */
1042: private void printPrettyEnd(String qName) throws IOException {
1043: int code = _isHtml ? _prettyMap.get(qName.toLowerCase()) : -1;
1044:
1045: if (code == PRE) {
1046: _preCount--;
1047: _lastTextChar = NULL_SPACE;
1048: return;
1049: } else if (_preCount > 0) {
1050: return;
1051: } else if (code == NO_PRETTY) {
1052: if (_lastTextChar <= OMITTED)
1053: println();
1054: _lastTextChar = 'a';
1055: // indent -= 2;
1056: return;
1057: } else if (code == INLINE) {
1058: _lastTextChar = NULL_SPACE;
1059: return;
1060: }
1061:
1062: if (!_isHtml || code < 0) {
1063: _indent -= 2;
1064: }
1065:
1066: if (_lastTextChar <= WHITESPACE) {
1067: if (_lastTextChar != NEWLINE)
1068: println();
1069: for (int i = 0; i < _indent; i++)
1070: print(' ');
1071: }
1072: _lastTextChar = NULL_SPACE;
1073: }
1074:
1075: /**
1076: * Handle the pretty printing after the closing of a tag.
1077: */
1078: private void closePretty(String qName) {
1079: int code = _isHtml ? _prettyMap.get(qName.toLowerCase()) : -1;
1080:
1081: if (code == PRE) {
1082: _preCount--;
1083: _lastTextChar = NULL_SPACE;
1084: return;
1085: }
1086: if (_preCount > 0)
1087: return;
1088:
1089: if (!_isHtml || code < 0) {
1090: _indent -= 2;
1091: }
1092:
1093: if (code != NO_PRETTY)
1094: _lastTextChar = NULL_SPACE;
1095: else
1096: _lastTextChar = 'a';
1097: }
1098:
1099: /**
1100: * Prints a processing instruction
1101: *
1102: * @param name the name of the processing instruction
1103: * @param data the processing instruction data
1104: */
1105: public void processingInstruction(String name, String data)
1106: throws IOException {
1107: if (_isText)
1108: return;
1109:
1110: if (_currentElement != null)
1111: completeOpenTag();
1112:
1113: if (_isTop && !_isHtml && !_isAutomaticMethod) {
1114: printHeader(null);
1115: _isTop = false;
1116: }
1117:
1118: print("<?");
1119: print(name);
1120:
1121: if (data != null && data.length() > 0) {
1122: print(" ");
1123: print(data);
1124: }
1125:
1126: if (isHtml())
1127: print(">");
1128: else
1129: print("?>");
1130:
1131: _lastTextChar = NULL_SPACE;
1132: }
1133:
1134: /**
1135: * Prints a comment
1136: *
1137: * @param data the comment data
1138: */
1139: public void comment(String data) throws IOException {
1140: if (_isText)
1141: return;
1142:
1143: int textChar = _lastTextChar;
1144:
1145: if (_currentElement != null)
1146: completeOpenTag();
1147:
1148: if (_isPretty
1149: && _preCount <= 0
1150: && (textChar == OMITTED_NEWLINE || textChar == NULL_SPACE)) {
1151: println();
1152:
1153: for (int i = 0; i < _indent; i++)
1154: print(' ');
1155: }
1156:
1157: print("<!--");
1158: print(data);
1159: print("-->");
1160:
1161: _lastTextChar = NULL_SPACE;
1162: }
1163:
1164: /**
1165: * Returns true if the text is currently being escaped
1166: */
1167: public boolean getEscapeText() {
1168: return _escapeText;
1169: }
1170:
1171: /**
1172: * Sets true if the text should be escaped, else it will be printed
1173: * verbatim.
1174: */
1175: public void setEscapeText(boolean isEscaped) {
1176: _escapeText = isEscaped;
1177: }
1178:
1179: /**
1180: * Prints text. If the text is escaped, codes like < will be printed as
1181: * &lt;.
1182: */
1183: public void text(String text) throws IOException {
1184: int length = text.length();
1185:
1186: for (int offset = 0; offset < length; offset += _cbuf.length) {
1187: int sublen = length - offset;
1188: if (sublen > _cbuf.length)
1189: sublen = _cbuf.length;
1190:
1191: text.getChars(offset, offset + sublen, _cbuf, 0);
1192: text(_cbuf, 0, sublen);
1193: }
1194: }
1195:
1196: /**
1197: * Prints text. If the text is escaped, codes like < will be printed as
1198: * &lt;.
1199: */
1200: public void text(char[] buffer, int offset, int length)
1201: throws IOException {
1202: if (length == 0)
1203: return;
1204:
1205: int prevTextChar = _lastTextChar;
1206: if ((_isPretty && _preCount <= 0 || _isTop) && !_isText
1207: && trimPrettyWhitespace(buffer, offset, length)) {
1208: if (prevTextChar <= WHITESPACE)
1209: return;
1210: if (_lastTextChar == OMITTED_SPACE)
1211: _lastTextChar = SPACE;
1212: if (_lastTextChar == OMITTED_NEWLINE)
1213: _lastTextChar = NEWLINE;
1214: }
1215:
1216: int nextTextChar = _lastTextChar;
1217: if (_currentElement != null) {
1218: completeOpenTag();
1219:
1220: if (_isPretty && _preCount <= 0 && prevTextChar <= OMITTED)
1221: println();
1222: }
1223:
1224: _lastTextChar = nextTextChar;
1225:
1226: if (!_isTop) {
1227: } else if (!_isJsp) {
1228: _isTop = false;
1229: } else if (_isAutomaticMethod) {
1230: } else if (!_isHtml) {
1231: printHeader(null);
1232: _isTop = false;
1233: }
1234:
1235: if (_isHtml && !_hasMetaContentType && !_inHead) {
1236: int textChar = _lastTextChar;
1237:
1238: if (_isPretty && _preCount <= 0 && prevTextChar <= OMITTED)
1239: println();
1240:
1241: // printHeadContentType();
1242: _lastTextChar = textChar;
1243: prevTextChar = 'a';
1244: }
1245:
1246: if (!_isPretty || _preCount > 0) {
1247: } else if (prevTextChar == OMITTED_NEWLINE) {
1248: if (buffer[offset] != '\n')
1249: println();
1250: } else if (prevTextChar == OMITTED_SPACE) {
1251: char ch = buffer[offset];
1252:
1253: if (ch != ' ' && ch != '\n')
1254: print(' ');
1255: }
1256:
1257: if (_lineMap == null) {
1258: } else if (_locator != null) {
1259: _lineMap.add(_locator.getFilename(), _locator
1260: .getLineNumber(), _line);
1261: } else if (_srcFilename != null)
1262: _lineMap.add(_srcFilename, _srcLine, _line);
1263:
1264: if (!_escapeText || _inVerbatim || _entities == null)
1265: print(buffer, offset, length);
1266: else
1267: _entities.printText(this , buffer, offset, length, false);
1268: }
1269:
1270: /**
1271: * If the text is completely whitespace, skip it.
1272: */
1273: boolean trimPrettyWhitespace(char[] buffer, int offset, int length) {
1274: char textChar = 'a';
1275: int i = length - 1;
1276:
1277: for (; i >= 0; i--) {
1278: char ch = buffer[offset + i];
1279:
1280: if (ch == '\r' || ch == '\n') {
1281: if (textChar != NEWLINE)
1282: textChar = OMITTED_NEWLINE;
1283: } else if (ch == ' ' || ch == '\t') {
1284: if (textChar == 'a' || textChar == NULL_SPACE)
1285: textChar = OMITTED_SPACE;
1286: } else if (textChar == OMITTED_NEWLINE) {
1287: textChar = NEWLINE;
1288: break;
1289: } else if (textChar == OMITTED_SPACE) {
1290: textChar = SPACE;
1291: break;
1292: } else
1293: break;
1294: }
1295:
1296: _lastTextChar = textChar;
1297:
1298: return (i < 0 && textChar <= WHITESPACE);
1299: }
1300:
1301: public void cdata(String text) throws IOException {
1302: if (text.length() == 0)
1303: return;
1304:
1305: _isTop = false;
1306:
1307: if (_currentElement != null)
1308: completeOpenTag();
1309:
1310: if (_lineMap != null && _srcFilename != null)
1311: _lineMap.add(_srcFilename, _srcLine, _line);
1312:
1313: print("<![CDATA[");
1314:
1315: print(text);
1316:
1317: print("]]>");
1318:
1319: _lastTextChar = NEWLINE;
1320: }
1321:
1322: private void completeOpenTag() throws IOException {
1323: boolean isHead = (_isHtml && !_hasMetaContentType && _currentElement
1324: .equalsIgnoreCase("head"));
1325:
1326: finishAttributes();
1327: print(">");
1328:
1329: if (isHead)
1330: printHeadContentType();
1331: }
1332:
1333: public void cdata(char[] buffer, int offset, int length)
1334: throws IOException {
1335: cdata(new String(buffer, offset, length));
1336: }
1337:
1338: private void printHeadContentType() throws IOException {
1339: printMetaContentType();
1340: }
1341:
1342: private void printMetaContentType() throws IOException {
1343: if (!_includeContentType)
1344: return;
1345:
1346: _hasMetaContentType = true;
1347: if (_lastTextChar != NEWLINE)
1348: println();
1349:
1350: if (_encoding == null || _encoding.equals("US-ASCII"))
1351: _encoding = "ISO-8859-1";
1352: String mimeType = _mimeType;
1353: if (mimeType == null)
1354: mimeType = "text/html";
1355:
1356: println(" <meta http-equiv=\"Content-Type\" content=\""
1357: + mimeType + "; charset=" + _encoding + "\">");
1358: _lastTextChar = NEWLINE;
1359: }
1360:
1361: void printDecl(String text) throws IOException {
1362: for (int i = 0; i < text.length(); i++) {
1363: char ch = text.charAt(i);
1364:
1365: switch (ch) {
1366: case '&':
1367: if (i + 1 < text.length() && text.charAt(i + 1) == '#')
1368: print("&");
1369: else
1370: print(ch);
1371: break;
1372:
1373: case '"':
1374: print(""");
1375: break;
1376:
1377: case '\'':
1378: print("'");
1379: break;
1380:
1381: case '\n':
1382: print("\n");
1383: break;
1384:
1385: default:
1386: print(ch);
1387: }
1388: }
1389: }
1390:
1391: /**
1392: * Prints a newline to the output stream.
1393: */
1394: void println() throws IOException {
1395: print('\n');
1396: }
1397:
1398: void println(String text) throws IOException {
1399: print(text);
1400: println();
1401: }
1402:
1403: /**
1404: * Prints a char buffer.
1405: */
1406: void print(char[] buf) throws IOException {
1407: print(buf, 0, buf.length);
1408: }
1409:
1410: /**
1411: * Prints a char buffer.
1412: */
1413: void print(char[] buf, int off, int len) throws IOException {
1414: for (int i = 0; i < len; i++)
1415: print(buf[off + i]);
1416: }
1417:
1418: /**
1419: * Prints a chunk of text.
1420: */
1421: void print(String text) throws IOException {
1422: int len = text.length();
1423:
1424: for (int i = 0; i < len; i++) {
1425: char ch = text.charAt(i);
1426: print(ch);
1427: }
1428: }
1429:
1430: /**
1431: * Prints a character.
1432: */
1433: void print(char ch) throws IOException {
1434: if (_capacity <= _length) {
1435: _os.print(_buffer, 0, _length);
1436: _length = 0;
1437: }
1438:
1439: _buffer[_length++] = ch;
1440: if (ch == '\n')
1441: _line++;
1442: }
1443:
1444: /**
1445: * Prints an integer to the output stream.
1446: */
1447: void print(int i) throws IOException {
1448: if (i < 0) {
1449: } else if (i < 10) {
1450: print((char) ('0' + i));
1451: return;
1452: } else if (i < 100) {
1453: print((char) ('0' + i / 10));
1454: print((char) ('0' + i % 10));
1455: return;
1456: }
1457:
1458: if (_length >= 0) {
1459: _os.print(_buffer, 0, _length);
1460: _length = 0;
1461: }
1462:
1463: _os.print(i);
1464: }
1465:
1466: private void flush() throws IOException {
1467: if (_length >= 0) {
1468: _os.print(_buffer, 0, _length);
1469: _length = 0;
1470: }
1471:
1472: if (_isEnclosedStream)
1473: _os.flush();
1474: }
1475:
1476: private void close() throws IOException {
1477: flush();
1478:
1479: if (_isEnclosedStream)
1480: _os.close();
1481: }
1482:
1483: static void add(IntMap map, String name, int value) {
1484: map.put(name, value);
1485: map.put(name.toUpperCase(), value);
1486: }
1487:
1488: static void add(HashMap<String, String> map, String name) {
1489: map.put(name, name);
1490: map.put(name.toUpperCase(), name);
1491: }
1492:
1493: static {
1494: _empties = new IntMap();
1495: add(_empties, "basefont", ALWAYS_EMPTY);
1496: add(_empties, "br", ALWAYS_EMPTY);
1497: add(_empties, "area", ALWAYS_EMPTY);
1498: add(_empties, "link", ALWAYS_EMPTY);
1499: add(_empties, "img", ALWAYS_EMPTY);
1500: add(_empties, "param", ALWAYS_EMPTY);
1501: add(_empties, "hr", ALWAYS_EMPTY);
1502: add(_empties, "input", ALWAYS_EMPTY);
1503: add(_empties, "col", ALWAYS_EMPTY);
1504: add(_empties, "frame", ALWAYS_EMPTY);
1505: add(_empties, "isindex", ALWAYS_EMPTY);
1506: add(_empties, "base", ALWAYS_EMPTY);
1507: add(_empties, "meta", ALWAYS_EMPTY);
1508:
1509: add(_empties, "p", ALWAYS_EMPTY);
1510: add(_empties, "li", ALWAYS_EMPTY);
1511:
1512: add(_empties, "option", EMPTY_IF_EMPTY);
1513:
1514: _booleanAttrs = new HashMap<String, String>();
1515: // input
1516: add(_booleanAttrs, "checked");
1517: // dir, menu, dl, ol, ul
1518: add(_booleanAttrs, "compact");
1519: // object
1520: add(_booleanAttrs, "declare");
1521: // script
1522: add(_booleanAttrs, "defer");
1523: // button, input, optgroup, option, select, textarea
1524: add(_booleanAttrs, "disabled");
1525: // img
1526: add(_booleanAttrs, "ismap");
1527: // select
1528: add(_booleanAttrs, "multiple");
1529: // area
1530: add(_booleanAttrs, "nohref");
1531: // frame
1532: add(_booleanAttrs, "noresize");
1533: // hr
1534: add(_booleanAttrs, "noshade");
1535: // td, th
1536: add(_booleanAttrs, "nowrap");
1537: // textarea, input
1538: add(_booleanAttrs, "readonly");
1539: // option
1540: add(_booleanAttrs, "selected");
1541:
1542: _prettyMap = new IntMap();
1543: // next two break browsers
1544: add(_prettyMap, "img", NO_PRETTY);
1545: add(_prettyMap, "a", NO_PRETTY);
1546: add(_prettyMap, "embed", NO_PRETTY);
1547: add(_prettyMap, "th", NO_PRETTY);
1548: add(_prettyMap, "td", NO_PRETTY);
1549: // inline tags look better without the indent
1550: add(_prettyMap, "tt", INLINE);
1551: add(_prettyMap, "i", INLINE);
1552: add(_prettyMap, "b", INLINE);
1553: add(_prettyMap, "big", INLINE);
1554: add(_prettyMap, "em", INLINE);
1555: add(_prettyMap, "string", INLINE);
1556: add(_prettyMap, "dfn", INLINE);
1557: add(_prettyMap, "code", INLINE);
1558: add(_prettyMap, "samp", INLINE);
1559: add(_prettyMap, "kbd", INLINE);
1560: add(_prettyMap, "var", INLINE);
1561: add(_prettyMap, "cite", INLINE);
1562: add(_prettyMap, "abbr", INLINE);
1563: add(_prettyMap, "acronym", INLINE);
1564: add(_prettyMap, "object", INLINE);
1565: add(_prettyMap, "q", INLINE);
1566: add(_prettyMap, "sub", INLINE);
1567: add(_prettyMap, "sup", INLINE);
1568: add(_prettyMap, "font", INLINE);
1569: add(_prettyMap, "small", INLINE);
1570: add(_prettyMap, "span", INLINE);
1571: add(_prettyMap, "bdo", INLINE);
1572: add(_prettyMap, "jsp:expression", INLINE);
1573:
1574: add(_prettyMap, "textarea", PRE);
1575: add(_prettyMap, "pre", PRE);
1576:
1577: add(_prettyMap, "html", NO_INDENT);
1578: add(_prettyMap, "body", NO_INDENT);
1579: add(_prettyMap, "ul", NO_INDENT);
1580: add(_prettyMap, "table", NO_INDENT);
1581: add(_prettyMap, "frameset", NO_INDENT);
1582:
1583: _verbatimTags = new HashMap<String, String>();
1584: add(_verbatimTags, "script");
1585: add(_verbatimTags, "style");
1586: }
1587: }
|