001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.serializers;
018:
019: import java.io.CharArrayWriter;
020:
021: import org.apache.cocoon.components.serializers.encoding.XMLEncoder;
022: import org.apache.cocoon.components.serializers.util.DocType;
023: import org.apache.cocoon.components.serializers.util.Namespaces;
024: import org.apache.commons.lang.SystemUtils;
025: import org.xml.sax.SAXException;
026:
027: /**
028: * <p>A fancy XML serializer not relying on the JAXP API.</p>
029: *
030: * <p>For configuration options of this serializer, please look at the
031: * {@link EncodingSerializer}.</p>
032: *
033: * @version CVS $Id: XMLSerializer.java 433543 2006-08-22 06:22:54Z crossley $
034: */
035: public class XMLSerializer extends EncodingSerializer {
036:
037: private static final XMLEncoder XML_ENCODER = new XMLEncoder();
038:
039: private static final char S_EOL[] = SystemUtils.LINE_SEPARATOR
040: .toCharArray();
041:
042: private static final char S_DOCUMENT_1[] = "<?xml version=\"1.0"
043: .toCharArray();
044: private static final char S_DOCUMENT_2[] = "\" encoding=\""
045: .toCharArray();
046: private static final char S_DOCUMENT_3[] = "\"?>".toCharArray();
047:
048: private static final char S_ELEMENT_1[] = "=\"".toCharArray();
049: private static final char S_ELEMENT_2[] = "</".toCharArray();
050: private static final char S_ELEMENT_3[] = " />".toCharArray();
051: private static final char S_ELEMENT_4[] = " xmlns".toCharArray();
052:
053: private static final char S_CDATA_1[] = "<[CDATA[".toCharArray();
054: private static final char S_CDATA_2[] = "]]>".toCharArray();
055:
056: private static final char S_COMMENT_1[] = "<!--".toCharArray();
057: private static final char S_COMMENT_2[] = "-->".toCharArray();
058:
059: private static final char S_PROCINSTR_1[] = "<?".toCharArray();
060: private static final char S_PROCINSTR_2[] = "?>".toCharArray();
061:
062: private static final char C_LT = '<';
063: private static final char C_GT = '>';
064: private static final char C_SPACE = ' ';
065: private static final char C_QUOTE = '"';
066: private static final char C_NSSEP = ':';
067:
068: private static final boolean DEBUG = false;
069:
070: /* ====================================================================== */
071:
072: /** Whether an element is left open like "<name ". */
073: private boolean hanging_element = false;
074:
075: /** True if we are processing the prolog. */
076: private boolean processing_prolog = true;
077:
078: /** True if we are processing the DTD. */
079: private boolean processing_dtd = false;
080:
081: /** A <code>Writer</code> for prolog elements. */
082: private PrologWriter prolog = new PrologWriter();
083:
084: /* ====================================================================== */
085:
086: /** The <code>DocType</code> instance representing the document. */
087: protected DocType doctype = null;
088:
089: /* ====================================================================== */
090:
091: /**
092: * Create a new instance of this <code>XMLSerializer</code>
093: */
094: public XMLSerializer() {
095: super (XML_ENCODER);
096: }
097:
098: /**
099: * Create a new instance of this <code>XMLSerializer</code>
100: */
101: protected XMLSerializer(XMLEncoder encoder) {
102: super (encoder);
103: }
104:
105: /**
106: * Reset this <code>XMLSerializer</code>.
107: */
108: public void recycle() {
109: super .recycle();
110: this .doctype = null;
111: this .hanging_element = false;
112: this .processing_prolog = true;
113: this .processing_dtd = false;
114: if (this .prolog != null)
115: this .prolog.reset();
116: }
117:
118: /**
119: * Return the MIME Content-Type produced by this serializer.
120: */
121: public String getMimeType() {
122: if (this .charset == null)
123: return ("text/xml");
124: return ("text/xml; charset=" + this .charset.getName());
125: }
126:
127: /* ====================================================================== */
128:
129: /**
130: * Receive notification of the beginning of a document.
131: */
132: public void startDocument() throws SAXException {
133: super .startDocument();
134: this .head();
135: }
136:
137: /**
138: * Receive notification of the end of a document.
139: */
140: public void endDocument() throws SAXException {
141: this .writeln();
142: super .endDocument();
143: }
144:
145: /**
146: * Write the XML document header.
147: * <p>
148: * This method will write out the <code><?xml version="1.0"
149: * ...></code> header.
150: * </p>
151: */
152: protected void head() throws SAXException {
153: this .write(S_DOCUMENT_1); // [<?xml version="1.0]
154: if (this .charset != null) {
155: this .write(S_DOCUMENT_2); // [" encoding="]
156: this .write(this .charset.getName());
157: }
158: this .write(S_DOCUMENT_3); // ["?>]
159: this .writeln();
160: }
161:
162: /**
163: * Report the start of DTD declarations, if any.
164: */
165: public void startDTD(String name, String public_id, String system_id)
166: throws SAXException {
167: this .processing_dtd = true;
168: this .doctype = new DocType(name, public_id, system_id);
169: }
170:
171: /**
172: * Report the start of DTD declarations, if any.
173: */
174: public void endDTD() throws SAXException {
175: this .processing_dtd = false;
176: }
177:
178: /**
179: * Receive notification of the beginning of the document body.
180: *
181: * @param uri The namespace URI of the root element.
182: * @param local The local name of the root element.
183: * @param qual The fully-qualified name of the root element.
184: */
185: public void body(String uri, String local, String qual)
186: throws SAXException {
187: this .processing_prolog = false;
188: this .writeln();
189:
190: /* We have a document type. */
191: if (this .doctype != null) {
192:
193: String root_name = this .doctype.getName();
194: /* Check the DTD and the root element */
195: if (!root_name.equals(qual)) {
196: throw new SAXException(
197: "Root element name \""
198: + root_name
199: + "\" declared by document type declaration differs "
200: + "from actual root element name \""
201: + qual + "\"");
202: }
203: /* Output the <!DOCTYPE ...> declaration. */
204: this .write(this .doctype.toString());
205: }
206:
207: /* Output all PIs and comments we cached in the prolog */
208: this .prolog.writeTo(this );
209: this .writeln();
210: }
211:
212: /**
213: * Receive notification of the beginning of an element.
214: *
215: * @param uri The namespace URI of the root element.
216: * @param local The local name of the root element.
217: * @param qual The fully-qualified name of the root element.
218: * @param namespaces An array of <code>String</code> objects containing
219: * the namespaces to be declared by this element.
220: * @param attributes An array of <code>String</code> objects containing
221: * all attributes of this element.
222: */
223: public void startElementImpl(String uri, String local, String qual,
224: String namespaces[][], String attributes[][])
225: throws SAXException {
226: this .closeElement(false);
227: this .write(C_LT); // [<]
228: if (DEBUG) {
229: this .write('[');
230: this .write(uri);
231: this .write(']');
232: }
233: this .write(qual);
234:
235: for (int x = 0; x < namespaces.length; x++) {
236: this .write(S_ELEMENT_4); // [ xmlns]
237: if (namespaces[x][Namespaces.NAMESPACE_PREFIX].length() > 0) {
238: this .write(C_NSSEP); // [:]
239: this .write(namespaces[x][Namespaces.NAMESPACE_PREFIX]);
240: }
241: this .write(S_ELEMENT_1); // [="]
242: this .encode(namespaces[x][Namespaces.NAMESPACE_URI]);
243: this .write(C_QUOTE); // ["]
244: }
245:
246: for (int x = 0; x < attributes.length; x++) {
247: this .write(C_SPACE); // [ ]
248: if (DEBUG) {
249: this .write('[');
250: this .write(attributes[x][ATTRIBUTE_NSURI]);
251: this .write(']');
252: }
253: this .write(attributes[x][ATTRIBUTE_QNAME]);
254: this .write(S_ELEMENT_1); // [="]
255: this .encode(attributes[x][ATTRIBUTE_VALUE]);
256: this .write(C_QUOTE); // ["]
257: }
258:
259: this .hanging_element = true;
260: }
261:
262: /**
263: * Receive notification of the end of an element.
264: *
265: * @param uri The namespace URI of the root element.
266: * @param local The local name of the root element.
267: * @param qual The fully-qualified name of the root element.
268: */
269: public void endElementImpl(String uri, String local, String qual)
270: throws SAXException {
271: if (closeElement(true))
272: return;
273: this .write(S_ELEMENT_2); // [</]
274: if (DEBUG) {
275: this .write('[');
276: this .write(uri);
277: this .write(']');
278: }
279: this .write(qual);
280: this .write(C_GT); // [>]
281: }
282:
283: /**
284: * Write the end part of a start element (if necessary).
285: *
286: * @param end_element Whether this method was called because an element
287: * is being closed or not.
288: * @return <b>true</b> if this call successfully closed the element (and
289: * no further <code></element></code> is required.
290: */
291: protected boolean closeElement(boolean end_element)
292: throws SAXException {
293: if (!hanging_element)
294: return false;
295: if (end_element)
296: this .write(S_ELEMENT_3); // [ />]
297: else
298: this .write(C_GT); // [>]
299: this .hanging_element = false;
300: return true;
301: }
302:
303: /**
304: * Report the start of a CDATA section.
305: */
306: public void startCDATA() throws SAXException {
307: if (this .processing_prolog)
308: return;
309: this .closeElement(false);
310: this .write(S_CDATA_1); // [<[CDATA[]
311: }
312:
313: /**
314: * Report the end of a CDATA section.
315: */
316: public void endCDATA() throws SAXException {
317: if (this .processing_prolog)
318: return;
319: this .closeElement(false);
320: this .write(S_CDATA_2); // []]>]
321: }
322:
323: /**
324: * Receive notification of character data.
325: */
326: public void charactersImpl(char data[], int start, int length)
327: throws SAXException {
328: if (this .processing_prolog)
329: return;
330: this .closeElement(false);
331: this .encode(data, start, length);
332: }
333:
334: /**
335: * Receive notification of ignorable whitespace in element content.
336: */
337: public void ignorableWhitespace(char data[], int start, int length)
338: throws SAXException {
339: this .charactersImpl(data, start, length);
340: }
341:
342: /**
343: * Report an XML comment anywhere in the document.
344: */
345: public void comment(char data[], int start, int length)
346: throws SAXException {
347: if (this .processing_dtd)
348: return;
349:
350: if (this .processing_prolog) {
351: this .prolog.write(S_COMMENT_1); // [<!--]
352: this .prolog.write(data, start, length);
353: this .prolog.write(S_COMMENT_2); // [-->]
354: this .prolog.write(S_EOL);
355: return;
356: }
357:
358: this .closeElement(false);
359: this .write(S_COMMENT_1); // [<!--]
360: this .write(data, start, length);
361: this .write(S_COMMENT_2); // [-->]
362: }
363:
364: /**
365: * Receive notification of a processing instruction.
366: */
367: public void processingInstruction(String target, String data)
368: throws SAXException {
369: if (this .processing_dtd)
370: return;
371:
372: if (this .processing_prolog) {
373: this .prolog.write(S_PROCINSTR_1); // [<?]
374: this .prolog.write(target);
375: if (data != null) {
376: this .prolog.write(C_SPACE); // [ ]
377: this .prolog.write(data);
378: }
379: this .prolog.write(S_PROCINSTR_2); // [?>]
380: this .prolog.write(S_EOL);
381: return;
382: }
383:
384: this .closeElement(false);
385:
386: this .write(S_PROCINSTR_1); // [<?]
387: this .write(target);
388: if (data != null) {
389: this .write(C_SPACE); // [ ]
390: this .write(data);
391: }
392: this .write(S_PROCINSTR_2); // [?>]
393: }
394:
395: /**
396: * Report the beginning of some internal and external XML entities.
397: */
398: public void startEntity(String name) throws SAXException {
399: }
400:
401: /**
402: * Report the end of an entity.
403: */
404: public void endEntity(String name) throws SAXException {
405: }
406:
407: /**
408: * Receive notification of a skipped entity.
409: */
410: public void skippedEntity(String name) throws SAXException {
411: }
412:
413: /* ====================================================================== */
414:
415: /**
416: * The <code>PrologWriter</code> is a simple extension to a
417: * <code>CharArrayWriter</code>.
418: */
419: private static final class PrologWriter extends CharArrayWriter {
420:
421: /** Create a new <code>PrologWriter</code> instance. */
422: private PrologWriter() {
423: super ();
424: }
425:
426: /**
427: * Write an array of characters.
428: * <p>
429: * The <code>CharArrayWriter</code> implementation of this method
430: * throws an unwanted <code>IOException</code>.
431: * </p>
432: */
433: public void write(char c[]) {
434: this .write(c, 0, c.length);
435: }
436:
437: /**
438: * Write a <code>String</code>.
439: * <p>
440: * The <code>CharArrayWriter</code> implementation of this method
441: * throws an unwanted <code>IOException</code>.
442: * </p>
443: */
444: public void write(String str) {
445: this .write(str, 0, str.length());
446: }
447:
448: /**
449: * Write our contents to a <code>BaseSerializer</code> without
450: * copying the buffer.
451: */
452: public void writeTo(XMLSerializer serializer)
453: throws SAXException {
454: serializer.write(this .buf, 0, this.count);
455: }
456: }
457: }
|