001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: ToStream.java 1225444 2011-12-29 05:52:39Z mrglavas $
020 */
021 package org.apache.xml.serializer;
022
023 import java.io.IOException;
024 import java.io.OutputStream;
025 import java.io.OutputStreamWriter;
026 import java.io.UnsupportedEncodingException;
027 import java.io.Writer;
028 import java.util.EmptyStackException;
029 import java.util.Enumeration;
030 import java.util.Iterator;
031 import java.util.Properties;
032 import java.util.Set;
033 import java.util.StringTokenizer;
034 import java.util.Vector;
035
036 import javax.xml.transform.ErrorListener;
037 import javax.xml.transform.OutputKeys;
038 import javax.xml.transform.Transformer;
039 import javax.xml.transform.TransformerException;
040
041 import org.apache.xml.serializer.utils.MsgKey;
042 import org.apache.xml.serializer.utils.Utils;
043 import org.apache.xml.serializer.utils.WrappedRuntimeException;
044 import org.w3c.dom.Node;
045 import org.xml.sax.Attributes;
046 import org.xml.sax.ContentHandler;
047 import org.xml.sax.SAXException;
048
049 /**
050 * This abstract class is a base class for other stream
051 * serializers (xml, html, text ...) that write output to a stream.
052 *
053 * @xsl.usage internal
054 */
055 abstract public class ToStream extends SerializerBase
056 {
057
058 private static final String COMMENT_BEGIN = "<!--";
059 private static final String COMMENT_END = "-->";
060
061 /** Stack to keep track of disabling output escaping. */
062 protected BoolStack m_disableOutputEscapingStates = new BoolStack();
063
064
065 /**
066 * The encoding information associated with this serializer.
067 * Although initially there is no encoding,
068 * there is a dummy EncodingInfo object that will say
069 * that every character is in the encoding. This is useful
070 * for a serializer that is in temporary output state and has
071 * no associated encoding. A serializer in final output state
072 * will have an encoding, and will worry about whether
073 * single chars or surrogate pairs of high/low chars form
074 * characters in the output encoding.
075 */
076 EncodingInfo m_encodingInfo = new EncodingInfo(null,null, '\u0000');
077
078 /**
079 * Stack to keep track of whether or not we need to
080 * preserve whitespace.
081 *
082 * Used to push/pop values used for the field m_ispreserve, but
083 * m_ispreserve is only relevant if m_doIndent is true.
084 * If m_doIndent is false this field has no impact.
085 *
086 */
087 protected BoolStack m_preserves = new BoolStack();
088
089 /**
090 * State flag to tell if preservation of whitespace
091 * is important.
092 *
093 * Used only in shouldIndent() but only if m_doIndent is true.
094 * If m_doIndent is false this flag has no impact.
095 *
096 */
097 protected boolean m_ispreserve = false;
098
099 /**
100 * State flag that tells if the previous node processed
101 * was text, so we can tell if we should preserve whitespace.
102 *
103 * Used in endDocument() and shouldIndent() but
104 * only if m_doIndent is true.
105 * If m_doIndent is false this flag has no impact.
106 */
107 protected boolean m_isprevtext = false;
108
109 private static final char[] s_systemLineSep;
110 static {
111 s_systemLineSep = SecuritySupport.getSystemProperty("line.separator").toCharArray();
112 }
113
114 /**
115 * The system line separator for writing out line breaks.
116 * The default value is from the system property,
117 * but this value can be set through the xsl:output
118 * extension attribute xalan:line-separator.
119 */
120 protected char[] m_lineSep = s_systemLineSep;
121
122
123 /**
124 * True if the the system line separator is to be used.
125 */
126 protected boolean m_lineSepUse = true;
127
128 /**
129 * The length of the line seperator, since the write is done
130 * one character at a time.
131 */
132 protected int m_lineSepLen = m_lineSep.length;
133
134 /**
135 * Map that tells which characters should have special treatment, and it
136 * provides character to entity name lookup.
137 */
138 protected CharInfo m_charInfo;
139
140 /** True if we control the buffer, and we should flush the output on endDocument. */
141 boolean m_shouldFlush = true;
142
143 /**
144 * Add space before '/>' for XHTML.
145 */
146 protected boolean m_spaceBeforeClose = false;
147
148 /**
149 * Flag to signal that a newline should be added.
150 *
151 * Used only in indent() which is called only if m_doIndent is true.
152 * If m_doIndent is false this flag has no impact.
153 */
154 boolean m_startNewLine;
155
156 /**
157 * Tells if we're in an internal document type subset.
158 */
159 protected boolean m_inDoctype = false;
160
161 /**
162 * Flag to quickly tell if the encoding is UTF8.
163 */
164 boolean m_isUTF8 = false;
165
166
167 /**
168 * remembers if we are in between the startCDATA() and endCDATA() callbacks
169 */
170 protected boolean m_cdataStartCalled = false;
171
172 /**
173 * If this flag is true DTD entity references are not left as-is,
174 * which is exiting older behavior.
175 */
176 private boolean m_expandDTDEntities = true;
177
178
179 /**
180 * Default constructor
181 */
182 public ToStream()
183 {
184 }
185
186 /**
187 * This helper method to writes out "]]>" when closing a CDATA section.
188 *
189 * @throws org.xml.sax.SAXException
190 */
191 protected void closeCDATA() throws org.xml.sax.SAXException
192 {
193 try
194 {
195 m_writer.write(CDATA_DELIMITER_CLOSE);
196 // write out a CDATA section closing "]]>"
197 m_cdataTagOpen = false; // Remember that we have done so.
198 }
199 catch (IOException e)
200 {
201 throw new SAXException(e);
202 }
203 }
204
205 /**
206 * Serializes the DOM node. Throws an exception only if an I/O
207 * exception occured while serializing.
208 *
209 * @param node Node to serialize.
210 * @throws IOException An I/O exception occured while serializing
211 */
212 public void serialize(Node node) throws IOException
213 {
214
215 try
216 {
217 TreeWalker walker =
218 new TreeWalker(this);
219
220 walker.traverse(node);
221 }
222 catch (org.xml.sax.SAXException se)
223 {
224 throw new WrappedRuntimeException(se);
225 }
226 }
227
228 /**
229 * Taken from XSLTC
230 */
231 protected boolean m_escaping = true;
232
233 /**
234 * Flush the formatter's result stream.
235 *
236 * @throws org.xml.sax.SAXException
237 */
238 protected final void flushWriter() throws org.xml.sax.SAXException
239 {
240 final java.io.Writer writer = m_writer;
241 if (null != writer)
242 {
243 try
244 {
245 if (writer instanceof WriterToUTF8Buffered)
246 {
247 if (m_shouldFlush)
248 ((WriterToUTF8Buffered) writer).flush();
249 else
250 ((WriterToUTF8Buffered) writer).flushBuffer();
251 }
252 if (writer instanceof WriterToASCI)
253 {
254 if (m_shouldFlush)
255 writer.flush();
256 }
257 else
258 {
259 // Flush always.
260 // Not a great thing if the writer was created
261 // by this class, but don't have a choice.
262 writer.flush();
263 }
264 }
265 catch (IOException ioe)
266 {
267 throw new org.xml.sax.SAXException(ioe);
268 }
269 }
270 }
271
272 OutputStream m_outputStream;
273 /**
274 * Get the output stream where the events will be serialized to.
275 *
276 * @return reference to the result stream, or null of only a writer was
277 * set.
278 */
279 public OutputStream getOutputStream()
280 {
281 return m_outputStream;
282 }
283
284 // Implement DeclHandler
285
286 /**
287 * Report an element type declaration.
288 *
289 * <p>The content model will consist of the string "EMPTY", the
290 * string "ANY", or a parenthesised group, optionally followed
291 * by an occurrence indicator. The model will be normalized so
292 * that all whitespace is removed,and will include the enclosing
293 * parentheses.</p>
294 *
295 * @param name The element type name.
296 * @param model The content model as a normalized string.
297 * @exception SAXException The application may raise an exception.
298 */
299 public void elementDecl(String name, String model) throws SAXException
300 {
301 // Do not inline external DTD
302 if (m_inExternalDTD)
303 return;
304 try
305 {
306 final java.io.Writer writer = m_writer;
307 DTDprolog();
308
309 writer.write("<!ELEMENT ");
310 writer.write(name);
311 writer.write(' ');
312 writer.write(model);
313 writer.write('>');
314 writer.write(m_lineSep, 0, m_lineSepLen);
315 }
316 catch (IOException e)
317 {
318 throw new SAXException(e);
319 }
320
321 }
322
323 /**
324 * Report an internal entity declaration.
325 *
326 * <p>Only the effective (first) declaration for each entity
327 * will be reported.</p>
328 *
329 * @param name The name of the entity. If it is a parameter
330 * entity, the name will begin with '%'.
331 * @param value The replacement text of the entity.
332 * @exception SAXException The application may raise an exception.
333 * @see #externalEntityDecl
334 * @see org.xml.sax.DTDHandler#unparsedEntityDecl
335 */
336 public void internalEntityDecl(String name, String value)
337 throws SAXException
338 {
339 // Do not inline external DTD
340 if (m_inExternalDTD)
341 return;
342 try
343 {
344 DTDprolog();
345 outputEntityDecl(name, value);
346 }
347 catch (IOException e)
348 {
349 throw new SAXException(e);
350 }
351
352 }
353
354 /**
355 * Output the doc type declaration.
356 *
357 * @param name non-null reference to document type name.
358 * NEEDSDOC @param value
359 *
360 * @throws org.xml.sax.SAXException
361 */
362 void outputEntityDecl(String name, String value) throws IOException
363 {
364 final java.io.Writer writer = m_writer;
365 writer.write("<!ENTITY ");
366 writer.write(name);
367 writer.write(" \"");
368 writer.write(value);
369 writer.write("\">");
370 writer.write(m_lineSep, 0, m_lineSepLen);
371 }
372
373 /**
374 * Output a system-dependent line break.
375 *
376 * @throws org.xml.sax.SAXException
377 */
378 protected final void outputLineSep() throws IOException
379 {
380
381 m_writer.write(m_lineSep, 0, m_lineSepLen);
382 }
383
384 void setProp(String name, String val, boolean defaultVal) {
385 if (val != null) {
386
387
388 char first = getFirstCharLocName(name);
389 switch (first) {
390 case 'c':
391 if (OutputKeys.CDATA_SECTION_ELEMENTS.equals(name)) {
392 String cdataSectionNames = val;
393 addCdataSectionElements(cdataSectionNames);
394 }
395 break;
396 case 'd':
397 if (OutputKeys.DOCTYPE_SYSTEM.equals(name)) {
398 this.m_doctypeSystem = val;
399 } else if (OutputKeys.DOCTYPE_PUBLIC.equals(name)) {
400 this.m_doctypePublic = val;
401 if (val.startsWith("-//W3C//DTD XHTML"))
402 m_spaceBeforeClose = true;
403 }
404 break;
405 case 'e':
406 String newEncoding = val;
407 if (OutputKeys.ENCODING.equals(name)) {
408 String possible_encoding = Encodings.getMimeEncoding(val);
409 if (possible_encoding != null) {
410 // if the encoding is being set, try to get the
411 // preferred
412 // mime-name and set it too.
413 super.setProp("mime-name", possible_encoding,
414 defaultVal);
415 }
416 final String oldExplicitEncoding = getOutputPropertyNonDefault(OutputKeys.ENCODING);
417 final String oldDefaultEncoding = getOutputPropertyDefault(OutputKeys.ENCODING);
418 if ( (defaultVal && ( oldDefaultEncoding == null || !oldDefaultEncoding.equalsIgnoreCase(newEncoding)))
419 || ( !defaultVal && (oldExplicitEncoding == null || !oldExplicitEncoding.equalsIgnoreCase(newEncoding) ))) {
420 // We are trying to change the default or the non-default setting of the encoding to a different value
421 // from what it was
422
423 EncodingInfo encodingInfo = Encodings.getEncodingInfo(newEncoding);
424 if (newEncoding != null && encodingInfo.name == null) {
425 // We tried to get an EncodingInfo for Object for the given
426 // encoding, but it came back with an internall null name
427 // so the encoding is not supported by the JDK, issue a message.
428 final String msg = Utils.messages.createMessage(
429 MsgKey.ER_ENCODING_NOT_SUPPORTED,new Object[]{ newEncoding });
430
431 final String msg2 =
432 "Warning: encoding \"" + newEncoding + "\" not supported, using "
433 + Encodings.DEFAULT_MIME_ENCODING;
434 try {
435 // Prepare to issue the warning message
436 final Transformer tran = super.getTransformer();
437 if (tran != null) {
438 final ErrorListener errHandler = tran
439 .getErrorListener();
440 // Issue the warning message
441 if (null != errHandler
442 && m_sourceLocator != null) {
443 errHandler
444 .warning(new TransformerException(
445 msg, m_sourceLocator));
446 errHandler
447 .warning(new TransformerException(
448 msg2, m_sourceLocator));
449 } else {
450 System.out.println(msg);
451 System.out.println(msg2);
452 }
453 } else {
454 System.out.println(msg);
455 System.out.println(msg2);
456 }
457 } catch (Exception e) {
458 }
459
460 // We said we are using UTF-8, so use it
461 newEncoding = Encodings.DEFAULT_MIME_ENCODING;
462 val = Encodings.DEFAULT_MIME_ENCODING; // to store the modified value into the properties a little later
463 encodingInfo = Encodings.getEncodingInfo(newEncoding);
464
465 }
466 // The encoding was good, or was forced to UTF-8 above
467
468
469 // If there is already a non-default set encoding and we
470 // are trying to set the default encoding, skip the this block
471 // as the non-default value is already the one to use.
472 if (defaultVal == false || oldExplicitEncoding == null) {
473 m_encodingInfo = encodingInfo;
474 if (newEncoding != null)
475 m_isUTF8 = newEncoding.equals(Encodings.DEFAULT_MIME_ENCODING);
476
477 // if there was a previously set OutputStream
478 OutputStream os = getOutputStream();
479 if (os != null) {
480 Writer w = getWriter();
481
482 // If the writer was previously set, but
483 // set by the user, or if the new encoding is the same
484 // as the old encoding, skip this block
485 String oldEncoding = getOutputProperty(OutputKeys.ENCODING);
486 if ((w == null || !m_writer_set_by_user)
487 && !newEncoding.equalsIgnoreCase(oldEncoding)) {
488 // Make the change of encoding in our internal
489 // table, then call setOutputStreamInternal
490 // which will stomp on the old Writer (if any)
491 // with a new Writer with the new encoding.
492 super.setProp(name, val, defaultVal);
493 setOutputStreamInternal(os,false);
494 }
495 }
496 }
497 }
498 }
499 break;
500 case 'i':
501 if (OutputPropertiesFactory.S_KEY_INDENT_AMOUNT.equals(name)) {
502 setIndentAmount(Integer.parseInt(val));
503 } else if (OutputKeys.INDENT.equals(name)) {
504 boolean b = "yes".equals(val) ? true : false;
505 m_doIndent = b;
506 }
507
508 break;
509 case 'l':
510 if (OutputPropertiesFactory.S_KEY_LINE_SEPARATOR.equals(name)) {
511 m_lineSep = val.toCharArray();
512 m_lineSepLen = m_lineSep.length;
513 }
514
515 break;
516 case 'm':
517 if (OutputKeys.MEDIA_TYPE.equals(name)) {
518 m_mediatype = val;
519 }
520 break;
521 case 'o':
522 if (OutputKeys.OMIT_XML_DECLARATION.equals(name)) {
523 boolean b = "yes".equals(val) ? true : false;
524 this.m_shouldNotWriteXMLHeader = b;
525 }
526 break;
527 case 's':
528 // if standalone was explicitly specified
529 if (OutputKeys.STANDALONE.equals(name)) {
530 if (defaultVal) {
531 setStandaloneInternal(val);
532 } else {
533 m_standaloneWasSpecified = true;
534 setStandaloneInternal(val);
535 }
536 }
537
538 break;
539 case 'v':
540 if (OutputKeys.VERSION.equals(name)) {
541 m_version = val;
542 }
543 break;
544 default:
545 break;
546
547 }
548 super.setProp(name, val, defaultVal);
549 }
550 }
551 /**
552 * Specifies an output format for this serializer. It the
553 * serializer has already been associated with an output format,
554 * it will switch to the new format. This method should not be
555 * called while the serializer is in the process of serializing
556 * a document.
557 *
558 * @param format The output format to use
559 */
560 public void setOutputFormat(Properties format)
561 {
562
563 boolean shouldFlush = m_shouldFlush;
564
565 if (format != null)
566 {
567 // Set the default values first,
568 // and the non-default values after that,
569 // just in case there is some unexpected
570 // residual values left over from over-ridden default values
571 Enumeration propNames;
572 propNames = format.propertyNames();
573 while (propNames.hasMoreElements())
574 {
575 String key = (String) propNames.nextElement();
576 // Get the value, possibly a default value
577 String value = format.getProperty(key);
578 // Get the non-default value (if any).
579 String explicitValue = (String) format.get(key);
580 if (explicitValue == null && value != null) {
581 // This is a default value
582 this.setOutputPropertyDefault(key,value);
583 }
584 if (explicitValue != null) {
585 // This is an explicit non-default value
586 this.setOutputProperty(key,explicitValue);
587 }
588 }
589 }
590
591 // Access this only from the Hashtable level... we don't want to
592 // get default properties.
593 String entitiesFileName =
594 (String) format.get(OutputPropertiesFactory.S_KEY_ENTITIES);
595
596 if (null != entitiesFileName)
597 {
598
599 String method =
600 (String) format.get(OutputKeys.METHOD);
601
602 m_charInfo = CharInfo.getCharInfo(entitiesFileName, method);
603 }
604
605
606
607
608 m_shouldFlush = shouldFlush;
609 }
610
611 /**
612 * Returns the output format for this serializer.
613 *
614 * @return The output format in use
615 */
616 public Properties getOutputFormat() {
617 Properties def = new Properties();
618 {
619 Set s = getOutputPropDefaultKeys();
620 Iterator i = s.iterator();
621 while (i.hasNext()) {
622 String key = (String) i.next();
623 String val = getOutputPropertyDefault(key);
624 def.put(key, val);
625 }
626 }
627
628 Properties props = new Properties(def);
629 {
630 Set s = getOutputPropKeys();
631 Iterator i = s.iterator();
632 while (i.hasNext()) {
633 String key = (String) i.next();
634 String val = getOutputPropertyNonDefault(key);
635 if (val != null)
636 props.put(key, val);
637 }
638 }
639 return props;
640 }
641
642 /**
643 * Specifies a writer to which the document should be serialized.
644 * This method should not be called while the serializer is in
645 * the process of serializing a document.
646 *
647 * @param writer The output writer stream
648 */
649 public void setWriter(Writer writer)
650 {
651 setWriterInternal(writer, true);
652 }
653
654 private boolean m_writer_set_by_user;
655 private void setWriterInternal(Writer writer, boolean setByUser) {
656
657 m_writer_set_by_user = setByUser;
658 m_writer = writer;
659 // if we are tracing events we need to trace what
660 // characters are written to the output writer.
661 if (m_tracer != null) {
662 boolean noTracerYet = true;
663 Writer w2 = m_writer;
664 while (w2 instanceof WriterChain) {
665 if (w2 instanceof SerializerTraceWriter) {
666 noTracerYet = false;
667 break;
668 }
669 w2 = ((WriterChain)w2).getWriter();
670 }
671 if (noTracerYet)
672 m_writer = new SerializerTraceWriter(m_writer, m_tracer);
673 }
674 }
675
676 /**
677 * Set if the operating systems end-of-line line separator should
678 * be used when serializing. If set false NL character
679 * (decimal 10) is left alone, otherwise the new-line will be replaced on
680 * output with the systems line separator. For example on UNIX this is
681 * NL, while on Windows it is two characters, CR NL, where CR is the
682 * carriage-return (decimal 13).
683 *
684 * @param use_sytem_line_break True if an input NL is replaced with the
685 * operating systems end-of-line separator.
686 * @return The previously set value of the serializer.
687 */
688 public boolean setLineSepUse(boolean use_sytem_line_break)
689 {
690 boolean oldValue = m_lineSepUse;
691 m_lineSepUse = use_sytem_line_break;
692 return oldValue;
693 }
694
695 /**
696 * Specifies an output stream to which the document should be
697 * serialized. This method should not be called while the
698 * serializer is in the process of serializing a document.
699 * <p>
700 * The encoding specified in the output properties is used, or
701 * if no encoding was specified, the default for the selected
702 * output method.
703 *
704 * @param output The output stream
705 */
706 public void setOutputStream(OutputStream output)
707 {
708 setOutputStreamInternal(output, true);
709 }
710
711 private void setOutputStreamInternal(OutputStream output, boolean setByUser)
712 {
713 m_outputStream = output;
714 String encoding = getOutputProperty(OutputKeys.ENCODING);
715 if (Encodings.DEFAULT_MIME_ENCODING.equalsIgnoreCase(encoding))
716 {
717 // We wrap the OutputStream with a writer, but
718 // not one set by the user
719 setWriterInternal(new WriterToUTF8Buffered(output), false);
720 } else if (
721 "WINDOWS-1250".equals(encoding)
722 || "US-ASCII".equals(encoding)
723 || "ASCII".equals(encoding))
724 {
725 setWriterInternal(new WriterToASCI(output), false);
726 } else if (encoding != null) {
727 Writer osw = null;
728 try
729 {
730 osw = Encodings.getWriter(output, encoding);
731 }
732 catch (UnsupportedEncodingException uee)
733 {
734 osw = null;
735 }
736
737
738 if (osw == null) {
739 System.out.println(
740 "Warning: encoding \""
741 + encoding
742 + "\" not supported"
743 + ", using "
744 + Encodings.DEFAULT_MIME_ENCODING);
745
746 encoding = Encodings.DEFAULT_MIME_ENCODING;
747 setEncoding(encoding);
748 try {
749 osw = Encodings.getWriter(output, encoding);
750 } catch (UnsupportedEncodingException e) {
751 // We can't really get here, UTF-8 is always supported
752 // This try-catch exists to make the compiler happy
753 e.printStackTrace();
754 }
755 }
756 setWriterInternal(osw,false);
757 }
758 else {
759 // don't have any encoding, but we have an OutputStream
760 Writer osw = new OutputStreamWriter(output);
761 setWriterInternal(osw,false);
762 }
763 }
764
765 /**
766 * @see SerializationHandler#setEscaping(boolean)
767 */
768 public boolean setEscaping(boolean escape)
769 {
770 final boolean temp = m_escaping;
771 m_escaping = escape;
772 return temp;
773
774 }
775
776
777 /**
778 * Might print a newline character and the indentation amount
779 * of the given depth.
780 *
781 * @param depth the indentation depth (element nesting depth)
782 *
783 * @throws org.xml.sax.SAXException if an error occurs during writing.
784 */
785 protected void indent(int depth) throws IOException
786 {
787
788 if (m_startNewLine)
789 outputLineSep();
790 /* For m_indentAmount > 0 this extra test might be slower
791 * but Xalan's default value is 0, so this extra test
792 * will run faster in that situation.
793 */
794 if (m_indentAmount > 0)
795 printSpace(depth * m_indentAmount);
796
797 }
798
799 /**
800 * Indent at the current element nesting depth.
801 * @throws IOException
802 */
803 protected void indent() throws IOException
804 {
805 indent(m_elemContext.m_currentElemDepth);
806 }
807 /**
808 * Prints <var>n</var> spaces.
809 * @param n Number of spaces to print.
810 *
811 * @throws org.xml.sax.SAXException if an error occurs when writing.
812 */
813 private void printSpace(int n) throws IOException
814 {
815 final java.io.Writer writer = m_writer;
816 for (int i = 0; i < n; i++)
817 {
818 writer.write(' ');
819 }
820
821 }
822
823 /**
824 * Report an attribute type declaration.
825 *
826 * <p>Only the effective (first) declaration for an attribute will
827 * be reported. The type will be one of the strings "CDATA",
828 * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
829 * "ENTITIES", or "NOTATION", or a parenthesized token group with
830 * the separator "|" and all whitespace removed.</p>
831 *
832 * @param eName The name of the associated element.
833 * @param aName The name of the attribute.
834 * @param type A string representing the attribute type.
835 * @param valueDefault A string representing the attribute default
836 * ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
837 * none of these applies.
838 * @param value A string representing the attribute's default value,
839 * or null if there is none.
840 * @exception SAXException The application may raise an exception.
841 */
842 public void attributeDecl(
843 String eName,
844 String aName,
845 String type,
846 String valueDefault,
847 String value)
848 throws SAXException
849 {
850 // Do not inline external DTD
851 if (m_inExternalDTD)
852 return;
853 try
854 {
855 final java.io.Writer writer = m_writer;
856 DTDprolog();
857
858 writer.write("<!ATTLIST ");
859 writer.write(eName);
860 writer.write(' ');
861
862 writer.write(aName);
863 writer.write(' ');
864 writer.write(type);
865 if (valueDefault != null)
866 {
867 writer.write(' ');
868 writer.write(valueDefault);
869 }
870
871 //writer.write(" ");
872 //writer.write(value);
873 writer.write('>');
874 writer.write(m_lineSep, 0, m_lineSepLen);
875 }
876 catch (IOException e)
877 {
878 throw new SAXException(e);
879 }
880 }
881
882 /**
883 * Get the character stream where the events will be serialized to.
884 *
885 * @return Reference to the result Writer, or null.
886 */
887 public Writer getWriter()
888 {
889 return m_writer;
890 }
891
892 /**
893 * Report a parsed external entity declaration.
894 *
895 * <p>Only the effective (first) declaration for each entity
896 * will be reported.</p>
897 *
898 * @param name The name of the entity. If it is a parameter
899 * entity, the name will begin with '%'.
900 * @param publicId The declared public identifier of the entity, or
901 * null if none was declared.
902 * @param systemId The declared system identifier of the entity.
903 * @exception SAXException The application may raise an exception.
904 * @see #internalEntityDecl
905 * @see org.xml.sax.DTDHandler#unparsedEntityDecl
906 */
907 public void externalEntityDecl(
908 String name,
909 String publicId,
910 String systemId)
911 throws SAXException
912 {
913 try {
914 DTDprolog();
915
916 m_writer.write("<!ENTITY ");
917 m_writer.write(name);
918 if (publicId != null) {
919 m_writer.write(" PUBLIC \"");
920 m_writer.write(publicId);
921
922 }
923 else {
924 m_writer.write(" SYSTEM \"");
925 m_writer.write(systemId);
926 }
927 m_writer.write("\" >");
928 m_writer.write(m_lineSep, 0, m_lineSepLen);
929 } catch (IOException e) {
930 // TODO Auto-generated catch block
931 e.printStackTrace();
932 }
933
934 }
935
936 /**
937 * Tell if this character can be written without escaping.
938 */
939 protected boolean escapingNotNeeded(char ch)
940 {
941 final boolean ret;
942 if (ch < 127)
943 {
944 // This is the old/fast code here, but is this
945 // correct for all encodings?
946 if (ch >= CharInfo.S_SPACE || (CharInfo.S_LINEFEED == ch ||
947 CharInfo.S_CARRIAGERETURN == ch || CharInfo.S_HORIZONAL_TAB == ch))
948 ret= true;
949 else
950 ret = false;
951 }
952 else {
953 ret = m_encodingInfo.isInEncoding(ch);
954 }
955 return ret;
956 }
957
958 /**
959 * Once a surrogate has been detected, write out the pair of
960 * characters if it is in the encoding, or if there is no
961 * encoding, otherwise write out an entity reference
962 * of the value of the unicode code point of the character
963 * represented by the high/low surrogate pair.
964 * <p>
965 * An exception is thrown if there is no low surrogate in the pair,
966 * because the array ends unexpectely, or if the low char is there
967 * but its value is such that it is not a low surrogate.
968 *
969 * @param c the first (high) part of the surrogate, which
970 * must be confirmed before calling this method.
971 * @param ch Character array.
972 * @param i position Where the surrogate was detected.
973 * @param end The end index of the significant characters.
974 * @return 0 if the pair of characters was written out as-is,
975 * the unicode code point of the character represented by
976 * the surrogate pair if an entity reference with that value
977 * was written out.
978 *
979 * @throws IOException
980 * @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected.
981 */
982 protected int writeUTF16Surrogate(char c, char ch[], int i, int end)
983 throws IOException
984 {
985 int codePoint = 0;
986 if (i + 1 >= end)
987 {
988 throw new IOException(
989 Utils.messages.createMessage(
990 MsgKey.ER_INVALID_UTF16_SURROGATE,
991 new Object[] { Integer.toHexString((int) c)}));
992 }
993
994 final char high = c;
995 final char low = ch[i+1];
996 if (!Encodings.isLowUTF16Surrogate(low)) {
997 throw new IOException(
998 Utils.messages.createMessage(
999 MsgKey.ER_INVALID_UTF16_SURROGATE,
1000 new Object[] {
1001 Integer.toHexString((int) c)
1002 + " "
1003 + Integer.toHexString(low)}));
1004 }
1005
1006 final java.io.Writer writer = m_writer;
1007
1008 // If we make it to here we have a valid high, low surrogate pair
1009 if (m_encodingInfo.isInEncoding(c,low)) {
1010 // If the character formed by the surrogate pair
1011 // is in the encoding, so just write it out
1012 writer.write(ch,i,2);
1013 }
1014 else {
1015 // Don't know what to do with this char, it is
1016 // not in the encoding and not a high char in
1017 // a surrogate pair, so write out as an entity ref
1018 final String encoding = getEncoding();
1019 if (encoding != null) {
1020 /* The output encoding is known,
1021 * so somthing is wrong.
1022 */
1023 codePoint = Encodings.toCodePoint(high, low);
1024 // not in the encoding, so write out a character reference
1025 writer.write('&');
1026 writer.write('#');
1027 writer.write(Integer.toString(codePoint));
1028 writer.write(';');
1029 } else {
1030 /* The output encoding is not known,
1031 * so just write it out as-is.
1032 */
1033 writer.write(ch, i, 2);
1034 }
1035 }
1036 // non-zero only if character reference was written out.
1037 return codePoint;
1038 }
1039
1040 /**
1041 * Handle one of the default entities, return false if it
1042 * is not a default entity.
1043 *
1044 * @param ch character to be escaped.
1045 * @param i index into character array.
1046 * @param chars non-null reference to character array.
1047 * @param len length of chars.
1048 * @param fromTextNode true if the characters being processed
1049 * are from a text node, false if they are from an attribute value
1050 * @param escLF true if the linefeed should be escaped.
1051 *
1052 * @return i+1 if the character was written, else i.
1053 *
1054 * @throws java.io.IOException
1055 */
1056 int accumDefaultEntity(
1057 java.io.Writer writer,
1058 char ch,
1059 int i,
1060 char[] chars,
1061 int len,
1062 boolean fromTextNode,
1063 boolean escLF)
1064 throws IOException
1065 {
1066
1067 if (!escLF && CharInfo.S_LINEFEED == ch)
1068 {
1069 writer.write(m_lineSep, 0, m_lineSepLen);
1070 }
1071 else
1072 {
1073 // if this is text node character and a special one of those,
1074 // or if this is a character from attribute value and a special one of those
1075 if ((fromTextNode && m_charInfo.shouldMapTextChar(ch)) || (!fromTextNode && m_charInfo.shouldMapAttrChar(ch)))
1076 {
1077 String outputStringForChar = m_charInfo.getOutputStringForChar(ch);
1078
1079 if (null != outputStringForChar)
1080 {
1081 writer.write(outputStringForChar);
1082 }
1083 else
1084 return i;
1085 }
1086 else
1087 return i;
1088 }
1089
1090 return i + 1;
1091
1092 }
1093 /**
1094 * Normalize the characters, but don't escape.
1095 *
1096 * @param ch The characters from the XML document.
1097 * @param start The start position in the array.
1098 * @param length The number of characters to read from the array.
1099 * @param isCData true if a CDATA block should be built around the characters.
1100 * @param useSystemLineSeparator true if the operating systems
1101 * end-of-line separator should be output rather than a new-line character.
1102 *
1103 * @throws IOException
1104 * @throws org.xml.sax.SAXException
1105 */
1106 void writeNormalizedChars(
1107 char ch[],
1108 int start,
1109 int length,
1110 boolean isCData,
1111 boolean useSystemLineSeparator)
1112 throws IOException, org.xml.sax.SAXException
1113 {
1114 final java.io.Writer writer = m_writer;
1115 int end = start + length;
1116
1117 for (int i = start; i < end; i++)
1118 {
1119 char c = ch[i];
1120
1121 if (CharInfo.S_LINEFEED == c && useSystemLineSeparator)
1122 {
1123 writer.write(m_lineSep, 0, m_lineSepLen);
1124 }
1125 else if (isCData && (!escapingNotNeeded(c)))
1126 {
1127 // if (i != 0)
1128 if (m_cdataTagOpen)
1129 closeCDATA();
1130
1131 // This needs to go into a function...
1132 if (Encodings.isHighUTF16Surrogate(c))
1133 {
1134 writeUTF16Surrogate(c, ch, i, end);
1135 i++ ; // process two input characters
1136 }
1137 else
1138 {
1139 writer.write("&#");
1140
1141 String intStr = Integer.toString((int) c);
1142
1143 writer.write(intStr);
1144 writer.write(';');
1145 }
1146
1147 // if ((i != 0) && (i < (end - 1)))
1148 // if (!m_cdataTagOpen && (i < (end - 1)))
1149 // {
1150 // writer.write(CDATA_DELIMITER_OPEN);
1151 // m_cdataTagOpen = true;
1152 // }
1153 }
1154 else if (
1155 isCData
1156 && ((i < (end - 2))
1157 && (']' == c)
1158 && (']' == ch[i + 1])
1159 && ('>' == ch[i + 2])))
1160 {
1161 writer.write(CDATA_CONTINUE);
1162
1163 i += 2;
1164 }
1165 else
1166 {
1167 if (escapingNotNeeded(c))
1168 {
1169 if (isCData && !m_cdataTagOpen)
1170 {
1171 writer.write(CDATA_DELIMITER_OPEN);
1172 m_cdataTagOpen = true;
1173 }
1174 writer.write(c);
1175 }
1176
1177 // This needs to go into a function...
1178 else if (Encodings.isHighUTF16Surrogate(c))
1179 {
1180 if (m_cdataTagOpen)
1181 closeCDATA();
1182 writeUTF16Surrogate(c, ch, i, end);
1183 i++; // process two input characters
1184 }
1185 else
1186 {
1187 if (m_cdataTagOpen)
1188 closeCDATA();
1189 writer.write("&#");
1190
1191 String intStr = Integer.toString((int) c);
1192
1193 writer.write(intStr);
1194 writer.write(';');
1195 }
1196 }
1197 }
1198
1199 }
1200
1201 /**
1202 * Ends an un-escaping section.
1203 *
1204 * @see #startNonEscaping
1205 *
1206 * @throws org.xml.sax.SAXException
1207 */
1208 public void endNonEscaping() throws org.xml.sax.SAXException
1209 {
1210 m_disableOutputEscapingStates.pop();
1211 }
1212
1213 /**
1214 * Starts an un-escaping section. All characters printed within an un-
1215 * escaping section are printed as is, without escaping special characters
1216 * into entity references. Only XML and HTML serializers need to support
1217 * this method.
1218 * <p> The contents of the un-escaping section will be delivered through the
1219 * regular <tt>characters</tt> event.
1220 *
1221 * @throws org.xml.sax.SAXException
1222 */
1223 public void startNonEscaping() throws org.xml.sax.SAXException
1224 {
1225 m_disableOutputEscapingStates.push(true);
1226 }
1227
1228 /**
1229 * Receive notification of cdata.
1230 *
1231 * <p>The Parser will call this method to report each chunk of
1232 * character data. SAX parsers may return all contiguous character
1233 * data in a single chunk, or they may split it into several
1234 * chunks; however, all of the characters in any single event
1235 * must come from the same external entity, so that the Locator
1236 * provides useful information.</p>
1237 *
1238 * <p>The application must not attempt to read from the array
1239 * outside of the specified range.</p>
1240 *
1241 * <p>Note that some parsers will report whitespace using the
1242 * ignorableWhitespace() method rather than this one (validating
1243 * parsers must do so).</p>
1244 *
1245 * @param ch The characters from the XML document.
1246 * @param start The start position in the array.
1247 * @param length The number of characters to read from the array.
1248 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1249 * wrapping another exception.
1250 * @see #ignorableWhitespace
1251 * @see org.xml.sax.Locator
1252 *
1253 * @throws org.xml.sax.SAXException
1254 */
1255 protected void cdata(char ch[], int start, final int length)
1256 throws org.xml.sax.SAXException
1257 {
1258
1259 try
1260 {
1261 final int old_start = start;
1262 if (m_elemContext.m_startTagOpen)
1263 {
1264 closeStartTag();
1265 m_elemContext.m_startTagOpen = false;
1266 }
1267 m_ispreserve = true;
1268
1269 if (shouldIndent())
1270 indent();
1271
1272 boolean writeCDataBrackets =
1273 (((length >= 1) && escapingNotNeeded(ch[start])));
1274
1275 /* Write out the CDATA opening delimiter only if
1276 * we are supposed to, and if we are not already in
1277 * the middle of a CDATA section
1278 */
1279 if (writeCDataBrackets && !m_cdataTagOpen)
1280 {
1281 m_writer.write(CDATA_DELIMITER_OPEN);
1282 m_cdataTagOpen = true;
1283 }
1284
1285 // writer.write(ch, start, length);
1286 if (isEscapingDisabled())
1287 {
1288 charactersRaw(ch, start, length);
1289 }
1290 else
1291 writeNormalizedChars(ch, start, length, true, m_lineSepUse);
1292
1293 /* used to always write out CDATA closing delimiter here,
1294 * but now we delay, so that we can merge CDATA sections on output.
1295 * need to write closing delimiter later
1296 */
1297 if (writeCDataBrackets)
1298 {
1299 /* if the CDATA section ends with ] don't leave it open
1300 * as there is a chance that an adjacent CDATA sections
1301 * starts with ]>.
1302 * We don't want to merge ]] with > , or ] with ]>
1303 */
1304 if (ch[start + length - 1] == ']')
1305 closeCDATA();
1306 }
1307
1308 // time to fire off CDATA event
1309 if (m_tracer != null)
1310 super.fireCDATAEvent(ch, old_start, length);
1311 }
1312 catch (IOException ioe)
1313 {
1314 throw new org.xml.sax.SAXException(
1315 Utils.messages.createMessage(
1316 MsgKey.ER_OIERROR,
1317 null),
1318 ioe);
1319 //"IO error", ioe);
1320 }
1321 }
1322
1323 /**
1324 * Tell if the character escaping should be disabled for the current state.
1325 *
1326 * @return true if the character escaping should be disabled.
1327 */
1328 private boolean isEscapingDisabled()
1329 {
1330 return m_disableOutputEscapingStates.peekOrFalse();
1331 }
1332
1333 /**
1334 * If available, when the disable-output-escaping attribute is used,
1335 * output raw text without escaping.
1336 *
1337 * @param ch The characters from the XML document.
1338 * @param start The start position in the array.
1339 * @param length The number of characters to read from the array.
1340 *
1341 * @throws org.xml.sax.SAXException
1342 */
1343 protected void charactersRaw(char ch[], int start, int length)
1344 throws org.xml.sax.SAXException
1345 {
1346
1347 if (m_inEntityRef)
1348 return;
1349 try
1350 {
1351 if (m_elemContext.m_startTagOpen)
1352 {
1353 closeStartTag();
1354 m_elemContext.m_startTagOpen = false;
1355 }
1356
1357 m_ispreserve = true;
1358
1359 m_writer.write(ch, start, length);
1360 }
1361 catch (IOException e)
1362 {
1363 throw new SAXException(e);
1364 }
1365
1366 }
1367
1368 /**
1369 * Receive notification of character data.
1370 *
1371 * <p>The Parser will call this method to report each chunk of
1372 * character data. SAX parsers may return all contiguous character
1373 * data in a single chunk, or they may split it into several
1374 * chunks; however, all of the characters in any single event
1375 * must come from the same external entity, so that the Locator
1376 * provides useful information.</p>
1377 *
1378 * <p>The application must not attempt to read from the array
1379 * outside of the specified range.</p>
1380 *
1381 * <p>Note that some parsers will report whitespace using the
1382 * ignorableWhitespace() method rather than this one (validating
1383 * parsers must do so).</p>
1384 *
1385 * @param chars The characters from the XML document.
1386 * @param start The start position in the array.
1387 * @param length The number of characters to read from the array.
1388 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1389 * wrapping another exception.
1390 * @see #ignorableWhitespace
1391 * @see org.xml.sax.Locator
1392 *
1393 * @throws org.xml.sax.SAXException
1394 */
1395 public void characters(final char chars[], final int start, final int length)
1396 throws org.xml.sax.SAXException
1397 {
1398 // It does not make sense to continue with rest of the method if the number of
1399 // characters to read from array is 0.
1400 // Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node
1401 // is created if string is empty.
1402 if (length == 0 || (m_inEntityRef && !m_expandDTDEntities))
1403 return;
1404
1405 m_docIsEmpty = false;
1406
1407 if (m_elemContext.m_startTagOpen)
1408 {
1409 closeStartTag();
1410 m_elemContext.m_startTagOpen = false;
1411 }
1412 else if (m_needToCallStartDocument)
1413 {
1414 startDocumentInternal();
1415 }
1416
1417 if (m_cdataStartCalled || m_elemContext.m_isCdataSection)
1418 {
1419 /* either due to startCDATA() being called or due to
1420 * cdata-section-elements atribute, we need this as cdata
1421 */
1422 cdata(chars, start, length);
1423
1424 return;
1425 }
1426
1427 if (m_cdataTagOpen)
1428 closeCDATA();
1429
1430 if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping))
1431 {
1432 charactersRaw(chars, start, length);
1433
1434 // time to fire off characters generation event
1435 if (m_tracer != null)
1436 super.fireCharEvent(chars, start, length);
1437
1438 return;
1439 }
1440
1441 if (m_elemContext.m_startTagOpen)
1442 {
1443 closeStartTag();
1444 m_elemContext.m_startTagOpen = false;
1445 }
1446
1447
1448 try
1449 {
1450 int i;
1451 int startClean;
1452
1453 // skip any leading whitspace
1454 // don't go off the end and use a hand inlined version
1455 // of isWhitespace(ch)
1456 final int end = start + length;
1457 int lastDirtyCharProcessed = start - 1; // last non-clean character that was processed
1458 // that was processed
1459 final Writer writer = m_writer;
1460 boolean isAllWhitespace = true;
1461
1462 // process any leading whitspace
1463 i = start;
1464 while (i < end && isAllWhitespace) {
1465 char ch1 = chars[i];
1466
1467 if (m_charInfo.shouldMapTextChar(ch1)) {
1468 // The character is supposed to be replaced by a String
1469 // so write out the clean whitespace characters accumulated
1470 // so far
1471 // then the String.
1472 writeOutCleanChars(chars, i, lastDirtyCharProcessed);
1473 String outputStringForChar = m_charInfo
1474 .getOutputStringForChar(ch1);
1475 writer.write(outputStringForChar);
1476 // We can't say that everything we are writing out is
1477 // all whitespace, we just wrote out a String.
1478 isAllWhitespace = false;
1479 lastDirtyCharProcessed = i; // mark the last non-clean
1480 // character processed
1481 i++;
1482 } else {
1483 // The character is clean, but is it a whitespace ?
1484 switch (ch1) {
1485 // TODO: Any other whitespace to consider?
1486 case CharInfo.S_SPACE:
1487 // Just accumulate the clean whitespace
1488 i++;
1489 break;
1490 case CharInfo.S_LINEFEED:
1491 lastDirtyCharProcessed = processLineFeed(chars, i,
1492 lastDirtyCharProcessed, writer);
1493 i++;
1494 break;
1495 case CharInfo.S_CARRIAGERETURN:
1496 writeOutCleanChars(chars, i, lastDirtyCharProcessed);
1497 writer.write(" ");
1498 lastDirtyCharProcessed = i;
1499 i++;
1500 break;
1501 case CharInfo.S_HORIZONAL_TAB:
1502 // Just accumulate the clean whitespace
1503 i++;
1504 break;
1505 default:
1506 // The character was clean, but not a whitespace
1507 // so break the loop to continue with this character
1508 // (we don't increment index i !!)
1509 isAllWhitespace = false;
1510 break;
1511 }
1512 }
1513 }
1514
1515 /* If there is some non-whitespace, mark that we may need
1516 * to preserve this. This is only important if we have indentation on.
1517 */
1518 if (i < end || !isAllWhitespace)
1519 m_ispreserve = true;
1520
1521
1522 for (; i < end; i++)
1523 {
1524 char ch = chars[i];
1525
1526 if (m_charInfo.shouldMapTextChar(ch)) {
1527 // The character is supposed to be replaced by a String
1528 // e.g. '&' --> "&"
1529 // e.g. '<' --> "<"
1530 writeOutCleanChars(chars, i, lastDirtyCharProcessed);
1531 String outputStringForChar = m_charInfo.getOutputStringForChar(ch);
1532 writer.write(outputStringForChar);
1533 lastDirtyCharProcessed = i;
1534 }
1535 else {
1536 if (ch <= 0x1F) {
1537 // Range 0x00 through 0x1F inclusive
1538 //
1539 // This covers the non-whitespace control characters
1540 // in the range 0x1 to 0x1F inclusive.
1541 // It also covers the whitespace control characters in the same way:
1542 // 0x9 TAB
1543 // 0xA NEW LINE
1544 // 0xD CARRIAGE RETURN
1545 //
1546 // We also cover 0x0 ... It isn't valid
1547 // but we will output "�"
1548
1549 // The default will handle this just fine, but this
1550 // is a little performance boost to handle the more
1551 // common TAB, NEW-LINE, CARRIAGE-RETURN
1552 switch (ch) {
1553
1554 case CharInfo.S_HORIZONAL_TAB:
1555 // Leave whitespace TAB as a real character
1556 break;
1557 case CharInfo.S_LINEFEED:
1558 lastDirtyCharProcessed = processLineFeed(chars, i, lastDirtyCharProcessed, writer);
1559 break;
1560 case CharInfo.S_CARRIAGERETURN:
1561 writeOutCleanChars(chars, i, lastDirtyCharProcessed);
1562 writer.write(" ");
1563 lastDirtyCharProcessed = i;
1564 // Leave whitespace carriage return as a real character
1565 break;
1566 default:
1567 writeOutCleanChars(chars, i, lastDirtyCharProcessed);
1568 writer.write("&#");
1569 writer.write(Integer.toString(ch));
1570 writer.write(';');
1571 lastDirtyCharProcessed = i;
1572 break;
1573
1574 }
1575 }
1576 else if (ch < 0x7F) {
1577 // Range 0x20 through 0x7E inclusive
1578 // Normal ASCII chars, do nothing, just add it to
1579 // the clean characters
1580
1581 }
1582 else if (ch <= 0x9F){
1583 // Range 0x7F through 0x9F inclusive
1584 // More control characters, including NEL (0x85)
1585 writeOutCleanChars(chars, i, lastDirtyCharProcessed);
1586 writer.write("&#");
1587 writer.write(Integer.toString(ch));
1588 writer.write(';');
1589 lastDirtyCharProcessed = i;
1590 }
1591 else if (ch == CharInfo.S_LINE_SEPARATOR) {
1592 // LINE SEPARATOR
1593 writeOutCleanChars(chars, i, lastDirtyCharProcessed);
1594 writer.write("
");
1595 lastDirtyCharProcessed = i;
1596 }
1597 else if (m_encodingInfo.isInEncoding(ch)) {
1598 // If the character is in the encoding, and
1599 // not in the normal ASCII range, we also
1600 // just leave it get added on to the clean characters
1601
1602 }
1603 else {
1604 // This is a fallback plan, we should never get here
1605 // but if the character wasn't previously handled
1606 // (i.e. isn't in the encoding, etc.) then what
1607 // should we do? We choose to write out an entity
1608 writeOutCleanChars(chars, i, lastDirtyCharProcessed);
1609 writer.write("&#");
1610 writer.write(Integer.toString(ch));
1611 writer.write(';');
1612 lastDirtyCharProcessed = i;
1613 }
1614 }
1615 }
1616
1617 // we've reached the end. Any clean characters at the
1618 // end of the array than need to be written out?
1619 startClean = lastDirtyCharProcessed + 1;
1620 if (i > startClean)
1621 {
1622 int lengthClean = i - startClean;
1623 m_writer.write(chars, startClean, lengthClean);
1624 }
1625
1626 // For indentation purposes, mark that we've just writen text out
1627 m_isprevtext = true;
1628 }
1629 catch (IOException e)
1630 {
1631 throw new SAXException(e);
1632 }
1633
1634 // time to fire off characters generation event
1635 if (m_tracer != null)
1636 super.fireCharEvent(chars, start, length);
1637 }
1638
1639 private int processLineFeed(final char[] chars, int i, int lastProcessed, final Writer writer) throws IOException {
1640 if (!m_lineSepUse
1641 || (m_lineSepLen ==1 && m_lineSep[0] == CharInfo.S_LINEFEED)){
1642 // We are leaving the new-line alone, and it is just
1643 // being added to the 'clean' characters,
1644 // so the last dirty character processed remains unchanged
1645 }
1646 else {
1647 writeOutCleanChars(chars, i, lastProcessed);
1648 writer.write(m_lineSep, 0, m_lineSepLen);
1649 lastProcessed = i;
1650 }
1651 return lastProcessed;
1652 }
1653
1654 private void writeOutCleanChars(final char[] chars, int i, int lastProcessed) throws IOException {
1655 int startClean;
1656 startClean = lastProcessed + 1;
1657 if (startClean < i)
1658 {
1659 int lengthClean = i - startClean;
1660 m_writer.write(chars, startClean, lengthClean);
1661 }
1662 }
1663 /**
1664 * This method checks if a given character is between C0 or C1 range
1665 * of Control characters.
1666 * This method is added to support Control Characters for XML 1.1
1667 * If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method
1668 * return false. Since they are whitespace characters, no special processing is needed.
1669 *
1670 * @param ch
1671 * @return boolean
1672 */
1673 private static boolean isCharacterInC0orC1Range(char ch)
1674 {
1675 if(ch == 0x09 || ch == 0x0A || ch == 0x0D)
1676 return false;
1677 else
1678 return (ch >= 0x7F && ch <= 0x9F)|| (ch >= 0x01 && ch <= 0x1F);
1679 }
1680 /**
1681 * This method checks if a given character either NEL (0x85) or LSEP (0x2028)
1682 * These are new end of line charcters added in XML 1.1. These characters must be
1683 * written as Numeric Character References (NCR) in XML 1.1 output document.
1684 *
1685 * @param ch
1686 * @return boolean
1687 */
1688 private static boolean isNELorLSEPCharacter(char ch)
1689 {
1690 return (ch == 0x85 || ch == 0x2028);
1691 }
1692 /**
1693 * Process a dirty character and any preeceding clean characters
1694 * that were not yet processed.
1695 * @param chars array of characters being processed
1696 * @param end one (1) beyond the last character
1697 * in chars to be processed
1698 * @param i the index of the dirty character
1699 * @param ch the character in chars[i]
1700 * @param lastDirty the last dirty character previous to i
1701 * @param fromTextNode true if the characters being processed are
1702 * from a text node, false if they are from an attribute value.
1703 * @return the index of the last character processed
1704 */
1705 private int processDirty(
1706 char[] chars,
1707 int end,
1708 int i,
1709 char ch,
1710 int lastDirty,
1711 boolean fromTextNode) throws IOException
1712 {
1713 int startClean = lastDirty + 1;
1714 // if we have some clean characters accumulated
1715 // process them before the dirty one.
1716 if (i > startClean)
1717 {
1718 int lengthClean = i - startClean;
1719 m_writer.write(chars, startClean, lengthClean);
1720 }
1721
1722 // process the "dirty" character
1723 if (CharInfo.S_LINEFEED == ch && fromTextNode)
1724 {
1725 m_writer.write(m_lineSep, 0, m_lineSepLen);
1726 }
1727 else
1728 {
1729 startClean =
1730 accumDefaultEscape(
1731 m_writer,
1732 (char)ch,
1733 i,
1734 chars,
1735 end,
1736 fromTextNode,
1737 false);
1738 i = startClean - 1;
1739 }
1740 // Return the index of the last character that we just processed
1741 // which is a dirty character.
1742 return i;
1743 }
1744
1745 /**
1746 * Receive notification of character data.
1747 *
1748 * @param s The string of characters to process.
1749 *
1750 * @throws org.xml.sax.SAXException
1751 */
1752 public void characters(String s) throws org.xml.sax.SAXException
1753 {
1754 if (m_inEntityRef && !m_expandDTDEntities)
1755 return;
1756 final int length = s.length();
1757 if (length > m_charsBuff.length)
1758 {
1759 m_charsBuff = new char[length * 2 + 1];
1760 }
1761 s.getChars(0, length, m_charsBuff, 0);
1762 characters(m_charsBuff, 0, length);
1763 }
1764
1765 /**
1766 * Escape and writer.write a character.
1767 *
1768 * @param ch character to be escaped.
1769 * @param i index into character array.
1770 * @param chars non-null reference to character array.
1771 * @param len length of chars.
1772 * @param fromTextNode true if the characters being processed are
1773 * from a text node, false if the characters being processed are from
1774 * an attribute value.
1775 * @param escLF true if the linefeed should be escaped.
1776 *
1777 * @return i+1 if a character was written, i+2 if two characters
1778 * were written out, else return i.
1779 *
1780 * @throws org.xml.sax.SAXException
1781 */
1782 private int accumDefaultEscape(
1783 Writer writer,
1784 char ch,
1785 int i,
1786 char[] chars,
1787 int len,
1788 boolean fromTextNode,
1789 boolean escLF)
1790 throws IOException
1791 {
1792
1793 int pos = accumDefaultEntity(writer, ch, i, chars, len, fromTextNode, escLF);
1794
1795 if (i == pos)
1796 {
1797 if (Encodings.isHighUTF16Surrogate(ch))
1798 {
1799
1800 // Should be the UTF-16 low surrogate of the hig/low pair.
1801 char next;
1802 // Unicode code point formed from the high/low pair.
1803 int codePoint = 0;
1804
1805 if (i + 1 >= len)
1806 {
1807 throw new IOException(
1808 Utils.messages.createMessage(
1809 MsgKey.ER_INVALID_UTF16_SURROGATE,
1810 new Object[] { Integer.toHexString(ch)}));
1811 //"Invalid UTF-16 surrogate detected: "
1812
1813 //+Integer.toHexString(ch)+ " ?");
1814 }
1815 else
1816 {
1817 next = chars[++i];
1818
1819 if (!(Encodings.isLowUTF16Surrogate(next)))
1820 throw new IOException(
1821 Utils.messages.createMessage(
1822 MsgKey
1823 .ER_INVALID_UTF16_SURROGATE,
1824 new Object[] {
1825 Integer.toHexString(ch)
1826 + " "
1827 + Integer.toHexString(next)}));
1828 //"Invalid UTF-16 surrogate detected: "
1829
1830 //+Integer.toHexString(ch)+" "+Integer.toHexString(next));
1831 codePoint = Encodings.toCodePoint(ch,next);
1832 }
1833
1834 writer.write("&#");
1835 writer.write(Integer.toString(codePoint));
1836 writer.write(';');
1837 pos += 2; // count the two characters that went into writing out this entity
1838 }
1839 else
1840 {
1841 /* This if check is added to support control characters in XML 1.1.
1842 * If a character is a Control Character within C0 and C1 range, it is desirable
1843 * to write it out as Numeric Character Reference(NCR) regardless of XML Version
1844 * being used for output document.
1845 */
1846 if (isCharacterInC0orC1Range(ch) || isNELorLSEPCharacter(ch))
1847 {
1848 writer.write("&#");
1849 writer.write(Integer.toString(ch));
1850 writer.write(';');
1851 }
1852 else if ((!escapingNotNeeded(ch) ||
1853 ( (fromTextNode && m_charInfo.shouldMapTextChar(ch))
1854 || (!fromTextNode && m_charInfo.shouldMapAttrChar(ch))))
1855 && m_elemContext.m_currentElemDepth > 0)
1856 {
1857 writer.write("&#");
1858 writer.write(Integer.toString(ch));
1859 writer.write(';');
1860 }
1861 else
1862 {
1863 writer.write(ch);
1864 }
1865 pos++; // count the single character that was processed
1866 }
1867
1868 }
1869 return pos;
1870 }
1871
1872 /**
1873 * Receive notification of the beginning of an element, although this is a
1874 * SAX method additional namespace or attribute information can occur before
1875 * or after this call, that is associated with this element.
1876 *
1877 *
1878 * @param namespaceURI The Namespace URI, or the empty string if the
1879 * element has no Namespace URI or if Namespace
1880 * processing is not being performed.
1881 * @param localName The local name (without prefix), or the
1882 * empty string if Namespace processing is not being
1883 * performed.
1884 * @param name The element type name.
1885 * @param atts The attributes attached to the element, if any.
1886 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1887 * wrapping another exception.
1888 * @see org.xml.sax.ContentHandler#startElement
1889 * @see org.xml.sax.ContentHandler#endElement
1890 * @see org.xml.sax.AttributeList
1891 *
1892 * @throws org.xml.sax.SAXException
1893 */
1894 public void startElement(
1895 String namespaceURI,
1896 String localName,
1897 String name,
1898 Attributes atts)
1899 throws org.xml.sax.SAXException
1900 {
1901 if (m_inEntityRef)
1902 return;
1903
1904 if (m_needToCallStartDocument)
1905 {
1906 startDocumentInternal();
1907 m_needToCallStartDocument = false;
1908 m_docIsEmpty = false;
1909 }
1910 else if (m_cdataTagOpen)
1911 closeCDATA();
1912 try
1913 {
1914 if (m_needToOutputDocTypeDecl) {
1915 if(null != getDoctypeSystem()) {
1916 outputDocTypeDecl(name, true);
1917 }
1918 m_needToOutputDocTypeDecl = false;
1919 }
1920
1921 /* before we over-write the current elementLocalName etc.
1922 * lets close out the old one (if we still need to)
1923 */
1924 if (m_elemContext.m_startTagOpen)
1925 {
1926 closeStartTag();
1927 m_elemContext.m_startTagOpen = false;
1928 }
1929
1930 if (namespaceURI != null)
1931 ensurePrefixIsDeclared(namespaceURI, name);
1932
1933 m_ispreserve = false;
1934
1935 if (shouldIndent() && m_startNewLine)
1936 {
1937 indent();
1938 }
1939
1940 m_startNewLine = true;
1941
1942 final java.io.Writer writer = m_writer;
1943 writer.write('<');
1944 writer.write(name);
1945 }
1946 catch (IOException e)
1947 {
1948 throw new SAXException(e);
1949 }
1950
1951 // process the attributes now, because after this SAX call they might be gone
1952 if (atts != null)
1953 addAttributes(atts);
1954
1955 m_elemContext = m_elemContext.push(namespaceURI,localName,name);
1956 m_isprevtext = false;
1957
1958 if (m_tracer != null)
1959 firePseudoAttributes();
1960 }
1961
1962 /**
1963 * Receive notification of the beginning of an element, additional
1964 * namespace or attribute information can occur before or after this call,
1965 * that is associated with this element.
1966 *
1967 *
1968 * @param elementNamespaceURI The Namespace URI, or the empty string if the
1969 * element has no Namespace URI or if Namespace
1970 * processing is not being performed.
1971 * @param elementLocalName The local name (without prefix), or the
1972 * empty string if Namespace processing is not being
1973 * performed.
1974 * @param elementName The element type name.
1975 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1976 * wrapping another exception.
1977 * @see org.xml.sax.ContentHandler#startElement
1978 * @see org.xml.sax.ContentHandler#endElement
1979 * @see org.xml.sax.AttributeList
1980 *
1981 * @throws org.xml.sax.SAXException
1982 */
1983 public void startElement(
1984 String elementNamespaceURI,
1985 String elementLocalName,
1986 String elementName)
1987 throws SAXException
1988 {
1989 startElement(elementNamespaceURI, elementLocalName, elementName, null);
1990 }
1991
1992 public void startElement(String elementName) throws SAXException
1993 {
1994 startElement(null, null, elementName, null);
1995 }
1996
1997 /**
1998 * Output the doc type declaration.
1999 *
2000 * @param name non-null reference to document type name.
2001 * NEEDSDOC @param closeDecl
2002 *
2003 * @throws java.io.IOException
2004 */
2005 void outputDocTypeDecl(String name, boolean closeDecl) throws SAXException
2006 {
2007 if (m_cdataTagOpen)
2008 closeCDATA();
2009 try
2010 {
2011 final java.io.Writer writer = m_writer;
2012 writer.write("<!DOCTYPE ");
2013 writer.write(name);
2014
2015 String doctypePublic = getDoctypePublic();
2016 if (null != doctypePublic)
2017 {
2018 writer.write(" PUBLIC \"");
2019 writer.write(doctypePublic);
2020 writer.write('\"');
2021 }
2022
2023 String doctypeSystem = getDoctypeSystem();
2024 if (null != doctypeSystem)
2025 {
2026 if (null == doctypePublic)
2027 writer.write(" SYSTEM \"");
2028 else
2029 writer.write(" \"");
2030
2031 writer.write(doctypeSystem);
2032
2033 if (closeDecl)
2034 {
2035 writer.write("\">");
2036 writer.write(m_lineSep, 0, m_lineSepLen);
2037 closeDecl = false; // done closing
2038 }
2039 else
2040 writer.write('\"');
2041 }
2042 }
2043 catch (IOException e)
2044 {
2045 throw new SAXException(e);
2046 }
2047 }
2048
2049 /**
2050 * Process the attributes, which means to write out the currently
2051 * collected attributes to the writer. The attributes are not
2052 * cleared by this method
2053 *
2054 * @param writer the writer to write processed attributes to.
2055 * @param nAttrs the number of attributes in m_attributes
2056 * to be processed
2057 *
2058 * @throws java.io.IOException
2059 * @throws org.xml.sax.SAXException
2060 */
2061 public void processAttributes(java.io.Writer writer, int nAttrs) throws IOException, SAXException
2062 {
2063 /* real SAX attributes are not passed in, so process the
2064 * attributes that were collected after the startElement call.
2065 * _attribVector is a "cheap" list for Stream serializer output
2066 * accumulated over a series of calls to attribute(name,value)
2067 */
2068
2069 String encoding = getEncoding();
2070 for (int i = 0; i < nAttrs; i++)
2071 {
2072 // elementAt is JDK 1.1.8
2073 final String name = m_attributes.getQName(i);
2074 final String value = m_attributes.getValue(i);
2075 writer.write(' ');
2076 writer.write(name);
2077 writer.write("=\"");
2078 writeAttrString(writer, value, encoding);
2079 writer.write('\"');
2080 }
2081 }
2082
2083 /**
2084 * Returns the specified <var>string</var> after substituting <VAR>specials</VAR>,
2085 * and UTF-16 surrogates for chracter references <CODE>&#xnn</CODE>.
2086 *
2087 * @param string String to convert to XML format.
2088 * @param encoding CURRENTLY NOT IMPLEMENTED.
2089 *
2090 * @throws java.io.IOException
2091 */
2092 public void writeAttrString(
2093 Writer writer,
2094 String string,
2095 String encoding)
2096 throws IOException
2097 {
2098 final int len = string.length();
2099 if (len > m_attrBuff.length)
2100 {
2101 m_attrBuff = new char[len*2 + 1];
2102 }
2103 string.getChars(0,len, m_attrBuff, 0);
2104 final char[] stringChars = m_attrBuff;
2105
2106 for (int i = 0; i < len; i++)
2107 {
2108 char ch = stringChars[i];
2109
2110 if (m_charInfo.shouldMapAttrChar(ch)) {
2111 // The character is supposed to be replaced by a String
2112 // e.g. '&' --> "&"
2113 // e.g. '<' --> "<"
2114 accumDefaultEscape(writer, ch, i, stringChars, len, false, true);
2115 }
2116 else {
2117 if (0x0 <= ch && ch <= 0x1F) {
2118 // Range 0x00 through 0x1F inclusive
2119 // This covers the non-whitespace control characters
2120 // in the range 0x1 to 0x1F inclusive.
2121 // It also covers the whitespace control characters in the same way:
2122 // 0x9 TAB
2123 // 0xA NEW LINE
2124 // 0xD CARRIAGE RETURN
2125 //
2126 // We also cover 0x0 ... It isn't valid
2127 // but we will output "�"
2128
2129 // The default will handle this just fine, but this
2130 // is a little performance boost to handle the more
2131 // common TAB, NEW-LINE, CARRIAGE-RETURN
2132 switch (ch) {
2133
2134 case CharInfo.S_HORIZONAL_TAB:
2135 writer.write("	");
2136 break;
2137 case CharInfo.S_LINEFEED:
2138 writer.write(" ");
2139 break;
2140 case CharInfo.S_CARRIAGERETURN:
2141 writer.write(" ");
2142 break;
2143 default:
2144 writer.write("&#");
2145 writer.write(Integer.toString(ch));
2146 writer.write(';');
2147 break;
2148
2149 }
2150 }
2151 else if (ch < 0x7F) {
2152 // Range 0x20 through 0x7E inclusive
2153 // Normal ASCII chars
2154 writer.write(ch);
2155 }
2156 else if (ch <= 0x9F){
2157 // Range 0x7F through 0x9F inclusive
2158 // More control characters
2159 writer.write("&#");
2160 writer.write(Integer.toString(ch));
2161 writer.write(';');
2162 }
2163 else if (ch == CharInfo.S_LINE_SEPARATOR) {
2164 // LINE SEPARATOR
2165 writer.write("
");
2166 }
2167 else if (m_encodingInfo.isInEncoding(ch)) {
2168 // If the character is in the encoding, and
2169 // not in the normal ASCII range, we also
2170 // just write it out
2171 writer.write(ch);
2172 }
2173 else {
2174 // This is a fallback plan, we should never get here
2175 // but if the character wasn't previously handled
2176 // (i.e. isn't in the encoding, etc.) then what
2177 // should we do? We choose to write out a character ref
2178 writer.write("&#");
2179 writer.write(Integer.toString(ch));
2180 writer.write(';');
2181 }
2182
2183 }
2184 }
2185 }
2186
2187 /**
2188 * Receive notification of the end of an element.
2189 *
2190 *
2191 * @param namespaceURI The Namespace URI, or the empty string if the
2192 * element has no Namespace URI or if Namespace
2193 * processing is not being performed.
2194 * @param localName The local name (without prefix), or the
2195 * empty string if Namespace processing is not being
2196 * performed.
2197 * @param name The element type name
2198 * @throws org.xml.sax.SAXException Any SAX exception, possibly
2199 * wrapping another exception.
2200 *
2201 * @throws org.xml.sax.SAXException
2202 */
2203 public void endElement(String namespaceURI, String localName, String name)
2204 throws org.xml.sax.SAXException
2205 {
2206 if (m_inEntityRef)
2207 return;
2208
2209 // namespaces declared at the current depth are no longer valid
2210 // so get rid of them
2211 m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null);
2212
2213 try
2214 {
2215 final java.io.Writer writer = m_writer;
2216 if (m_elemContext.m_startTagOpen)
2217 {
2218 if (m_tracer != null)
2219 super.fireStartElem(m_elemContext.m_elementName);
2220 int nAttrs = m_attributes.getLength();
2221 if (nAttrs > 0)
2222 {
2223 processAttributes(m_writer, nAttrs);
2224 // clear attributes object for re-use with next element
2225 m_attributes.clear();
2226 }
2227 if (m_spaceBeforeClose)
2228 writer.write(" />");
2229 else
2230 writer.write("/>");
2231 /* don't need to pop cdataSectionState because
2232 * this element ended so quickly that we didn't get
2233 * to push the state.
2234 */
2235
2236 }
2237 else
2238 {
2239 if (m_cdataTagOpen)
2240 closeCDATA();
2241
2242 if (shouldIndent())
2243 indent(m_elemContext.m_currentElemDepth - 1);
2244 writer.write('<');
2245 writer.write('/');
2246 writer.write(name);
2247 writer.write('>');
2248 }
2249 }
2250 catch (IOException e)
2251 {
2252 throw new SAXException(e);
2253 }
2254
2255 if (!m_elemContext.m_startTagOpen && m_doIndent)
2256 {
2257 m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
2258 }
2259
2260 m_isprevtext = false;
2261
2262 // fire off the end element event
2263 if (m_tracer != null)
2264 super.fireEndElem(name);
2265 m_elemContext = m_elemContext.m_prev;
2266 }
2267
2268 /**
2269 * Receive notification of the end of an element.
2270 * @param name The element type name
2271 * @throws org.xml.sax.SAXException Any SAX exception, possibly
2272 * wrapping another exception.
2273 */
2274 public void endElement(String name) throws org.xml.sax.SAXException
2275 {
2276 endElement(null, null, name);
2277 }
2278
2279 /**
2280 * Begin the scope of a prefix-URI Namespace mapping
2281 * just before another element is about to start.
2282 * This call will close any open tags so that the prefix mapping
2283 * will not apply to the current element, but the up comming child.
2284 *
2285 * @see org.xml.sax.ContentHandler#startPrefixMapping
2286 *
2287 * @param prefix The Namespace prefix being declared.
2288 * @param uri The Namespace URI the prefix is mapped to.
2289 *
2290 * @throws org.xml.sax.SAXException The client may throw
2291 * an exception during processing.
2292 *
2293 */
2294 public void startPrefixMapping(String prefix, String uri)
2295 throws org.xml.sax.SAXException
2296 {
2297 // the "true" causes the flush of any open tags
2298 startPrefixMapping(prefix, uri, true);
2299 }
2300
2301 /**
2302 * Handle a prefix/uri mapping, which is associated with a startElement()
2303 * that is soon to follow. Need to close any open start tag to make
2304 * sure than any name space attributes due to this event are associated wih
2305 * the up comming element, not the current one.
2306 * @see ExtendedContentHandler#startPrefixMapping
2307 *
2308 * @param prefix The Namespace prefix being declared.
2309 * @param uri The Namespace URI the prefix is mapped to.
2310 * @param shouldFlush true if any open tags need to be closed first, this
2311 * will impact which element the mapping applies to (open parent, or its up
2312 * comming child)
2313 * @return returns true if the call made a change to the current
2314 * namespace information, false if it did not change anything, e.g. if the
2315 * prefix/namespace mapping was already in scope from before.
2316 *
2317 * @throws org.xml.sax.SAXException The client may throw
2318 * an exception during processing.
2319 *
2320 *
2321 */
2322 public boolean startPrefixMapping(
2323 String prefix,
2324 String uri,
2325 boolean shouldFlush)
2326 throws org.xml.sax.SAXException
2327 {
2328
2329 /* Remember the mapping, and at what depth it was declared
2330 * This is one greater than the current depth because these
2331 * mappings will apply to the next depth. This is in
2332 * consideration that startElement() will soon be called
2333 */
2334
2335 boolean pushed;
2336 int pushDepth;
2337 if (shouldFlush)
2338 {
2339 flushPending();
2340 // the prefix mapping applies to the child element (one deeper)
2341 pushDepth = m_elemContext.m_currentElemDepth + 1;
2342 }
2343 else
2344 {
2345 // the prefix mapping applies to the current element
2346 pushDepth = m_elemContext.m_currentElemDepth;
2347 }
2348 pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
2349
2350 if (pushed)
2351 {
2352 /* Brian M.: don't know if we really needto do this. The
2353 * callers of this object should have injected both
2354 * startPrefixMapping and the attributes. We are
2355 * just covering our butt here.
2356 */
2357 String name;
2358 if (EMPTYSTRING.equals(prefix))
2359 {
2360 name = "xmlns";
2361 addAttributeAlways(XMLNS_URI, name, name, "CDATA", uri, false);
2362 }
2363 else
2364 {
2365 if (!EMPTYSTRING.equals(uri))
2366 // hack for XSLTC attribset16 test
2367 { // that maps ns1 prefix to "" URI
2368 name = "xmlns:" + prefix;
2369
2370 /* for something like xmlns:abc="w3.pretend.org"
2371 * the uri is the value, that is why we pass it in the
2372 * value, or 5th slot of addAttributeAlways()
2373 */
2374 addAttributeAlways(XMLNS_URI, prefix, name, "CDATA", uri, false);
2375 }
2376 }
2377 }
2378 return pushed;
2379 }
2380
2381 /**
2382 * Receive notification of an XML comment anywhere in the document. This
2383 * callback will be used for comments inside or outside the document
2384 * element, including comments in the external DTD subset (if read).
2385 * @param ch An array holding the characters in the comment.
2386 * @param start The starting position in the array.
2387 * @param length The number of characters to use from the array.
2388 * @throws org.xml.sax.SAXException The application may raise an exception.
2389 */
2390 public void comment(char ch[], int start, int length)
2391 throws org.xml.sax.SAXException
2392 {
2393
2394 int start_old = start;
2395 if (m_inEntityRef)
2396 return;
2397 if (m_elemContext.m_startTagOpen)
2398 {
2399 closeStartTag();
2400 m_elemContext.m_startTagOpen = false;
2401 }
2402 else if (m_needToCallStartDocument)
2403 {
2404 startDocumentInternal();
2405 m_needToCallStartDocument = false;
2406 }
2407
2408 try
2409 {
2410 final int limit = start + length;
2411 boolean wasDash = false;
2412 if (m_cdataTagOpen)
2413 closeCDATA();
2414
2415 if (shouldIndent())
2416 indent();
2417
2418 final java.io.Writer writer = m_writer;
2419 writer.write(COMMENT_BEGIN);
2420 // Detect occurrences of two consecutive dashes, handle as necessary.
2421 for (int i = start; i < limit; i++)
2422 {
2423 if (wasDash && ch[i] == '-')
2424 {
2425 writer.write(ch, start, i - start);
2426 writer.write(" -");
2427 start = i + 1;
2428 }
2429 wasDash = (ch[i] == '-');
2430 }
2431
2432 // if we have some chars in the comment
2433 if (length > 0)
2434 {
2435 // Output the remaining characters (if any)
2436 final int remainingChars = (limit - start);
2437 if (remainingChars > 0)
2438 writer.write(ch, start, remainingChars);
2439 // Protect comment end from a single trailing dash
2440 if (ch[limit - 1] == '-')
2441 writer.write(' ');
2442 }
2443 writer.write(COMMENT_END);
2444 }
2445 catch (IOException e)
2446 {
2447 throw new SAXException(e);
2448 }
2449
2450 /*
2451 * Don't write out any indentation whitespace now,
2452 * because there may be non-whitespace text after this.
2453 *
2454 * Simply mark that at this point if we do decide
2455 * to indent that we should
2456 * add a newline on the end of the current line before
2457 * the indentation at the start of the next line.
2458 */
2459 m_startNewLine = true;
2460 // time to generate comment event
2461 if (m_tracer != null)
2462 super.fireCommentEvent(ch, start_old,length);
2463 }
2464
2465 /**
2466 * Report the end of a CDATA section.
2467 * @throws org.xml.sax.SAXException The application may raise an exception.
2468 *
2469 * @see #startCDATA
2470 */
2471 public void endCDATA() throws org.xml.sax.SAXException
2472 {
2473 if (m_cdataTagOpen)
2474 closeCDATA();
2475 m_cdataStartCalled = false;
2476 }
2477
2478 /**
2479 * Report the end of DTD declarations.
2480 * @throws org.xml.sax.SAXException The application may raise an exception.
2481 * @see #startDTD
2482 */
2483 public void endDTD() throws org.xml.sax.SAXException
2484 {
2485 try
2486 {
2487 if (m_needToOutputDocTypeDecl)
2488 {
2489 outputDocTypeDecl(m_elemContext.m_elementName, false);
2490 m_needToOutputDocTypeDecl = false;
2491 }
2492 final java.io.Writer writer = m_writer;
2493 if (!m_inDoctype)
2494 writer.write("]>");
2495 else
2496 {
2497 writer.write('>');
2498 }
2499
2500 writer.write(m_lineSep, 0, m_lineSepLen);
2501 }
2502 catch (IOException e)
2503 {
2504 throw new SAXException(e);
2505 }
2506
2507 }
2508
2509 /**
2510 * End the scope of a prefix-URI Namespace mapping.
2511 * @see org.xml.sax.ContentHandler#endPrefixMapping
2512 *
2513 * @param prefix The prefix that was being mapping.
2514 * @throws org.xml.sax.SAXException The client may throw
2515 * an exception during processing.
2516 */
2517 public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException
2518 { // do nothing
2519 }
2520
2521 /**
2522 * Receive notification of ignorable whitespace in element content.
2523 *
2524 * Not sure how to get this invoked quite yet.
2525 *
2526 * @param ch The characters from the XML document.
2527 * @param start The start position in the array.
2528 * @param length The number of characters to read from the array.
2529 * @throws org.xml.sax.SAXException Any SAX exception, possibly
2530 * wrapping another exception.
2531 * @see #characters
2532 *
2533 * @throws org.xml.sax.SAXException
2534 */
2535 public void ignorableWhitespace(char ch[], int start, int length)
2536 throws org.xml.sax.SAXException
2537 {
2538
2539 if (0 == length)
2540 return;
2541 characters(ch, start, length);
2542 }
2543
2544 /**
2545 * Receive notification of a skipped entity.
2546 * @see org.xml.sax.ContentHandler#skippedEntity
2547 *
2548 * @param name The name of the skipped entity. If it is a
2549 * parameter entity, the name will begin with '%',
2550 * and if it is the external DTD subset, it will be the string
2551 * "[dtd]".
2552 * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping
2553 * another exception.
2554 */
2555 public void skippedEntity(String name) throws org.xml.sax.SAXException
2556 { // TODO: Should handle
2557 }
2558
2559 /**
2560 * Report the start of a CDATA section.
2561 *
2562 * @throws org.xml.sax.SAXException The application may raise an exception.
2563 * @see #endCDATA
2564 */
2565 public void startCDATA() throws org.xml.sax.SAXException
2566 {
2567 m_cdataStartCalled = true;
2568 }
2569
2570 /**
2571 * Report the beginning of an entity.
2572 *
2573 * The start and end of the document entity are not reported.
2574 * The start and end of the external DTD subset are reported
2575 * using the pseudo-name "[dtd]". All other events must be
2576 * properly nested within start/end entity events.
2577 *
2578 * @param name The name of the entity. If it is a parameter
2579 * entity, the name will begin with '%'.
2580 * @throws org.xml.sax.SAXException The application may raise an exception.
2581 * @see #endEntity
2582 * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
2583 * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
2584 */
2585 public void startEntity(String name) throws org.xml.sax.SAXException
2586 {
2587 if (name.equals("[dtd]"))
2588 m_inExternalDTD = true;
2589
2590 if (!m_expandDTDEntities && !m_inExternalDTD) {
2591 /* Only leave the entity as-is if
2592 * we've been told not to expand them
2593 * and this is not the magic [dtd] name.
2594 */
2595 startNonEscaping();
2596 characters("&" + name + ';');
2597 endNonEscaping();
2598 }
2599
2600 m_inEntityRef = true;
2601 }
2602
2603 /**
2604 * For the enclosing elements starting tag write out
2605 * out any attributes followed by ">"
2606 *
2607 * @throws org.xml.sax.SAXException
2608 */
2609 protected void closeStartTag() throws SAXException
2610 {
2611
2612 if (m_elemContext.m_startTagOpen)
2613 {
2614
2615 try
2616 {
2617 if (m_tracer != null)
2618 super.fireStartElem(m_elemContext.m_elementName);
2619 int nAttrs = m_attributes.getLength();
2620 if (nAttrs > 0)
2621 {
2622 processAttributes(m_writer, nAttrs);
2623 // clear attributes object for re-use with next element
2624 m_attributes.clear();
2625 }
2626 m_writer.write('>');
2627 }
2628 catch (IOException e)
2629 {
2630 throw new SAXException(e);
2631 }
2632
2633 /* whether Xalan or XSLTC, we have the prefix mappings now, so
2634 * lets determine if the current element is specified in the cdata-
2635 * section-elements list.
2636 */
2637 if (m_CdataElems != null)
2638 m_elemContext.m_isCdataSection = isCdataSection();
2639
2640 if (m_doIndent)
2641 {
2642 m_isprevtext = false;
2643 m_preserves.push(m_ispreserve);
2644 }
2645 }
2646
2647 }
2648
2649 /**
2650 * Report the start of DTD declarations, if any.
2651 *
2652 * Any declarations are assumed to be in the internal subset unless
2653 * otherwise indicated.
2654 *
2655 * @param name The document type name.
2656 * @param publicId The declared public identifier for the
2657 * external DTD subset, or null if none was declared.
2658 * @param systemId The declared system identifier for the
2659 * external DTD subset, or null if none was declared.
2660 * @throws org.xml.sax.SAXException The application may raise an
2661 * exception.
2662 * @see #endDTD
2663 * @see #startEntity
2664 */
2665 public void startDTD(String name, String publicId, String systemId)
2666 throws org.xml.sax.SAXException
2667 {
2668 setDoctypeSystem(systemId);
2669 setDoctypePublic(publicId);
2670
2671 m_elemContext.m_elementName = name;
2672 m_inDoctype = true;
2673 }
2674
2675 /**
2676 * Returns the m_indentAmount.
2677 * @return int
2678 */
2679 public int getIndentAmount()
2680 {
2681 return m_indentAmount;
2682 }
2683
2684 /**
2685 * Sets the m_indentAmount.
2686 *
2687 * @param m_indentAmount The m_indentAmount to set
2688 */
2689 public void setIndentAmount(int m_indentAmount)
2690 {
2691 this.m_indentAmount = m_indentAmount;
2692 }
2693
2694 /**
2695 * Tell if, based on space preservation constraints and the doIndent property,
2696 * if an indent should occur.
2697 *
2698 * @return True if an indent should occur.
2699 */
2700 protected boolean shouldIndent()
2701 {
2702 return m_doIndent && (!m_ispreserve && !m_isprevtext) && m_elemContext.m_currentElemDepth > 0;
2703 }
2704
2705 /**
2706 * Searches for the list of qname properties with the specified key in the
2707 * property list. If the key is not found in this property list, the default
2708 * property list, and its defaults, recursively, are then checked. The
2709 * method returns <code>null</code> if the property is not found.
2710 *
2711 * @param key the property key.
2712 * @param props the list of properties to search in.
2713 *
2714 * Sets the vector of local-name/URI pairs of the cdata section elements
2715 * specified in the cdata-section-elements property.
2716 *
2717 * This method is essentially a copy of getQNameProperties() from
2718 * OutputProperties. Eventually this method should go away and a call
2719 * to setCdataSectionElements(Vector v) should be made directly.
2720 */
2721 private void setCdataSectionElements(String key, Properties props)
2722 {
2723
2724 String s = props.getProperty(key);
2725
2726 if (null != s)
2727 {
2728 // Vector of URI/LocalName pairs
2729 Vector v = new Vector();
2730 int l = s.length();
2731 boolean inCurly = false;
2732 StringBuffer buf = new StringBuffer();
2733
2734 // parse through string, breaking on whitespaces. I do this instead
2735 // of a tokenizer so I can track whitespace inside of curly brackets,
2736 // which theoretically shouldn't happen if they contain legal URLs.
2737 for (int i = 0; i < l; i++)
2738 {
2739 char c = s.charAt(i);
2740
2741 if (Character.isWhitespace(c))
2742 {
2743 if (!inCurly)
2744 {
2745 if (buf.length() > 0)
2746 {
2747 addCdataSectionElement(buf.toString(), v);
2748 buf.setLength(0);
2749 }
2750 continue;
2751 }
2752 }
2753 else if ('{' == c)
2754 inCurly = true;
2755 else if ('}' == c)
2756 inCurly = false;
2757
2758 buf.append(c);
2759 }
2760
2761 if (buf.length() > 0)
2762 {
2763 addCdataSectionElement(buf.toString(), v);
2764 buf.setLength(0);
2765 }
2766 // call the official, public method to set the collected names
2767 setCdataSectionElements(v);
2768 }
2769
2770 }
2771
2772 /**
2773 * Adds a URI/LocalName pair of strings to the list.
2774 *
2775 * @param URI_and_localName String of the form "{uri}local" or "local"
2776 *
2777 * @return a QName object
2778 */
2779 private void addCdataSectionElement(String URI_and_localName, Vector v)
2780 {
2781
2782 StringTokenizer tokenizer =
2783 new StringTokenizer(URI_and_localName, "{}", false);
2784 String s1 = tokenizer.nextToken();
2785 String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
2786
2787 if (null == s2)
2788 {
2789 // add null URI and the local name
2790 v.addElement(null);
2791 v.addElement(s1);
2792 }
2793 else
2794 {
2795 // add URI, then local name
2796 v.addElement(s1);
2797 v.addElement(s2);
2798 }
2799 }
2800
2801 /**
2802 * Remembers the cdata sections specified in the cdata-section-elements.
2803 * The "official way to set URI and localName pairs.
2804 * This method should be used by both Xalan and XSLTC.
2805 *
2806 * @param URI_and_localNames a vector of pairs of Strings (URI/local)
2807 */
2808 public void setCdataSectionElements(Vector URI_and_localNames)
2809 {
2810 // convert to the new way.
2811 if (URI_and_localNames != null)
2812 {
2813 final int len = URI_and_localNames.size() - 1;
2814 if (len > 0)
2815 {
2816 final StringBuffer sb = new StringBuffer();
2817 for (int i = 0; i < len; i += 2)
2818 {
2819 // whitspace separated "{uri1}local1 {uri2}local2 ..."
2820 if (i != 0)
2821 sb.append(' ');
2822 final String uri = (String) URI_and_localNames.elementAt(i);
2823 final String localName =
2824 (String) URI_and_localNames.elementAt(i + 1);
2825 if (uri != null)
2826 {
2827 // If there is no URI don't put this in, just the localName then.
2828 sb.append('{');
2829 sb.append(uri);
2830 sb.append('}');
2831 }
2832 sb.append(localName);
2833 }
2834 m_StringOfCDATASections = sb.toString();
2835 }
2836 }
2837 initCdataElems(m_StringOfCDATASections);
2838 }
2839
2840 /**
2841 * Makes sure that the namespace URI for the given qualified attribute name
2842 * is declared.
2843 * @param ns the namespace URI
2844 * @param rawName the qualified name
2845 * @return returns null if no action is taken, otherwise it returns the
2846 * prefix used in declaring the namespace.
2847 * @throws SAXException
2848 */
2849 protected String ensureAttributesNamespaceIsDeclared(
2850 String ns,
2851 String localName,
2852 String rawName)
2853 throws org.xml.sax.SAXException
2854 {
2855
2856 if (ns != null && ns.length() > 0)
2857 {
2858
2859 // extract the prefix in front of the raw name
2860 int index = 0;
2861 String prefixFromRawName =
2862 (index = rawName.indexOf(":")) < 0
2863 ? ""
2864 : rawName.substring(0, index);
2865
2866 if (index > 0)
2867 {
2868 // we have a prefix, lets see if it maps to a namespace
2869 String uri = m_prefixMap.lookupNamespace(prefixFromRawName);
2870 if (uri != null && uri.equals(ns))
2871 {
2872 // the prefix in the raw name is already maps to the given namespace uri
2873 // so we don't need to do anything
2874 return null;
2875 }
2876 else
2877 {
2878 // The uri does not map to the prefix in the raw name,
2879 // so lets make the mapping.
2880 this.startPrefixMapping(prefixFromRawName, ns, false);
2881 this.addAttribute(
2882 "http://www.w3.org/2000/xmlns/",
2883 prefixFromRawName,
2884 "xmlns:" + prefixFromRawName,
2885 "CDATA",
2886 ns, false);
2887 return prefixFromRawName;
2888 }
2889 }
2890 else
2891 {
2892 // we don't have a prefix in the raw name.
2893 // Does the URI map to a prefix already?
2894 String prefix = m_prefixMap.lookupPrefix(ns);
2895 if (prefix == null)
2896 {
2897 // uri is not associated with a prefix,
2898 // so lets generate a new prefix to use
2899 prefix = m_prefixMap.generateNextPrefix();
2900 this.startPrefixMapping(prefix, ns, false);
2901 this.addAttribute(
2902 "http://www.w3.org/2000/xmlns/",
2903 prefix,
2904 "xmlns:" + prefix,
2905 "CDATA",
2906 ns, false);
2907 }
2908
2909 return prefix;
2910
2911 }
2912 }
2913 return null;
2914 }
2915
2916 void ensurePrefixIsDeclared(String ns, String rawName)
2917 throws org.xml.sax.SAXException
2918 {
2919
2920 if (ns != null && ns.length() > 0)
2921 {
2922 int index;
2923 final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
2924 String prefix = (no_prefix) ? "" : rawName.substring(0, index);
2925
2926 if (null != prefix)
2927 {
2928 String foundURI = m_prefixMap.lookupNamespace(prefix);
2929
2930 if ((null == foundURI) || !foundURI.equals(ns))
2931 {
2932 this.startPrefixMapping(prefix, ns);
2933
2934 // Bugzilla1133: Generate attribute as well as namespace event.
2935 // SAX does expect both.
2936
2937 this.addAttributeAlways(
2938 "http://www.w3.org/2000/xmlns/",
2939 no_prefix ? "xmlns" : prefix, // local name
2940 no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname
2941 "CDATA",
2942 ns,
2943 false);
2944 }
2945
2946 }
2947 }
2948 }
2949
2950 /**
2951 * This method flushes any pending events, which can be startDocument()
2952 * closing the opening tag of an element, or closing an open CDATA section.
2953 */
2954 public void flushPending() throws SAXException
2955 {
2956 if (m_needToCallStartDocument)
2957 {
2958 startDocumentInternal();
2959 m_needToCallStartDocument = false;
2960 }
2961 if (m_elemContext.m_startTagOpen)
2962 {
2963 closeStartTag();
2964 m_elemContext.m_startTagOpen = false;
2965 }
2966
2967 if (m_cdataTagOpen)
2968 {
2969 closeCDATA();
2970 m_cdataTagOpen = false;
2971 }
2972 if (m_writer != null) {
2973 try {
2974 m_writer.flush();
2975 }
2976 catch(IOException e) {
2977 // what? me worry?
2978 }
2979 }
2980 }
2981
2982 public void setContentHandler(ContentHandler ch)
2983 {
2984 // this method is really only useful in the ToSAXHandler classes but it is
2985 // in the interface. If the method defined here is ever called
2986 // we are probably in trouble.
2987 }
2988
2989 /**
2990 * Adds the given attribute to the set of attributes, even if there is
2991 * no currently open element. This is useful if a SAX startPrefixMapping()
2992 * should need to add an attribute before the element name is seen.
2993 *
2994 * This method is a copy of its super classes method, except that some
2995 * tracing of events is done. This is so the tracing is only done for
2996 * stream serializers, not for SAX ones.
2997 *
2998 * @param uri the URI of the attribute
2999 * @param localName the local name of the attribute
3000 * @param rawName the qualified name of the attribute
3001 * @param type the type of the attribute (probably CDATA)
3002 * @param value the value of the attribute
3003 * @param xslAttribute true if this attribute is coming from an xsl:attribute element.
3004 * @return true if the attribute value was added,
3005 * false if the attribute already existed and the value was
3006 * replaced with the new value.
3007 */
3008 public boolean addAttributeAlways(
3009 String uri,
3010 String localName,
3011 String rawName,
3012 String type,
3013 String value,
3014 boolean xslAttribute)
3015 {
3016 boolean was_added;
3017 int index;
3018 if (uri == null || localName == null || uri.length() == 0)
3019 index = m_attributes.getIndex(rawName);
3020 else {
3021 index = m_attributes.getIndex(uri, localName);
3022 }
3023
3024 if (index >= 0)
3025 {
3026 String old_value = null;
3027 if (m_tracer != null)
3028 {
3029 old_value = m_attributes.getValue(index);
3030 if (value.equals(old_value))
3031 old_value = null;
3032 }
3033
3034 /* We've seen the attribute before.
3035 * We may have a null uri or localName, but all we really
3036 * want to re-set is the value anyway.
3037 */
3038 m_attributes.setValue(index, value);
3039 was_added = false;
3040 if (old_value != null)
3041 firePseudoAttributes();
3042
3043 }
3044 else
3045 {
3046 // the attribute doesn't exist yet, create it
3047 if (xslAttribute)
3048 {
3049 /*
3050 * This attribute is from an xsl:attribute element so we take some care in
3051 * adding it, e.g.
3052 * <elem1 foo:attr1="1" xmlns:foo="uri1">
3053 * <xsl:attribute name="foo:attr2">2</xsl:attribute>
3054 * </elem1>
3055 *
3056 * We are adding attr1 and attr2 both as attributes of elem1,
3057 * and this code is adding attr2 (the xsl:attribute ).
3058 * We could have a collision with the prefix like in the example above.
3059 */
3060
3061 // In the example above, is there a prefix like foo ?
3062 final int colonIndex = rawName.indexOf(':');
3063 if (colonIndex > 0)
3064 {
3065 String prefix = rawName.substring(0,colonIndex);
3066 NamespaceMappings.MappingRecord existing_mapping = m_prefixMap.getMappingFromPrefix(prefix);
3067
3068 /* Before adding this attribute (foo:attr2),
3069 * is the prefix for it (foo) already mapped at the current depth?
3070 */
3071 if (existing_mapping != null
3072 && existing_mapping.m_declarationDepth == m_elemContext.m_currentElemDepth
3073 && !existing_mapping.m_uri.equals(uri))
3074 {
3075 /*
3076 * There is an existing mapping of this prefix,
3077 * it differs from the one we need,
3078 * and unfortunately it is at the current depth so we
3079 * can not over-ride it.
3080 */
3081
3082 /*
3083 * Are we lucky enough that an existing other prefix maps to this URI ?
3084 */
3085 prefix = m_prefixMap.lookupPrefix(uri);
3086 if (prefix == null)
3087 {
3088 /* Unfortunately there is no existing prefix that happens to map to ours,
3089 * so to avoid a prefix collision we must generated a new prefix to use.
3090 * This is OK because the prefix URI mapping
3091 * defined in the xsl:attribute is short in scope,
3092 * just the xsl:attribute element itself,
3093 * and at this point in serialization the body of the
3094 * xsl:attribute, if any, is just a String. Right?
3095 * . . . I sure hope so - Brian M.
3096 */
3097 prefix = m_prefixMap.generateNextPrefix();
3098 }
3099
3100 rawName = prefix + ':' + localName;
3101 }
3102 }
3103
3104 try
3105 {
3106 /* This is our last chance to make sure the namespace for this
3107 * attribute is declared, especially if we just generated an alternate
3108 * prefix to avoid a collision (the new prefix/rawName will go out of scope
3109 * soon and be lost ... last chance here.
3110 */
3111 String prefixUsed =
3112 ensureAttributesNamespaceIsDeclared(
3113 uri,
3114 localName,
3115 rawName);
3116 }
3117 catch (SAXException e)
3118 {
3119 // TODO Auto-generated catch block
3120 e.printStackTrace();
3121 }
3122 }
3123 m_attributes.addAttribute(uri, localName, rawName, type, value);
3124 was_added = true;
3125 if (m_tracer != null)
3126 firePseudoAttributes();
3127 }
3128 return was_added;
3129 }
3130
3131 /**
3132 * To fire off the pseudo characters of attributes, as they currently
3133 * exist. This method should be called everytime an attribute is added,
3134 * or when an attribute value is changed, or an element is created.
3135 */
3136
3137 protected void firePseudoAttributes()
3138 {
3139 if (m_tracer != null)
3140 {
3141 try
3142 {
3143 // flush out the "<elemName" if not already flushed
3144 m_writer.flush();
3145
3146 // make a StringBuffer to write the name="value" pairs to.
3147 StringBuffer sb = new StringBuffer();
3148 int nAttrs = m_attributes.getLength();
3149 if (nAttrs > 0)
3150 {
3151 // make a writer that internally appends to the same
3152 // StringBuffer
3153 java.io.Writer writer =
3154 new ToStream.WritertoStringBuffer(sb);
3155
3156 processAttributes(writer, nAttrs);
3157 // Don't clear the attributes!
3158 // We only want to see what would be written out
3159 // at this point, we don't want to loose them.
3160 }
3161 sb.append('>'); // the potential > after the attributes.
3162 // convert the StringBuffer to a char array and
3163 // emit the trace event that these characters "might"
3164 // be written
3165 char ch[] = sb.toString().toCharArray();
3166 m_tracer.fireGenerateEvent(
3167 SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS,
3168 ch,
3169 0,
3170 ch.length);
3171 }
3172 catch (IOException ioe)
3173 {
3174 // ignore ?
3175 }
3176 catch (SAXException se)
3177 {
3178 // ignore ?
3179 }
3180 }
3181 }
3182
3183 /**
3184 * This inner class is used only to collect attribute values
3185 * written by the method writeAttrString() into a string buffer.
3186 * In this manner trace events, and the real writing of attributes will use
3187 * the same code.
3188 */
3189 private static class WritertoStringBuffer extends java.io.Writer
3190 {
3191 final private StringBuffer m_stringbuf;
3192 /**
3193 * @see java.io.Writer#write(char[], int, int)
3194 */
3195 WritertoStringBuffer(StringBuffer sb)
3196 {
3197 m_stringbuf = sb;
3198 }
3199
3200 public void write(char[] arg0, int arg1, int arg2) throws IOException
3201 {
3202 m_stringbuf.append(arg0, arg1, arg2);
3203 }
3204 /**
3205 * @see java.io.Writer#flush()
3206 */
3207 public void flush() throws IOException
3208 {
3209 }
3210 /**
3211 * @see java.io.Writer#close()
3212 */
3213 public void close() throws IOException
3214 {
3215 }
3216
3217 public void write(int i)
3218 {
3219 m_stringbuf.append((char) i);
3220 }
3221
3222 public void write(String s)
3223 {
3224 m_stringbuf.append(s);
3225 }
3226 }
3227
3228 /**
3229 * @see SerializationHandler#setTransformer(Transformer)
3230 */
3231 public void setTransformer(Transformer transformer) {
3232 super.setTransformer(transformer);
3233 if (m_tracer != null
3234 && !(m_writer instanceof SerializerTraceWriter) )
3235 setWriterInternal(new SerializerTraceWriter(m_writer, m_tracer), false);
3236
3237
3238 }
3239 /**
3240 * Try's to reset the super class and reset this class for
3241 * re-use, so that you don't need to create a new serializer
3242 * (mostly for performance reasons).
3243 *
3244 * @return true if the class was successfuly reset.
3245 */
3246 public boolean reset()
3247 {
3248 boolean wasReset = false;
3249 if (super.reset())
3250 {
3251 resetToStream();
3252 wasReset = true;
3253 }
3254 return wasReset;
3255 }
3256
3257 /**
3258 * Reset all of the fields owned by ToStream class
3259 *
3260 */
3261 private void resetToStream()
3262 {
3263 this.m_cdataStartCalled = false;
3264 /* The stream is being reset. It is one of
3265 * ToXMLStream, ToHTMLStream ... and this type can't be changed
3266 * so neither should m_charInfo which is associated with the
3267 * type of Stream. Just leave m_charInfo as-is for the next re-use.
3268 *
3269 */
3270 // this.m_charInfo = null; // don't set to null
3271 this.m_disableOutputEscapingStates.clear();
3272 // this.m_encodingInfo = null; // don't set to null
3273
3274 this.m_escaping = true;
3275 // Leave m_format alone for now - Brian M.
3276 // this.m_format = null;
3277 this.m_expandDTDEntities = true;
3278 this.m_inDoctype = false;
3279 this.m_ispreserve = false;
3280 this.m_isprevtext = false;
3281 this.m_isUTF8 = false; // ?? used anywhere ??
3282 this.m_lineSep = s_systemLineSep;
3283 this.m_lineSepLen = s_systemLineSep.length;
3284 this.m_lineSepUse = true;
3285 // this.m_outputStream = null; // Don't reset it may be re-used
3286 this.m_preserves.clear();
3287 this.m_shouldFlush = true;
3288 this.m_spaceBeforeClose = false;
3289 this.m_startNewLine = false;
3290 this.m_writer_set_by_user = false;
3291 }
3292
3293 /**
3294 * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
3295 * @param encoding the character encoding
3296 */
3297 public void setEncoding(String encoding)
3298 {
3299 setOutputProperty(OutputKeys.ENCODING,encoding);
3300 }
3301
3302 /**
3303 * Simple stack for boolean values.
3304 *
3305 * This class is a copy of the one in org.apache.xml.utils.
3306 * It exists to cut the serializers dependancy on that package.
3307 * A minor changes from that package are:
3308 * doesn't implement Clonable
3309 *
3310 * @xsl.usage internal
3311 */
3312 static final class BoolStack
3313 {
3314
3315 /** Array of boolean values */
3316 private boolean m_values[];
3317
3318 /** Array size allocated */
3319 private int m_allocatedSize;
3320
3321 /** Index into the array of booleans */
3322 private int m_index;
3323
3324 /**
3325 * Default constructor. Note that the default
3326 * block size is very small, for small lists.
3327 */
3328 public BoolStack()
3329 {
3330 this(32);
3331 }
3332
3333 /**
3334 * Construct a IntVector, using the given block size.
3335 *
3336 * @param size array size to allocate
3337 */
3338 public BoolStack(int size)
3339 {
3340
3341 m_allocatedSize = size;
3342 m_values = new boolean[size];
3343 m_index = -1;
3344 }
3345
3346 /**
3347 * Get the length of the list.
3348 *
3349 * @return Current length of the list
3350 */
3351 public final int size()
3352 {
3353 return m_index + 1;
3354 }
3355
3356 /**
3357 * Clears the stack.
3358 *
3359 */
3360 public final void clear()
3361 {
3362 m_index = -1;
3363 }
3364
3365 /**
3366 * Pushes an item onto the top of this stack.
3367 *
3368 *
3369 * @param val the boolean to be pushed onto this stack.
3370 * @return the <code>item</code> argument.
3371 */
3372 public final boolean push(boolean val)
3373 {
3374
3375 if (m_index == m_allocatedSize - 1)
3376 grow();
3377
3378 return (m_values[++m_index] = val);
3379 }
3380
3381 /**
3382 * Removes the object at the top of this stack and returns that
3383 * object as the value of this function.
3384 *
3385 * @return The object at the top of this stack.
3386 * @throws EmptyStackException if this stack is empty.
3387 */
3388 public final boolean pop()
3389 {
3390 return m_values[m_index--];
3391 }
3392
3393 /**
3394 * Removes the object at the top of this stack and returns the
3395 * next object at the top as the value of this function.
3396 *
3397 *
3398 * @return Next object to the top or false if none there
3399 */
3400 public final boolean popAndTop()
3401 {
3402
3403 m_index--;
3404
3405 return (m_index >= 0) ? m_values[m_index] : false;
3406 }
3407
3408 /**
3409 * Set the item at the top of this stack
3410 *
3411 *
3412 * @param b Object to set at the top of this stack
3413 */
3414 public final void setTop(boolean b)
3415 {
3416 m_values[m_index] = b;
3417 }
3418
3419 /**
3420 * Looks at the object at the top of this stack without removing it
3421 * from the stack.
3422 *
3423 * @return the object at the top of this stack.
3424 * @throws EmptyStackException if this stack is empty.
3425 */
3426 public final boolean peek()
3427 {
3428 return m_values[m_index];
3429 }
3430
3431 /**
3432 * Looks at the object at the top of this stack without removing it
3433 * from the stack. If the stack is empty, it returns false.
3434 *
3435 * @return the object at the top of this stack.
3436 */
3437 public final boolean peekOrFalse()
3438 {
3439 return (m_index > -1) ? m_values[m_index] : false;
3440 }
3441
3442 /**
3443 * Looks at the object at the top of this stack without removing it
3444 * from the stack. If the stack is empty, it returns true.
3445 *
3446 * @return the object at the top of this stack.
3447 */
3448 public final boolean peekOrTrue()
3449 {
3450 return (m_index > -1) ? m_values[m_index] : true;
3451 }
3452
3453 /**
3454 * Tests if this stack is empty.
3455 *
3456 * @return <code>true</code> if this stack is empty;
3457 * <code>false</code> otherwise.
3458 */
3459 public boolean isEmpty()
3460 {
3461 return (m_index == -1);
3462 }
3463
3464 /**
3465 * Grows the size of the stack
3466 *
3467 */
3468 private void grow()
3469 {
3470
3471 m_allocatedSize *= 2;
3472
3473 boolean newVector[] = new boolean[m_allocatedSize];
3474
3475 System.arraycopy(m_values, 0, newVector, 0, m_index + 1);
3476
3477 m_values = newVector;
3478 }
3479 }
3480
3481 // Implement DTDHandler
3482 /**
3483 * If this method is called, the serializer is used as a
3484 * DTDHandler, which changes behavior how the serializer
3485 * handles document entities.
3486 * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
3487 */
3488 public void notationDecl(String name, String pubID, String sysID) throws SAXException {
3489 // TODO Auto-generated method stub
3490 try {
3491 DTDprolog();
3492
3493 m_writer.write("<!NOTATION ");
3494 m_writer.write(name);
3495 if (pubID != null) {
3496 m_writer.write(" PUBLIC \"");
3497 m_writer.write(pubID);
3498
3499 }
3500 else {
3501 m_writer.write(" SYSTEM \"");
3502 m_writer.write(sysID);
3503 }
3504 m_writer.write("\" >");
3505 m_writer.write(m_lineSep, 0, m_lineSepLen);
3506 } catch (IOException e) {
3507 // TODO Auto-generated catch block
3508 e.printStackTrace();
3509 }
3510 }
3511
3512 /**
3513 * If this method is called, the serializer is used as a
3514 * DTDHandler, which changes behavior how the serializer
3515 * handles document entities.
3516 * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
3517 */
3518 public void unparsedEntityDecl(String name, String pubID, String sysID, String notationName) throws SAXException {
3519 // TODO Auto-generated method stub
3520 try {
3521 DTDprolog();
3522
3523 m_writer.write("<!ENTITY ");
3524 m_writer.write(name);
3525 if (pubID != null) {
3526 m_writer.write(" PUBLIC \"");
3527 m_writer.write(pubID);
3528
3529 }
3530 else {
3531 m_writer.write(" SYSTEM \"");
3532 m_writer.write(sysID);
3533 }
3534 m_writer.write("\" NDATA ");
3535 m_writer.write(notationName);
3536 m_writer.write(" >");
3537 m_writer.write(m_lineSep, 0, m_lineSepLen);
3538 } catch (IOException e) {
3539 // TODO Auto-generated catch block
3540 e.printStackTrace();
3541 }
3542 }
3543
3544 /**
3545 * A private helper method to output the
3546 * @throws SAXException
3547 * @throws IOException
3548 */
3549 private void DTDprolog() throws SAXException, IOException {
3550 final java.io.Writer writer = m_writer;
3551 if (m_needToOutputDocTypeDecl)
3552 {
3553 outputDocTypeDecl(m_elemContext.m_elementName, false);
3554 m_needToOutputDocTypeDecl = false;
3555 }
3556 if (m_inDoctype)
3557 {
3558 writer.write(" [");
3559 writer.write(m_lineSep, 0, m_lineSepLen);
3560 m_inDoctype = false;
3561 }
3562 }
3563
3564 /**
3565 * If set to false the serializer does not expand DTD entities,
3566 * but leaves them as is, the default value is true;
3567 */
3568 public void setDTDEntityExpansion(boolean expand) {
3569 m_expandDTDEntities = expand;
3570 }
3571
3572 /**
3573 * Sets the end of line characters to be used during serialization
3574 * @param eolChars A character array corresponding to the characters to be used.
3575 */
3576 public void setNewLine (char[] eolChars) {
3577 m_lineSep = eolChars;
3578 m_lineSepLen = eolChars.length;
3579 }
3580
3581 /**
3582 * Remembers the cdata sections specified in the cdata-section-elements by appending the given
3583 * cdata section elements to the list. This method can be called multiple times, but once an
3584 * element is put in the list of cdata section elements it can not be removed.
3585 * This method should be used by both Xalan and XSLTC.
3586 *
3587 * @param URI_and_localNames a whitespace separated list of element names, each element
3588 * is a URI in curly braces (optional) and a local name. An example of such a parameter is:
3589 * "{http://company.com}price {myURI2}book chapter"
3590 */
3591 public void addCdataSectionElements(String URI_and_localNames)
3592 {
3593 if (URI_and_localNames != null)
3594 initCdataElems(URI_and_localNames);
3595 if (m_StringOfCDATASections == null)
3596 m_StringOfCDATASections = URI_and_localNames;
3597 else
3598 m_StringOfCDATASections += (" " + URI_and_localNames);
3599 }
3600 }