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.xml2;
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("UTF-16")
0709: || encoding.equalsIgnoreCase("US-ASCII")) {
0710: } else
0711: print(" encoding=\"" + encoding + "\"");
0712:
0713: if (_standalone != null
0714: && (_standalone.equals("true") || _standalone
0715: .equals("yes")))
0716: print(" standalone=\"yes\"");
0717:
0718: println("?>");
0719: }
0720:
0721: printDoctype(top);
0722:
0723: if (encoding == null
0724: || encoding.equalsIgnoreCase("US-ASCII")
0725: || encoding.equalsIgnoreCase("ISO-8859-1"))
0726: _entities = XmlLatin1Entities.create();
0727: else
0728: _entities = XmlEntities.create();
0729: }
0730:
0731: _lastTextChar = NEWLINE;
0732: }
0733:
0734: /**
0735: * Prints the doctype declaration
0736: *
0737: * @param topElt name of the top element
0738: */
0739: private void printDoctype(String topElt) throws IOException {
0740: if (_publicId != null && _systemId != null)
0741: println("<!DOCTYPE " + topElt + " PUBLIC \"" + _publicId
0742: + "\" \"" + _systemId + "\">");
0743: else if (_publicId != null)
0744: println("<!DOCTYPE " + topElt + " PUBLIC \"" + _publicId
0745: + "\">");
0746: else if (_systemId != null)
0747: println("<!DOCTYPE " + topElt + " SYSTEM \"" + _systemId
0748: + "\">");
0749: else if (_currentDocument instanceof QDocument) {
0750: QDocumentType dtd = (QDocumentType) _currentDocument
0751: .getDoctype();
0752:
0753: if (dtd != null && dtd.getName() != null
0754: && dtd.getParentNode() == null) {
0755: dtd.print(this );
0756: println();
0757: }
0758: }
0759: }
0760:
0761: public void startPrefixMapping(String prefix, String uri)
0762: throws IOException {
0763: }
0764:
0765: public void endPrefixMapping(String prefix) throws IOException {
0766: }
0767:
0768: /**
0769: * Pretty printing for a start tag.
0770: *
0771: * @param qName the name of the element
0772: */
0773: private void printPrettyStart(String qName) throws IOException {
0774: int code = _isHtml ? _prettyMap.get(qName.toLowerCase()) : -1;
0775:
0776: if (code == NO_PRETTY) {
0777: if (_lastTextChar == OMITTED_NEWLINE)
0778: println();
0779: else if (_lastTextChar == OMITTED_SPACE)
0780: print(' ');
0781: } else if (code != INLINE && _lastTextChar < WHITESPACE) {
0782: if (_lastTextChar != NEWLINE)
0783: println();
0784: for (int i = 0; i < _indent; i++)
0785: print(' ');
0786: } else if (code == INLINE && _lastTextChar < WHITESPACE) {
0787: if (_lastTextChar == OMITTED_NEWLINE)
0788: println();
0789: else if (_lastTextChar == OMITTED_SPACE)
0790: print(' ');
0791: }
0792:
0793: if (!_isHtml || code < 0) {
0794: _indent += 2;
0795: }
0796:
0797: if (code == PRE) {
0798: _preCount++;
0799: _lastTextChar = 'a';
0800: } else if (code == NO_PRETTY || code == INLINE)
0801: _lastTextChar = 'a';
0802: else
0803: _lastTextChar = NULL_SPACE;
0804: }
0805:
0806: /**
0807: * Prints an attribute
0808: *
0809: * @param uri namespace uri
0810: * @param localName localname of the attribute
0811: * @param qName qualified name of the attribute
0812: * @param value value of the attribute.
0813: */
0814: public void attribute(String uri, String localName, String qName,
0815: String value) throws IOException {
0816: if (_isText)
0817: return;
0818:
0819: if (_currentElement != null) {
0820: } else if (qName.equals("encoding")) {
0821: _encoding = value;
0822: return;
0823: } else if (qName.startsWith("xmlns")) {
0824: } else
0825: throw new IOException(L.l(
0826: "attribute `{0}' outside element.", qName));
0827:
0828: qName = qName.intern();
0829:
0830: if (qName.startsWith("xmlns")) {
0831: if (localName == null)
0832: localName = "";
0833:
0834: if (_isHtml && localName.equals("") && value.equals(""))
0835: return;
0836:
0837: _namespace.put(localName, value);
0838: if (_prefixList == null)
0839: _prefixList = new ArrayList<String>();
0840: if (!_prefixList.contains(localName))
0841: _prefixList.add(localName);
0842: return;
0843: } else if (qName.equals("xtp:jsp-attribute")) {
0844: _attributeNames.add("<%= " + value + "%>");
0845: _attributeValues.add(null);
0846: return;
0847: }
0848:
0849: if (_isHtml && !_hasMetaContentType
0850: && _currentElement.equals("meta")
0851: && qName.equalsIgnoreCase("http-equiv")
0852: && value.equalsIgnoreCase("content-type")) {
0853: _hasMetaContentType = true;
0854: }
0855:
0856: for (int i = 0; i < _attributeNames.size(); i++) {
0857: String oldName = _attributeNames.get(i);
0858:
0859: if (oldName == qName) {
0860: _attributeValues.set(i, value);
0861: return;
0862: }
0863: }
0864:
0865: if (qName == null || qName.equals(""))
0866: throw new NullPointerException();
0867:
0868: _attributeNames.add(qName);
0869: _attributeValues.add(value);
0870: }
0871:
0872: /**
0873: * Complete printing of the attributes when the open tag completes.
0874: */
0875: public boolean finishAttributes() throws IOException {
0876: if (_currentElement == null)
0877: return false;
0878:
0879: for (int i = 0; i < _attributeNames.size(); i++) {
0880: String qName = _attributeNames.get(i);
0881: String value = _attributeValues.get(i);
0882:
0883: if (_isHtml
0884: && _booleanAttrs.get(qName.toLowerCase()) != null
0885: && (value == null || value.equals("") || value
0886: .equals(qName))) {
0887: print(' ');
0888: print(qName);
0889: } else {
0890: print(' ');
0891: print(qName);
0892:
0893: if (value != null) {
0894: print("=\"");
0895:
0896: if (!_escapeText || _inVerbatim)
0897: print(value);
0898: /*
0899: else if (isHtml && isURIAttribute(currentElement, qName)) {
0900: int len = value.length();
0901: int offset = 0;
0902:
0903: while (len > abuf.length) {
0904: value.getChars(offset, offset + abuf.length, abuf, 0);
0905: entities.printURIAttr(this, abuf, 0, abuf.length);
0906: len -= abuf.length;
0907: offset += abuf.length;
0908: }
0909:
0910: value.getChars(offset, offset + len, abuf, 0);
0911: entities.printURIAttr(this, abuf, 0, len);
0912: }
0913: */
0914: else {
0915: int len = value.length();
0916: int offset = 0;
0917:
0918: while (len > _abuf.length) {
0919: value.getChars(offset, offset
0920: + _abuf.length, _abuf, 0);
0921: _entities.printText(this , _abuf, 0,
0922: _abuf.length, true);
0923: len -= _abuf.length;
0924: offset += _abuf.length;
0925: }
0926:
0927: value.getChars(offset, offset + len, _abuf, 0);
0928: _entities.printText(this , _abuf, 0, len, true);
0929: }
0930: print('\"');
0931: } else if (!_isHtml) {
0932: print("=\"\"");
0933: }
0934: }
0935: }
0936:
0937: if (_prefixList != null && _prefixList.size() > 0) {
0938: for (int i = 0; i < _prefixList.size(); i++) {
0939: String prefix = _prefixList.get(i);
0940: String url = _namespace.get(prefix);
0941:
0942: if (prefix.equals("")) {
0943: print(" xmlns=\"");
0944: print(url);
0945: print('\"');
0946: } else if (prefix.startsWith("xmlns")) {
0947: print(" ");
0948: print(prefix);
0949: print("=\"");
0950: print(url);
0951: print('\"');
0952: } else {
0953: print(" xmlns:");
0954: print(prefix);
0955: print("=\"");
0956: print(url);
0957: print('\"');
0958: }
0959: }
0960:
0961: _prefixList.clear();
0962: _namespace.clear();
0963: }
0964:
0965: _currentElement = null;
0966: // lastTextChar = NULL_SPACE;
0967:
0968: return true;
0969: }
0970:
0971: /**
0972: * Prints the end tag of an element
0973: *
0974: * @param uri the namespace uri of the element
0975: * @param localName the localname of the element tag
0976: * @param qName qualified name of the element
0977: */
0978: public void endElement(String uri, String localName, String qName)
0979: throws IOException {
0980: if (_isText)
0981: return;
0982:
0983: String normalName = _isHtml ? qName.toLowerCase() : qName;
0984:
0985: if (_isHtml && _verbatimTags.get(normalName) != null)
0986: _inVerbatim = false;
0987:
0988: int prevTextChar = _lastTextChar;
0989: boolean isEmpty = _currentElement != null;
0990: if (_currentElement != null)
0991: finishAttributes();
0992:
0993: if (!_isHtml || _hasMetaContentType) {
0994: } else if (normalName.equals("head")) {
0995: if (isEmpty)
0996: print(">");
0997: isEmpty = false;
0998: printMetaContentType();
0999: _currentElement = null;
1000: }
1001:
1002: if (isEmpty) {
1003: if (_isHtml && _empties.get(normalName) >= 0)
1004: print(">");
1005: else if (prevTextChar <= OMITTED) {
1006: print(">");
1007: printPrettyEnd(qName);
1008: print("</");
1009: print(qName);
1010: print(">");
1011: return;
1012: } else if (_isHtml) {
1013: print("></");
1014: print(qName);
1015: print(">");
1016: } else {
1017: print("/>");
1018: }
1019:
1020: if (_isPretty)
1021: closePretty(qName);
1022: } else if (_isHtml && _empties.get(normalName) >= 0
1023: && !normalName.equals("p")) {
1024: if (_isPretty)
1025: closePretty(qName);
1026: } else if (_isPretty) {
1027: printPrettyEnd(qName);
1028: print("</");
1029: print(qName);
1030: print(">");
1031: } else {
1032: print("</");
1033: print(qName);
1034: print(">");
1035: }
1036:
1037: _currentElement = null;
1038: }
1039:
1040: /**
1041: * Handle pretty printing at an end tag.
1042: */
1043: private void printPrettyEnd(String qName) throws IOException {
1044: int code = _isHtml ? _prettyMap.get(qName.toLowerCase()) : -1;
1045:
1046: if (code == PRE) {
1047: _preCount--;
1048: _lastTextChar = NULL_SPACE;
1049: return;
1050: } else if (_preCount > 0) {
1051: return;
1052: } else if (code == NO_PRETTY) {
1053: if (_lastTextChar <= OMITTED)
1054: println();
1055: _lastTextChar = 'a';
1056: // indent -= 2;
1057: return;
1058: } else if (code == INLINE) {
1059: _lastTextChar = NULL_SPACE;
1060: return;
1061: }
1062:
1063: if (!_isHtml || code < 0) {
1064: _indent -= 2;
1065: }
1066:
1067: if (_lastTextChar <= WHITESPACE) {
1068: if (_lastTextChar != NEWLINE)
1069: println();
1070: for (int i = 0; i < _indent; i++)
1071: print(' ');
1072: }
1073: _lastTextChar = NULL_SPACE;
1074: }
1075:
1076: /**
1077: * Handle the pretty printing after the closing of a tag.
1078: */
1079: private void closePretty(String qName) {
1080: int code = _isHtml ? _prettyMap.get(qName.toLowerCase()) : -1;
1081:
1082: if (code == PRE) {
1083: _preCount--;
1084: _lastTextChar = NULL_SPACE;
1085: return;
1086: }
1087: if (_preCount > 0)
1088: return;
1089:
1090: if (!_isHtml || code < 0) {
1091: _indent -= 2;
1092: }
1093:
1094: if (code != NO_PRETTY)
1095: _lastTextChar = NULL_SPACE;
1096: else
1097: _lastTextChar = 'a';
1098: }
1099:
1100: /**
1101: * Prints a processing instruction
1102: *
1103: * @param name the name of the processing instruction
1104: * @param data the processing instruction data
1105: */
1106: public void processingInstruction(String name, String data)
1107: throws IOException {
1108: if (_isText)
1109: return;
1110:
1111: if (_currentElement != null)
1112: completeOpenTag();
1113:
1114: if (_isTop && !_isHtml && !_isAutomaticMethod) {
1115: printHeader(null);
1116: _isTop = false;
1117: }
1118:
1119: print("<?");
1120: print(name);
1121:
1122: if (data != null && data.length() > 0) {
1123: print(" ");
1124: print(data);
1125: }
1126:
1127: if (isHtml())
1128: print(">");
1129: else
1130: print("?>");
1131:
1132: _lastTextChar = NULL_SPACE;
1133: }
1134:
1135: /**
1136: * Prints a comment
1137: *
1138: * @param data the comment data
1139: */
1140: public void comment(String data) throws IOException {
1141: if (_isText)
1142: return;
1143:
1144: int textChar = _lastTextChar;
1145:
1146: if (_currentElement != null)
1147: completeOpenTag();
1148:
1149: if (_isPretty
1150: && _preCount <= 0
1151: && (textChar == OMITTED_NEWLINE || textChar == NULL_SPACE)) {
1152: println();
1153:
1154: for (int i = 0; i < _indent; i++)
1155: print(' ');
1156: }
1157:
1158: print("<!--");
1159: print(data);
1160: print("-->");
1161:
1162: _lastTextChar = NULL_SPACE;
1163: }
1164:
1165: /**
1166: * Returns true if the text is currently being escaped
1167: */
1168: public boolean getEscapeText() {
1169: return _escapeText;
1170: }
1171:
1172: /**
1173: * Sets true if the text should be escaped, else it will be printed
1174: * verbatim.
1175: */
1176: public void setEscapeText(boolean isEscaped) {
1177: _escapeText = isEscaped;
1178: }
1179:
1180: /**
1181: * Prints text. If the text is escaped, codes like < will be printed as
1182: * &lt;.
1183: */
1184: public void text(String text) throws IOException {
1185: int length = text.length();
1186:
1187: for (int offset = 0; offset < length; offset += _cbuf.length) {
1188: int sublen = length - offset;
1189: if (sublen > _cbuf.length)
1190: sublen = _cbuf.length;
1191:
1192: text.getChars(offset, offset + sublen, _cbuf, 0);
1193: text(_cbuf, 0, sublen);
1194: }
1195: }
1196:
1197: /**
1198: * Prints text. If the text is escaped, codes like < will be printed as
1199: * &lt;.
1200: */
1201: public void text(char[] buffer, int offset, int length)
1202: throws IOException {
1203: if (length == 0)
1204: return;
1205:
1206: int prevTextChar = _lastTextChar;
1207: if ((_isPretty && _preCount <= 0 || _isTop) && !_isText
1208: && trimPrettyWhitespace(buffer, offset, length)) {
1209: if (prevTextChar <= WHITESPACE)
1210: return;
1211: if (_lastTextChar == OMITTED_SPACE)
1212: _lastTextChar = SPACE;
1213: if (_lastTextChar == OMITTED_NEWLINE)
1214: _lastTextChar = NEWLINE;
1215: }
1216:
1217: int nextTextChar = _lastTextChar;
1218: if (_currentElement != null) {
1219: completeOpenTag();
1220:
1221: if (_isPretty && _preCount <= 0 && prevTextChar <= OMITTED)
1222: println();
1223: }
1224:
1225: _lastTextChar = nextTextChar;
1226:
1227: if (!_isTop) {
1228: } else if (!_isJsp) {
1229: _isTop = false;
1230: } else if (_isAutomaticMethod) {
1231: } else if (!_isHtml) {
1232: printHeader(null);
1233: _isTop = false;
1234: }
1235:
1236: if (_isHtml && !_hasMetaContentType && !_inHead) {
1237: int textChar = _lastTextChar;
1238:
1239: if (_isPretty && _preCount <= 0 && prevTextChar <= OMITTED)
1240: println();
1241:
1242: // printHeadContentType();
1243: _lastTextChar = textChar;
1244: prevTextChar = 'a';
1245: }
1246:
1247: if (!_isPretty || _preCount > 0) {
1248: } else if (prevTextChar == OMITTED_NEWLINE) {
1249: if (buffer[offset] != '\n')
1250: println();
1251: } else if (prevTextChar == OMITTED_SPACE) {
1252: char ch = buffer[offset];
1253:
1254: if (ch != ' ' && ch != '\n')
1255: print(' ');
1256: }
1257:
1258: if (_lineMap == null) {
1259: } else if (_locator != null) {
1260: _lineMap.add(_locator.getFilename(), _locator
1261: .getLineNumber(), _line);
1262: } else if (_srcFilename != null)
1263: _lineMap.add(_srcFilename, _srcLine, _line);
1264:
1265: if (!_escapeText || _inVerbatim || _entities == null)
1266: print(buffer, offset, length);
1267: else
1268: _entities.printText(this , buffer, offset, length, false);
1269: }
1270:
1271: /**
1272: * If the text is completely whitespace, skip it.
1273: */
1274: boolean trimPrettyWhitespace(char[] buffer, int offset, int length) {
1275: char textChar = 'a';
1276: int i = length - 1;
1277:
1278: for (; i >= 0; i--) {
1279: char ch = buffer[offset + i];
1280:
1281: if (ch == '\r' || ch == '\n') {
1282: if (textChar != NEWLINE)
1283: textChar = OMITTED_NEWLINE;
1284: } else if (ch == ' ' || ch == '\t') {
1285: if (textChar == 'a' || textChar == NULL_SPACE)
1286: textChar = OMITTED_SPACE;
1287: } else if (textChar == OMITTED_NEWLINE) {
1288: textChar = NEWLINE;
1289: break;
1290: } else if (textChar == OMITTED_SPACE) {
1291: textChar = SPACE;
1292: break;
1293: } else
1294: break;
1295: }
1296:
1297: _lastTextChar = textChar;
1298:
1299: return (i < 0 && textChar <= WHITESPACE);
1300: }
1301:
1302: public void cdata(String text) throws IOException {
1303: if (text.length() == 0)
1304: return;
1305:
1306: _isTop = false;
1307:
1308: if (_currentElement != null)
1309: completeOpenTag();
1310:
1311: if (_lineMap != null && _srcFilename != null)
1312: _lineMap.add(_srcFilename, _srcLine, _line);
1313:
1314: print("<![CDATA[");
1315:
1316: print(text);
1317:
1318: print("]]>");
1319:
1320: _lastTextChar = NEWLINE;
1321: }
1322:
1323: private void completeOpenTag() throws IOException {
1324: boolean isHead = (_isHtml && !_hasMetaContentType && _currentElement
1325: .equalsIgnoreCase("head"));
1326:
1327: finishAttributes();
1328: print(">");
1329:
1330: if (isHead)
1331: printHeadContentType();
1332: }
1333:
1334: public void cdata(char[] buffer, int offset, int length)
1335: throws IOException {
1336: cdata(new String(buffer, offset, length));
1337: }
1338:
1339: private void printHeadContentType() throws IOException {
1340: printMetaContentType();
1341: }
1342:
1343: private void printMetaContentType() throws IOException {
1344: if (!_includeContentType)
1345: return;
1346:
1347: _hasMetaContentType = true;
1348: if (_lastTextChar != NEWLINE)
1349: println();
1350:
1351: if (_encoding == null || _encoding.equals("US-ASCII"))
1352: _encoding = "ISO-8859-1";
1353: String mimeType = _mimeType;
1354: if (mimeType == null)
1355: mimeType = "text/html";
1356:
1357: println(" <meta http-equiv=\"Content-Type\" content=\""
1358: + mimeType + "; charset=" + _encoding + "\">");
1359: _lastTextChar = NEWLINE;
1360: }
1361:
1362: void printDecl(String text) throws IOException {
1363: for (int i = 0; i < text.length(); i++) {
1364: char ch = text.charAt(i);
1365:
1366: switch (ch) {
1367: case '&':
1368: if (i + 1 < text.length() && text.charAt(i + 1) == '#')
1369: print("&");
1370: else
1371: print(ch);
1372: break;
1373:
1374: case '"':
1375: print(""");
1376: break;
1377:
1378: case '\'':
1379: print("'");
1380: break;
1381:
1382: case '\n':
1383: print("\n");
1384: break;
1385:
1386: default:
1387: print(ch);
1388: }
1389: }
1390: }
1391:
1392: /**
1393: * Prints a newline to the output stream.
1394: */
1395: void println() throws IOException {
1396: print('\n');
1397: }
1398:
1399: void println(String text) throws IOException {
1400: print(text);
1401: println();
1402: }
1403:
1404: /**
1405: * Prints a char buffer.
1406: */
1407: void print(char[] buf) throws IOException {
1408: print(buf, 0, buf.length);
1409: }
1410:
1411: /**
1412: * Prints a char buffer.
1413: */
1414: void print(char[] buf, int off, int len) throws IOException {
1415: for (int i = 0; i < len; i++)
1416: print(buf[off + i]);
1417: }
1418:
1419: /**
1420: * Prints a chunk of text.
1421: */
1422: void print(String text) throws IOException {
1423: int len = text.length();
1424:
1425: for (int i = 0; i < len; i++) {
1426: char ch = text.charAt(i);
1427: print(ch);
1428: }
1429: }
1430:
1431: /**
1432: * Prints a character.
1433: */
1434: void print(char ch) throws IOException {
1435: if (_capacity <= _length) {
1436: _os.print(_buffer, 0, _length);
1437: _length = 0;
1438: }
1439:
1440: _buffer[_length++] = ch;
1441: if (ch == '\n')
1442: _line++;
1443: }
1444:
1445: /**
1446: * Prints an integer to the output stream.
1447: */
1448: void print(int i) throws IOException {
1449: if (i < 0) {
1450: } else if (i < 10) {
1451: print((char) ('0' + i));
1452: return;
1453: } else if (i < 100) {
1454: print((char) ('0' + i / 10));
1455: print((char) ('0' + i % 10));
1456: return;
1457: }
1458:
1459: if (_length >= 0) {
1460: _os.print(_buffer, 0, _length);
1461: _length = 0;
1462: }
1463:
1464: _os.print(i);
1465: }
1466:
1467: private void flush() throws IOException {
1468: if (_length >= 0) {
1469: _os.print(_buffer, 0, _length);
1470: _length = 0;
1471: }
1472:
1473: if (_isEnclosedStream)
1474: _os.flush();
1475: }
1476:
1477: private void close() throws IOException {
1478: flush();
1479:
1480: if (_isEnclosedStream)
1481: _os.close();
1482: }
1483:
1484: static void add(IntMap map, String name, int value) {
1485: map.put(name, value);
1486: map.put(name.toUpperCase(), value);
1487: }
1488:
1489: static void add(HashMap<String, String> map, String name) {
1490: map.put(name, name);
1491: map.put(name.toUpperCase(), name);
1492: }
1493:
1494: static {
1495: _empties = new IntMap();
1496: add(_empties, "basefont", ALWAYS_EMPTY);
1497: add(_empties, "br", ALWAYS_EMPTY);
1498: add(_empties, "area", ALWAYS_EMPTY);
1499: add(_empties, "link", ALWAYS_EMPTY);
1500: add(_empties, "img", ALWAYS_EMPTY);
1501: add(_empties, "param", ALWAYS_EMPTY);
1502: add(_empties, "hr", ALWAYS_EMPTY);
1503: add(_empties, "input", ALWAYS_EMPTY);
1504: add(_empties, "col", ALWAYS_EMPTY);
1505: add(_empties, "frame", ALWAYS_EMPTY);
1506: add(_empties, "isindex", ALWAYS_EMPTY);
1507: add(_empties, "base", ALWAYS_EMPTY);
1508: add(_empties, "meta", ALWAYS_EMPTY);
1509:
1510: add(_empties, "p", ALWAYS_EMPTY);
1511: add(_empties, "li", ALWAYS_EMPTY);
1512:
1513: add(_empties, "option", EMPTY_IF_EMPTY);
1514:
1515: _booleanAttrs = new HashMap<String, String>();
1516: // input
1517: add(_booleanAttrs, "checked");
1518: // dir, menu, dl, ol, ul
1519: add(_booleanAttrs, "compact");
1520: // object
1521: add(_booleanAttrs, "declare");
1522: // script
1523: add(_booleanAttrs, "defer");
1524: // button, input, optgroup, option, select, textarea
1525: add(_booleanAttrs, "disabled");
1526: // img
1527: add(_booleanAttrs, "ismap");
1528: // select
1529: add(_booleanAttrs, "multiple");
1530: // area
1531: add(_booleanAttrs, "nohref");
1532: // frame
1533: add(_booleanAttrs, "noresize");
1534: // hr
1535: add(_booleanAttrs, "noshade");
1536: // td, th
1537: add(_booleanAttrs, "nowrap");
1538: // textarea, input
1539: add(_booleanAttrs, "readonly");
1540: // option
1541: add(_booleanAttrs, "selected");
1542:
1543: _prettyMap = new IntMap();
1544: // next two break browsers
1545: add(_prettyMap, "img", NO_PRETTY);
1546: add(_prettyMap, "a", NO_PRETTY);
1547: add(_prettyMap, "embed", NO_PRETTY);
1548: add(_prettyMap, "th", NO_PRETTY);
1549: add(_prettyMap, "td", NO_PRETTY);
1550: // inline tags look better without the indent
1551: add(_prettyMap, "tt", INLINE);
1552: add(_prettyMap, "i", INLINE);
1553: add(_prettyMap, "b", INLINE);
1554: add(_prettyMap, "big", INLINE);
1555: add(_prettyMap, "em", INLINE);
1556: add(_prettyMap, "string", INLINE);
1557: add(_prettyMap, "dfn", INLINE);
1558: add(_prettyMap, "code", INLINE);
1559: add(_prettyMap, "samp", INLINE);
1560: add(_prettyMap, "kbd", INLINE);
1561: add(_prettyMap, "var", INLINE);
1562: add(_prettyMap, "cite", INLINE);
1563: add(_prettyMap, "abbr", INLINE);
1564: add(_prettyMap, "acronym", INLINE);
1565: add(_prettyMap, "object", INLINE);
1566: add(_prettyMap, "q", INLINE);
1567: add(_prettyMap, "sub", INLINE);
1568: add(_prettyMap, "sup", INLINE);
1569: add(_prettyMap, "font", INLINE);
1570: add(_prettyMap, "small", INLINE);
1571: add(_prettyMap, "span", INLINE);
1572: add(_prettyMap, "bdo", INLINE);
1573: add(_prettyMap, "jsp:expression", INLINE);
1574:
1575: add(_prettyMap, "textarea", PRE);
1576: add(_prettyMap, "pre", PRE);
1577:
1578: add(_prettyMap, "html", NO_INDENT);
1579: add(_prettyMap, "body", NO_INDENT);
1580: add(_prettyMap, "ul", NO_INDENT);
1581: add(_prettyMap, "table", NO_INDENT);
1582: add(_prettyMap, "frameset", NO_INDENT);
1583:
1584: _verbatimTags = new HashMap<String, String>();
1585: add(_verbatimTags, "script");
1586: add(_verbatimTags, "style");
1587: }
1588: }
|