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.IOException;
020: import java.io.OutputStream;
021: import java.io.OutputStreamWriter;
022: import java.io.UnsupportedEncodingException;
023: import java.util.Arrays;
024:
025: import org.apache.avalon.excalibur.pool.Recyclable;
026: import org.apache.avalon.framework.configuration.Configurable;
027: import org.apache.avalon.framework.configuration.Configuration;
028: import org.apache.avalon.framework.configuration.ConfigurationException;
029: import org.apache.cocoon.components.serializers.encoding.Charset;
030: import org.apache.cocoon.components.serializers.encoding.CharsetFactory;
031: import org.apache.cocoon.components.serializers.encoding.Encoder;
032: import org.apache.cocoon.components.serializers.util.Namespaces;
033: import org.apache.cocoon.serialization.Serializer;
034: import org.apache.commons.lang.SystemUtils;
035: import org.xml.sax.Attributes;
036: import org.xml.sax.Locator;
037: import org.xml.sax.SAXException;
038:
039: /**
040: * <p>An abstract serializer supporting multiple encodings.</p>
041: *
042: * <p>This serializer can accept the following configuration whenever it
043: * is declared into a sitemap:</p>
044: *
045: * <pre>
046: * <serializer class="org.apache.cocoon.components.serializers..." ... >
047: * <encoding>myencoding</encoding>
048: * <indent>myindenting</indent>
049: * </serializer>
050: * </pre>
051: *
052: * <p>The value indicated by <i>myencoding</i> must be replaced with a valid
053: * charset encoding (this serializer does not rely on the JVM for character
054: * encoding, you can look into the <code>cocoon-serializers-charsets</code>
055: * JAR file for a list).<p>
056: *
057: * <p>The value indicated by <i>myindenting</i> will control the indenting
058: * level for each element.<p>
059: *
060: * @version CVS $Id: EncodingSerializer.java 433543 2006-08-22 06:22:54Z crossley $
061: */
062: public abstract class EncodingSerializer implements Serializer,
063: Locator, Recyclable, Configurable {
064:
065: /** The line separator string */
066: private static final char S_EOL[] = SystemUtils.LINE_SEPARATOR
067: .toCharArray();
068:
069: /* ====================================================================== */
070:
071: /** The position of the namespace URI in the attributes array. */
072: public static final int ATTRIBUTE_NSURI = 0;
073:
074: /** The position of the local name in the attributes array. */
075: public static final int ATTRIBUTE_LOCAL = 1;
076:
077: /** The position of the qualified name in the attributes array. */
078: public static final int ATTRIBUTE_QNAME = 2;
079:
080: /** The position of the value in the attributes array. */
081: public static final int ATTRIBUTE_VALUE = 3;
082:
083: /** The length of the array of strings representing an attribute. */
084: public static final int ATTRIBUTE_LENGTH = 4;
085:
086: /* ====================================================================== */
087:
088: /** Our <code>Encoder</code> instance. */
089: private Encoder encoder = null;
090:
091: /** Our <code>Locator</code> instance. */
092: private Locator locator = null;
093:
094: /** Our <code>Writer</code> instance. */
095: private OutputStreamWriter out = null;
096:
097: /** Flag indicating if the document prolog is being processed. */
098: private boolean prolog = true;
099:
100: /** Flag indicating if the document is being processed. */
101: private boolean processing = false;
102:
103: /** Current nesting level */
104: private int level = 0;
105:
106: /** Whitespace buffer for indentation */
107: private char[] indentBuffer = null;
108:
109: /* ====================================================================== */
110:
111: /** The <code>Charset</code> associated with the character encoding. */
112: protected Charset charset = null;
113:
114: /** The <code>Namespace</code> associated with this instance. */
115: protected Namespaces namespaces = new Namespaces();
116:
117: /** Per level indent spaces */
118: protected int indentPerLevel = 0;
119:
120: /* ====================================================================== */
121:
122: /**
123: * Create a new instance of this <code>EncodingSerializer</code>
124: */
125: protected EncodingSerializer(Encoder encoder) {
126: super ();
127: this .encoder = encoder;
128: this .recycle();
129: }
130:
131: /* ====================================================================== */
132:
133: /**
134: * Test if the component wants to set the content length.
135: */
136: public boolean shouldSetContentLength() {
137: return (false);
138: }
139:
140: /**
141: * Reset this <code>EncodingSerializer</code>.
142: */
143: public void recycle() {
144: if (processing)
145: throw new IllegalStateException();
146: this .namespaces = new Namespaces();
147: this .locator = null;
148: this .out = null;
149: this .prolog = true;
150: }
151:
152: /**
153: * Set the <code>OutputStream</code> where this serializer will
154: * write data to.
155: *
156: * @param out The <code>OutputStream</code> used for output.
157: */
158: public void setOutputStream(OutputStream out) throws IOException {
159: if (out == null)
160: throw new NullPointerException("Null output");
161:
162: this .out = new OutputStreamWriter(out, this .charset.getName());
163: }
164:
165: /**
166: * Set the configurations for this serializer.
167: */
168: public void configure(Configuration conf)
169: throws ConfigurationException {
170: String encoding = conf.getChild("encoding").getValue(null);
171: try {
172: this .charset = CharsetFactory.newInstance().getCharset(
173: encoding);
174: } catch (UnsupportedEncodingException exception) {
175: throw new ConfigurationException("Encoding not supported: "
176: + encoding, exception);
177: }
178:
179: indentPerLevel = conf.getChild("indent").getValueAsInteger(0);
180: if (indentPerLevel > 0) {
181: assureIndentBuffer(indentPerLevel * 6);
182: }
183: }
184:
185: /* ====================================================================== */
186:
187: private char[] assureIndentBuffer(int size) {
188: if (indentBuffer == null || indentBuffer.length < size) {
189: indentBuffer = new char[size];
190: Arrays.fill(indentBuffer, ' ');
191: }
192: return indentBuffer;
193: }
194:
195: /**
196: * Encode and write a <code>String</code>
197: */
198: protected void encode(String data) throws SAXException {
199: char array[] = data.toCharArray();
200: this .encode(array, 0, array.length);
201: }
202:
203: /**
204: * Encode and write an array of characters.
205: */
206: protected void encode(char data[]) throws SAXException {
207: this .encode(data, 0, data.length);
208: }
209:
210: /**
211: * Encode and write a specific part of an array of characters.
212: */
213: protected void encode(char data[], int start, int length)
214: throws SAXException {
215: int end = start + length;
216:
217: if (data == null)
218: throw new NullPointerException("Null data");
219: if ((start < 0) || (start > data.length) || (length < 0)
220: || (end > data.length) || (end < 0))
221: throw new IndexOutOfBoundsException("Invalid data");
222: if (length == 0)
223: return;
224:
225: for (int x = start; x < end; x++) {
226: char c = data[x];
227:
228: if (this .charset.allows(c) && this .encoder.allows(c)) {
229: continue;
230: }
231:
232: if (start != x)
233: this .write(data, start, x - start);
234: this .write(this .encoder.encode(c));
235: start = x + 1;
236: continue;
237: }
238: if (start != end)
239: this .write(data, start, end - start);
240: }
241:
242: /* ====================================================================== */
243:
244: /**
245: * Receive an object for locating the origin of SAX document events.
246: */
247: public final void setDocumentLocator(Locator locator) {
248: this .locator = locator;
249: }
250:
251: /**
252: * Return the public identifier for the current document event.
253: *
254: * @return A <code>String</code> containing the public identifier,
255: * or <b>null</b> if none is available.
256: */
257: public String getPublicId() {
258: return (this .locator == null ? null : this .locator
259: .getPublicId());
260: }
261:
262: /**
263: * Return the system identifier for the current document event.
264: *
265: * @return A <code>String</code> containing the system identifier,
266: * or <b>null</b> if none is available.
267: */
268: public String getSystemId() {
269: return (this .locator == null ? null : this .locator
270: .getSystemId());
271: }
272:
273: /**
274: * Return the line number where the current document event ends.
275: *
276: * @return The line number, or -1 if none is available.
277: */
278: public int getLineNumber() {
279: return (this .locator == null ? -1 : this .locator
280: .getLineNumber());
281: }
282:
283: /**
284: * Return the column number where the current document event ends.
285: *
286: * @return The column number, or -1 if none is available.
287: */
288: public int getColumnNumber() {
289: return (this .locator == null ? -1 : this .locator
290: .getColumnNumber());
291: }
292:
293: /**
294: * Return a <code>String</code> describing the current location.
295: */
296: protected String getLocation() {
297: if (this .locator == null)
298: return ("");
299: StringBuffer buf = new StringBuffer(" (");
300: if (this .getSystemId() != null) {
301: buf.append(this .getSystemId());
302: buf.append(' ');
303: }
304: buf.append("line " + this .getLineNumber());
305: buf.append(" col " + this .getColumnNumber());
306: buf.append(')');
307: return (buf.toString());
308: }
309:
310: /* ====================================================================== */
311:
312: /**
313: * Flush the stream.
314: */
315: protected void flush() throws SAXException {
316: try {
317: this .out.flush();
318: } catch (IOException e) {
319: throw new SAXException("I/O error flushing: "
320: + e.getMessage(), e);
321: }
322: }
323:
324: /**
325: * Write an array of characters.
326: */
327: protected void write(char data[]) throws SAXException {
328: try {
329: this .out.write(data, 0, data.length);
330: } catch (IOException e) {
331: throw new SAXException("I/O error writing: "
332: + e.getMessage(), e);
333: }
334: }
335:
336: /**
337: * Write a portion of an array of characters.
338: */
339: protected void write(char data[], int start, int length)
340: throws SAXException {
341: try {
342: this .out.write(data, start, length);
343: } catch (IOException e) {
344: throw new SAXException("I/O error writing: "
345: + e.getMessage(), e);
346: }
347: }
348:
349: /**
350: * Write a single character.
351: */
352: protected void write(int c) throws SAXException {
353: try {
354: this .out.write(c);
355: } catch (IOException e) {
356: throw new SAXException("I/O error writing: "
357: + e.getMessage(), e);
358: }
359: }
360:
361: /**
362: * Write a string.
363: */
364: protected void write(String data) throws SAXException {
365: try {
366: this .out.write(data);
367: } catch (IOException e) {
368: throw new SAXException("I/O error writing: "
369: + e.getMessage(), e);
370: }
371: }
372:
373: /**
374: * Write a portion of a string.
375: */
376: protected void write(String data, int start, int length)
377: throws SAXException {
378: try {
379: this .out.write(data, start, length);
380: } catch (IOException e) {
381: throw new SAXException("I/O error writing: "
382: + e.getMessage(), e);
383: }
384: }
385:
386: /**
387: * Write a end-of-line character.
388: */
389: protected void writeln() throws SAXException {
390: try {
391: this .out.write(S_EOL);
392: } catch (IOException e) {
393: throw new SAXException("I/O error writing: "
394: + e.getMessage(), e);
395: }
396: }
397:
398: /**
399: * Write a string and a end-of-line character.
400: */
401: protected void writeln(String data) throws SAXException {
402: try {
403: this .out.write(data);
404: this .out.write(S_EOL);
405: } catch (IOException e) {
406: throw new SAXException("I/O error writing: "
407: + e.getMessage(), e);
408: }
409: }
410:
411: /**
412: * Write out character to indent the output according
413: * to the level of nesting
414: * @param indent
415: * @throws SAXException
416: */
417: protected void writeIndent(int indent) throws SAXException {
418: this .charactersImpl("\n".toCharArray(), 0, 1);
419: if (indent > 0) {
420: this .charactersImpl(assureIndentBuffer(indent), 0, indent);
421: }
422: }
423:
424: /* ====================================================================== */
425:
426: /**
427: * Receive notification of the beginning of a document.
428: */
429: public void startDocument() throws SAXException {
430: this .processing = true;
431: this .level = 0;
432: }
433:
434: /**
435: * Receive notification of the end of a document.
436: */
437: public void endDocument() throws SAXException {
438: this .processing = false;
439: this .flush();
440: }
441:
442: /**
443: * Begin the scope of a prefix-URI Namespace mapping.
444: */
445: public void startPrefixMapping(String prefix, String uri)
446: throws SAXException {
447: this .namespaces.push(prefix, uri);
448: }
449:
450: /**
451: * End the scope of a prefix-URI mapping.
452: */
453: public void endPrefixMapping(String prefix) throws SAXException {
454: this .namespaces.pop(prefix);
455: }
456:
457: /**
458: * Receive notification of the beginning of an element.
459: */
460: public void startElement(String nsuri, String local, String qual,
461: Attributes attributes) throws SAXException {
462: if (indentPerLevel > 0) {
463: this .writeIndent(indentPerLevel * level);
464: level++;
465: }
466:
467: String name = this .namespaces.qualify(nsuri, local, qual);
468:
469: if (this .prolog) {
470: this .body(nsuri, local, name);
471: this .prolog = false;
472: }
473:
474: String ns[][] = this .namespaces.commit();
475:
476: String at[][] = new String[attributes.getLength()][4];
477: for (int x = 0; x < at.length; x++) {
478: at[x][ATTRIBUTE_NSURI] = attributes.getURI(x);
479: at[x][ATTRIBUTE_LOCAL] = attributes.getLocalName(x);
480: at[x][ATTRIBUTE_QNAME] = namespaces.qualify(attributes
481: .getURI(x), attributes.getLocalName(x), attributes
482: .getQName(x));
483: at[x][ATTRIBUTE_VALUE] = attributes.getValue(x);
484: }
485:
486: this .startElementImpl(nsuri, local, name, ns, at);
487: }
488:
489: public void characters(char ch[], int start, int length)
490: throws SAXException {
491: if (indentPerLevel > 0) {
492: this .writeIndent(indentPerLevel * level + 1);
493: }
494: this .charactersImpl(ch, start, length);
495: }
496:
497: /**
498: * Receive notification of the end of an element.
499: */
500: public void endElement(String nsuri, String local, String qual)
501: throws SAXException {
502: if (indentPerLevel > 0) {
503: level--;
504: this .writeIndent(indentPerLevel * level);
505: }
506:
507: String name = this .namespaces.qualify(nsuri, local, qual);
508: this .endElementImpl(nsuri, local, name);
509: }
510:
511: /**
512: * Receive notification of the beginning of the document body.
513: *
514: * @param uri The namespace URI of the root element.
515: * @param local The local name of the root element.
516: * @param qual The fully-qualified name of the root element.
517: */
518: public abstract void body(String uri, String local, String qual)
519: throws SAXException;
520:
521: /**
522: * Receive notification of the beginning of an element.
523: *
524: * @param uri The namespace URI of the root element.
525: * @param local The local name of the root element.
526: * @param qual The fully-qualified name of the root element.
527: * @param namespaces An array of <code>String</code> objects containing
528: * the namespaces to be declared by this element.
529: * @param attributes An array of <code>String</code> objects containing
530: * all attributes of this element.
531: */
532: public abstract void startElementImpl(String uri, String local,
533: String qual, String namespaces[][], String attributes[][])
534: throws SAXException;
535:
536: /**
537: * Receive character notifications
538: * @param ch
539: * @param start
540: * @param length
541: * @throws SAXException
542: */
543: public abstract void charactersImpl(char ch[], int start, int length)
544: throws SAXException;
545:
546: /**
547: * Receive notification of the end of an element.
548: *
549: * @param uri The namespace URI of the root element.
550: * @param local The local name of the root element.
551: * @param qual The fully-qualified name of the root element.
552: */
553: public abstract void endElementImpl(String uri, String local,
554: String qual) throws SAXException;
555: }
|