001: /*
002: * Copyright 2001-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id: ToXMLStream.java,v 1.21 2005/08/03 19:20:31 minchau Exp $
018: */
019: package org.apache.xml.serializer;
020:
021: import java.io.IOException;
022:
023: import javax.xml.transform.ErrorListener;
024: import javax.xml.transform.Result;
025: import javax.xml.transform.Transformer;
026: import javax.xml.transform.TransformerException;
027:
028: import org.apache.xml.serializer.utils.MsgKey;
029: import org.apache.xml.serializer.utils.Utils;
030: import org.xml.sax.SAXException;
031:
032: /**
033: * This class converts SAX or SAX-like calls to a
034: * serialized xml document. The xsl:output method is "xml".
035: *
036: * This class is used explicitly in code generated by XSLTC,
037: * so it is "public", but it should
038: * be viewed as internal or package private, this is not an API.
039: *
040: * @xsl.usage internal
041: */
042: public final class ToXMLStream extends ToStream {
043:
044: /**
045: * remembers if we need to write out "]]>" to close the CDATA
046: */
047: boolean m_cdataTagOpen = false;
048:
049: /**
050: * Map that tells which XML characters should have special treatment, and it
051: * provides character to entity name lookup.
052: */
053: private static CharInfo m_xmlcharInfo =
054: // new CharInfo(CharInfo.XML_ENTITIES_RESOURCE);
055: CharInfo.getCharInfo(CharInfo.XML_ENTITIES_RESOURCE, Method.XML);
056:
057: /**
058: * Default constructor.
059: */
060: public ToXMLStream() {
061: m_charInfo = m_xmlcharInfo;
062:
063: initCDATA();
064: // initialize namespaces
065: m_prefixMap = new NamespaceMappings();
066:
067: }
068:
069: /**
070: * Copy properties from another SerializerToXML.
071: *
072: * @param xmlListener non-null reference to a SerializerToXML object.
073: */
074: public void CopyFrom(ToXMLStream xmlListener) {
075:
076: m_writer = xmlListener.m_writer;
077:
078: // m_outputStream = xmlListener.m_outputStream;
079: String encoding = xmlListener.getEncoding();
080: setEncoding(encoding);
081:
082: setOmitXMLDeclaration(xmlListener.getOmitXMLDeclaration());
083:
084: m_ispreserve = xmlListener.m_ispreserve;
085: m_preserves = xmlListener.m_preserves;
086: m_isprevtext = xmlListener.m_isprevtext;
087: m_doIndent = xmlListener.m_doIndent;
088: setIndentAmount(xmlListener.getIndentAmount());
089: m_startNewLine = xmlListener.m_startNewLine;
090: m_needToOutputDocTypeDecl = xmlListener.m_needToOutputDocTypeDecl;
091: setDoctypeSystem(xmlListener.getDoctypeSystem());
092: setDoctypePublic(xmlListener.getDoctypePublic());
093: setStandalone(xmlListener.getStandalone());
094: setMediaType(xmlListener.getMediaType());
095: m_encodingInfo = xmlListener.m_encodingInfo;
096: m_spaceBeforeClose = xmlListener.m_spaceBeforeClose;
097: m_cdataStartCalled = xmlListener.m_cdataStartCalled;
098:
099: }
100:
101: /**
102: * Receive notification of the beginning of a document.
103: *
104: * @throws org.xml.sax.SAXException Any SAX exception, possibly
105: * wrapping another exception.
106: *
107: * @throws org.xml.sax.SAXException
108: */
109: public void startDocumentInternal() throws org.xml.sax.SAXException {
110:
111: if (m_needToCallStartDocument) {
112: super .startDocumentInternal();
113: m_needToCallStartDocument = false;
114:
115: if (m_inEntityRef)
116: return;
117:
118: m_needToOutputDocTypeDecl = true;
119: m_startNewLine = false;
120: /* The call to getXMLVersion() might emit an error message
121: * and we should emit this message regardless of if we are
122: * writing out an XML header or not.
123: */
124: final String version = getXMLVersion();
125: if (getOmitXMLDeclaration() == false) {
126: String encoding = Encodings
127: .getMimeEncoding(getEncoding());
128: String standalone;
129:
130: if (m_standaloneWasSpecified) {
131: standalone = " standalone=\"" + getStandalone()
132: + "\"";
133: } else {
134: standalone = "";
135: }
136:
137: try {
138: final java.io.Writer writer = m_writer;
139: writer.write("<?xml version=\"");
140: writer.write(version);
141: writer.write("\" encoding=\"");
142: writer.write(encoding);
143: writer.write('\"');
144: writer.write(standalone);
145: writer.write("?>");
146: if (m_doIndent)
147: writer.write(m_lineSep, 0, m_lineSepLen);
148: } catch (IOException e) {
149: throw new SAXException(e);
150: }
151:
152: }
153: }
154: }
155:
156: /**
157: * Receive notification of the end of a document.
158: *
159: * @throws org.xml.sax.SAXException Any SAX exception, possibly
160: * wrapping another exception.
161: *
162: * @throws org.xml.sax.SAXException
163: */
164: public void endDocument() throws org.xml.sax.SAXException {
165: flushPending();
166: if (m_doIndent && !m_isprevtext) {
167: try {
168: outputLineSep();
169: } catch (IOException e) {
170: throw new SAXException(e);
171: }
172: }
173:
174: flushWriter();
175:
176: if (m_tracer != null)
177: super .fireEndDoc();
178: }
179:
180: /**
181: * Starts a whitespace preserving section. All characters printed
182: * within a preserving section are printed without indentation and
183: * without consolidating multiple spaces. This is equivalent to
184: * the <tt>xml:space="preserve"</tt> attribute. Only XML
185: * and HTML serializers need to support this method.
186: * <p>
187: * The contents of the whitespace preserving section will be delivered
188: * through the regular <tt>characters</tt> event.
189: *
190: * @throws org.xml.sax.SAXException
191: */
192: public void startPreserving() throws org.xml.sax.SAXException {
193:
194: // Not sure this is really what we want. -sb
195: m_preserves.push(true);
196:
197: m_ispreserve = true;
198: }
199:
200: /**
201: * Ends a whitespace preserving section.
202: *
203: * @see #startPreserving
204: *
205: * @throws org.xml.sax.SAXException
206: */
207: public void endPreserving() throws org.xml.sax.SAXException {
208:
209: // Not sure this is really what we want. -sb
210: m_ispreserve = m_preserves.isEmpty() ? false : m_preserves
211: .pop();
212: }
213:
214: /**
215: * Receive notification of a processing instruction.
216: *
217: * @param target The processing instruction target.
218: * @param data The processing instruction data, or null if
219: * none was supplied.
220: * @throws org.xml.sax.SAXException Any SAX exception, possibly
221: * wrapping another exception.
222: *
223: * @throws org.xml.sax.SAXException
224: */
225: public void processingInstruction(String target, String data)
226: throws org.xml.sax.SAXException {
227: if (m_inEntityRef)
228: return;
229:
230: flushPending();
231:
232: if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING)) {
233: startNonEscaping();
234: } else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING)) {
235: endNonEscaping();
236: } else {
237: try {
238: if (m_elemContext.m_startTagOpen) {
239: closeStartTag();
240: m_elemContext.m_startTagOpen = false;
241: } else if (m_needToCallStartDocument)
242: startDocumentInternal();
243:
244: if (shouldIndent())
245: indent();
246:
247: final java.io.Writer writer = m_writer;
248: writer.write("<?");
249: writer.write(target);
250:
251: if (data.length() > 0
252: && !Character.isSpaceChar(data.charAt(0)))
253: writer.write(' ');
254:
255: int indexOfQLT = data.indexOf("?>");
256:
257: if (indexOfQLT >= 0) {
258:
259: // See XSLT spec on error recovery of "?>" in PIs.
260: if (indexOfQLT > 0) {
261: writer.write(data.substring(0, indexOfQLT));
262: }
263:
264: writer.write("? >"); // add space between.
265:
266: if ((indexOfQLT + 2) < data.length()) {
267: writer.write(data.substring(indexOfQLT + 2));
268: }
269: } else {
270: writer.write(data);
271: }
272:
273: writer.write('?');
274: writer.write('>');
275:
276: // Always output a newline char if not inside of an
277: // element. The whitespace is not significant in that
278: // case.
279: if (m_elemContext.m_currentElemDepth <= 0)
280: writer.write(m_lineSep, 0, m_lineSepLen);
281:
282: m_startNewLine = true;
283: } catch (IOException e) {
284: throw new SAXException(e);
285: }
286: }
287:
288: if (m_tracer != null)
289: super .fireEscapingEvent(target, data);
290: }
291:
292: /**
293: * Receive notivication of a entityReference.
294: *
295: * @param name The name of the entity.
296: *
297: * @throws org.xml.sax.SAXException
298: */
299: public void entityReference(String name)
300: throws org.xml.sax.SAXException {
301: if (m_elemContext.m_startTagOpen) {
302: closeStartTag();
303: m_elemContext.m_startTagOpen = false;
304: }
305:
306: try {
307: if (shouldIndent())
308: indent();
309:
310: final java.io.Writer writer = m_writer;
311: writer.write('&');
312: writer.write(name);
313: writer.write(';');
314: } catch (IOException e) {
315: throw new SAXException(e);
316: }
317:
318: if (m_tracer != null)
319: super .fireEntityReference(name);
320: }
321:
322: /**
323: * This method is used to add an attribute to the currently open element.
324: * The caller has guaranted that this attribute is unique, which means that it
325: * not been seen before and will not be seen again.
326: *
327: * @param name the qualified name of the attribute
328: * @param value the value of the attribute which can contain only
329: * ASCII printable characters characters in the range 32 to 127 inclusive.
330: * @param flags the bit values of this integer give optimization information.
331: */
332: public void addUniqueAttribute(String name, String value, int flags)
333: throws SAXException {
334: if (m_elemContext.m_startTagOpen) {
335:
336: try {
337: final String patchedName = patchName(name);
338: final java.io.Writer writer = m_writer;
339: if ((flags & NO_BAD_CHARS) > 0
340: && m_xmlcharInfo.onlyQuotAmpLtGt) {
341: // "flags" has indicated that the characters
342: // '>' '<' '&' and '"' are not in the value and
343: // m_htmlcharInfo has recorded that there are no other
344: // entities in the range 32 to 127 so we write out the
345: // value directly
346:
347: writer.write(' ');
348: writer.write(patchedName);
349: writer.write("=\"");
350: writer.write(value);
351: writer.write('"');
352: } else {
353: writer.write(' ');
354: writer.write(patchedName);
355: writer.write("=\"");
356: writeAttrString(writer, value, this .getEncoding());
357: writer.write('"');
358: }
359: } catch (IOException e) {
360: throw new SAXException(e);
361: }
362: }
363: }
364:
365: /**
366: * Add an attribute to the current element.
367: * @param uri the URI associated with the element name
368: * @param localName local part of the attribute name
369: * @param rawName prefix:localName
370: * @param type
371: * @param value the value of the attribute
372: * @param xslAttribute true if this attribute is from an xsl:attribute,
373: * false if declared within the elements opening tag.
374: * @throws SAXException
375: */
376: public void addAttribute(String uri, String localName,
377: String rawName, String type, String value,
378: boolean xslAttribute) throws SAXException {
379: if (m_elemContext.m_startTagOpen) {
380: boolean was_added = addAttributeAlways(uri, localName,
381: rawName, type, value, xslAttribute);
382:
383: /*
384: * We don't run this block of code if:
385: * 1. The attribute value was only replaced (was_added is false).
386: * 2. The attribute is from an xsl:attribute element (that is handled
387: * in the addAttributeAlways() call just above.
388: * 3. The name starts with "xmlns", i.e. it is a namespace declaration.
389: */
390: if (was_added && !xslAttribute
391: && !rawName.startsWith("xmlns")) {
392: String prefixUsed = ensureAttributesNamespaceIsDeclared(
393: uri, localName, rawName);
394: if (prefixUsed != null && rawName != null
395: && !rawName.startsWith(prefixUsed)) {
396: // use a different raw name, with the prefix used in the
397: // generated namespace declaration
398: rawName = prefixUsed + ":" + localName;
399:
400: }
401: }
402: addAttributeAlways(uri, localName, rawName, type, value,
403: xslAttribute);
404: } else {
405: /*
406: * The startTag is closed, yet we are adding an attribute?
407: *
408: * Section: 7.1.3 Creating Attributes Adding an attribute to an
409: * element after a PI (for example) has been added to it is an
410: * error. The attributes can be ignored. The spec doesn't explicitly
411: * say this is disallowed, as it does for child elements, but it
412: * makes sense to have the same treatment.
413: *
414: * We choose to ignore the attribute which is added too late.
415: */
416: // Generate a warning of the ignored attributes
417: // Create the warning message
418: String msg = Utils.messages.createMessage(
419: MsgKey.ER_ILLEGAL_ATTRIBUTE_POSITION,
420: new Object[] { localName });
421:
422: try {
423: // Prepare to issue the warning message
424: Transformer tran = super .getTransformer();
425: ErrorListener errHandler = tran.getErrorListener();
426:
427: // Issue the warning message
428: if (null != errHandler && m_sourceLocator != null)
429: errHandler.warning(new TransformerException(msg,
430: m_sourceLocator));
431: else
432: System.out.println(msg);
433: } catch (Exception e) {
434: }
435: }
436: }
437:
438: /**
439: * @see ExtendedContentHandler#endElement(String)
440: */
441: public void endElement(String elemName) throws SAXException {
442: endElement(null, null, elemName);
443: }
444:
445: /**
446: * This method is used to notify the serializer of a namespace mapping (or node)
447: * that applies to the current element whose startElement() call has already been seen.
448: * The official SAX startPrefixMapping(prefix,uri) is to define a mapping for a child
449: * element that is soon to be seen with a startElement() call. The official SAX call
450: * does not apply to the current element, hence the reason for this method.
451: */
452: public void namespaceAfterStartElement(final String prefix,
453: final String uri) throws SAXException {
454:
455: // hack for XSLTC with finding URI for default namespace
456: if (m_elemContext.m_elementURI == null) {
457: String prefix1 = getPrefixPart(m_elemContext.m_elementName);
458: if (prefix1 == null && EMPTYSTRING.equals(prefix)) {
459: // the elements URI is not known yet, and it
460: // doesn't have a prefix, and we are currently
461: // setting the uri for prefix "", so we have
462: // the uri for the element... lets remember it
463: m_elemContext.m_elementURI = uri;
464: }
465: }
466: startPrefixMapping(prefix, uri, false);
467: return;
468:
469: }
470:
471: /**
472: * From XSLTC
473: * Declare a prefix to point to a namespace URI. Inform SAX handler
474: * if this is a new prefix mapping.
475: */
476: protected boolean pushNamespace(String prefix, String uri) {
477: try {
478: if (m_prefixMap.pushNamespace(prefix, uri,
479: m_elemContext.m_currentElemDepth)) {
480: startPrefixMapping(prefix, uri);
481: return true;
482: }
483: } catch (SAXException e) {
484: // falls through
485: }
486: return false;
487: }
488:
489: /**
490: * Try's to reset the super class and reset this class for
491: * re-use, so that you don't need to create a new serializer
492: * (mostly for performance reasons).
493: *
494: * @return true if the class was successfuly reset.
495: */
496: public boolean reset() {
497: boolean wasReset = false;
498: if (super .reset()) {
499: resetToXMLStream();
500: wasReset = true;
501: }
502: return wasReset;
503: }
504:
505: /**
506: * Reset all of the fields owned by ToStream class
507: *
508: */
509: private void resetToXMLStream() {
510: this .m_cdataTagOpen = false;
511:
512: }
513:
514: /**
515: * This method checks for the XML version of output document.
516: * If XML version of output document is not specified, then output
517: * document is of version XML 1.0.
518: * If XML version of output doucment is specified, but it is not either
519: * XML 1.0 or XML 1.1, a warning message is generated, the XML Version of
520: * output document is set to XML 1.0 and processing continues.
521: * @return string (XML version)
522: */
523: private String getXMLVersion() {
524: String xmlVersion = getVersion();
525: if (xmlVersion == null || xmlVersion.equals(XMLVERSION10)) {
526: xmlVersion = XMLVERSION10;
527: } else if (xmlVersion.equals(XMLVERSION11)) {
528: xmlVersion = XMLVERSION11;
529: } else {
530: String msg = Utils.messages.createMessage(
531: MsgKey.ER_XML_VERSION_NOT_SUPPORTED,
532: new Object[] { xmlVersion });
533: try {
534: // Prepare to issue the warning message
535: Transformer tran = super .getTransformer();
536: ErrorListener errHandler = tran.getErrorListener();
537: // Issue the warning message
538: if (null != errHandler && m_sourceLocator != null)
539: errHandler.warning(new TransformerException(msg,
540: m_sourceLocator));
541: else
542: System.out.println(msg);
543: } catch (Exception e) {
544: }
545: xmlVersion = XMLVERSION10;
546: }
547: return xmlVersion;
548: }
549: }
|