0001: /*
0002: * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package com.sun.xml.internal.stream.writers;
0027:
0028: import java.io.FileOutputStream;
0029: import java.io.IOException;
0030: import java.io.OutputStream;
0031: import java.io.OutputStreamWriter;
0032: import java.io.Writer;
0033: import java.nio.charset.Charset;
0034: import java.nio.charset.CharsetEncoder;
0035: import java.util.AbstractMap;
0036: import java.util.ArrayList;
0037: import java.util.Iterator;
0038: import java.util.Random;
0039: import java.util.Vector;
0040: import java.util.Set;
0041: import java.util.Iterator;
0042:
0043: import javax.xml.XMLConstants;
0044: import javax.xml.namespace.NamespaceContext;
0045: import javax.xml.stream.XMLOutputFactory;
0046: import javax.xml.stream.XMLStreamException;
0047: import javax.xml.stream.XMLStreamWriter;
0048: import javax.xml.transform.stream.StreamResult;
0049:
0050: import com.sun.org.apache.xerces.internal.impl.Constants;
0051: import com.sun.org.apache.xerces.internal.impl.PropertyManager;
0052: import com.sun.org.apache.xerces.internal.util.NamespaceSupport;
0053: import com.sun.org.apache.xerces.internal.util.SymbolTable;
0054: import com.sun.org.apache.xerces.internal.xni.QName;
0055:
0056: import com.sun.xml.internal.stream.util.ReadOnlyIterator;
0057:
0058: /**
0059: * This class implements a StAX XMLStreamWriter. It extends
0060: * <code>AbstractMap</code> in order to support a getter for
0061: * implementation-specific properties. For example, you can get
0062: * the underlying <code>OutputStream</code> by casting an instance
0063: * of this class to <code>Map</code> and calling
0064: * <code>getProperty(OUTPUTSTREAM_PROPERTY)</code>.
0065: *
0066: * @author Neeraj Bajaj
0067: * @author K.Venugopal
0068: * @author Santiago.Pericas-Geertsen@sun.com
0069: * @author Sunitha.Reddy@sun.com
0070: */
0071: public final class XMLStreamWriterImpl extends AbstractMap implements
0072: XMLStreamWriter {
0073:
0074: public static final String START_COMMENT = "<!--";
0075: public static final String END_COMMENT = "-->";
0076: public static final String DEFAULT_ENCODING = " encoding=\"utf-8\"";
0077: public static final String DEFAULT_XMLDECL = "<?xml version=\"1.0\" ?>";
0078: public static final String DEFAULT_XML_VERSION = "1.0";
0079: public static final char CLOSE_START_TAG = '>';
0080: public static final char OPEN_START_TAG = '<';
0081: public static final String OPEN_END_TAG = "</";
0082: public static final char CLOSE_END_TAG = '>';
0083: public static final String START_CDATA = "<![CDATA[";
0084: public static final String END_CDATA = "]]>";
0085: public static final String CLOSE_EMPTY_ELEMENT = "/>";
0086: public static final String SPACE = " ";
0087: public static final String UTF_8 = "UTF-8";
0088:
0089: public static final String OUTPUTSTREAM_PROPERTY = "sjsxp-outputstream";
0090:
0091: /**
0092: * This flag can be used to turn escaping off for content. It does
0093: * not apply to attribute content.
0094: */
0095: boolean fEscapeCharacters = true;
0096:
0097: /**
0098: * Flag for the value of repairNamespace property
0099: */
0100: private boolean fIsRepairingNamespace = false;
0101:
0102: /**
0103: * Underlying Writer to which characters are written.
0104: */
0105: private Writer fWriter;
0106:
0107: /**
0108: * Underlying OutputStream to which <code>fWriter</code>
0109: * writes to. May be null if unknown.
0110: */
0111: private OutputStream fOutputStream = null;
0112:
0113: /**
0114: * Collects attributes when the writer is in reparing mode.
0115: */
0116: private ArrayList fAttributeCache;
0117:
0118: /**
0119: * Collects namespace declarations when the writer is in reparing mode.
0120: */
0121: private ArrayList fNamespaceDecls;
0122:
0123: /**
0124: * Namespace context encapsulating user specified context
0125: * and context built by the writer
0126: */
0127: private NamespaceContextImpl fNamespaceContext = null;
0128:
0129: private NamespaceSupport fInternalNamespaceContext = null;
0130:
0131: private Random fPrefixGen = null;
0132:
0133: /**
0134: * Reference to PropertyManager
0135: */
0136: private PropertyManager fPropertyManager = null;
0137:
0138: /**
0139: * Flag to track if start tag is opened
0140: */
0141: private boolean fStartTagOpened = false;
0142:
0143: /**
0144: * Boolean flag to indicate, if instance can be reused
0145: */
0146: private boolean fReuse;
0147:
0148: private SymbolTable fSymbolTable = new SymbolTable();
0149:
0150: private ElementStack fElementStack = new ElementStack(); //Change this .-Venu
0151:
0152: final private String DEFAULT_PREFIX = fSymbolTable.addSymbol("");
0153:
0154: private final ReadOnlyIterator fReadOnlyIterator = new ReadOnlyIterator();
0155:
0156: /**
0157: * In some cases, this charset encoder is used to determine if a char is
0158: * encodable by underlying writer. For example, an 8-bit char from the
0159: * extended ASCII set is not encodable by 7-bit ASCII encoder. Unencodable
0160: * chars are escaped using XML numeric entities.
0161: */
0162: private CharsetEncoder fEncoder = null;
0163:
0164: /**
0165: * Creates a new instance of XMLStreamWriterImpl. Uses platform's default
0166: * encoding.
0167: *
0168: * @param outputStream Underlying stream to write the bytes to
0169: * @param props Properties used by this writer
0170: */
0171: public XMLStreamWriterImpl(OutputStream outputStream,
0172: PropertyManager props) throws IOException {
0173:
0174: // cannot call this(outputStream, null, props); for constructor,
0175: // OutputStreamWriter charsetName cannot be null
0176:
0177: // use default encoding
0178: this (new OutputStreamWriter(outputStream), props);
0179: }
0180:
0181: /**
0182: * Creates a new instance of XMLStreamWriterImpl.
0183: *
0184: * @param outputStream Underlying stream to write the bytes
0185: * @param encoding Encoding used to convert chars into bytes
0186: * @param props Properties used by this writer
0187: */
0188: public XMLStreamWriterImpl(OutputStream outputStream,
0189: String encoding, PropertyManager props)
0190: throws java.io.IOException {
0191: this (new StreamResult(outputStream), encoding, props);
0192: }
0193:
0194: /**
0195: * Creates a new instance of XMLStreamWriterImpl using a Writer.
0196: *
0197: * @param writer Underlying writer to which chars are written
0198: * @param props Properties used by this writer
0199: */
0200: public XMLStreamWriterImpl(Writer writer, PropertyManager props)
0201: throws java.io.IOException {
0202: this (new StreamResult(writer), null, props);
0203: }
0204:
0205: /**
0206: * Creates a new instance of XMLStreamWriterImpl using a StreamResult.
0207: * A StreamResult encasupates an OutputStream, a Writer or a SystemId.
0208: *
0209: * @param writer Underlying writer to which chars are written
0210: * @param props Properties used by this writer
0211: */
0212: public XMLStreamWriterImpl(StreamResult sr, String encoding,
0213: PropertyManager props) throws java.io.IOException {
0214: setOutput(sr, encoding);
0215: fPropertyManager = props;
0216: init();
0217: }
0218:
0219: /**
0220: * Initialize an instance of this XMLStreamWriter. Allocate new instances
0221: * for all the data structures. Set internal flags based on property values.
0222: */
0223: private void init() {
0224: fReuse = false;
0225: fNamespaceDecls = new ArrayList();
0226: fPrefixGen = new Random();
0227: fAttributeCache = new ArrayList();
0228: fInternalNamespaceContext = new NamespaceSupport();
0229: fNamespaceContext = new NamespaceContextImpl();
0230: fNamespaceContext.internalContext = fInternalNamespaceContext;
0231:
0232: // Set internal state based on property values
0233: Boolean ob = (Boolean) fPropertyManager
0234: .getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES);
0235: fIsRepairingNamespace = ob.booleanValue();
0236: ob = (Boolean) fPropertyManager
0237: .getProperty(Constants.ESCAPE_CHARACTERS);
0238: setEscapeCharacters(ob.booleanValue());
0239: }
0240:
0241: /**
0242: * Reset this instance so that it can be re-used. Do not read properties
0243: * again. The method <code>setOutput(StreamResult, encoding)</code> must
0244: * be called after this one.
0245: */
0246: public void reset() {
0247: reset(false);
0248: }
0249:
0250: /**
0251: * Reset this instance so that it can be re-used. Clears but does not
0252: * re-allocate internal data structures.
0253: *
0254: * @param resetProperties Indicates if properties should be read again
0255: */
0256: void reset(boolean resetProperties) {
0257: if (!fReuse) {
0258: throw new java.lang.IllegalStateException(
0259: "close() Must be called before calling reset()");
0260: }
0261:
0262: fReuse = false;
0263: fNamespaceDecls.clear();
0264: fAttributeCache.clear();
0265:
0266: // reset Element/NamespaceContext stacks
0267: fElementStack.clear();
0268: fInternalNamespaceContext.reset();
0269:
0270: fStartTagOpened = false;
0271: fNamespaceContext.userContext = null;
0272:
0273: if (resetProperties) {
0274: Boolean ob = (Boolean) fPropertyManager
0275: .getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES);
0276: fIsRepairingNamespace = ob.booleanValue();
0277: ob = (Boolean) fPropertyManager
0278: .getProperty(Constants.ESCAPE_CHARACTERS);
0279: setEscapeCharacters(ob.booleanValue());
0280: }
0281: }
0282:
0283: /**
0284: * Use a StreamResult to initialize the output for this XMLStreamWriter. Check
0285: * for OutputStream, Writer and then systemId, in that order.
0286: *
0287: * @param sr StreamResult encapsulating output information
0288: * @param encoding Encoding to be used except when a Writer is available
0289: */
0290: public void setOutput(StreamResult sr, String encoding)
0291: throws IOException {
0292:
0293: if (sr.getOutputStream() != null) {
0294: setOutputUsingStream(sr.getOutputStream(), encoding);
0295: } else if (sr.getWriter() != null) {
0296: setOutputUsingWriter(sr.getWriter());
0297: } else if (sr.getSystemId() != null) {
0298: setOutputUsingStream(
0299: new FileOutputStream(sr.getSystemId()), encoding);
0300: }
0301: }
0302:
0303: private void setOutputUsingWriter(Writer writer) throws IOException {
0304: fWriter = writer;
0305:
0306: if (writer instanceof OutputStreamWriter) {
0307: String charset = ((OutputStreamWriter) writer)
0308: .getEncoding();
0309: if (charset != null && !charset.equalsIgnoreCase("utf-8")) {
0310: fEncoder = Charset.forName(charset).newEncoder();
0311: }
0312: }
0313: }
0314:
0315: /**
0316: * Utility method to create a writer when passed an OutputStream. Make
0317: * sure to wrap an <code>OutputStreamWriter</code> using an
0318: * <code>XMLWriter</code> for performance reasons.
0319: *
0320: * @param os Underlying OutputStream
0321: * @param encoding Encoding used to convert chars into bytes
0322: */
0323: private void setOutputUsingStream(OutputStream os, String encoding)
0324: throws IOException {
0325: fOutputStream = os;
0326:
0327: if (encoding != null) {
0328: if (encoding.equalsIgnoreCase("utf-8")) {
0329: fWriter = new UTF8OutputStreamWriter(os);
0330: } else {
0331: fWriter = new XMLWriter(new OutputStreamWriter(os,
0332: encoding));
0333: fEncoder = Charset.forName(encoding).newEncoder();
0334: }
0335: } else {
0336: encoding = System.getProperty("file.encoding");
0337: if (encoding != null && encoding.equalsIgnoreCase("utf-8")) {
0338: fWriter = new UTF8OutputStreamWriter(os);
0339: } else {
0340: fWriter = new XMLWriter(new OutputStreamWriter(os));
0341: }
0342: }
0343: }
0344:
0345: /** Can this instance be reused
0346: *
0347: * @return boolean boolean value to indicate if this instance can be reused or not
0348: */
0349: public boolean canReuse() {
0350: return fReuse;
0351: }
0352:
0353: public void setEscapeCharacters(boolean escape) {
0354: fEscapeCharacters = escape;
0355: }
0356:
0357: public boolean getEscapeCharacters() {
0358: return fEscapeCharacters;
0359: }
0360:
0361: /**
0362: * Close this XMLStreamWriter by closing underlying writer.
0363: */
0364: public void close() throws XMLStreamException {
0365: if (fWriter != null) {
0366: try {
0367: //fWriter.close();
0368: fWriter.flush();
0369: } catch (IOException e) {
0370: throw new XMLStreamException(e);
0371: }
0372: }
0373: fWriter = null;
0374: fOutputStream = null;
0375: fNamespaceDecls.clear();
0376: fAttributeCache.clear();
0377: fElementStack.clear();
0378: fInternalNamespaceContext.reset();
0379: fReuse = true;
0380: }
0381:
0382: /**
0383: * Flush this XMLStreamWriter by flushin underlying writer.
0384: */
0385: public void flush() throws XMLStreamException {
0386: try {
0387: fWriter.flush();
0388: } catch (IOException e) {
0389: throw new XMLStreamException(e);
0390: }
0391: }
0392:
0393: /**
0394: * Return <code>NamespaceContext</code> being used by the writer.
0395: *
0396: * @return NamespaceContext
0397: */
0398: public NamespaceContext getNamespaceContext() {
0399: return fNamespaceContext;
0400: }
0401:
0402: /**
0403: * Return a prefix associated with specified uri, or null if the
0404: * uri is unknown.
0405: *
0406: * @param uri The namespace uri
0407: * @throws XMLStreamException if uri specified is "" or null
0408: */
0409: public String getPrefix(String uri) throws XMLStreamException {
0410: return fNamespaceContext.getPrefix(uri);
0411: }
0412:
0413: /**
0414: * Returns value associated with the specified property name.
0415: *
0416: * @param str Property name
0417: * @throws IllegalArgumentException if the specified property is not supported
0418: * @return value associated with the specified property.
0419: */
0420: public Object getProperty(String str)
0421: throws IllegalArgumentException {
0422: if (str == null) {
0423: throw new NullPointerException();
0424: }
0425:
0426: if (!fPropertyManager.containsProperty(str)) {
0427: throw new IllegalArgumentException("Property '" + str
0428: + "' is not supported");
0429: }
0430:
0431: return fPropertyManager.getProperty(str);
0432: }
0433:
0434: /**
0435: * Set the specified URI as default namespace in the current namespace context.
0436: *
0437: * @param uri Namespace URI
0438: */
0439: public void setDefaultNamespace(String uri)
0440: throws XMLStreamException {
0441: if (uri != null) {
0442: uri = fSymbolTable.addSymbol(uri);
0443: }
0444:
0445: if (fIsRepairingNamespace) {
0446: if (isDefaultNamespace(uri)) {
0447: return;
0448: }
0449:
0450: QName qname = new QName();
0451: qname.setValues(DEFAULT_PREFIX, "xmlns", null, uri);
0452: fNamespaceDecls.add(qname);
0453: } else {
0454: fInternalNamespaceContext
0455: .declarePrefix(DEFAULT_PREFIX, uri);
0456: }
0457: }
0458:
0459: /**
0460: * Sets the current <code>NamespaceContext</code> for prefix and uri bindings.
0461: * This context becomes the root namespace context for writing and
0462: * will replace the current root namespace context. Subsequent calls
0463: * to setPrefix and setDefaultNamespace will bind namespaces using
0464: * the context passed to the method as the root context for resolving
0465: * namespaces. This method may only be called once at the start of the
0466: * document. It does not cause the namespaces to be declared. If a
0467: * namespace URI to prefix mapping is found in the namespace context
0468: * it is treated as declared and the prefix may be used by the
0469: * <code>XMLStreamWriter</code>.
0470: *
0471: * @param namespaceContext the namespace context to use for this writer, may not be null
0472: * @throws XMLStreamException
0473: */
0474: public void setNamespaceContext(NamespaceContext namespaceContext)
0475: throws XMLStreamException {
0476: fNamespaceContext.userContext = namespaceContext;
0477: }
0478:
0479: /**
0480: * Sets the prefix the uri is bound to. This prefix is bound in the scope of
0481: * the current START_ELEMENT / END_ELEMENT pair. If this method is called before
0482: * a START_ELEMENT has been written the prefix is bound in the root scope.
0483: *
0484: * @param prefix
0485: * @param uri
0486: * @throws XMLStreamException
0487: */
0488: public void setPrefix(String prefix, String uri)
0489: throws XMLStreamException {
0490:
0491: if (prefix == null) {
0492: throw new XMLStreamException("Prefix cannot be null");
0493: }
0494:
0495: if (uri == null) {
0496: throw new XMLStreamException("URI cannot be null");
0497: }
0498:
0499: prefix = fSymbolTable.addSymbol(prefix);
0500: uri = fSymbolTable.addSymbol(uri);
0501:
0502: if (fIsRepairingNamespace) {
0503: String tmpURI = fInternalNamespaceContext.getURI(prefix);
0504:
0505: if ((tmpURI != null) && (tmpURI == uri)) {
0506: return;
0507: }
0508:
0509: if (checkUserNamespaceContext(prefix, uri))
0510: return;
0511: QName qname = new QName();
0512: qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null,
0513: uri);
0514: fNamespaceDecls.add(qname);
0515:
0516: return;
0517: }
0518:
0519: fInternalNamespaceContext.declarePrefix(prefix, uri);
0520: }
0521:
0522: public void writeAttribute(String localName, String value)
0523: throws XMLStreamException {
0524: try {
0525: if (!fStartTagOpened) {
0526: throw new XMLStreamException(
0527: "Attribute not associated with any element");
0528: }
0529:
0530: if (fIsRepairingNamespace) {
0531: Attribute attr = new Attribute(value); // Revisit:Dont create new one's. Reuse.-Venu
0532: attr.setValues(null, localName, null, null);
0533: fAttributeCache.add(attr);
0534:
0535: return;
0536: }
0537:
0538: fWriter.write(" ");
0539: fWriter.write(localName);
0540: fWriter.write("=\"");
0541: writeXMLContent(value, true, // true = escapeChars
0542: true); // true = escapeDoubleQuotes
0543: fWriter.write("\"");
0544: } catch (IOException e) {
0545: throw new XMLStreamException(e);
0546: }
0547: }
0548:
0549: public void writeAttribute(String namespaceURI, String localName,
0550: String value) throws XMLStreamException {
0551: try {
0552: if (!fStartTagOpened) {
0553: throw new XMLStreamException(
0554: "Attribute not associated with any element");
0555: }
0556:
0557: if (namespaceURI == null) {
0558: throw new XMLStreamException(
0559: "NamespaceURI cannot be null");
0560: }
0561:
0562: namespaceURI = fSymbolTable.addSymbol(namespaceURI);
0563:
0564: String prefix = fInternalNamespaceContext
0565: .getPrefix(namespaceURI);
0566:
0567: if (!fIsRepairingNamespace) {
0568: if (prefix == null) {
0569: throw new XMLStreamException(
0570: "Prefix cannot be null");
0571: }
0572:
0573: writeAttributeWithPrefix(prefix, localName, value);
0574: } else {
0575: Attribute attr = new Attribute(value);
0576: attr.setValues(null, localName, null, namespaceURI);
0577: fAttributeCache.add(attr);
0578: }
0579: } catch (IOException e) {
0580: throw new XMLStreamException(e);
0581: }
0582: }
0583:
0584: private void writeAttributeWithPrefix(String prefix,
0585: String localName, String value) throws IOException {
0586: fWriter.write(SPACE);
0587:
0588: if ((prefix != null)
0589: && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
0590: fWriter.write(prefix);
0591: fWriter.write(":");
0592: }
0593:
0594: fWriter.write(localName);
0595: fWriter.write("=\"");
0596: writeXMLContent(value, true, // true = escapeChars
0597: true); // true = escapeDoubleQuotes
0598: fWriter.write("\"");
0599: }
0600:
0601: public void writeAttribute(String prefix, String namespaceURI,
0602: String localName, String value) throws XMLStreamException {
0603: try {
0604: if (!fStartTagOpened) {
0605: throw new XMLStreamException(
0606: "Attribute not associated with any element");
0607: }
0608:
0609: if (namespaceURI == null) {
0610: throw new XMLStreamException(
0611: "NamespaceURI cannot be null");
0612: }
0613:
0614: if (localName == null) {
0615: throw new XMLStreamException(
0616: "Local name cannot be null");
0617: }
0618:
0619: if (!fIsRepairingNamespace) {
0620: if (prefix == null || prefix.equals("")) {
0621: if (!namespaceURI.equals("")) {
0622: throw new XMLStreamException(
0623: "prefix cannot be null or empty");
0624: } else {
0625: writeAttributeWithPrefix(null, localName, value);
0626: return;
0627: }
0628: }
0629:
0630: if (!prefix.equals(XMLConstants.XML_NS_PREFIX)
0631: || !namespaceURI
0632: .equals(XMLConstants.XML_NS_URI)) {
0633:
0634: prefix = fSymbolTable.addSymbol(prefix);
0635: namespaceURI = fSymbolTable.addSymbol(namespaceURI);
0636:
0637: if (fInternalNamespaceContext
0638: .containsPrefixInCurrentContext(prefix)) {
0639:
0640: String tmpURI = fInternalNamespaceContext
0641: .getURI(prefix);
0642:
0643: if (tmpURI != null && tmpURI != namespaceURI) {
0644: throw new XMLStreamException("Prefix "
0645: + prefix + " is "
0646: + "already bound to " + tmpURI
0647: + ". Trying to rebind it to "
0648: + namespaceURI + " is an error.");
0649: }
0650: }
0651: fInternalNamespaceContext.declarePrefix(prefix,
0652: namespaceURI);
0653: }
0654: writeAttributeWithPrefix(prefix, localName, value);
0655: } else {
0656: if (prefix != null) {
0657: prefix = fSymbolTable.addSymbol(prefix);
0658: }
0659:
0660: namespaceURI = fSymbolTable.addSymbol(namespaceURI);
0661:
0662: Attribute attr = new Attribute(value);
0663: attr.setValues(prefix, localName, null, namespaceURI);
0664: fAttributeCache.add(attr);
0665: }
0666: } catch (IOException e) {
0667: throw new XMLStreamException(e);
0668: }
0669: }
0670:
0671: public void writeCData(String cdata) throws XMLStreamException {
0672: try {
0673: if (cdata == null) {
0674: throw new XMLStreamException("cdata cannot be null");
0675: }
0676:
0677: if (fStartTagOpened) {
0678: closeStartTag();
0679: }
0680:
0681: fWriter.write(START_CDATA);
0682: fWriter.write(cdata);
0683: fWriter.write(END_CDATA);
0684: } catch (IOException e) {
0685: throw new XMLStreamException(e);
0686: }
0687: }
0688:
0689: public void writeCharacters(String data) throws XMLStreamException {
0690: try {
0691: if (fStartTagOpened) {
0692: closeStartTag();
0693: }
0694:
0695: writeXMLContent(data);
0696: } catch (IOException e) {
0697: throw new XMLStreamException(e);
0698: }
0699: }
0700:
0701: public void writeCharacters(char[] data, int start, int len)
0702: throws XMLStreamException {
0703: try {
0704: if (fStartTagOpened) {
0705: closeStartTag();
0706: }
0707:
0708: writeXMLContent(data, start, len, fEscapeCharacters);
0709: } catch (IOException e) {
0710: throw new XMLStreamException(e);
0711: }
0712: }
0713:
0714: public void writeComment(String comment) throws XMLStreamException {
0715: try {
0716: if (fStartTagOpened) {
0717: closeStartTag();
0718: }
0719:
0720: fWriter.write(START_COMMENT);
0721:
0722: if (comment != null) {
0723: fWriter.write(comment);
0724: }
0725:
0726: fWriter.write(END_COMMENT);
0727: } catch (IOException e) {
0728: throw new XMLStreamException(e);
0729: }
0730: }
0731:
0732: public void writeDTD(String dtd) throws XMLStreamException {
0733: try {
0734: if (fStartTagOpened) {
0735: closeStartTag();
0736: }
0737:
0738: fWriter.write(dtd);
0739: } catch (IOException e) {
0740: throw new XMLStreamException(e);
0741: }
0742: }
0743:
0744: /*
0745: * Write default Namespace.
0746: *
0747: * If namespaceURI == null,
0748: * then it is assumed to be equivilent to {@link XMLConstants.NULL_NS_URI},
0749: * i.e. there is no Namespace.
0750: *
0751: * @param namespaceURI NamespaceURI to declare.
0752: *
0753: * @throws XMLStreamException
0754: *
0755: * @see <a href="http://www.w3.org/TR/REC-xml-names/#defaulting">
0756: * Namespaces in XML, 5.2 Namespace Defaulting</a>
0757: */
0758: public void writeDefaultNamespace(String namespaceURI)
0759: throws XMLStreamException {
0760:
0761: // normalize namespaceURI
0762: String namespaceURINormalized = null;
0763: if (namespaceURI == null) {
0764: namespaceURINormalized = ""; // XMLConstants.NULL_NS_URI
0765: } else {
0766: namespaceURINormalized = namespaceURI;
0767: }
0768:
0769: try {
0770: if (!fStartTagOpened) {
0771: throw new IllegalStateException(
0772: "Namespace Attribute not associated with any element");
0773: }
0774:
0775: if (fIsRepairingNamespace) {
0776: QName qname = new QName();
0777: qname.setValues(XMLConstants.DEFAULT_NS_PREFIX,
0778: XMLConstants.XMLNS_ATTRIBUTE, null,
0779: namespaceURINormalized);
0780: fNamespaceDecls.add(qname);
0781:
0782: return;
0783: }
0784:
0785: namespaceURINormalized = fSymbolTable
0786: .addSymbol(namespaceURINormalized);
0787:
0788: if (fInternalNamespaceContext
0789: .containsPrefixInCurrentContext("")) {
0790:
0791: String tmp = fInternalNamespaceContext.getURI("");
0792:
0793: if (tmp != null && tmp != namespaceURINormalized) {
0794: throw new XMLStreamException(
0795: "xmlns has been already bound to " + tmp
0796: + ". Rebinding it to "
0797: + namespaceURINormalized
0798: + " is an error");
0799: }
0800: }
0801: fInternalNamespaceContext.declarePrefix("",
0802: namespaceURINormalized);
0803:
0804: // use common namespace code with a prefix == null for xmlns="..."
0805: writenamespace(null, namespaceURINormalized);
0806: } catch (IOException e) {
0807: throw new XMLStreamException(e);
0808: }
0809: }
0810:
0811: public void writeEmptyElement(String localName)
0812: throws XMLStreamException {
0813: try {
0814: if (fStartTagOpened) {
0815: closeStartTag();
0816: }
0817:
0818: openStartTag();
0819: fElementStack.push(null, localName, null, null, true);
0820: fInternalNamespaceContext.pushContext();
0821:
0822: if (!fIsRepairingNamespace) {
0823: fWriter.write(localName);
0824: }
0825: } catch (IOException e) {
0826: throw new XMLStreamException(e);
0827: }
0828: }
0829:
0830: public void writeEmptyElement(String namespaceURI, String localName)
0831: throws XMLStreamException {
0832: if (namespaceURI == null) {
0833: throw new XMLStreamException("NamespaceURI cannot be null");
0834: }
0835:
0836: namespaceURI = fSymbolTable.addSymbol(namespaceURI);
0837:
0838: String prefix = fNamespaceContext.getPrefix(namespaceURI);
0839: writeEmptyElement(prefix, localName, namespaceURI);
0840: }
0841:
0842: public void writeEmptyElement(String prefix, String localName,
0843: String namespaceURI) throws XMLStreamException {
0844: try {
0845: if (localName == null) {
0846: throw new XMLStreamException(
0847: "Local Name cannot be null");
0848: }
0849:
0850: if (namespaceURI == null) {
0851: throw new XMLStreamException(
0852: "NamespaceURI cannot be null");
0853: }
0854:
0855: if (prefix != null) {
0856: prefix = fSymbolTable.addSymbol(prefix);
0857: }
0858:
0859: namespaceURI = fSymbolTable.addSymbol(namespaceURI);
0860:
0861: if (fStartTagOpened) {
0862: closeStartTag();
0863: }
0864:
0865: openStartTag();
0866:
0867: fElementStack.push(prefix, localName, null, namespaceURI,
0868: true);
0869: fInternalNamespaceContext.pushContext();
0870:
0871: if (!fIsRepairingNamespace) {
0872: if (prefix == null) {
0873: throw new XMLStreamException("NamespaceURI "
0874: + namespaceURI
0875: + " has not been bound to any prefix");
0876: }
0877: } else {
0878: return;
0879: }
0880:
0881: if ((prefix != null)
0882: && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
0883: fWriter.write(prefix);
0884: fWriter.write(":");
0885: }
0886:
0887: fWriter.write(localName);
0888: } catch (IOException e) {
0889: throw new XMLStreamException(e);
0890: }
0891: }
0892:
0893: public void writeEndDocument() throws XMLStreamException {
0894: try {
0895: if (fStartTagOpened) {
0896: closeStartTag();
0897: }
0898:
0899: ElementState elem = null;
0900:
0901: while (!fElementStack.empty()) {
0902: elem = (ElementState) fElementStack.pop();
0903: fInternalNamespaceContext.popContext();
0904:
0905: if (elem.isEmpty) {
0906: //fWriter.write(CLOSE_EMPTY_ELEMENT);
0907: } else {
0908: fWriter.write(OPEN_END_TAG);
0909:
0910: if ((elem.prefix != null)
0911: && !(elem.prefix).equals("")) {
0912: fWriter.write(elem.prefix);
0913: fWriter.write(":");
0914: }
0915:
0916: fWriter.write(elem.localpart);
0917: fWriter.write(CLOSE_END_TAG);
0918: }
0919: }
0920: } catch (IOException e) {
0921: throw new XMLStreamException(e);
0922: } catch (ArrayIndexOutOfBoundsException e) {
0923: throw new XMLStreamException("No more elements to write");
0924: }
0925: }
0926:
0927: public void writeEndElement() throws XMLStreamException {
0928: try {
0929: if (fStartTagOpened) {
0930: closeStartTag();
0931: }
0932:
0933: ElementState currentElement = (ElementState) fElementStack
0934: .pop();
0935:
0936: if (currentElement == null) {
0937: throw new XMLStreamException(
0938: "No element was found to write");
0939: }
0940:
0941: if (currentElement.isEmpty) {
0942: //fWriter.write(CLOSE_EMPTY_ELEMENT);
0943: return;
0944: }
0945:
0946: fWriter.write(OPEN_END_TAG);
0947:
0948: if ((currentElement.prefix != null)
0949: && !(currentElement.prefix).equals("")) {
0950: fWriter.write(currentElement.prefix);
0951: fWriter.write(":");
0952: }
0953:
0954: fWriter.write(currentElement.localpart);
0955: fWriter.write(CLOSE_END_TAG);
0956: fInternalNamespaceContext.popContext();
0957: } catch (IOException e) {
0958: throw new XMLStreamException(e);
0959: } catch (ArrayIndexOutOfBoundsException e) {
0960: throw new XMLStreamException(
0961: "No element was found to write: " + e.toString(), e);
0962: }
0963: }
0964:
0965: public void writeEntityRef(String refName)
0966: throws XMLStreamException {
0967: try {
0968: if (fStartTagOpened) {
0969: closeStartTag();
0970: }
0971:
0972: fWriter.write('&');
0973: fWriter.write(refName);
0974: fWriter.write(';');
0975: } catch (IOException e) {
0976: throw new XMLStreamException(e);
0977: }
0978: }
0979:
0980: /**
0981: * Write a Namespace declaration.
0982: *
0983: * If namespaceURI == null,
0984: * then it is assumed to be equivilent to {@link XMLConstants.NULL_NS_URI},
0985: * i.e. there is no Namespace.
0986: *
0987: * @param prefix Prefix to bind.
0988: * @param namespaceURI NamespaceURI to declare.
0989: *
0990: * @throws XMLStreamException
0991: *
0992: * @see <a href="http://www.w3.org/TR/REC-xml-names/#defaulting">
0993: * Namespaces in XML, 5.2 Namespace Defaulting</a>
0994: */
0995: public void writeNamespace(String prefix, String namespaceURI)
0996: throws XMLStreamException {
0997:
0998: // normalize namespaceURI
0999: String namespaceURINormalized = null;
1000: if (namespaceURI == null) {
1001: namespaceURINormalized = ""; // XMLConstants.NULL_NS_URI
1002: } else {
1003: namespaceURINormalized = namespaceURI;
1004: }
1005:
1006: try {
1007: QName qname = null;
1008:
1009: if (!fStartTagOpened) {
1010: throw new IllegalStateException(
1011: "Invalid state: start tag is not opened at writeNamespace("
1012: + prefix + ", "
1013: + namespaceURINormalized + ")");
1014: }
1015:
1016: // is this the default Namespace?
1017: if (prefix == null
1018: || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)
1019: || prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
1020: writeDefaultNamespace(namespaceURINormalized);
1021: return;
1022: }
1023:
1024: if (prefix.equals(XMLConstants.XML_NS_PREFIX)
1025: && namespaceURINormalized
1026: .equals(XMLConstants.XML_NS_URI))
1027: return;
1028:
1029: prefix = fSymbolTable.addSymbol(prefix);
1030: namespaceURINormalized = fSymbolTable
1031: .addSymbol(namespaceURINormalized);
1032:
1033: if (fIsRepairingNamespace) {
1034: String tmpURI = fInternalNamespaceContext
1035: .getURI(prefix);
1036:
1037: if ((tmpURI != null)
1038: && (tmpURI == namespaceURINormalized)) {
1039: return;
1040: }
1041:
1042: qname = new QName();
1043: qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE,
1044: null, namespaceURINormalized);
1045: fNamespaceDecls.add(qname);
1046:
1047: return;
1048: }
1049:
1050: if (fInternalNamespaceContext
1051: .containsPrefixInCurrentContext(prefix)) {
1052:
1053: String tmp = fInternalNamespaceContext.getURI(prefix);
1054:
1055: if (tmp != null && tmp != namespaceURINormalized) {
1056:
1057: throw new XMLStreamException("prefix " + prefix
1058: + " has been already bound to " + tmp
1059: + ". Rebinding it to "
1060: + namespaceURINormalized + " is an error");
1061: }
1062: }
1063:
1064: fInternalNamespaceContext.declarePrefix(prefix,
1065: namespaceURINormalized);
1066: writenamespace(prefix, namespaceURINormalized);
1067:
1068: } catch (IOException e) {
1069: throw new XMLStreamException(e);
1070: }
1071: }
1072:
1073: private void writenamespace(String prefix, String namespaceURI)
1074: throws IOException {
1075: fWriter.write(" xmlns");
1076:
1077: if ((prefix != null)
1078: && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
1079: fWriter.write(":");
1080: fWriter.write(prefix);
1081: }
1082:
1083: fWriter.write("=\"");
1084: writeXMLContent(namespaceURI, true, // true = escapeChars
1085: true); // true = escapeDoubleQuotes
1086: fWriter.write("\"");
1087: }
1088:
1089: public void writeProcessingInstruction(String target)
1090: throws XMLStreamException {
1091: try {
1092: if (fStartTagOpened) {
1093: closeStartTag();
1094: }
1095:
1096: if (target != null) {
1097: fWriter.write("<?");
1098: fWriter.write(target);
1099: fWriter.write("?>");
1100:
1101: return;
1102: }
1103: } catch (IOException e) {
1104: throw new XMLStreamException(e);
1105: }
1106:
1107: throw new XMLStreamException("PI target cannot be null");
1108: }
1109:
1110: /**
1111: * @param target
1112: * @param data
1113: * @throws XMLStreamException
1114: */
1115: public void writeProcessingInstruction(String target, String data)
1116: throws XMLStreamException {
1117: try {
1118: if (fStartTagOpened) {
1119: closeStartTag();
1120: }
1121:
1122: if ((target == null) || (data == null)) {
1123: throw new XMLStreamException("PI target cannot be null");
1124: }
1125:
1126: fWriter.write("<?");
1127: fWriter.write(target);
1128: fWriter.write(SPACE);
1129: fWriter.write(data);
1130: fWriter.write("?>");
1131: } catch (IOException e) {
1132: throw new XMLStreamException(e);
1133: }
1134: }
1135:
1136: /**
1137: * @throws XMLStreamException
1138: */
1139: public void writeStartDocument() throws XMLStreamException {
1140: try {
1141: fWriter.write(DEFAULT_XMLDECL);
1142: } catch (IOException ex) {
1143: throw new XMLStreamException(ex);
1144: }
1145: }
1146:
1147: /**
1148: * @param version
1149: * @throws XMLStreamException
1150: */
1151: public void writeStartDocument(String version)
1152: throws XMLStreamException {
1153: try {
1154: if ((version == null) || version.equals("")) {
1155: writeStartDocument();
1156:
1157: return;
1158: }
1159:
1160: fWriter.write("<?xml version=\"");
1161: fWriter.write(version);
1162: fWriter.write("\"");
1163:
1164: //fWriter.write(DEFAULT_ENCODING);
1165: fWriter.write("?>");
1166: } catch (IOException ex) {
1167: throw new XMLStreamException(ex);
1168: }
1169: }
1170:
1171: /**
1172: * @param encoding
1173: * @param version
1174: * @throws XMLStreamException
1175: */
1176: public void writeStartDocument(String encoding, String version)
1177: throws XMLStreamException {
1178: //Revisit : What about standalone ?
1179: try {
1180: if ((encoding == null) && (version == null)) {
1181: writeStartDocument();
1182:
1183: return;
1184: }
1185:
1186: if (encoding == null) {
1187: writeStartDocument(version);
1188:
1189: return;
1190: }
1191:
1192: String streamEncoding = null;
1193: if (fWriter instanceof OutputStreamWriter) {
1194: streamEncoding = ((OutputStreamWriter) fWriter)
1195: .getEncoding();
1196: } else if (fWriter instanceof UTF8OutputStreamWriter) {
1197: streamEncoding = ((UTF8OutputStreamWriter) fWriter)
1198: .getEncoding();
1199: } else if (fWriter instanceof XMLWriter) {
1200: streamEncoding = ((OutputStreamWriter) ((XMLWriter) fWriter)
1201: .getWriter()).getEncoding();
1202: }
1203:
1204: if (streamEncoding != null
1205: && !streamEncoding.equalsIgnoreCase(encoding)) {
1206: // If the equality check failed, check for charset encoding aliases
1207: boolean foundAlias = false;
1208: Set aliases = Charset.forName(encoding).aliases();
1209: for (Iterator it = aliases.iterator(); !foundAlias
1210: && it.hasNext();) {
1211: if (streamEncoding.equalsIgnoreCase((String) it
1212: .next())) {
1213: foundAlias = true;
1214: }
1215: }
1216: // If no alias matches the encoding name, then report error
1217: if (!foundAlias) {
1218: throw new XMLStreamException(
1219: "Underlying stream encoding '"
1220: + streamEncoding
1221: + "' and input paramter for writeStartDocument() method '"
1222: + encoding + "' do not match.");
1223: }
1224: }
1225:
1226: fWriter.write("<?xml version=\"");
1227:
1228: if ((version == null) || version.equals("")) {
1229: fWriter.write(DEFAULT_XML_VERSION);
1230: } else {
1231: fWriter.write(version);
1232: }
1233:
1234: if (!encoding.equals("")) {
1235: fWriter.write("\" encoding=\"");
1236: fWriter.write(encoding);
1237: }
1238:
1239: fWriter.write("\"?>");
1240: } catch (IOException ex) {
1241: throw new XMLStreamException(ex);
1242: }
1243: }
1244:
1245: /**
1246: * @param localName
1247: * @throws XMLStreamException
1248: */
1249: public void writeStartElement(String localName)
1250: throws XMLStreamException {
1251: try {
1252: if (localName == null) {
1253: throw new XMLStreamException(
1254: "Local Name cannot be null");
1255: }
1256:
1257: if (fStartTagOpened) {
1258: closeStartTag();
1259: }
1260:
1261: openStartTag();
1262: fElementStack.push(null, localName, null, null, false);
1263: fInternalNamespaceContext.pushContext();
1264:
1265: if (fIsRepairingNamespace) {
1266: return;
1267: }
1268:
1269: fWriter.write(localName);
1270: } catch (IOException ex) {
1271: throw new XMLStreamException(ex);
1272: }
1273: }
1274:
1275: /**
1276: * @param namespaceURI
1277: * @param localName
1278: * @throws XMLStreamException
1279: */
1280: public void writeStartElement(String namespaceURI, String localName)
1281: throws XMLStreamException {
1282: if (localName == null) {
1283: throw new XMLStreamException("Local Name cannot be null");
1284: }
1285:
1286: if (namespaceURI == null) {
1287: throw new XMLStreamException("NamespaceURI cannot be null");
1288: }
1289:
1290: namespaceURI = fSymbolTable.addSymbol(namespaceURI);
1291:
1292: String prefix = null;
1293:
1294: if (!fIsRepairingNamespace) {
1295: prefix = fNamespaceContext.getPrefix(namespaceURI);
1296:
1297: if (prefix != null) {
1298: prefix = fSymbolTable.addSymbol(prefix);
1299: }
1300: }
1301:
1302: writeStartElement(prefix, localName, namespaceURI);
1303: }
1304:
1305: /**
1306: * @param prefix
1307: * @param localName
1308: * @param namespaceURI
1309: * @throws XMLStreamException
1310: */
1311: public void writeStartElement(String prefix, String localName,
1312: String namespaceURI) throws XMLStreamException {
1313: try {
1314: if (localName == null) {
1315: throw new XMLStreamException(
1316: "Local Name cannot be null");
1317: }
1318:
1319: if (namespaceURI == null) {
1320: throw new XMLStreamException(
1321: "NamespaceURI cannot be null");
1322: }
1323:
1324: if (!fIsRepairingNamespace) {
1325: if (prefix == null) {
1326: throw new XMLStreamException(
1327: "Prefix cannot be null");
1328: }
1329: }
1330:
1331: if (fStartTagOpened) {
1332: closeStartTag();
1333: }
1334:
1335: openStartTag();
1336: namespaceURI = fSymbolTable.addSymbol(namespaceURI);
1337:
1338: if (prefix != null) {
1339: prefix = fSymbolTable.addSymbol(prefix);
1340: }
1341:
1342: fElementStack.push(prefix, localName, null, namespaceURI,
1343: false);
1344: fInternalNamespaceContext.pushContext();
1345:
1346: String tmpPrefix = fNamespaceContext
1347: .getPrefix(namespaceURI);
1348:
1349: if ((prefix != null)
1350: && ((tmpPrefix == null) || !prefix
1351: .equals(tmpPrefix))) {
1352: fInternalNamespaceContext.declarePrefix(prefix,
1353: namespaceURI);
1354:
1355: }
1356:
1357: if (fIsRepairingNamespace) {
1358: if ((prefix == null)
1359: || ((tmpPrefix != null) && prefix
1360: .equals(tmpPrefix))) {
1361: return;
1362: }
1363:
1364: QName qname = new QName();
1365: qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE,
1366: null, namespaceURI);
1367: fNamespaceDecls.add(qname);
1368:
1369: return;
1370: }
1371:
1372: if ((prefix != null)
1373: && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
1374: fWriter.write(prefix);
1375: fWriter.write(":");
1376: }
1377:
1378: fWriter.write(localName);
1379:
1380: } catch (IOException ex) {
1381: throw new XMLStreamException(ex);
1382: }
1383: }
1384:
1385: /**
1386: * Writes XML content to underlying writer. Escapes characters unless
1387: * escaping character feature is turned off.
1388: */
1389: private void writeXMLContent(char[] content, int start, int length,
1390: boolean escapeChars) throws IOException {
1391: if (!escapeChars) {
1392: fWriter.write(content, start, length);
1393:
1394: return;
1395: }
1396:
1397: // Index of the next char to be written
1398: int startWritePos = start;
1399:
1400: final int end = start + length;
1401:
1402: for (int index = start; index < end; index++) {
1403: char ch = content[index];
1404:
1405: if (fEncoder != null && !fEncoder.canEncode(ch)) {
1406: fWriter.write(content, startWritePos, index
1407: - startWritePos);
1408:
1409: // Escape this char as underlying encoder cannot handle it
1410: fWriter.write("&#x");
1411: fWriter.write(Integer.toHexString(ch));
1412: fWriter.write(';');
1413: startWritePos = index + 1;
1414: continue;
1415: }
1416:
1417: switch (ch) {
1418: case '<':
1419: fWriter.write(content, startWritePos, index
1420: - startWritePos);
1421: fWriter.write("<");
1422: startWritePos = index + 1;
1423:
1424: break;
1425:
1426: case '&':
1427: fWriter.write(content, startWritePos, index
1428: - startWritePos);
1429: fWriter.write("&");
1430: startWritePos = index + 1;
1431:
1432: break;
1433:
1434: case '>':
1435: fWriter.write(content, startWritePos, index
1436: - startWritePos);
1437: fWriter.write(">");
1438: startWritePos = index + 1;
1439:
1440: break;
1441: }
1442: }
1443:
1444: // Write any pending data
1445: fWriter.write(content, startWritePos, end - startWritePos);
1446: }
1447:
1448: private void writeXMLContent(String content) throws IOException {
1449: if ((content != null) && (content.length() > 0)) {
1450: writeXMLContent(content, fEscapeCharacters, // boolean = escapeChars
1451: false); // false = escapeDoubleQuotes
1452: }
1453: }
1454:
1455: /**
1456: * Writes XML content to underlying writer. Escapes characters unless
1457: * escaping character feature is turned off.
1458: */
1459: private void writeXMLContent(String content, boolean escapeChars,
1460: boolean escapeDoubleQuotes) throws IOException {
1461:
1462: if (!escapeChars) {
1463: fWriter.write(content);
1464:
1465: return;
1466: }
1467:
1468: // Index of the next char to be written
1469: int startWritePos = 0;
1470:
1471: final int end = content.length();
1472:
1473: for (int index = 0; index < end; index++) {
1474: char ch = content.charAt(index);
1475:
1476: if (fEncoder != null && !fEncoder.canEncode(ch)) {
1477: fWriter.write(content, startWritePos, index
1478: - startWritePos);
1479:
1480: // Escape this char as underlying encoder cannot handle it
1481: fWriter.write("&#x");
1482: fWriter.write(Integer.toHexString(ch));
1483: fWriter.write(';');
1484: startWritePos = index + 1;
1485: continue;
1486: }
1487:
1488: switch (ch) {
1489: case '<':
1490: fWriter.write(content, startWritePos, index
1491: - startWritePos);
1492: fWriter.write("<");
1493: startWritePos = index + 1;
1494:
1495: break;
1496:
1497: case '&':
1498: fWriter.write(content, startWritePos, index
1499: - startWritePos);
1500: fWriter.write("&");
1501: startWritePos = index + 1;
1502:
1503: break;
1504:
1505: case '>':
1506: fWriter.write(content, startWritePos, index
1507: - startWritePos);
1508: fWriter.write(">");
1509: startWritePos = index + 1;
1510:
1511: break;
1512:
1513: case '"':
1514: fWriter.write(content, startWritePos, index
1515: - startWritePos);
1516: if (escapeDoubleQuotes) {
1517: fWriter.write(""");
1518: } else {
1519: fWriter.write('"');
1520: }
1521: startWritePos = index + 1;
1522:
1523: break;
1524: }
1525: }
1526:
1527: // Write any pending data
1528: fWriter.write(content, startWritePos, end - startWritePos);
1529: }
1530:
1531: /**
1532: * marks close of start tag and writes the same into the writer.
1533: */
1534: private void closeStartTag() throws XMLStreamException {
1535: try {
1536: ElementState currentElement = fElementStack.peek();
1537:
1538: if (fIsRepairingNamespace) {
1539: repair();
1540: correctPrefix(currentElement);
1541:
1542: if ((currentElement.prefix != null)
1543: && (currentElement.prefix != XMLConstants.DEFAULT_NS_PREFIX)) {
1544: fWriter.write(currentElement.prefix);
1545: fWriter.write(":");
1546: }
1547:
1548: fWriter.write(currentElement.localpart);
1549:
1550: int len = fNamespaceDecls.size();
1551: QName qname = null;
1552:
1553: for (int i = 0; i < len; i++) {
1554: qname = (QName) fNamespaceDecls.get(i);
1555:
1556: if (qname != null) {
1557: if (fInternalNamespaceContext.declarePrefix(
1558: qname.prefix, qname.uri)) {
1559: writenamespace(qname.prefix, qname.uri);
1560: }
1561: }
1562: }
1563:
1564: fNamespaceDecls.clear();
1565:
1566: Attribute attr = null;
1567:
1568: for (int j = 0; j < fAttributeCache.size(); j++) {
1569: attr = (Attribute) fAttributeCache.get(j);
1570:
1571: if ((attr.prefix != null) && (attr.uri != null)) {
1572: if (!attr.prefix.equals("")
1573: && !attr.uri.equals("")) {
1574: String tmp = fInternalNamespaceContext
1575: .getPrefix(attr.uri);
1576:
1577: if ((tmp == null) || (tmp != attr.prefix)) {
1578: if (fInternalNamespaceContext
1579: .declarePrefix(attr.prefix,
1580: attr.uri)) {
1581: writenamespace(attr.prefix,
1582: attr.uri);
1583: }
1584: }
1585: }
1586: }
1587:
1588: writeAttributeWithPrefix(attr.prefix,
1589: attr.localpart, attr.value);
1590: }
1591:
1592: fAttributeCache.clear();
1593: }
1594:
1595: if (currentElement.isEmpty) {
1596: fElementStack.pop();
1597: fInternalNamespaceContext.popContext();
1598: fWriter.write(CLOSE_EMPTY_ELEMENT);
1599: } else {
1600: fWriter.write(CLOSE_START_TAG);
1601: }
1602:
1603: fStartTagOpened = false;
1604: } catch (IOException ex) {
1605: fStartTagOpened = false;
1606: throw new XMLStreamException(ex);
1607: }
1608: }
1609:
1610: /**
1611: * marks open of start tag and writes the same into the writer.
1612: */
1613: private void openStartTag() throws IOException {
1614: fStartTagOpened = true;
1615: fWriter.write(OPEN_START_TAG);
1616: }
1617:
1618: /**
1619: *
1620: * @param uri
1621: * @return
1622: */
1623: private void correctPrefix(QName attr) {
1624: String tmpPrefix = null;
1625: String prefix;
1626: String uri;
1627: prefix = attr.prefix;
1628: uri = attr.uri;
1629:
1630: if (prefix == null || prefix.equals("")) {
1631: if (uri == null) {
1632: return;
1633: }
1634:
1635: if (prefix == XMLConstants.DEFAULT_NS_PREFIX
1636: && uri == XMLConstants.DEFAULT_NS_PREFIX)
1637: return;
1638:
1639: uri = fSymbolTable.addSymbol(uri);
1640:
1641: QName decl = null;
1642:
1643: for (int i = 0; i < fNamespaceDecls.size(); i++) {
1644: decl = (QName) fNamespaceDecls.get(i);
1645:
1646: if ((decl != null) && (decl.uri == attr.uri)) {
1647: attr.prefix = decl.prefix;
1648:
1649: return;
1650: }
1651: }
1652:
1653: tmpPrefix = fNamespaceContext.getPrefix(uri);
1654:
1655: if (tmpPrefix == XMLConstants.DEFAULT_NS_PREFIX) {
1656: return;
1657: }
1658:
1659: if (tmpPrefix == null) {
1660: StringBuffer genPrefix = new StringBuffer("zdef");
1661:
1662: for (int i = 0; i < 1; i++) {
1663: genPrefix.append(fPrefixGen.nextInt());
1664: }
1665:
1666: prefix = genPrefix.toString();
1667: prefix = fSymbolTable.addSymbol(prefix);
1668: } else {
1669: prefix = fSymbolTable.addSymbol(tmpPrefix);
1670: }
1671:
1672: if (tmpPrefix == null) {
1673: QName qname = new QName();
1674: qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE,
1675: null, uri);
1676: fNamespaceDecls.add(qname);
1677: fInternalNamespaceContext.declarePrefix(fSymbolTable
1678: .addSymbol(prefix), uri);
1679: }
1680: }
1681:
1682: attr.prefix = prefix;
1683: }
1684:
1685: /**
1686: * @param uri
1687: * @return
1688: */
1689: private boolean isDefaultNamespace(String uri) {
1690: String defaultNamespace = fInternalNamespaceContext
1691: .getURI(DEFAULT_PREFIX);
1692:
1693: if (uri == defaultNamespace) {
1694: return true;
1695: }
1696:
1697: return false;
1698: }
1699:
1700: /**
1701: * @param prefix
1702: * @param uri
1703: * @return
1704: */
1705: private boolean checkUserNamespaceContext(String prefix, String uri) {
1706: if (fNamespaceContext.userContext != null) {
1707: String tmpURI = fNamespaceContext.userContext
1708: .getNamespaceURI(prefix);
1709:
1710: if ((tmpURI != null) && tmpURI.equals(uri)) {
1711: return true;
1712: }
1713: }
1714:
1715: return false;
1716: }
1717:
1718: /**
1719: * Correct's namespaces as per requirements of isReparisingNamespace property.
1720: */
1721: protected void repair() {
1722: Attribute attr = null;
1723: Attribute attr2 = null;
1724: ElementState currentElement = fElementStack.peek();
1725: removeDuplicateDecls();
1726:
1727: for (int i = 0; i < fAttributeCache.size(); i++) {
1728: attr = (Attribute) fAttributeCache.get(i);
1729: if ((attr.prefix != null && !attr.prefix.equals(""))
1730: || (attr.uri != null && !attr.uri.equals(""))) {
1731: correctPrefix(currentElement, attr);
1732: }
1733: }
1734:
1735: if (!isDeclared(currentElement)) {
1736: if ((currentElement.prefix != null)
1737: && (currentElement.uri != null)) {
1738: if ((!currentElement.prefix.equals(""))
1739: && (!currentElement.uri.equals(""))) {
1740: fNamespaceDecls.add(currentElement);
1741: }
1742: }
1743: }
1744:
1745: for (int i = 0; i < fAttributeCache.size(); i++) {
1746: attr = (Attribute) fAttributeCache.get(i);
1747: for (int j = i + 1; j < fAttributeCache.size(); j++) {
1748: attr2 = (Attribute) fAttributeCache.get(j);
1749: if (!"".equals(attr.prefix) && !"".equals(attr2.prefix)) {
1750: correctPrefix(attr, attr2);
1751: }
1752: }
1753: }
1754:
1755: repairNamespaceDecl(currentElement);
1756:
1757: int i = 0;
1758:
1759: for (i = 0; i < fAttributeCache.size(); i++) {
1760: attr = (Attribute) fAttributeCache.get(i);
1761: /* If 'attr' is an attribute and it is in no namespace(which means that prefix="", uri=""), attr's
1762: namespace should not be redinded. See [http://www.w3.org/TR/REC-xml-names/#defaulting].
1763: */
1764: if (attr.prefix != null && attr.prefix.equals("")
1765: && attr.uri != null && attr.uri.equals("")) {
1766: repairNamespaceDecl(attr);
1767: }
1768: }
1769:
1770: QName qname = null;
1771:
1772: for (i = 0; i < fNamespaceDecls.size(); i++) {
1773: qname = (QName) fNamespaceDecls.get(i);
1774:
1775: if (qname != null) {
1776: fInternalNamespaceContext.declarePrefix(qname.prefix,
1777: qname.uri);
1778: }
1779: }
1780:
1781: for (i = 0; i < fAttributeCache.size(); i++) {
1782: attr = (Attribute) fAttributeCache.get(i);
1783: correctPrefix(attr);
1784: }
1785: }
1786:
1787: /*
1788: *If element and/or attribute names in the same start or empty-element tag
1789: *are bound to different namespace URIs and are using the same prefix then
1790: *the element or the first occurring attribute retains the original prefix
1791: *and the following attributes have their prefixes replaced with a new prefix
1792: *that is bound to the namespace URIs of those attributes.
1793: */
1794: void correctPrefix(QName attr1, QName attr2) {
1795: String tmpPrefix = null;
1796: QName decl = null;
1797: boolean done = false;
1798:
1799: checkForNull(attr1);
1800: checkForNull(attr2);
1801:
1802: if (attr1.prefix.equals(attr2.prefix)
1803: && !(attr1.uri.equals(attr2.uri))) {
1804:
1805: tmpPrefix = fNamespaceContext.getPrefix(attr2.uri);
1806:
1807: if (tmpPrefix != null) {
1808: attr2.prefix = fSymbolTable.addSymbol(tmpPrefix);
1809: } else {
1810: decl = null;
1811: for (int n = 0; n < fNamespaceDecls.size(); n++) {
1812: decl = (QName) fNamespaceDecls.get(n);
1813: if (decl != null && (decl.uri == attr2.uri)) {
1814: attr2.prefix = decl.prefix;
1815:
1816: return;
1817: }
1818: }
1819:
1820: //No namespace mapping found , so declare prefix.
1821: StringBuffer genPrefix = new StringBuffer("zdef");
1822:
1823: for (int k = 0; k < 1; k++) {
1824: genPrefix.append(fPrefixGen.nextInt());
1825: }
1826:
1827: tmpPrefix = genPrefix.toString();
1828: tmpPrefix = fSymbolTable.addSymbol(tmpPrefix);
1829: attr2.prefix = tmpPrefix;
1830:
1831: QName qname = new QName();
1832: qname.setValues(tmpPrefix,
1833: XMLConstants.XMLNS_ATTRIBUTE, null, attr2.uri);
1834: fNamespaceDecls.add(qname);
1835: }
1836: }
1837: }
1838:
1839: void checkForNull(QName attr) {
1840: if (attr.prefix == null)
1841: attr.prefix = XMLConstants.DEFAULT_NS_PREFIX;
1842: if (attr.uri == null)
1843: attr.uri = XMLConstants.DEFAULT_NS_PREFIX;
1844: }
1845:
1846: void removeDuplicateDecls() {
1847: QName decl1, decl2;
1848: for (int i = 0; i < fNamespaceDecls.size(); i++) {
1849: decl1 = (QName) fNamespaceDecls.get(i);
1850: if (decl1 != null) {
1851: for (int j = i + 1; j < fNamespaceDecls.size(); j++) {
1852: decl2 = (QName) fNamespaceDecls.get(j);
1853: // QName.equals relies on identity equality, so we can't use it,
1854: // because prefixes aren't interned
1855: if (decl2 != null
1856: && decl1.prefix.equals(decl2.prefix)
1857: && decl1.uri.equals(decl2.uri))
1858: fNamespaceDecls.remove(j);
1859: }
1860: }
1861: }
1862: }
1863:
1864: /*
1865: *If an element or attribute name is bound to a prefix and there is a namespace
1866: *declaration that binds that prefix to a different URI then that namespace declaration
1867: *is either removed if the correct mapping is inherited from the parent context of that element,
1868: *or changed to the namespace URI of the element or attribute using that prefix.
1869: *
1870: */
1871: void repairNamespaceDecl(QName attr) {
1872: QName decl = null;
1873: String tmpURI;
1874:
1875: //check for null prefix.
1876: for (int j = 0; j < fNamespaceDecls.size(); j++) {
1877: decl = (QName) fNamespaceDecls.get(j);
1878:
1879: if (decl != null) {
1880: if ((attr.prefix != null)
1881: && (attr.prefix.equals(decl.prefix) && !(attr.uri
1882: .equals(decl.uri)))) {
1883: tmpURI = fNamespaceContext
1884: .getNamespaceURI(attr.prefix);
1885:
1886: //see if you need to add to symbole table.
1887: if (tmpURI != null) {
1888: if (tmpURI.equals(attr.uri)) {
1889: fNamespaceDecls.set(j, null);
1890: } else {
1891: decl.uri = attr.uri;
1892: }
1893: }
1894: }
1895: }
1896: }
1897: }
1898:
1899: boolean isDeclared(QName attr) {
1900: QName decl = null;
1901:
1902: for (int n = 0; n < fNamespaceDecls.size(); n++) {
1903: decl = (QName) fNamespaceDecls.get(n);
1904:
1905: if ((attr.prefix != null)
1906: && ((attr.prefix == decl.prefix) && (decl.uri == attr.uri))) {
1907: return true;
1908: }
1909: }
1910:
1911: if (attr.uri != null) {
1912: if (fNamespaceContext.getPrefix(attr.uri) != null) {
1913: return true;
1914: }
1915: }
1916:
1917: return false;
1918: }
1919:
1920: /*
1921: * Start of Internal classes.
1922: *
1923: */
1924: protected class ElementStack {
1925: /** The stack data. */
1926: protected ElementState[] fElements;
1927:
1928: /** The size of the stack. */
1929: protected short fDepth;
1930:
1931: /** Default constructor. */
1932: public ElementStack() {
1933: fElements = new ElementState[10];
1934:
1935: for (int i = 0; i < fElements.length; i++) {
1936: fElements[i] = new ElementState();
1937: }
1938: }
1939:
1940: /**
1941: * Pushes an element on the stack.
1942: * <p>
1943: * <strong>Note:</strong> The QName values are copied into the
1944: * stack. In other words, the caller does <em>not</em> orphan
1945: * the element to the stack. Also, the QName object returned
1946: * is <em>not</em> orphaned to the caller. It should be
1947: * considered read-only.
1948: *
1949: * @param element The element to push onto the stack.
1950: *
1951: * @return Returns the actual QName object that stores the
1952: */
1953: public ElementState push(ElementState element) {
1954: if (fDepth == fElements.length) {
1955: ElementState[] array = new ElementState[fElements.length * 2];
1956: System.arraycopy(fElements, 0, array, 0, fDepth);
1957: fElements = array;
1958:
1959: for (int i = fDepth; i < fElements.length; i++) {
1960: fElements[i] = new ElementState();
1961: }
1962: }
1963:
1964: fElements[fDepth].setValues(element);
1965:
1966: return fElements[fDepth++];
1967: }
1968:
1969: /**
1970: *
1971: * @param prefix
1972: * @param localpart
1973: * @param rawname
1974: * @param uri
1975: * @param isEmpty
1976: * @return
1977: */
1978: public ElementState push(String prefix, String localpart,
1979: String rawname, String uri, boolean isEmpty) {
1980: if (fDepth == fElements.length) {
1981: ElementState[] array = new ElementState[fElements.length * 2];
1982: System.arraycopy(fElements, 0, array, 0, fDepth);
1983: fElements = array;
1984:
1985: for (int i = fDepth; i < fElements.length; i++) {
1986: fElements[i] = new ElementState();
1987: }
1988: }
1989:
1990: fElements[fDepth].setValues(prefix, localpart, rawname,
1991: uri, isEmpty);
1992:
1993: return fElements[fDepth++];
1994: }
1995:
1996: /**
1997: * Pops an element off of the stack by setting the values of
1998: * the specified QName.
1999: * <p>
2000: * <strong>Note:</strong> The object returned is <em>not</em>
2001: * orphaned to the caller. Therefore, the caller should consider
2002: * the object to be read-only.
2003: */
2004: public ElementState pop() {
2005: return fElements[--fDepth];
2006: }
2007:
2008: /** Clears the stack without throwing away existing QName objects. */
2009: public void clear() {
2010: fDepth = 0;
2011: }
2012:
2013: /**
2014: * This function is as a result of optimization done for endElement --
2015: * we dont need to set the value for every end element we encouter.
2016: * For Well formedness checks we can have the same QName object that was pushed.
2017: * the values will be set only if application need to know about the endElement
2018: * -- neeraj.bajaj@sun.com
2019: */
2020: public ElementState peek() {
2021: return fElements[fDepth - 1];
2022: }
2023:
2024: /**
2025: *
2026: * @return
2027: */
2028: public boolean empty() {
2029: return (fDepth > 0) ? false : true;
2030: }
2031: }
2032:
2033: /**
2034: * Maintains element state . localName for now.
2035: */
2036: class ElementState extends QName {
2037: public boolean isEmpty = false;
2038:
2039: public ElementState() {
2040: }
2041:
2042: public ElementState(String prefix, String localpart,
2043: String rawname, String uri) {
2044: super (prefix, localpart, rawname, uri);
2045: }
2046:
2047: public void setValues(String prefix, String localpart,
2048: String rawname, String uri, boolean isEmpty) {
2049: super .setValues(prefix, localpart, rawname, uri);
2050: this .isEmpty = isEmpty;
2051: }
2052: }
2053:
2054: /**
2055: * Attributes
2056: */
2057: class Attribute extends QName {
2058: String value;
2059:
2060: Attribute(String value) {
2061: super ();
2062: this .value = value;
2063: }
2064: }
2065:
2066: /**
2067: * Implementation of NamespaceContext .
2068: *
2069: */
2070: class NamespaceContextImpl implements NamespaceContext {
2071: //root namespace context set by user.
2072: NamespaceContext userContext = null;
2073:
2074: //context built by the writer.
2075: NamespaceSupport internalContext = null;
2076:
2077: public String getNamespaceURI(String prefix) {
2078: String uri = null;
2079:
2080: if (prefix != null) {
2081: prefix = fSymbolTable.addSymbol(prefix);
2082: }
2083:
2084: if (internalContext != null) {
2085: uri = internalContext.getURI(prefix);
2086:
2087: if (uri != null) {
2088: return uri;
2089: }
2090: }
2091:
2092: if (userContext != null) {
2093: uri = userContext.getNamespaceURI(prefix);
2094:
2095: return uri;
2096: }
2097:
2098: return null;
2099: }
2100:
2101: public String getPrefix(String uri) {
2102: String prefix = null;
2103:
2104: if (uri != null) {
2105: uri = fSymbolTable.addSymbol(uri);
2106: }
2107:
2108: if (internalContext != null) {
2109: prefix = internalContext.getPrefix(uri);
2110:
2111: if (prefix != null) {
2112: return prefix;
2113: }
2114: }
2115:
2116: if (userContext != null) {
2117: return userContext.getPrefix(uri);
2118: }
2119:
2120: return null;
2121: }
2122:
2123: public java.util.Iterator getPrefixes(String uri) {
2124: Vector prefixes = null;
2125: Iterator itr = null;
2126:
2127: if (uri != null) {
2128: uri = fSymbolTable.addSymbol(uri);
2129: }
2130:
2131: if (userContext != null) {
2132: itr = userContext.getPrefixes(uri);
2133: }
2134:
2135: if (internalContext != null) {
2136: prefixes = internalContext.getPrefixes(uri);
2137: }
2138:
2139: if ((prefixes == null) && (itr != null)) {
2140: return itr;
2141: } else if ((prefixes != null) && (itr == null)) {
2142: return new ReadOnlyIterator(prefixes.iterator());
2143: } else if ((prefixes != null) && (itr != null)) {
2144: String ob = null;
2145:
2146: while (itr.hasNext()) {
2147: ob = (String) itr.next();
2148:
2149: if (ob != null) {
2150: ob = fSymbolTable.addSymbol(ob);
2151: }
2152:
2153: if (!prefixes.contains(ob)) {
2154: prefixes.add(ob);
2155: }
2156: }
2157:
2158: return new ReadOnlyIterator(prefixes.iterator());
2159: }
2160:
2161: return fReadOnlyIterator;
2162: }
2163: }
2164:
2165: // -- Map Interface --------------------------------------------------
2166:
2167: public int size() {
2168: return 1;
2169: }
2170:
2171: public boolean isEmpty() {
2172: return false;
2173: }
2174:
2175: public boolean containsKey(Object key) {
2176: return key.equals(OUTPUTSTREAM_PROPERTY);
2177: }
2178:
2179: /**
2180: * Returns the value associated to an implementation-specific
2181: * property.
2182: */
2183: public Object get(Object key) {
2184: if (key.equals(OUTPUTSTREAM_PROPERTY)) {
2185: return fOutputStream;
2186: }
2187: return null;
2188: }
2189:
2190: public java.util.Set entrySet() {
2191: throw new UnsupportedOperationException();
2192: }
2193: }
|