001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Adam Megacz
028: */
029:
030: package com.caucho.xml.stream;
031:
032: import com.caucho.vfs.WriteStream;
033: import com.caucho.vfs.Vfs;
034: import com.caucho.util.L10N;
035:
036: import javax.xml.XMLConstants;
037: import javax.xml.namespace.NamespaceContext;
038: import javax.xml.namespace.QName;
039: import javax.xml.stream.XMLOutputFactory;
040: import javax.xml.stream.XMLStreamException;
041: import javax.xml.stream.XMLStreamWriter;
042: import java.io.IOException;
043: import java.io.OutputStream;
044: import java.io.Writer;
045: import java.util.ArrayList;
046: import java.util.logging.Logger;
047:
048: public class XMLStreamWriterImpl implements XMLStreamWriter {
049: private static final L10N L = new L10N(XMLStreamWriterImpl.class);
050: private static final Logger log = Logger
051: .getLogger(XMLStreamReaderImpl.class.getName());
052:
053: private WriteStream _out;
054: private NamespaceWriterContext _tracker;
055:
056: private QName _pendingTagName = null;
057: private boolean _shortTag = false;
058: private boolean _repair = false;
059: private ArrayList<QName> _pendingAttributeNames = new ArrayList<QName>();
060: private ArrayList<String> _pendingAttributeValues = new ArrayList<String>();
061:
062: private int _indent = -1;
063: private int _currentIndent;
064:
065: public XMLStreamWriterImpl(WriteStream ws) {
066: this (ws, false);
067: }
068:
069: public XMLStreamWriterImpl(WriteStream ws, boolean repair) {
070: _out = ws;
071: _repair = repair;
072: _tracker = new NamespaceWriterContext(repair);
073: }
074:
075: public XMLStreamWriterImpl(Writer w, boolean repair) {
076: this (Vfs.openWrite(w), repair);
077: }
078:
079: public XMLStreamWriterImpl(OutputStream os, boolean repair) {
080: this (Vfs.openWrite(os), repair);
081: }
082:
083: public void setIndent(int indent) {
084: _indent = indent;
085: }
086:
087: public void setRepair(boolean repair) {
088: _repair = repair;
089: _tracker.setRepair(repair);
090: }
091:
092: public void close() throws XMLStreamException {
093: flushPending();
094: // DO NOT close _out!
095: // This will cause XFire/CXF and possibly others to blow up.
096: }
097:
098: public void flush() throws XMLStreamException {
099: try {
100: _out.flush();
101: } catch (IOException e) {
102: throw new XMLStreamException(e);
103: }
104: }
105:
106: public NamespaceContext getNamespaceContext() {
107: return _tracker;
108: }
109:
110: public String getPrefix(String uri) throws XMLStreamException {
111: return _tracker.getPrefix(uri);
112: }
113:
114: public Object getProperty(String name)
115: throws IllegalArgumentException {
116: if (XMLOutputFactory.IS_REPAIRING_NAMESPACES.equals(name))
117: return Boolean.valueOf(_repair);
118:
119: throw new PropertyNotSupportedException(name);
120: }
121:
122: public void setDefaultNamespace(String uri)
123: throws XMLStreamException {
124: _tracker.declare(XMLConstants.DEFAULT_NS_PREFIX, uri, _repair);
125: }
126:
127: public void setNamespaceContext(NamespaceContext context)
128: throws XMLStreamException {
129: String message = "please do not set the NamespaceContext";
130: throw new UnsupportedOperationException(message);
131: }
132:
133: public void setPrefix(String prefix, String uri)
134: throws XMLStreamException {
135: _tracker.declare(prefix, uri);
136: }
137:
138: public void writeAttribute(String localName, String value)
139: throws XMLStreamException {
140: _pendingAttributeNames.add(new QName(localName));
141: _pendingAttributeValues.add(value);
142: }
143:
144: public void writeAttribute(String namespaceURI, String localName,
145: String value) throws XMLStreamException {
146: if (_repair) {
147: String prefix = _tracker.declare(namespaceURI);
148:
149: if (prefix == null)
150: _pendingAttributeNames.add(new QName(namespaceURI,
151: localName));
152: else
153: _pendingAttributeNames.add(new QName(namespaceURI,
154: localName, prefix));
155: } else {
156: String prefix = _tracker.getPrefix(namespaceURI);
157:
158: if (prefix == null)
159: throw new XMLStreamException(L.l(
160: "No prefix defined for namespace {0}",
161: namespaceURI));
162:
163: _pendingAttributeNames.add(new QName(namespaceURI,
164: localName, prefix));
165: }
166:
167: _pendingAttributeValues.add(value);
168: }
169:
170: public void writeAttribute(String prefix, String namespaceURI,
171: String localName, String value) throws XMLStreamException {
172: if (_repair && _tracker.getPrefix(namespaceURI) == null)
173: _tracker.declare(prefix, namespaceURI, true);
174: else
175: _tracker.declare(prefix, namespaceURI);
176:
177: _pendingAttributeNames.add(new QName(namespaceURI, localName,
178: prefix));
179: _pendingAttributeValues.add(value);
180: }
181:
182: public void writeCData(String data) throws XMLStreamException {
183: flushPending();
184: try {
185: _out.print("<![CDATA[");
186: _out.print(data);
187: _out.print("]]>");
188: } catch (IOException e) {
189: throw new XMLStreamException(e);
190: }
191: }
192:
193: public void writeCharacters(char[] text, int start, int len)
194: throws XMLStreamException {
195: flushPending();
196: try {
197: Escapifier.escape(text, start, len, _out);
198: } catch (IOException e) {
199: throw new XMLStreamException(e);
200: }
201: }
202:
203: public void writeCharacters(String text) throws XMLStreamException {
204: flushPending();
205: try {
206: Escapifier.escape(text, _out);
207: } catch (IOException e) {
208: throw new XMLStreamException(e);
209: }
210: }
211:
212: public void writeComment(String data) throws XMLStreamException {
213: flushPending();
214: try {
215: _out.print("<!--");
216: _out.print(data);
217: _out.print("-->");
218: } catch (IOException e) {
219: throw new XMLStreamException(e);
220: }
221: }
222:
223: public void writeDefaultNamespace(String namespaceURI)
224: throws XMLStreamException {
225: _tracker.declare("", namespaceURI, true);
226: }
227:
228: public void writeDTD(String dtd) throws XMLStreamException {
229: flushPending();
230:
231: try {
232: _out.print(dtd);
233: } catch (IOException e) {
234: throw new XMLStreamException(e);
235: }
236: }
237:
238: public void writeElement(String localName, String contents)
239: throws XMLStreamException {
240: writeStartElement(localName);
241: if (contents != null)
242: writeCharacters(contents);
243: writeEndElement();
244: }
245:
246: public void writeEmptyElement(String localName)
247: throws XMLStreamException {
248: flushPending();
249: try {
250: QName qname = new QName(localName);
251: pushContext(qname);
252: _pendingTagName = qname;
253: _shortTag = true;
254: } catch (IOException e) {
255: throw new XMLStreamException(e);
256: }
257: }
258:
259: public void writeEmptyElement(String namespaceURI, String localName)
260: throws XMLStreamException {
261: flushPending();
262:
263: try {
264: QName qname = null;
265:
266: if (_repair) {
267: // NOTE: We have to push before we declare because declare will
268: // declare the namespace in the parent context if we don't
269: flushContext();
270: _tracker.push();
271:
272: String prefix = _tracker.declare(namespaceURI);
273:
274: if (prefix == null)
275: qname = new QName(namespaceURI, localName);
276: else
277: qname = new QName(namespaceURI, localName, prefix);
278:
279: _tracker.setElementName(qname);
280: _flushed = false;
281: } else {
282: String prefix = _tracker.getPrefix(namespaceURI);
283:
284: if (prefix == null)
285: throw new XMLStreamException(L.l(
286: "No prefix defined for namespace {0}",
287: namespaceURI));
288:
289: qname = new QName(namespaceURI, localName, prefix);
290: pushContext(qname);
291: }
292:
293: _pendingTagName = qname;
294: _shortTag = true;
295: } catch (IOException e) {
296: throw new XMLStreamException(e);
297: }
298: }
299:
300: public void writeEmptyElement(String prefix, String localName,
301: String namespaceURI) throws XMLStreamException {
302: flushPending();
303: try {
304: QName qname = new QName(namespaceURI, localName, prefix);
305:
306: if (_repair && _tracker.getPrefix(namespaceURI) == null) {
307: // NOTE: We have to push before we declare because declare will
308: // declare the namespace in the parent context if we don't
309: flushContext();
310: _tracker.push();
311:
312: _tracker.declare(prefix, namespaceURI, true);
313:
314: _tracker.setElementName(qname);
315: _flushed = false;
316: } else
317: pushContext(qname);
318:
319: _pendingTagName = qname;
320: _shortTag = true;
321: } catch (IOException e) {
322: throw new XMLStreamException(e);
323: }
324: }
325:
326: public void writeEndDocument() throws XMLStreamException {
327: }
328:
329: public void writeEndElement() throws XMLStreamException {
330: writeEndElement(null, null);
331: }
332:
333: public void writeEndElement(String localName)
334: throws XMLStreamException {
335: writeEndElement(null, localName);
336: }
337:
338: public void writeEndElement(String namespaceURI, String localName)
339: throws XMLStreamException {
340: flushPending();
341:
342: try {
343: QName name = popContext();
344:
345: if ((localName != null && !localName.equals(name
346: .getLocalPart()))
347: || (namespaceURI != null && !namespaceURI
348: .equals(name.getNamespaceURI())))
349: throw new XMLStreamException(L.l(
350: "unbalanced close, expecting `{0}' not `{1}'",
351: name, new QName(namespaceURI, localName)));
352:
353: _out.print("</");
354: _out.print(printQName(name));
355: _out.print(">");
356:
357: if (_indent >= 0) {
358: _out.println();
359: _currentIndent -= _indent;
360: }
361: } catch (IOException e) {
362: throw new XMLStreamException(e);
363: }
364: }
365:
366: private static String printQName(QName name) {
367:
368: if (name.getPrefix() == null || name.getPrefix().equals(""))
369: return name.getLocalPart();
370:
371: return name.getPrefix() + ":" + name.getLocalPart();
372: }
373:
374: public void writeEntityRef(String name) throws XMLStreamException {
375: flushPending();
376: try {
377: _out.print("&");
378: _out.print(name);
379: _out.print(";");
380: } catch (IOException e) {
381: throw new XMLStreamException(e);
382: }
383: }
384:
385: public void writeNamespace(String prefix, String namespaceURI)
386: throws XMLStreamException {
387: if (_pendingTagName == null)
388: throw new XMLStreamException(
389: "Namespace written before element");
390:
391: if (prefix == null || "".equals(prefix)
392: || "xmlns".equals(prefix))
393: writeDefaultNamespace(namespaceURI);
394: else
395: _tracker.declare(prefix, namespaceURI, true);
396: }
397:
398: public void writeProcessingInstruction(String target)
399: throws XMLStreamException {
400: flushPending();
401: try {
402: _out.print("<?");
403: _out.print(target);
404: _out.print("?>");
405: } catch (IOException e) {
406: throw new XMLStreamException(e);
407: }
408: }
409:
410: public void writeProcessingInstruction(String target, String data)
411: throws XMLStreamException {
412: flushPending();
413: try {
414: _out.print("<?");
415: _out.print(target);
416: _out.print(" ");
417: _out.print(data);
418: _out.print("?>");
419: } catch (IOException e) {
420: throw new XMLStreamException(e);
421: }
422: }
423:
424: public void writeStartDocument() throws XMLStreamException {
425: writeStartDocument("1.0");
426: }
427:
428: public void writeStartDocument(String version)
429: throws XMLStreamException {
430: writeStartDocument("utf-8", version);
431: }
432:
433: public void writeStartDocument(String encoding, String version)
434: throws XMLStreamException {
435: try {
436: _out.print("<?xml version=\"" + version + "\" encoding=\""
437: + encoding + "\"?>");
438: } catch (IOException e) {
439: throw new XMLStreamException(e);
440: }
441: }
442:
443: public void writeStartElement(String localName)
444: throws XMLStreamException {
445: flushPending();
446: try {
447: QName qname = new QName(localName);
448: pushContext(qname);
449: _pendingTagName = qname;
450: } catch (IOException e) {
451: throw new XMLStreamException(e);
452: }
453: }
454:
455: public void writeStartElement(String namespaceURI, String localName)
456: throws XMLStreamException {
457: flushPending();
458: try {
459: QName qname = null;
460:
461: if (_repair) {
462: // NOTE: We have to push before we declare because declare will
463: // declare the namespace in the parent context if we don't
464: flushContext();
465: _tracker.push();
466:
467: String prefix = _tracker.declare(namespaceURI);
468:
469: if (prefix == null)
470: qname = new QName(namespaceURI, localName);
471: else
472: qname = new QName(namespaceURI, localName, prefix);
473:
474: _tracker.setElementName(qname);
475: _flushed = false;
476: } else {
477: String prefix = _tracker.getPrefix(namespaceURI);
478:
479: if (prefix == null)
480: throw new XMLStreamException(L.l(
481: "No prefix defined for namespace {0}",
482: namespaceURI));
483:
484: qname = new QName(namespaceURI, localName, prefix);
485: pushContext(qname);
486: }
487:
488: _pendingTagName = qname;
489: } catch (IOException e) {
490: throw new XMLStreamException(e);
491: }
492: }
493:
494: public void writeStartElement(String prefix, String localName,
495: String namespaceURI) throws XMLStreamException {
496: flushPending();
497: try {
498: QName qname = new QName(namespaceURI, localName, prefix);
499:
500: if (_repair && _tracker.getPrefix(namespaceURI) == null) {
501: // NOTE: We have to push before we declare because declare will
502: // declare the namespace in the parent context if we don't
503: flushContext();
504: _tracker.push();
505:
506: _tracker.declare(prefix, namespaceURI, true);
507:
508: _tracker.setElementName(qname);
509: _flushed = false;
510: } else
511: pushContext(qname);
512:
513: _pendingTagName = qname;
514: } catch (IOException e) {
515: throw new XMLStreamException(e);
516: }
517: }
518:
519: /////////////////////////////////////////////////////////////////////////
520:
521: private boolean _flushed = true;
522:
523: private void pushContext(QName elementName) throws IOException {
524: flushContext();
525: _tracker.push();
526: _tracker.setElementName(elementName);
527: _flushed = false;
528: }
529:
530: private QName popContext() throws IOException, XMLStreamException {
531: flushContext();
532: QName name = _tracker.getElementName();
533: _tracker.pop();
534: return name;
535: }
536:
537: private void flushContext() throws IOException {
538: if (_flushed)
539: return;
540: _tracker.emitDeclarations(_out);
541: _flushed = true;
542: }
543:
544: private void flushPending() throws XMLStreamException {
545: try {
546: if (_pendingTagName == null)
547: return;
548:
549: _out.print("<");
550: _out.print(printQName(_pendingTagName));
551:
552: for (int i = 0; i < _pendingAttributeNames.size(); i++) {
553: _out.print(" ");
554: _out.print(printQName(_pendingAttributeNames.get(i)));
555: _out.print("=\"");
556: Escapifier.escape(_pendingAttributeValues.get(i), _out);
557: _out.print('"');
558: }
559: flushContext();
560:
561: if (_shortTag) {
562: _out.print("/>");
563: popContext();
564: } else {
565: _out.print(">");
566:
567: if (_indent > -1)
568: _currentIndent += _indent;
569: }
570:
571: _pendingTagName = null;
572: _pendingAttributeNames.clear();
573: _pendingAttributeValues.clear();
574: _shortTag = false;
575: } catch (IOException e) {
576: throw new XMLStreamException(e);
577: }
578: }
579: }
|