0001: /*
0002: * Copyright 1999-2002,2004,2005 The Apache Software Foundation.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: // Sep 14, 2000:
0018: // Fixed comments to preserve whitespaces and add a line break
0019: // when indenting. Reported by Gervase Markham <gerv@gerv.net>
0020: // Sep 14, 2000:
0021: // Fixed serializer to report IO exception directly, instead at
0022: // the end of document processing.
0023: // Reported by Patrick Higgins <phiggins@transzap.com>
0024: // Sep 13, 2000:
0025: // CR in character data will print as �D;
0026: // Aug 25, 2000:
0027: // Fixed processing instruction printing inside element content
0028: // to not escape content. Reported by Mikael Staldal
0029: // <d96-mst@d.kth.se>
0030: // Aug 25, 2000:
0031: // Added ability to omit comments.
0032: // Contributed by Anupam Bagchi <abagchi@jtcsv.com>
0033: // Aug 26, 2000:
0034: // Fixed bug in newline handling when preserving spaces.
0035: // Contributed by Mike Dusseault <mdusseault@home.com>
0036: // Aug 29, 2000:
0037: // Fixed state.unescaped not being set to false when
0038: // entering element state.
0039: // Reported by Lowell Vaughn <lvaughn@agillion.com>
0040:
0041: package org.jasig.portal.serialize;
0042:
0043: import java.io.IOException;
0044: import java.io.OutputStream;
0045: import java.io.Writer;
0046: import java.util.Hashtable;
0047: import java.util.Vector;
0048:
0049: import javax.xml.transform.Result;
0050:
0051: import org.apache.xerces.dom.DOMErrorImpl;
0052: import org.apache.xerces.dom.DOMLocatorImpl;
0053: import org.apache.xerces.dom.DOMMessageFormatter;
0054: import org.apache.xerces.util.XMLChar;
0055: import org.apache.xml.serialize.DOMSerializerImpl;
0056: import org.jasig.portal.IAnchoringSerializer;
0057: import org.jasig.portal.properties.PropertiesManager;
0058: import org.w3c.dom.DOMImplementation;
0059: import org.w3c.dom.Document;
0060: import org.w3c.dom.DocumentFragment;
0061: import org.w3c.dom.DocumentType;
0062: import org.w3c.dom.DOMError;
0063: import org.w3c.dom.DOMErrorHandler;
0064: import org.w3c.dom.Element;
0065: import org.w3c.dom.Entity;
0066: import org.w3c.dom.NamedNodeMap;
0067: import org.w3c.dom.Node;
0068: import org.w3c.dom.Notation;
0069: import org.w3c.dom.ls.LSException;
0070: import org.w3c.dom.ls.LSSerializerFilter;
0071: import org.w3c.dom.traversal.NodeFilter;
0072: import org.xml.sax.ContentHandler;
0073: import org.xml.sax.DTDHandler;
0074: import org.xml.sax.DocumentHandler;
0075: import org.xml.sax.Locator;
0076: import org.xml.sax.SAXException;
0077: import org.xml.sax.ext.DeclHandler;
0078: import org.xml.sax.ext.LexicalHandler;
0079:
0080: /**
0081: * Base class for a serializer supporting both DOM and SAX pretty
0082: * serializing of XML/HTML/XHTML documents. Derives classes perform
0083: * the method-specific serializing, this class provides the common
0084: * serializing mechanisms.
0085: * <p>
0086: * The serializer must be initialized with the proper writer and
0087: * output format before it can be used by calling {@link #setOutputCharStream}
0088: * or {@link #setOutputByteStream} for the writer and {@link #setOutputFormat}
0089: * for the output format.
0090: * <p>
0091: * The serializer can be reused any number of times, but cannot
0092: * be used concurrently by two threads.
0093: * <p>
0094: * If an output stream is used, the encoding is taken from the
0095: * output format (defaults to <tt>UTF-8</tt>). If a writer is
0096: * used, make sure the writer uses the same encoding (if applies)
0097: * as specified in the output format.
0098: * <p>
0099: * The serializer supports both DOM and SAX. DOM serializing is done
0100: * by calling {@link #serialize(Document)} and SAX serializing is done by firing
0101: * SAX events and using the serializer as a document handler.
0102: * This also applies to derived class.
0103: * <p>
0104: * If an I/O exception occurs while serializing, the serializer
0105: * will not throw an exception directly, but only throw it
0106: * at the end of serializing (either DOM or SAX's {@link
0107: * org.xml.sax.DocumentHandler#endDocument}.
0108: * <p>
0109: * For elements that are not specified as whitespace preserving,
0110: * the serializer will potentially break long text lines at space
0111: * boundaries, indent lines, and serialize elements on separate
0112: * lines. Line terminators will be regarded as spaces, and
0113: * spaces at beginning of line will be stripped.
0114: * <p>
0115: * When indenting, the serializer is capable of detecting seemingly
0116: * element content, and serializing these elements indented on separate
0117: * lines. An element is serialized indented when it is the first or
0118: * last child of an element, or immediate following or preceding
0119: * another element.
0120: *
0121: *
0122: * @version $Revision: 36683 $ $Date: 2006-08-23 15:08:00 -0700 (Wed, 23 Aug 2006) $
0123: * @author <a href="mailto:arkin@intalio.com">Assaf Arkin</a>
0124: * @author <a href="mailto:rahul.srivastava@sun.com">Rahul Srivastava</a>
0125: * @author Elena Litani, IBM
0126: * @see Serializer
0127: */
0128: public abstract class BaseMarkupSerializer implements ContentHandler,
0129: DocumentHandler, LexicalHandler, DTDHandler, DeclHandler,
0130: DOMSerializer, Serializer, IAnchoringSerializer {
0131:
0132: // DOM L3 implementation
0133: protected short features = 0xFFFFFFFF;
0134: protected DOMErrorHandler fDOMErrorHandler;
0135: protected final DOMErrorImpl fDOMError = new DOMErrorImpl();
0136: protected LSSerializerFilter fDOMFilter;
0137:
0138: protected EncodingInfo _encodingInfo;
0139:
0140: /**
0141: * Holds array of all element states that have been entered.
0142: * The array is automatically resized. When leaving an element,
0143: * it's state is not removed but reused when later returning
0144: * to the same nesting level.
0145: */
0146: private ElementState[] _elementStates;
0147:
0148: /**
0149: * The index of the next state to place in the array,
0150: * or one plus the index of the current state. When zero,
0151: * we are in no state.
0152: */
0153: private int _elementStateCount;
0154:
0155: /**
0156: * Vector holding comments and PIs that come before the root
0157: * element (even after it), see {@link #serializePreRoot}.
0158: */
0159: private Vector _preRoot;
0160:
0161: /**
0162: * If the document has been started (header serialized), this
0163: * flag is set to true so it's not started twice.
0164: */
0165: protected boolean _started;
0166:
0167: /**
0168: * True if the serializer has been prepared. This flag is set
0169: * to false when the serializer is reset prior to using it,
0170: * and to true after it has been prepared for usage.
0171: */
0172: private boolean _prepared;
0173:
0174: /**
0175: * Association between namespace URIs (keys) and prefixes (values).
0176: * Accumulated here prior to starting an element and placing this
0177: * list in the element state.
0178: */
0179: protected Hashtable _prefixes;
0180:
0181: /**
0182: * The system identifier of the document type, if known.
0183: */
0184: protected String _docTypePublicId;
0185:
0186: /**
0187: * The system identifier of the document type, if known.
0188: */
0189: protected String _docTypeSystemId;
0190:
0191: /**
0192: * The output format associated with this serializer. This will never
0193: * be a null reference. If no format was passed to the constructor,
0194: * the default one for this document type will be used. The format
0195: * object is never changed by the serializer.
0196: */
0197: protected OutputFormat _format;
0198:
0199: /**
0200: * The printer used for printing text parts.
0201: */
0202: protected Printer _printer;
0203:
0204: /**
0205: * True if indenting printer.
0206: */
0207: protected boolean _indenting;
0208:
0209: /** Temporary buffer to store character data */
0210: protected final StringBuffer fStrBuffer = new StringBuffer(40);
0211:
0212: /**
0213: * The underlying writer.
0214: */
0215: private Writer _writer;
0216:
0217: /**
0218: * The output stream.
0219: */
0220: private OutputStream _output;
0221:
0222: /** Current node that is being processed */
0223: protected Node fCurrentNode = null;
0224:
0225: //--------------------------------//
0226: // Constructor and initialization //
0227: //--------------------------------//
0228:
0229: /**
0230: * Protected constructor can only be used by derived class.
0231: * Must initialize the serializer before serializing any document,
0232: * by calling {@link #setOutputCharStream} or {@link #setOutputByteStream}
0233: * first
0234: */
0235: protected BaseMarkupSerializer(OutputFormat format) {
0236: int i;
0237:
0238: _elementStates = new ElementState[10];
0239: for (i = 0; i < _elementStates.length; ++i)
0240: _elementStates[i] = new ElementState();
0241: _format = format;
0242:
0243: try {
0244: _allowDisableOutputEscaping = PropertiesManager
0245: .getPropertyAsBoolean("org.jasig.portal.serialize.BaseMarkupSerializer.allow_disable_output_escaping");
0246: } catch (Exception e) {
0247: _allowDisableOutputEscaping = false;
0248: }
0249: }
0250:
0251: public DocumentHandler asDocumentHandler() throws IOException {
0252: prepare();
0253: return this ;
0254: }
0255:
0256: public ContentHandler asContentHandler() throws IOException {
0257: prepare();
0258: return this ;
0259: }
0260:
0261: public DOMSerializer asDOMSerializer() throws IOException {
0262: prepare();
0263: return this ;
0264: }
0265:
0266: public void setOutputByteStream(OutputStream output) {
0267: if (output == null) {
0268: String msg = DOMMessageFormatter.formatMessage(
0269: DOMMessageFormatter.SERIALIZER_DOMAIN,
0270: "ArgumentIsNull", new Object[] { "output" });
0271: throw new NullPointerException(msg);
0272: }
0273: _output = output;
0274: _writer = null;
0275: reset();
0276: }
0277:
0278: public void setOutputCharStream(Writer writer) {
0279: if (writer == null) {
0280: String msg = DOMMessageFormatter.formatMessage(
0281: DOMMessageFormatter.SERIALIZER_DOMAIN,
0282: "ArgumentIsNull", new Object[] { "writer" });
0283: throw new NullPointerException(msg);
0284: }
0285: _writer = writer;
0286: _output = null;
0287: reset();
0288: }
0289:
0290: public void setOutputFormat(OutputFormat format) {
0291: if (format == null) {
0292: String msg = DOMMessageFormatter.formatMessage(
0293: DOMMessageFormatter.SERIALIZER_DOMAIN,
0294: "ArgumentIsNull", new Object[] { "format" });
0295: throw new NullPointerException(msg);
0296: }
0297: _format = format;
0298: reset();
0299: }
0300:
0301: public boolean reset() {
0302: if (_elementStateCount > 1) {
0303: String msg = DOMMessageFormatter.formatMessage(
0304: DOMMessageFormatter.SERIALIZER_DOMAIN,
0305: "ResetInMiddle", null);
0306: throw new IllegalStateException(msg);
0307: }
0308: _prepared = false;
0309: fCurrentNode = null;
0310: fStrBuffer.setLength(0);
0311: return true;
0312: }
0313:
0314: protected void prepare() throws IOException {
0315: if (_prepared)
0316: return;
0317:
0318: if (_writer == null && _output == null) {
0319: String msg = DOMMessageFormatter.formatMessage(
0320: DOMMessageFormatter.SERIALIZER_DOMAIN,
0321: "NoWriterSupplied", null);
0322: throw new IOException(msg);
0323: }
0324: // If the output stream has been set, use it to construct
0325: // the writer. It is possible that the serializer has been
0326: // reused with the same output stream and different encoding.
0327:
0328: _encodingInfo = _format.getEncodingInfo();
0329:
0330: if (_output != null) {
0331: _writer = _encodingInfo.getWriter(_output);
0332: }
0333:
0334: if (_format.getIndenting()) {
0335: _indenting = true;
0336: _printer = new IndentPrinter(_writer, _format);
0337: } else {
0338: _indenting = false;
0339: _printer = new Printer(_writer, _format);
0340: }
0341:
0342: ElementState state;
0343:
0344: _elementStateCount = 0;
0345: state = _elementStates[0];
0346: state.namespaceURI = null;
0347: state.localName = null;
0348: state.rawName = null;
0349: state.preserveSpace = _format.getPreserveSpace();
0350: state.empty = true;
0351: state.afterElement = false;
0352: state.afterComment = false;
0353: state.doCData = state.inCData = false;
0354: state.prefixes = null;
0355:
0356: _docTypePublicId = _format.getDoctypePublic();
0357: _docTypeSystemId = _format.getDoctypeSystem();
0358: _started = false;
0359: _prepared = true;
0360: }
0361:
0362: //----------------------------------//
0363: // DOM document serializing methods //
0364: //----------------------------------//
0365:
0366: /**
0367: * Serializes the DOM element using the previously specified
0368: * writer and output format. Throws an exception only if
0369: * an I/O exception occured while serializing.
0370: *
0371: * @param elem The element to serialize
0372: * @throws IOException An I/O exception occured while
0373: * serializing
0374: */
0375: public void serialize(Element elem) throws IOException {
0376: reset();
0377: prepare();
0378: serializeNode(elem);
0379: _printer.flush();
0380: if (_printer.getException() != null)
0381: throw _printer.getException();
0382: }
0383:
0384: /**
0385: * Serializes the DOM document fragmnt using the previously specified
0386: * writer and output format. Throws an exception only if
0387: * an I/O exception occured while serializing.
0388: *
0389: * @param frag The element to serialize
0390: * @throws IOException An I/O exception occured while
0391: * serializing
0392: */
0393: public void serialize(DocumentFragment frag) throws IOException {
0394: reset();
0395: prepare();
0396: serializeNode(frag);
0397: _printer.flush();
0398: if (_printer.getException() != null)
0399: throw _printer.getException();
0400: }
0401:
0402: /**
0403: * Serializes the DOM document using the previously specified
0404: * writer and output format. Throws an exception only if
0405: * an I/O exception occured while serializing.
0406: *
0407: * @param doc The document to serialize
0408: * @throws IOException An I/O exception occured while
0409: * serializing
0410: */
0411: public void serialize(Document doc) throws IOException {
0412: reset();
0413: prepare();
0414: serializeNode(doc);
0415: serializePreRoot();
0416: _printer.flush();
0417: if (_printer.getException() != null)
0418: throw _printer.getException();
0419: }
0420:
0421: //------------------------------------------//
0422: // SAX document handler serializing methods //
0423: //------------------------------------------//
0424:
0425: public void startDocument() throws SAXException {
0426: try {
0427: prepare();
0428: } catch (IOException except) {
0429: throw new SAXException(except.toString());
0430: }
0431: // Nothing to do here. All the magic happens in startDocument(String)
0432: }
0433:
0434: public void characters(char[] chars, int start, int length)
0435: throws SAXException {
0436: ElementState state;
0437:
0438: try {
0439: state = content();
0440:
0441: // Check if text should be print as CDATA section or unescaped
0442: // based on elements listed in the output format (the element
0443: // state) or whether we are inside a CDATA section or entity.
0444:
0445: if (state.inCData || state.doCData) {
0446: int saveIndent;
0447:
0448: // Print a CDATA section. The text is not escaped, but ']]>'
0449: // appearing in the code must be identified and dealt with.
0450: // The contents of a text node is considered space preserving.
0451: if (!state.inCData) {
0452: if (html4compat && state.inScript) {
0453: _printer.printText("\n//<![CDATA[\n");
0454: } else {
0455: // _printer.printText( "\n<!--/*--><![CDATA[/*><!--*/\n" );
0456: _printer.printText("\n/*<![CDATA[*/\n");
0457: // _printer.printText( "<![CDATA[" );
0458: }
0459: state.inCData = true;
0460: }
0461: saveIndent = _printer.getNextIndent();
0462: _printer.setNextIndent(0);
0463: char ch;
0464: final int end = start + length;
0465: for (int index = start; index < end; ++index) {
0466: ch = chars[index];
0467: if (ch == ']' && index + 2 < end
0468: && chars[index + 1] == ']'
0469: && chars[index + 2] == '>') {
0470: _printer.printText("]]]]><![CDATA[>");
0471: index += 2;
0472: continue;
0473: }
0474: if (!XMLChar.isValid(ch)) {
0475: // check if it is surrogate
0476: if (++index < end) {
0477: surrogates(ch, chars[index]);
0478: } else {
0479: fatalError("The character '" + (char) ch
0480: + "' is an invalid XML character");
0481: }
0482: continue;
0483: } else {
0484: if ((ch >= ' '
0485: && _encodingInfo.isPrintable((char) ch) && ch != 0xF7)
0486: || ch == '\n'
0487: || ch == '\r'
0488: || ch == '\t') {
0489: _printer.printText((char) ch);
0490: } else {
0491: // The character is not printable -- split CDATA section
0492: _printer.printText("]]>&#x");
0493: _printer.printText(Integer.toHexString(ch));
0494: _printer.printText(";<![CDATA[");
0495: }
0496: }
0497: }
0498: _printer.setNextIndent(saveIndent);
0499:
0500: } else {
0501:
0502: int saveIndent;
0503:
0504: if (state.preserveSpace) {
0505: // If preserving space then hold of indentation so no
0506: // excessive spaces are printed at line breaks, escape
0507: // the text content without replacing spaces and print
0508: // the text breaking only at line breaks.
0509: saveIndent = _printer.getNextIndent();
0510: _printer.setNextIndent(0);
0511: printText(chars, start, length, true,
0512: state.unescaped);
0513: _printer.setNextIndent(saveIndent);
0514: } else {
0515: printText(chars, start, length, false,
0516: state.unescaped);
0517: }
0518: }
0519: } catch (IOException except) {
0520: throw new SAXException(except);
0521: }
0522: }
0523:
0524: public void ignorableWhitespace(char[] chars, int start, int length)
0525: throws SAXException {
0526: int i;
0527:
0528: try {
0529: content();
0530:
0531: // Print ignorable whitespaces only when indenting, after
0532: // all they are indentation. Cancel the indentation to
0533: // not indent twice.
0534: if (_indenting) {
0535: _printer.setThisIndent(0);
0536: for (i = start; length-- > 0; ++i)
0537: _printer.printText(chars[i]);
0538: }
0539: } catch (IOException except) {
0540: throw new SAXException(except);
0541: }
0542: }
0543:
0544: public final void processingInstruction(String target, String code)
0545: throws SAXException {
0546: try {
0547: processingInstructionIO(target, code);
0548: } catch (IOException except) {
0549: throw new SAXException(except);
0550: }
0551: }
0552:
0553: public void processingInstructionIO(String target, String code)
0554: throws IOException {
0555: int index;
0556: ElementState state;
0557:
0558: state = content();
0559:
0560: // Create the processing instruction textual representation.
0561: // Make sure we don't have '?>' inside either target or code.
0562: index = target.indexOf("?>");
0563: if (index >= 0)
0564: fStrBuffer.append("<?").append(target.substring(0, index));
0565: else
0566: fStrBuffer.append("<?").append(target);
0567: if (code != null) {
0568: fStrBuffer.append(' ');
0569: index = code.indexOf("?>");
0570: if (index >= 0)
0571: fStrBuffer.append(code.substring(0, index));
0572: else
0573: fStrBuffer.append(code);
0574: }
0575: fStrBuffer.append("?>");
0576:
0577: // If before the root element (or after it), do not print
0578: // the PI directly but place it in the pre-root vector.
0579: if (isDocumentState()) {
0580: if (_preRoot == null)
0581: _preRoot = new Vector();
0582: _preRoot.addElement(fStrBuffer.toString());
0583: } else {
0584: _printer.indent();
0585: printText(fStrBuffer.toString(), true, true);
0586: _printer.unindent();
0587: if (_indenting)
0588: state.afterElement = true;
0589: }
0590:
0591: fStrBuffer.setLength(0);
0592:
0593: if (_allowDisableOutputEscaping) {
0594: if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING))
0595: startNonEscaping();
0596: else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING))
0597: endNonEscaping();
0598: }
0599: }
0600:
0601: public void comment(char[] chars, int start, int length)
0602: throws SAXException {
0603: try {
0604: comment(new String(chars, start, length));
0605: } catch (IOException except) {
0606: throw new SAXException(except);
0607: }
0608: }
0609:
0610: public void comment(String text) throws IOException {
0611: int index;
0612: ElementState state;
0613:
0614: if (_format.getOmitComments())
0615: return;
0616:
0617: state = content();
0618: // Create the processing comment textual representation.
0619: // Make sure we don't have '-->' inside the comment.
0620: index = text.indexOf("-->");
0621: if (index >= 0)
0622: fStrBuffer.append("<!--").append(text.substring(0, index))
0623: .append("-->");
0624: else
0625: fStrBuffer.append("<!--").append(text).append("-->");
0626:
0627: // If before the root element (or after it), do not print
0628: // the comment directly but place it in the pre-root vector.
0629: if (isDocumentState()) {
0630: if (_preRoot == null)
0631: _preRoot = new Vector();
0632: _preRoot.addElement(fStrBuffer.toString());
0633: } else {
0634: // Indent this element on a new line if the first
0635: // content of the parent element or immediately
0636: // following an element.
0637: if (_indenting && !state.preserveSpace)
0638: _printer.breakLine();
0639: _printer.indent();
0640: printText(fStrBuffer.toString(), true, true);
0641: _printer.unindent();
0642: if (_indenting)
0643: state.afterElement = true;
0644: }
0645:
0646: fStrBuffer.setLength(0);
0647: state.afterComment = true;
0648: state.afterElement = false;
0649: }
0650:
0651: public void startCDATA() {
0652: ElementState state;
0653:
0654: state = getElementState();
0655: state.doCData = true;
0656: }
0657:
0658: public void endCDATA() {
0659: ElementState state;
0660:
0661: state = getElementState();
0662: state.doCData = false;
0663: }
0664:
0665: public void startNonEscaping() {
0666: ElementState state;
0667:
0668: state = getElementState();
0669: state.unescaped = true;
0670: }
0671:
0672: public void endNonEscaping() {
0673: ElementState state;
0674:
0675: state = getElementState();
0676: state.unescaped = false;
0677: }
0678:
0679: public void startPreserving() {
0680: ElementState state;
0681:
0682: state = getElementState();
0683: state.preserveSpace = true;
0684: }
0685:
0686: public void endPreserving() {
0687: ElementState state;
0688:
0689: state = getElementState();
0690: state.preserveSpace = false;
0691: }
0692:
0693: /**
0694: * Called at the end of the document to wrap it up.
0695: * Will flush the output stream and throw an exception
0696: * if any I/O error occured while serializing.
0697: *
0698: * @throws SAXException An I/O exception occured during
0699: * serializing
0700: */
0701: public void endDocument() throws SAXException {
0702: try {
0703: // Print all the elements accumulated outside of
0704: // the root element.
0705: serializePreRoot();
0706: // Flush the output, this is necessary for fStrBuffered output.
0707: _printer.flush();
0708: } catch (IOException except) {
0709: throw new SAXException(except);
0710: }
0711: }
0712:
0713: public void startEntity(String name) {
0714: // ???
0715: }
0716:
0717: public void endEntity(String name) {
0718: // ???
0719: }
0720:
0721: public void setDocumentLocator(Locator locator) {
0722: // Nothing to do
0723: }
0724:
0725: //-----------------------------------------//
0726: // SAX content handler serializing methods //
0727: //-----------------------------------------//
0728:
0729: public void skippedEntity(String name) throws SAXException {
0730: try {
0731: endCDATA();
0732: content();
0733: _printer.printText('&');
0734: _printer.printText(name);
0735: _printer.printText(';');
0736: } catch (IOException except) {
0737: throw new SAXException(except);
0738: }
0739: }
0740:
0741: public void startPrefixMapping(String prefix, String uri)
0742: throws SAXException {
0743: if (_prefixes == null)
0744: _prefixes = new Hashtable();
0745: _prefixes.put(uri, prefix == null ? "" : prefix);
0746: }
0747:
0748: public void endPrefixMapping(String prefix) throws SAXException {
0749: }
0750:
0751: //------------------------------------------//
0752: // SAX DTD/Decl handler serializing methods //
0753: //------------------------------------------//
0754:
0755: public final void startDTD(String name, String publicId,
0756: String systemId) throws SAXException {
0757: try {
0758: _printer.enterDTD();
0759: _docTypePublicId = publicId;
0760: _docTypeSystemId = systemId;
0761: } catch (IOException except) {
0762: throw new SAXException(except);
0763: }
0764: }
0765:
0766: public void endDTD() {
0767: // Nothing to do here, all the magic occurs in startDocument(String).
0768: }
0769:
0770: public void elementDecl(String name, String model)
0771: throws SAXException {
0772: try {
0773: _printer.enterDTD();
0774: _printer.printText("<!ELEMENT ");
0775: _printer.printText(name);
0776: _printer.printText(' ');
0777: _printer.printText(model);
0778: _printer.printText('>');
0779: if (_indenting)
0780: _printer.breakLine();
0781: } catch (IOException except) {
0782: throw new SAXException(except);
0783: }
0784: }
0785:
0786: public void attributeDecl(String eName, String aName, String type,
0787: String valueDefault, String value) throws SAXException {
0788: try {
0789: _printer.enterDTD();
0790: _printer.printText("<!ATTLIST ");
0791: _printer.printText(eName);
0792: _printer.printText(' ');
0793: _printer.printText(aName);
0794: _printer.printText(' ');
0795: _printer.printText(type);
0796: if (valueDefault != null) {
0797: _printer.printText(' ');
0798: _printer.printText(valueDefault);
0799: }
0800: if (value != null) {
0801: _printer.printText(" \"");
0802: printEscaped(value);
0803: _printer.printText('"');
0804: }
0805: _printer.printText('>');
0806: if (_indenting)
0807: _printer.breakLine();
0808: } catch (IOException except) {
0809: throw new SAXException(except);
0810: }
0811: }
0812:
0813: public void internalEntityDecl(String name, String value)
0814: throws SAXException {
0815: try {
0816: _printer.enterDTD();
0817: _printer.printText("<!ENTITY ");
0818: _printer.printText(name);
0819: _printer.printText(" \"");
0820: printEscaped(value);
0821: _printer.printText("\">");
0822: if (_indenting)
0823: _printer.breakLine();
0824: } catch (IOException except) {
0825: throw new SAXException(except);
0826: }
0827: }
0828:
0829: public void externalEntityDecl(String name, String publicId,
0830: String systemId) throws SAXException {
0831: try {
0832: _printer.enterDTD();
0833: unparsedEntityDecl(name, publicId, systemId, null);
0834: } catch (IOException except) {
0835: throw new SAXException(except);
0836: }
0837: }
0838:
0839: public void unparsedEntityDecl(String name, String publicId,
0840: String systemId, String notationName) throws SAXException {
0841: try {
0842: _printer.enterDTD();
0843: if (publicId == null) {
0844: _printer.printText("<!ENTITY ");
0845: _printer.printText(name);
0846: _printer.printText(" SYSTEM ");
0847: printDoctypeURL(systemId);
0848: } else {
0849: _printer.printText("<!ENTITY ");
0850: _printer.printText(name);
0851: _printer.printText(" PUBLIC ");
0852: printDoctypeURL(publicId);
0853: _printer.printText(' ');
0854: printDoctypeURL(systemId);
0855: }
0856: if (notationName != null) {
0857: _printer.printText(" NDATA ");
0858: _printer.printText(notationName);
0859: }
0860: _printer.printText('>');
0861: if (_indenting)
0862: _printer.breakLine();
0863: } catch (IOException except) {
0864: throw new SAXException(except);
0865: }
0866: }
0867:
0868: public void notationDecl(String name, String publicId,
0869: String systemId) throws SAXException {
0870: try {
0871: _printer.enterDTD();
0872: if (publicId != null) {
0873: _printer.printText("<!NOTATION ");
0874: _printer.printText(name);
0875: _printer.printText(" PUBLIC ");
0876: printDoctypeURL(publicId);
0877: if (systemId != null) {
0878: _printer.printText(' ');
0879: printDoctypeURL(systemId);
0880: }
0881: } else {
0882: _printer.printText("<!NOTATION ");
0883: _printer.printText(name);
0884: _printer.printText(" SYSTEM ");
0885: printDoctypeURL(systemId);
0886: }
0887: _printer.printText('>');
0888: if (_indenting)
0889: _printer.breakLine();
0890: } catch (IOException except) {
0891: throw new SAXException(except);
0892: }
0893: }
0894:
0895: //------------------------------------------//
0896: // Generic node serializing methods methods //
0897: //------------------------------------------//
0898:
0899: /**
0900: * Serialize the DOM node. This method is shared across XML, HTML and XHTML
0901: * serializers and the differences are masked out in a separate {@link
0902: * #serializeElement}.
0903: *
0904: * @param node The node to serialize
0905: * @see #serializeElement
0906: * @throws IOException An I/O exception occured while
0907: * serializing
0908: */
0909: protected void serializeNode(Node node) throws IOException {
0910: fCurrentNode = node;
0911:
0912: // Based on the node type call the suitable SAX handler.
0913: // Only comments entities and documents which are not
0914: // handled by SAX are serialized directly.
0915: switch (node.getNodeType()) {
0916: case Node.TEXT_NODE: {
0917: String text;
0918:
0919: text = node.getNodeValue();
0920: if (text != null) {
0921: if (fDOMFilter != null
0922: && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_TEXT) != 0) {
0923: short code = fDOMFilter.acceptNode(node);
0924: switch (code) {
0925: case NodeFilter.FILTER_REJECT:
0926: case NodeFilter.FILTER_SKIP: {
0927: break;
0928: }
0929: default: {
0930: characters(text);
0931: }
0932: }
0933: } else if (!_indenting
0934: || getElementState().preserveSpace
0935: || (text.replace('\n', ' ').trim().length() != 0))
0936: characters(text);
0937:
0938: }
0939: break;
0940: }
0941:
0942: case Node.CDATA_SECTION_NODE: {
0943: String text = node.getNodeValue();
0944: if ((features & /*DOMSerializerImpl.CDATA*/0x1 << 3) != 0) {
0945: if (text != null) {
0946: if (fDOMFilter != null
0947: && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_CDATA_SECTION) != 0) {
0948: short code = fDOMFilter.acceptNode(node);
0949: switch (code) {
0950: case NodeFilter.FILTER_REJECT:
0951: case NodeFilter.FILTER_SKIP: {
0952: // skip the CDATA node
0953: return;
0954: }
0955: default: {
0956: //fall through..
0957: }
0958: }
0959: }
0960: startCDATA();
0961: characters(text);
0962: endCDATA();
0963: }
0964: } else {
0965: // transform into a text node
0966: characters(text);
0967: }
0968: break;
0969: }
0970: case Node.COMMENT_NODE: {
0971: String text;
0972:
0973: if (!_format.getOmitComments()) {
0974: text = node.getNodeValue();
0975: if (text != null) {
0976:
0977: if (fDOMFilter != null
0978: && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_COMMENT) != 0) {
0979: short code = fDOMFilter.acceptNode(node);
0980: switch (code) {
0981: case NodeFilter.FILTER_REJECT:
0982: case NodeFilter.FILTER_SKIP: {
0983: // skip the comment node
0984: return;
0985: }
0986: default: {
0987: // fall through
0988: }
0989: }
0990: }
0991: comment(text);
0992: }
0993: }
0994: break;
0995: }
0996:
0997: case Node.ENTITY_REFERENCE_NODE: {
0998: Node child;
0999:
1000: endCDATA();
1001: content();
1002:
1003: if (((features & /*DOMSerializerImpl.ENTITIES*/0x1 << 2) != 0)
1004: || (node.getFirstChild() == null)) {
1005: if (fDOMFilter != null
1006: && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ENTITY_REFERENCE) != 0) {
1007: short code = fDOMFilter.acceptNode(node);
1008: switch (code) {
1009: case NodeFilter.FILTER_REJECT: {
1010: return; // remove the node
1011: }
1012: case NodeFilter.FILTER_SKIP: {
1013: child = node.getFirstChild();
1014: while (child != null) {
1015: serializeNode(child);
1016: child = child.getNextSibling();
1017: }
1018: return;
1019: }
1020:
1021: default: {
1022: // fall through
1023: }
1024: }
1025: }
1026: checkUnboundNamespacePrefixedNode(node);
1027:
1028: _printer.printText("&");
1029: _printer.printText(node.getNodeName());
1030: _printer.printText(";");
1031: } else {
1032: child = node.getFirstChild();
1033: while (child != null) {
1034: serializeNode(child);
1035: child = child.getNextSibling();
1036: }
1037: }
1038:
1039: break;
1040: }
1041:
1042: case Node.PROCESSING_INSTRUCTION_NODE: {
1043:
1044: if (fDOMFilter != null
1045: && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_PROCESSING_INSTRUCTION) != 0) {
1046: short code = fDOMFilter.acceptNode(node);
1047: switch (code) {
1048: case NodeFilter.FILTER_REJECT:
1049: case NodeFilter.FILTER_SKIP: {
1050: return; // skip this node
1051: }
1052: default: { // fall through
1053: }
1054: }
1055: }
1056: processingInstructionIO(node.getNodeName(), node
1057: .getNodeValue());
1058: break;
1059: }
1060: case Node.ELEMENT_NODE: {
1061:
1062: if (fDOMFilter != null
1063: && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ELEMENT) != 0) {
1064: short code = fDOMFilter.acceptNode(node);
1065: switch (code) {
1066: case NodeFilter.FILTER_REJECT: {
1067: return;
1068: }
1069: case NodeFilter.FILTER_SKIP: {
1070: Node child = node.getFirstChild();
1071: while (child != null) {
1072: serializeNode(child);
1073: child = child.getNextSibling();
1074: }
1075: return; // skip this node
1076: }
1077:
1078: default: { // fall through
1079: }
1080: }
1081: }
1082: serializeElement((Element) node);
1083: break;
1084: }
1085: case Node.DOCUMENT_NODE: {
1086: DocumentType docType;
1087: DOMImplementation domImpl;
1088: NamedNodeMap map;
1089: Entity entity;
1090: Notation notation;
1091: int i;
1092:
1093: // If there is a document type, use the SAX events to
1094: // serialize it.
1095: docType = ((Document) node).getDoctype();
1096: if (docType != null) {
1097: // DOM Level 2 (or higher)
1098: domImpl = ((Document) node).getImplementation();
1099: try {
1100: String internal;
1101:
1102: _printer.enterDTD();
1103: _docTypePublicId = docType.getPublicId();
1104: _docTypeSystemId = docType.getSystemId();
1105: internal = docType.getInternalSubset();
1106: if (internal != null && internal.length() > 0)
1107: _printer.printText(internal);
1108: endDTD();
1109: }
1110: // DOM Level 1 -- does implementation have methods?
1111: catch (NoSuchMethodError nsme) {
1112: Class docTypeClass = docType.getClass();
1113:
1114: String docTypePublicId = null;
1115: String docTypeSystemId = null;
1116: try {
1117: java.lang.reflect.Method getPublicId = docTypeClass
1118: .getMethod("getPublicId",
1119: (Class[]) null);
1120: if (getPublicId.getReturnType().equals(
1121: String.class)) {
1122: docTypePublicId = (String) getPublicId
1123: .invoke(docType, (Object[]) null);
1124: }
1125: } catch (Exception e) {
1126: // ignore
1127: }
1128: try {
1129: java.lang.reflect.Method getSystemId = docTypeClass
1130: .getMethod("getSystemId",
1131: (Class[]) null);
1132: if (getSystemId.getReturnType().equals(
1133: String.class)) {
1134: docTypeSystemId = (String) getSystemId
1135: .invoke(docType, (Object[]) null);
1136: }
1137: } catch (Exception e) {
1138: // ignore
1139: }
1140: _printer.enterDTD();
1141: _docTypePublicId = docTypePublicId;
1142: _docTypeSystemId = docTypeSystemId;
1143: endDTD();
1144: }
1145: }
1146: // !! Fall through
1147: }
1148: case Node.DOCUMENT_FRAGMENT_NODE: {
1149: Node child;
1150:
1151: // By definition this will happen if the node is a document,
1152: // document fragment, etc. Just serialize its contents. It will
1153: // work well for other nodes that we do not know how to serialize.
1154: child = node.getFirstChild();
1155: while (child != null) {
1156: serializeNode(child);
1157: child = child.getNextSibling();
1158: }
1159: break;
1160: }
1161:
1162: default:
1163: break;
1164: }
1165: }
1166:
1167: /**
1168: * Must be called by a method about to print any type of content.
1169: * If the element was just opened, the opening tag is closed and
1170: * will be matched to a closing tag. Returns the current element
1171: * state with <tt>empty</tt> and <tt>afterElement</tt> set to false.
1172: *
1173: * @return The current element state
1174: * @throws IOException An I/O exception occured while
1175: * serializing
1176: */
1177: protected ElementState content() throws IOException {
1178: ElementState state;
1179:
1180: state = getElementState();
1181: if (!isDocumentState()) {
1182: // Need to close CData section first
1183: if (state.inCData && !state.doCData) {
1184: _printer.printText("]]>");
1185: state.inCData = false;
1186: }
1187: // If this is the first content in the element,
1188: // change the state to not-empty and close the
1189: // opening element tag.
1190: if (state.empty) {
1191: _printer.printText('>');
1192: state.empty = false;
1193: }
1194: // Except for one content type, all of them
1195: // are not last element. That one content
1196: // type will take care of itself.
1197: state.afterElement = false;
1198: // Except for one content type, all of them
1199: // are not last comment. That one content
1200: // type will take care of itself.
1201: state.afterComment = false;
1202: }
1203: return state;
1204: }
1205:
1206: /**
1207: * Called to print the text contents in the prevailing element format.
1208: * Since this method is capable of printing text as CDATA, it is used
1209: * for that purpose as well. White space handling is determined by the
1210: * current element state. In addition, the output format can dictate
1211: * whether the text is printed as CDATA or unescaped.
1212: *
1213: * @param text The text to print
1214: * @param unescaped True is should print unescaped
1215: * @throws IOException An I/O exception occured while
1216: * serializing
1217: */
1218: protected void characters(String text) throws IOException {
1219: ElementState state;
1220:
1221: state = content();
1222: // Check if text should be print as CDATA section or unescaped
1223: // based on elements listed in the output format (the element
1224: // state) or whether we are inside a CDATA section or entity.
1225:
1226: if (state.inCData || state.doCData) {
1227: int index;
1228: int saveIndent;
1229:
1230: // Print a CDATA section. The text is not escaped, but ']]>'
1231: // appearing in the code must be identified and dealt with.
1232: // The contents of a text node is considered space preserving.
1233: if (!state.inCData) {
1234: _printer.printText("<![CDATA[");
1235: state.inCData = true;
1236: }
1237: saveIndent = _printer.getNextIndent();
1238: _printer.setNextIndent(0);
1239: printCDATAText(text);
1240: _printer.setNextIndent(saveIndent);
1241:
1242: } else {
1243:
1244: int saveIndent;
1245:
1246: if (state.preserveSpace) {
1247: // If preserving space then hold of indentation so no
1248: // excessive spaces are printed at line breaks, escape
1249: // the text content without replacing spaces and print
1250: // the text breaking only at line breaks.
1251: saveIndent = _printer.getNextIndent();
1252: _printer.setNextIndent(0);
1253: printText(text, true, state.unescaped);
1254: _printer.setNextIndent(saveIndent);
1255: } else {
1256: printText(text, false, state.unescaped);
1257: }
1258: }
1259: }
1260:
1261: /**
1262: * Returns the suitable entity reference for this character value,
1263: * or null if no such entity exists. Calling this method with <tt>'&'</tt>
1264: * will return <tt>"&amp;"</tt>.
1265: *
1266: * @param ch Character value
1267: * @return Character entity name, or null
1268: */
1269: protected abstract String getEntityRef(int ch);
1270:
1271: /**
1272: * Called to serializee the DOM element. The element is serialized based on
1273: * the serializer's method (XML, HTML, XHTML).
1274: *
1275: * @param elem The element to serialize
1276: * @throws IOException An I/O exception occured while
1277: * serializing
1278: */
1279: protected abstract void serializeElement(Element elem)
1280: throws IOException;
1281:
1282: /**
1283: * Comments and PIs cannot be serialized before the root element,
1284: * because the root element serializes the document type, which
1285: * generally comes first. Instead such PIs and comments are
1286: * accumulated inside a vector and serialized by calling this
1287: * method. Will be called when the root element is serialized
1288: * and when the document finished serializing.
1289: *
1290: * @throws IOException An I/O exception occured while
1291: * serializing
1292: */
1293: protected void serializePreRoot() throws IOException {
1294: int i;
1295:
1296: if (_preRoot != null) {
1297: for (i = 0; i < _preRoot.size(); ++i) {
1298: printText((String) _preRoot.elementAt(i), true, true);
1299: if (_indenting)
1300: _printer.breakLine();
1301: }
1302: _preRoot.removeAllElements();
1303: }
1304: }
1305:
1306: //---------------------------------------------//
1307: // Text pretty printing and formatting methods //
1308: //---------------------------------------------//
1309:
1310: protected void printCDATAText(String text) throws IOException {
1311: int length = text.length();
1312: char ch;
1313:
1314: for (int index = 0; index < length; ++index) {
1315: ch = text.charAt(index);
1316: if (ch == ']' && index + 2 < length
1317: && text.charAt(index + 1) == ']'
1318: && text.charAt(index + 2) == '>') { // check for ']]>'
1319: if (fDOMErrorHandler != null) {
1320: // REVISIT: this means that if DOM Error handler is not registered we don't report any
1321: // fatal errors and might serialize not wellformed document
1322: if ((features & /*DOMSerializerImpl.SPLITCDATA*/0x1 << 4) == 0) {
1323: String msg = DOMMessageFormatter.formatMessage(
1324: DOMMessageFormatter.SERIALIZER_DOMAIN,
1325: "EndingCDATA", null);
1326: if ((features & /*DOMSerializerImpl.WELLFORMED*/0x1 << 1) != 0) {
1327: // issue fatal error
1328: modifyDOMError(msg,
1329: DOMError.SEVERITY_FATAL_ERROR,
1330: "wf-invalid-character",
1331: fCurrentNode);
1332: fDOMErrorHandler
1333: .handleError((DOMError) fDOMError);
1334: throw new LSException(
1335: LSException.SERIALIZE_ERR, msg);
1336: } else {
1337: // issue error
1338: modifyDOMError(msg,
1339: DOMError.SEVERITY_ERROR,
1340: "cdata-section-not-splitted",
1341: fCurrentNode);
1342: if (!fDOMErrorHandler
1343: .handleError((DOMError) fDOMError)) {
1344: throw new LSException(
1345: LSException.SERIALIZE_ERR, msg);
1346: }
1347: }
1348: } else {
1349: // issue warning
1350: String msg = DOMMessageFormatter.formatMessage(
1351: DOMMessageFormatter.SERIALIZER_DOMAIN,
1352: "SplittingCDATA", null);
1353: modifyDOMError(msg, DOMError.SEVERITY_WARNING,
1354: null, fCurrentNode);
1355: fDOMErrorHandler
1356: .handleError((DOMError) fDOMError);
1357: }
1358: }
1359: // split CDATA section
1360: _printer.printText("]]]]><![CDATA[>");
1361: index += 2;
1362: continue;
1363: }
1364:
1365: if (!XMLChar.isValid(ch)) {
1366: // check if it is surrogate
1367: if (++index < length) {
1368: surrogates(ch, text.charAt(index));
1369: } else {
1370: fatalError("The character '" + (char) ch
1371: + "' is an invalid XML character");
1372: }
1373: continue;
1374: } else {
1375: if ((ch >= ' ' && _encodingInfo.isPrintable((char) ch) && ch != 0xF7)
1376: || ch == '\n' || ch == '\r' || ch == '\t') {
1377: _printer.printText((char) ch);
1378: } else {
1379:
1380: // The character is not printable -- split CDATA section
1381: _printer.printText("]]>&#x");
1382: _printer.printText(Integer.toHexString(ch));
1383: _printer.printText(";<![CDATA[");
1384: }
1385: }
1386: }
1387: }
1388:
1389: protected void surrogates(int high, int low) throws IOException {
1390: if (XMLChar.isHighSurrogate(high)) {
1391: if (!XMLChar.isLowSurrogate(low)) {
1392: //Invalid XML
1393: fatalError("The character '" + (char) low
1394: + "' is an invalid XML character");
1395: } else {
1396: int supplemental = XMLChar.supplemental((char) high,
1397: (char) low);
1398: if (!XMLChar.isValid(supplemental)) {
1399: //Invalid XML
1400: fatalError("The character '" + (char) supplemental
1401: + "' is an invalid XML character");
1402: } else {
1403: if (content().inCData) {
1404: _printer.printText("]]>&#x");
1405: _printer.printText(Integer
1406: .toHexString(supplemental));
1407: _printer.printText(";<![CDATA[");
1408: } else {
1409: printHex(supplemental);
1410: }
1411: }
1412: }
1413: } else {
1414: fatalError("The character '" + (char) high
1415: + "' is an invalid XML character");
1416: }
1417:
1418: }
1419:
1420: /**
1421: * Called to print additional text with whitespace handling.
1422: * If spaces are preserved, the text is printed as if by calling
1423: * {@link #printText(String,boolean,boolean)} with a call to {@link Printer#breakLine}
1424: * for each new line. If spaces are not preserved, the text is
1425: * broken at space boundaries if longer than the line width;
1426: * Multiple spaces are printed as such, but spaces at beginning
1427: * of line are removed.
1428: *
1429: * @param text The text to print
1430: * @param preserveSpace Space preserving flag
1431: * @param unescaped Print unescaped
1432: */
1433: protected void printText(char[] chars, int start, int length,
1434: boolean preserveSpace, boolean unescaped)
1435: throws IOException {
1436: int index;
1437: char ch;
1438:
1439: if (preserveSpace) {
1440: // Preserving spaces: the text must print exactly as it is,
1441: // without breaking when spaces appear in the text and without
1442: // consolidating spaces. If a line terminator is used, a line
1443: // break will occur.
1444: while (length-- > 0) {
1445: ch = chars[start];
1446: ++start;
1447: if (ch == '\n' || ch == '\r' || unescaped)
1448: _printer.printText(ch);
1449: else
1450: printEscaped(ch);
1451: }
1452: } else {
1453: // Not preserving spaces: print one part at a time, and
1454: // use spaces between parts to break them into different
1455: // lines. Spaces at beginning of line will be stripped
1456: // by printing mechanism. Line terminator is treated
1457: // no different than other text part.
1458: while (length-- > 0) {
1459: ch = chars[start];
1460: ++start;
1461: if (ch == ' ' || ch == '\f' || ch == '\t' || ch == '\n'
1462: || ch == '\r')
1463: _printer.printSpace();
1464: else if (unescaped)
1465: _printer.printText(ch);
1466: else
1467: printEscaped(ch);
1468: }
1469: }
1470: }
1471:
1472: protected void printText(String text, boolean preserveSpace,
1473: boolean unescaped) throws IOException {
1474: int index;
1475: char ch;
1476:
1477: if (preserveSpace) {
1478: // Preserving spaces: the text must print exactly as it is,
1479: // without breaking when spaces appear in the text and without
1480: // consolidating spaces. If a line terminator is used, a line
1481: // break will occur.
1482: for (index = 0; index < text.length(); ++index) {
1483: ch = text.charAt(index);
1484: if (ch == '\n' || ch == '\r' || unescaped)
1485: _printer.printText(ch);
1486: else
1487: printEscaped(ch);
1488: }
1489: } else {
1490: // Not preserving spaces: print one part at a time, and
1491: // use spaces between parts to break them into different
1492: // lines. Spaces at beginning of line will be stripped
1493: // by printing mechanism. Line terminator is treated
1494: // no different than other text part.
1495: for (index = 0; index < text.length(); ++index) {
1496: ch = text.charAt(index);
1497: if (ch == ' ' || ch == '\f' || ch == '\t' || ch == '\n'
1498: || ch == '\r')
1499: _printer.printSpace();
1500: else if (unescaped)
1501: _printer.printText(ch);
1502: else
1503: printEscaped(ch);
1504: }
1505: }
1506: }
1507:
1508: /**
1509: * Print a document type public or system identifier URL.
1510: * Encapsulates the URL in double quotes, escapes non-printing
1511: * characters and print it equivalent to {@link #printText}.
1512: *
1513: * @param url The document type url to print
1514: */
1515: protected void printDoctypeURL(String url) throws IOException {
1516: int i;
1517:
1518: _printer.printText('"');
1519: for (i = 0; i < url.length(); ++i) {
1520: if (url.charAt(i) == '"' || url.charAt(i) < 0x20
1521: || url.charAt(i) > 0x7F) {
1522: _printer.printText('%');
1523: _printer.printText(Integer.toHexString(url.charAt(i)));
1524: } else
1525: _printer.printText(url.charAt(i));
1526: }
1527: _printer.printText('"');
1528: }
1529:
1530: protected void printEscaped(int ch) throws IOException {
1531: String charRef;
1532: // If there is a suitable entity reference for this
1533: // character, print it. The list of available entity
1534: // references is almost but not identical between
1535: // XML and HTML.
1536: charRef = getEntityRef(ch);
1537: if (charRef != null) {
1538: _printer.printText('&');
1539: _printer.printText(charRef);
1540: _printer.printText(';');
1541: } else if ((ch >= ' ' && _encodingInfo.isPrintable((char) ch) && ch != 0xF7)
1542: || ch == '\n' || ch == '\r' || ch == '\t') {
1543: // Non printables are below ASCII space but not tab or line
1544: // terminator, ASCII delete, or above a certain Unicode threshold.
1545: if (ch < 0x10000) {
1546: _printer.printText((char) ch);
1547: } else {
1548: _printer
1549: .printText((char) (((ch - 0x10000) >> 10) + 0xd800));
1550: _printer
1551: .printText((char) (((ch - 0x10000) & 0x3ff) + 0xdc00));
1552: }
1553: } else {
1554: printHex(ch);
1555: }
1556: }
1557:
1558: /**
1559: * Escapes chars
1560: */
1561: final void printHex(int ch) throws IOException {
1562: _printer.printText("&#x");
1563: _printer.printText(Integer.toHexString(ch));
1564: _printer.printText(';');
1565:
1566: }
1567:
1568: /**
1569: * Escapes a string so it may be printed as text content or attribute
1570: * value. Non printable characters are escaped using character references.
1571: * Where the format specifies a deault entity reference, that reference
1572: * is used (e.g. <tt>&lt;</tt>).
1573: *
1574: * @param source The string to escape
1575: */
1576: protected void printEscaped(String source) throws IOException {
1577: for (int i = 0; i < source.length(); ++i) {
1578: int ch = source.charAt(i);
1579: if ((ch & 0xfc00) == 0xd800 && i + 1 < source.length()) {
1580: int lowch = source.charAt(i + 1);
1581: if ((lowch & 0xfc00) == 0xdc00) {
1582: ch = 0x10000 + ((ch - 0xd800) << 10) + lowch
1583: - 0xdc00;
1584: i++;
1585: }
1586: }
1587: printEscaped(ch);
1588: }
1589: }
1590:
1591: //--------------------------------//
1592: // Element state handling methods //
1593: //--------------------------------//
1594:
1595: /**
1596: * Return the state of the current element.
1597: *
1598: * @return Current element state
1599: */
1600: protected ElementState getElementState() {
1601: return _elementStates[_elementStateCount];
1602: }
1603:
1604: /**
1605: * Enter a new element state for the specified element.
1606: * Tag name and space preserving is specified, element
1607: * state is initially empty.
1608: *
1609: * @return Current element state, or null
1610: */
1611: protected ElementState enterElementState(String namespaceURI,
1612: String localName, String rawName, boolean preserveSpace) {
1613: ElementState state;
1614:
1615: if (_elementStateCount + 1 == _elementStates.length) {
1616: ElementState[] newStates;
1617:
1618: // Need to create a larger array of states. This does not happen
1619: // often, unless the document is really deep.
1620: newStates = new ElementState[_elementStates.length + 10];
1621: for (int i = 0; i < _elementStates.length; ++i)
1622: newStates[i] = _elementStates[i];
1623: for (int i = _elementStates.length; i < newStates.length; ++i)
1624: newStates[i] = new ElementState();
1625: _elementStates = newStates;
1626: }
1627:
1628: ++_elementStateCount;
1629: state = _elementStates[_elementStateCount];
1630: state.namespaceURI = namespaceURI;
1631: state.localName = localName;
1632: state.rawName = rawName;
1633: state.preserveSpace = preserveSpace;
1634: state.empty = true;
1635: state.afterElement = false;
1636: state.afterComment = false;
1637: state.doCData = state.inCData = false;
1638: state.unescaped = false;
1639: state.prefixes = _prefixes;
1640:
1641: _prefixes = null;
1642: return state;
1643: }
1644:
1645: /**
1646: * Leave the current element state and return to the
1647: * state of the parent element. If this was the root
1648: * element, return to the state of the document.
1649: *
1650: * @return Previous element state
1651: */
1652: protected ElementState leaveElementState() {
1653: if (_elementStateCount > 0) {
1654: /*Corrected by David Blondeau (blondeau@intalio.com)*/
1655: _prefixes = null;
1656: //_prefixes = _elementStates[ _elementStateCount ].prefixes;
1657: --_elementStateCount;
1658: return _elementStates[_elementStateCount];
1659: } else {
1660: String msg = DOMMessageFormatter.formatMessage(
1661: DOMMessageFormatter.SERIALIZER_DOMAIN, "Internal",
1662: null);
1663: throw new IllegalStateException(msg);
1664: }
1665: }
1666:
1667: /**
1668: * Returns true if in the state of the document.
1669: * Returns true before entering any element and after
1670: * leaving the root element.
1671: *
1672: * @return True if in the state of the document
1673: */
1674: protected boolean isDocumentState() {
1675: return _elementStateCount == 0;
1676: }
1677:
1678: /**
1679: * Returns the namespace prefix for the specified URI.
1680: * If the URI has been mapped to a prefix, returns the
1681: * prefix, otherwise returns null.
1682: *
1683: * @param namespaceURI The namespace URI
1684: * @return The namespace prefix if known, or null
1685: */
1686: protected String getPrefix(String namespaceURI) {
1687: String prefix;
1688:
1689: if (_prefixes != null) {
1690: prefix = (String) _prefixes.get(namespaceURI);
1691: if (prefix != null)
1692: return prefix;
1693: }
1694: if (_elementStateCount == 0)
1695: return null;
1696: else {
1697: for (int i = _elementStateCount; i > 0; --i) {
1698: if (_elementStates[i].prefixes != null) {
1699: prefix = (String) _elementStates[i].prefixes
1700: .get(namespaceURI);
1701: if (prefix != null)
1702: return prefix;
1703: }
1704: }
1705: }
1706: return null;
1707: }
1708:
1709: /**
1710: * The method modifies global DOM error object
1711: *
1712: * @param message
1713: * @param severity
1714: * @param type
1715: * @return a DOMError
1716: */
1717: protected DOMError modifyDOMError(String message, short severity,
1718: String type, Node node) {
1719: fDOMError.reset();
1720: fDOMError.fMessage = message;
1721: fDOMError.fType = type;
1722: fDOMError.fSeverity = severity;
1723: fDOMError.fLocator = new DOMLocatorImpl(-1, -1, -1, node, null);
1724: return (DOMError) fDOMError;
1725:
1726: }
1727:
1728: protected void fatalError(String message) throws IOException {
1729: if (fDOMErrorHandler != null) {
1730: modifyDOMError(message, DOMError.SEVERITY_FATAL_ERROR,
1731: null, fCurrentNode);
1732: fDOMErrorHandler.handleError((DOMError) fDOMError);
1733: } else {
1734: throw new IOException(message);
1735: }
1736: }
1737:
1738: /**
1739: * DOM level 3:
1740: * Check a node to determine if it contains unbound namespace prefixes.
1741: *
1742: * @param node The node to check for unbound namespace prefices
1743: */
1744: protected void checkUnboundNamespacePrefixedNode(Node node)
1745: throws IOException {
1746:
1747: }
1748:
1749: public void startAnchoring(String anchorId) {
1750: this .anchorId = anchorId;
1751: }
1752:
1753: public void stopAnchoring() {
1754: this .anchorId = null;
1755: }
1756:
1757: protected String appendAnchorIfNecessary(String elementName,
1758: String attributeName, String attributeValue) {
1759: if (anchorId != null) {
1760: // looking for an <a> or <form> tag element
1761: if (elementName.equalsIgnoreCase("a")
1762: || elementName.equalsIgnoreCase("form")) {
1763: // found an <a> or <form>, let's peek at the attributes it contains
1764: // does it contain either an "href" or "action" attribute
1765: if (attributeName.equalsIgnoreCase("href")
1766: || attributeName.equalsIgnoreCase("action")) {
1767: // found the attribute, now lets make sure it points back to a channel
1768: if (attributeValue.indexOf(".render.") != -1
1769: && attributeValue.indexOf("javascript:") == -1) {
1770: // this link points back to a channel, so let's
1771: // rewrite it and place back into the Attribute Object
1772: attributeValue += "#" + anchorId;
1773: }
1774: }
1775: }
1776: }
1777: return attributeValue;
1778: }
1779:
1780: /**
1781: * A portal property indicating whether or not to allow the disabling
1782: * of output escaping. When allowed, XSLT stylesheets can request
1783: * to disable output escaping, therefore enabling the direct pass-through
1784: * of markup such as HTML.
1785: */
1786: private boolean _allowDisableOutputEscaping;
1787:
1788: protected String anchorId = null;
1789:
1790: boolean html4compat = true; // make script tags in an xhtml document compatable with html 4
1791: }
|