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: ToXMLSAXHandler.java,v 1.17 2005/04/07 04:29:03 minchau Exp $
018: */
019: package org.apache.xml.serializer;
020:
021: import java.io.IOException;
022: import java.io.OutputStream;
023: import java.io.Writer;
024: import java.util.Properties;
025:
026: import javax.xml.transform.Result;
027:
028: import org.w3c.dom.Node;
029: import org.xml.sax.Attributes;
030: import org.xml.sax.ContentHandler;
031: import org.xml.sax.Locator;
032: import org.xml.sax.SAXException;
033: import org.xml.sax.ext.LexicalHandler;
034:
035: /**
036: * This class receives notification of SAX-like events, and with gathered
037: * information over these calls it will invoke the equivalent SAX methods
038: * on a handler, the ultimate xsl:output method is known to be "xml".
039: *
040: * This class is not a public API, it is only public because it is used by Xalan.
041: * @xsl.usage internal
042: */
043: public final class ToXMLSAXHandler extends ToSAXHandler {
044:
045: /**
046: * Keeps track of whether output escaping is currently enabled
047: */
048: protected boolean m_escapeSetting = false;
049:
050: public ToXMLSAXHandler() {
051: // default constructor (need to set content handler ASAP !)
052: m_prefixMap = new NamespaceMappings();
053: initCDATA();
054: }
055:
056: /**
057: * @see Serializer#getOutputFormat()
058: */
059: public Properties getOutputFormat() {
060: return null;
061: }
062:
063: /**
064: * @see Serializer#getOutputStream()
065: */
066: public OutputStream getOutputStream() {
067: return null;
068: }
069:
070: /**
071: * @see Serializer#getWriter()
072: */
073: public Writer getWriter() {
074: return null;
075: }
076:
077: /**
078: * Do nothing for SAX.
079: */
080: public void indent(int n) throws SAXException {
081: }
082:
083: /**
084: * @see DOMSerializer#serialize(Node)
085: */
086: public void serialize(Node node) throws IOException {
087: }
088:
089: /**
090: * @see SerializationHandler#setEscaping(boolean)
091: */
092: public boolean setEscaping(boolean escape) throws SAXException {
093: boolean oldEscapeSetting = m_escapeSetting;
094: m_escapeSetting = escape;
095:
096: if (escape) {
097: processingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, "");
098: } else {
099: processingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, "");
100: }
101:
102: return oldEscapeSetting;
103: }
104:
105: /**
106: * @see Serializer#setOutputFormat(Properties)
107: */
108: public void setOutputFormat(Properties format) {
109: }
110:
111: /**
112: * @see Serializer#setOutputStream(OutputStream)
113: */
114: public void setOutputStream(OutputStream output) {
115: }
116:
117: /**
118: * @see Serializer#setWriter(Writer)
119: */
120: public void setWriter(Writer writer) {
121: }
122:
123: /**
124: * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
125: */
126: public void attributeDecl(String arg0, String arg1, String arg2,
127: String arg3, String arg4) throws SAXException {
128: }
129:
130: /**
131: * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
132: */
133: public void elementDecl(String arg0, String arg1)
134: throws SAXException {
135: }
136:
137: /**
138: * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
139: */
140: public void externalEntityDecl(String arg0, String arg1, String arg2)
141: throws SAXException {
142: }
143:
144: /**
145: * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(String, String)
146: */
147: public void internalEntityDecl(String arg0, String arg1)
148: throws SAXException {
149: }
150:
151: /**
152: * Receives notification of the end of the document.
153: * @see org.xml.sax.ContentHandler#endDocument()
154: */
155: public void endDocument() throws SAXException {
156:
157: flushPending();
158:
159: // Close output document
160: m_saxHandler.endDocument();
161:
162: if (m_tracer != null)
163: super .fireEndDoc();
164: }
165:
166: /**
167: * This method is called when all the data needed for a call to the
168: * SAX handler's startElement() method has been gathered.
169: */
170: protected void closeStartTag() throws SAXException {
171:
172: m_elemContext.m_startTagOpen = false;
173:
174: final String localName = getLocalName(m_elemContext.m_elementName);
175: final String uri = getNamespaceURI(m_elemContext.m_elementName,
176: true);
177:
178: // Now is time to send the startElement event
179: if (m_needToCallStartDocument) {
180: startDocumentInternal();
181: }
182: m_saxHandler.startElement(uri, localName,
183: m_elemContext.m_elementName, m_attributes);
184: // we've sent the official SAX attributes on their way,
185: // now we don't need them anymore.
186: m_attributes.clear();
187:
188: if (m_state != null)
189: m_state.setCurrentNode(null);
190: }
191:
192: /**
193: * Closes ane open cdata tag, and
194: * unlike the this.endCDATA() method (from the LexicalHandler) interface,
195: * this "internal" method will send the endCDATA() call to the wrapped
196: * handler.
197: *
198: */
199: public void closeCDATA() throws SAXException {
200:
201: // Output closing bracket - "]]>"
202: if (m_lexHandler != null && m_cdataTagOpen) {
203: m_lexHandler.endCDATA();
204: }
205:
206: // There are no longer any calls made to
207: // m_lexHandler.startCDATA() without a balancing call to
208: // m_lexHandler.endCDATA()
209: // so we set m_cdataTagOpen to false to remember this.
210: m_cdataTagOpen = false;
211: }
212:
213: /**
214: * @see org.xml.sax.ContentHandler#endElement(String, String, String)
215: */
216: public void endElement(String namespaceURI, String localName,
217: String qName) throws SAXException {
218: // Close any open elements etc.
219: flushPending();
220:
221: if (namespaceURI == null) {
222: if (m_elemContext.m_elementURI != null)
223: namespaceURI = m_elemContext.m_elementURI;
224: else
225: namespaceURI = getNamespaceURI(qName, true);
226: }
227:
228: if (localName == null) {
229: if (m_elemContext.m_elementLocalName != null)
230: localName = m_elemContext.m_elementLocalName;
231: else
232: localName = getLocalName(qName);
233: }
234:
235: m_saxHandler.endElement(namespaceURI, localName, qName);
236:
237: if (m_tracer != null)
238: super .fireEndElem(qName);
239:
240: /* Pop all namespaces at the current element depth.
241: * We are not waiting for official endPrefixMapping() calls.
242: */
243: m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth,
244: m_saxHandler);
245: m_elemContext = m_elemContext.m_prev;
246: }
247:
248: /**
249: * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
250: */
251: public void endPrefixMapping(String prefix) throws SAXException {
252: /* poping all prefix mappings should have been done
253: * in endElement() already
254: */
255: return;
256: }
257:
258: /**
259: * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
260: */
261: public void ignorableWhitespace(char[] arg0, int arg1, int arg2)
262: throws SAXException {
263: m_saxHandler.ignorableWhitespace(arg0, arg1, arg2);
264: }
265:
266: /**
267: * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
268: */
269: public void setDocumentLocator(Locator arg0) {
270: m_saxHandler.setDocumentLocator(arg0);
271: }
272:
273: /**
274: * @see org.xml.sax.ContentHandler#skippedEntity(String)
275: */
276: public void skippedEntity(String arg0) throws SAXException {
277: m_saxHandler.skippedEntity(arg0);
278: }
279:
280: /**
281: * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
282: * @param prefix The prefix that maps to the URI
283: * @param uri The URI for the namespace
284: */
285: public void startPrefixMapping(String prefix, String uri)
286: throws SAXException {
287: startPrefixMapping(prefix, uri, true);
288: }
289:
290: /**
291: * Remember the prefix/uri mapping at the current nested element depth.
292: *
293: * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
294: * @param prefix The prefix that maps to the URI
295: * @param uri The URI for the namespace
296: * @param shouldFlush a flag indicating if the mapping applies to the
297: * current element or an up coming child (not used).
298: */
299:
300: public boolean startPrefixMapping(String prefix, String uri,
301: boolean shouldFlush) throws org.xml.sax.SAXException {
302:
303: /* Remember the mapping, and at what depth it was declared
304: * This is one greater than the current depth because these
305: * mappings will apply to the next depth. This is in
306: * consideration that startElement() will soon be called
307: */
308:
309: boolean pushed;
310: int pushDepth;
311: if (shouldFlush) {
312: flushPending();
313: // the prefix mapping applies to the child element (one deeper)
314: pushDepth = m_elemContext.m_currentElemDepth + 1;
315: } else {
316: // the prefix mapping applies to the current element
317: pushDepth = m_elemContext.m_currentElemDepth;
318: }
319: pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
320:
321: if (pushed) {
322: m_saxHandler.startPrefixMapping(prefix, uri);
323:
324: if (getShouldOutputNSAttr()) {
325:
326: /* Brian M.: don't know if we really needto do this. The
327: * callers of this object should have injected both
328: * startPrefixMapping and the attributes. We are
329: * just covering our butt here.
330: */
331: String name;
332: if (EMPTYSTRING.equals(prefix)) {
333: name = "xmlns";
334: addAttributeAlways(XMLNS_URI, name, name, "CDATA",
335: uri, false);
336: } else {
337: if (!EMPTYSTRING.equals(uri)) // hack for XSLTC attribset16 test
338: { // that maps ns1 prefix to "" URI
339: name = "xmlns:" + prefix;
340:
341: /* for something like xmlns:abc="w3.pretend.org"
342: * the uri is the value, that is why we pass it in the
343: * value, or 5th slot of addAttributeAlways()
344: */
345: addAttributeAlways(XMLNS_URI, prefix, name,
346: "CDATA", uri, false);
347: }
348: }
349: }
350: }
351: return pushed;
352: }
353:
354: /**
355: * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
356: */
357: public void comment(char[] arg0, int arg1, int arg2)
358: throws SAXException {
359: flushPending();
360: if (m_lexHandler != null)
361: m_lexHandler.comment(arg0, arg1, arg2);
362:
363: if (m_tracer != null)
364: super .fireCommentEvent(arg0, arg1, arg2);
365: }
366:
367: /**
368: * @see org.xml.sax.ext.LexicalHandler#endCDATA()
369: */
370: public void endCDATA() throws SAXException {
371: /* Normally we would do somthing with this but we ignore it.
372: * The neccessary call to m_lexHandler.endCDATA() will be made
373: * in flushPending().
374: *
375: * This is so that if we get calls like these:
376: * this.startCDATA();
377: * this.characters(chars1, off1, len1);
378: * this.endCDATA();
379: * this.startCDATA();
380: * this.characters(chars2, off2, len2);
381: * this.endCDATA();
382: *
383: * that we will only make these calls to the wrapped handlers:
384: *
385: * m_lexHandler.startCDATA();
386: * m_saxHandler.characters(chars1, off1, len1);
387: * m_saxHandler.characters(chars1, off2, len2);
388: * m_lexHandler.endCDATA();
389: *
390: * We will merge adjacent CDATA blocks.
391: */
392: }
393:
394: /**
395: * @see org.xml.sax.ext.LexicalHandler#endDTD()
396: */
397: public void endDTD() throws SAXException {
398: if (m_lexHandler != null)
399: m_lexHandler.endDTD();
400: }
401:
402: /**
403: * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
404: */
405: public void startEntity(String arg0) throws SAXException {
406: if (m_lexHandler != null)
407: m_lexHandler.startEntity(arg0);
408: }
409:
410: /**
411: * @see ExtendedContentHandler#characters(String)
412: */
413: public void characters(String chars) throws SAXException {
414: final int length = chars.length();
415: if (length > m_charsBuff.length) {
416: m_charsBuff = new char[length * 2 + 1];
417: }
418: chars.getChars(0, length, m_charsBuff, 0);
419: this .characters(m_charsBuff, 0, length);
420: }
421:
422: /////////////////// from XSLTC //////////////
423: public ToXMLSAXHandler(ContentHandler handler, String encoding) {
424: super (handler, encoding);
425:
426: initCDATA();
427: // initNamespaces();
428: m_prefixMap = new NamespaceMappings();
429: }
430:
431: public ToXMLSAXHandler(ContentHandler handler, LexicalHandler lex,
432: String encoding) {
433: super (handler, lex, encoding);
434:
435: initCDATA();
436: // initNamespaces();
437: m_prefixMap = new NamespaceMappings();
438: }
439:
440: /**
441: * Start an element in the output document. This might be an XML element
442: * (<elem>data</elem> type) or a CDATA section.
443: */
444: public void startElement(String elementNamespaceURI,
445: String elementLocalName, String elementName)
446: throws SAXException {
447: startElement(elementNamespaceURI, elementLocalName,
448: elementName, null);
449:
450: }
451:
452: public void startElement(String elementName) throws SAXException {
453: startElement(null, null, elementName, null);
454: }
455:
456: public void characters(char[] ch, int off, int len)
457: throws SAXException {
458: // We do the first two things in flushPending() but we don't
459: // close any open CDATA calls.
460: if (m_needToCallStartDocument) {
461: startDocumentInternal();
462: m_needToCallStartDocument = false;
463: }
464:
465: if (m_elemContext.m_startTagOpen) {
466: closeStartTag();
467: m_elemContext.m_startTagOpen = false;
468: }
469:
470: if (m_elemContext.m_isCdataSection && !m_cdataTagOpen
471: && m_lexHandler != null) {
472: m_lexHandler.startCDATA();
473: // We have made a call to m_lexHandler.startCDATA() with
474: // no balancing call to m_lexHandler.endCDATA()
475: // so we set m_cdataTagOpen true to remember this.
476: m_cdataTagOpen = true;
477: }
478:
479: /* If there are any occurances of "]]>" in the character data
480: * let m_saxHandler worry about it, we've already warned them with
481: * the previous call of m_lexHandler.startCDATA();
482: */
483: m_saxHandler.characters(ch, off, len);
484:
485: // time to generate characters event
486: if (m_tracer != null)
487: fireCharEvent(ch, off, len);
488: }
489:
490: /**
491: * @see ExtendedContentHandler#endElement(String)
492: */
493: public void endElement(String elemName) throws SAXException {
494: endElement(null, null, elemName);
495: }
496:
497: /**
498: * Send a namespace declaration in the output document. The namespace
499: * declaration will not be include if the namespace is already in scope
500: * with the same prefix.
501: */
502: public void namespaceAfterStartElement(final String prefix,
503: final String uri) throws SAXException {
504: startPrefixMapping(prefix, uri, false);
505: }
506:
507: /**
508: *
509: * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
510: * Send a processing instruction to the output document
511: */
512: public void processingInstruction(String target, String data)
513: throws SAXException {
514: flushPending();
515:
516: // Pass the processing instruction to the SAX handler
517: m_saxHandler.processingInstruction(target, data);
518:
519: // we don't want to leave serializer to fire off this event,
520: // so do it here.
521: if (m_tracer != null)
522: super .fireEscapingEvent(target, data);
523: }
524:
525: /**
526: * Undeclare the namespace that is currently pointed to by a given
527: * prefix. Inform SAX handler if prefix was previously mapped.
528: */
529: protected boolean popNamespace(String prefix) {
530: try {
531: if (m_prefixMap.popNamespace(prefix)) {
532: m_saxHandler.endPrefixMapping(prefix);
533: return true;
534: }
535: } catch (SAXException e) {
536: // falls through
537: }
538: return false;
539: }
540:
541: public void startCDATA() throws SAXException {
542: /* m_cdataTagOpen can only be true here if we have ignored the
543: * previous call to this.endCDATA() and the previous call
544: * this.startCDATA() before that is still "open". In this way
545: * we merge adjacent CDATA. If anything else happened after the
546: * ignored call to this.endCDATA() and this call then a call to
547: * flushPending() would have been made which would have
548: * closed the CDATA and set m_cdataTagOpen to false.
549: */
550: if (!m_cdataTagOpen) {
551: flushPending();
552: if (m_lexHandler != null) {
553: m_lexHandler.startCDATA();
554:
555: // We have made a call to m_lexHandler.startCDATA() with
556: // no balancing call to m_lexHandler.endCDATA()
557: // so we set m_cdataTagOpen true to remember this.
558: m_cdataTagOpen = true;
559: }
560: }
561: }
562:
563: /**
564: * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
565: */
566: public void startElement(String namespaceURI, String localName,
567: String name, Attributes atts) throws SAXException {
568: flushPending();
569: super .startElement(namespaceURI, localName, name, atts);
570:
571: // Handle document type declaration (for first element only)
572: if (m_needToOutputDocTypeDecl) {
573: String doctypeSystem = getDoctypeSystem();
574: if (doctypeSystem != null && m_lexHandler != null) {
575: String doctypePublic = getDoctypePublic();
576: if (doctypeSystem != null)
577: m_lexHandler.startDTD(name, doctypePublic,
578: doctypeSystem);
579: }
580: m_needToOutputDocTypeDecl = false;
581: }
582: m_elemContext = m_elemContext.push(namespaceURI, localName,
583: name);
584:
585: // ensurePrefixIsDeclared depends on the current depth, so
586: // the previous increment is necessary where it is.
587: if (namespaceURI != null)
588: ensurePrefixIsDeclared(namespaceURI, name);
589:
590: // add the attributes to the collected ones
591: if (atts != null)
592: addAttributes(atts);
593:
594: // do we really need this CDATA section state?
595: m_elemContext.m_isCdataSection = isCdataSection();
596:
597: }
598:
599: private void ensurePrefixIsDeclared(String ns, String rawName)
600: throws org.xml.sax.SAXException {
601:
602: if (ns != null && ns.length() > 0) {
603: int index;
604: final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
605: String prefix = (no_prefix) ? "" : rawName.substring(0,
606: index);
607:
608: if (null != prefix) {
609: String foundURI = m_prefixMap.lookupNamespace(prefix);
610:
611: if ((null == foundURI) || !foundURI.equals(ns)) {
612: this .startPrefixMapping(prefix, ns, false);
613:
614: if (getShouldOutputNSAttr()) {
615: // Bugzilla1133: Generate attribute as well as namespace event.
616: // SAX does expect both.
617: this .addAttributeAlways(
618: "http://www.w3.org/2000/xmlns/",
619: no_prefix ? "xmlns" : prefix, // local name
620: no_prefix ? "xmlns"
621: : ("xmlns:" + prefix), // qname
622: "CDATA", ns, false);
623: }
624: }
625:
626: }
627: }
628: }
629:
630: /**
631: * Adds the given attribute to the set of attributes, and also makes sure
632: * that the needed prefix/uri mapping is declared, but only if there is a
633: * currently open element.
634: *
635: * @param uri the URI of the attribute
636: * @param localName the local name of the attribute
637: * @param rawName the qualified name of the attribute
638: * @param type the type of the attribute (probably CDATA)
639: * @param value the value of the attribute
640: * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
641: * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
642: */
643: public void addAttribute(String uri, String localName,
644: String rawName, String type, String value,
645: boolean XSLAttribute) throws SAXException {
646: if (m_elemContext.m_startTagOpen) {
647: ensurePrefixIsDeclared(uri, rawName);
648: addAttributeAlways(uri, localName, rawName, type, value,
649: false);
650: }
651:
652: }
653:
654: /**
655: * Try's to reset the super class and reset this class for
656: * re-use, so that you don't need to create a new serializer
657: * (mostly for performance reasons).
658: *
659: * @return true if the class was successfuly reset.
660: * @see Serializer#reset()
661: */
662: public boolean reset() {
663: boolean wasReset = false;
664: if (super .reset()) {
665: resetToXMLSAXHandler();
666: wasReset = true;
667: }
668: return wasReset;
669: }
670:
671: /**
672: * Reset all of the fields owned by ToXMLSAXHandler class
673: *
674: */
675: private void resetToXMLSAXHandler() {
676: this .m_escapeSetting = false;
677: }
678:
679: }
|