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