1 /*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5 /*
6 * Copyright 2001-2004 The Apache Software Foundation.
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 /*
21 * $Id: ToXMLStream.java,v 1.2.4.2 2005/09/15 12:01:25 suresh_emailid Exp $
22 */
23 package com.sun.org.apache.xml.internal.serializer;
24
25 import java.io.IOException;
26
27 import javax.xml.transform.ErrorListener;
28 import javax.xml.transform.Result;
29 import javax.xml.transform.Transformer;
30 import javax.xml.transform.TransformerException;
31
32 import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
33 import com.sun.org.apache.xml.internal.serializer.utils.Utils;
34 import org.xml.sax.SAXException;
35
36 /**
37 * This class converts SAX or SAX-like calls to a
38 * serialized xml document. The xsl:output method is "xml".
39 *
40 * This class is used explicitly in code generated by XSLTC,
41 * so it is "public", but it should
42 * be viewed as internal or package private, this is not an API.
43 *
44 * @xsl.usage internal
45 */
46 public final class ToXMLStream extends ToStream
47 {
48
49 /**
50 * remembers if we need to write out "]]>" to close the CDATA
51 */
52 boolean m_cdataTagOpen = false;
53
54
55 /**
56 * Map that tells which XML characters should have special treatment, and it
57 * provides character to entity name lookup.
58 */
59 private static CharInfo m_xmlcharInfo =
60 // new CharInfo(CharInfo.XML_ENTITIES_RESOURCE);
61 CharInfo.getCharInfo(CharInfo.XML_ENTITIES_RESOURCE, Method.XML);
62
63 /**
64 * Default constructor.
65 */
66 public ToXMLStream()
67 {
68 m_charInfo = m_xmlcharInfo;
69
70 initCDATA();
71 // initialize namespaces
72 m_prefixMap = new NamespaceMappings();
73
74 }
75
76 /**
77 * Copy properties from another SerializerToXML.
78 *
79 * @param xmlListener non-null reference to a SerializerToXML object.
80 */
81 public void CopyFrom(ToXMLStream xmlListener)
82 {
83
84 m_writer = xmlListener.m_writer;
85
86
87 // m_outputStream = xmlListener.m_outputStream;
88 String encoding = xmlListener.getEncoding();
89 setEncoding(encoding);
90
91 setOmitXMLDeclaration(xmlListener.getOmitXMLDeclaration());
92
93 m_ispreserve = xmlListener.m_ispreserve;
94 m_preserves = xmlListener.m_preserves;
95 m_isprevtext = xmlListener.m_isprevtext;
96 m_doIndent = xmlListener.m_doIndent;
97 setIndentAmount(xmlListener.getIndentAmount());
98 m_startNewLine = xmlListener.m_startNewLine;
99 m_needToOutputDocTypeDecl = xmlListener.m_needToOutputDocTypeDecl;
100 setDoctypeSystem(xmlListener.getDoctypeSystem());
101 setDoctypePublic(xmlListener.getDoctypePublic());
102 setStandalone(xmlListener.getStandalone());
103 setMediaType(xmlListener.getMediaType());
104 m_maxCharacter = xmlListener.m_maxCharacter;
105 m_encodingInfo = xmlListener.m_encodingInfo;
106 m_spaceBeforeClose = xmlListener.m_spaceBeforeClose;
107 m_cdataStartCalled = xmlListener.m_cdataStartCalled;
108
109 }
110
111 /**
112 * Receive notification of the beginning of a document.
113 *
114 * @throws org.xml.sax.SAXException Any SAX exception, possibly
115 * wrapping another exception.
116 *
117 * @throws org.xml.sax.SAXException
118 */
119 public void startDocumentInternal() throws org.xml.sax.SAXException
120 {
121
122 if (m_needToCallStartDocument)
123 {
124 super.startDocumentInternal();
125 m_needToCallStartDocument = false;
126
127 if (m_inEntityRef)
128 return;
129
130 m_needToOutputDocTypeDecl = true;
131 m_startNewLine = false;
132 /* The call to getXMLVersion() might emit an error message
133 * and we should emit this message regardless of if we are
134 * writing out an XML header or not.
135 */
136 if (getOmitXMLDeclaration() == false)
137 {
138 String encoding = Encodings.getMimeEncoding(getEncoding());
139 String version = getVersion();
140 if (version == null)
141 version = "1.0";
142 String standalone;
143
144 if (m_standaloneWasSpecified)
145 {
146 standalone = " standalone=\"" + getStandalone() + "\"";
147 }
148 else
149 {
150 standalone = "";
151 }
152
153 try
154 {
155 final java.io.Writer writer = m_writer;
156 writer.write("<?xml version=\"");
157 writer.write(version);
158 writer.write("\" encoding=\"");
159 writer.write(encoding);
160 writer.write('\"');
161 writer.write(standalone);
162 writer.write("?>");
163 if (m_doIndent)
164 writer.write(m_lineSep, 0, m_lineSepLen);
165 }
166 catch(IOException e)
167 {
168 throw new SAXException(e);
169 }
170
171 }
172 }
173 }
174
175 /**
176 * Receive notification of the end of a document.
177 *
178 * @throws org.xml.sax.SAXException Any SAX exception, possibly
179 * wrapping another exception.
180 *
181 * @throws org.xml.sax.SAXException
182 */
183 public void endDocument() throws org.xml.sax.SAXException
184 {
185 flushPending();
186 if (m_doIndent && !m_isprevtext)
187 {
188 try
189 {
190 outputLineSep();
191 }
192 catch(IOException e)
193 {
194 throw new SAXException(e);
195 }
196 }
197
198 flushWriter();
199
200 if (m_tracer != null)
201 super.fireEndDoc();
202 }
203
204 /**
205 * Starts a whitespace preserving section. All characters printed
206 * within a preserving section are printed without indentation and
207 * without consolidating multiple spaces. This is equivalent to
208 * the <tt>xml:space="preserve"</tt> attribute. Only XML
209 * and HTML serializers need to support this method.
210 * <p>
211 * The contents of the whitespace preserving section will be delivered
212 * through the regular <tt>characters</tt> event.
213 *
214 * @throws org.xml.sax.SAXException
215 */
216 public void startPreserving() throws org.xml.sax.SAXException
217 {
218
219 // Not sure this is really what we want. -sb
220 m_preserves.push(true);
221
222 m_ispreserve = true;
223 }
224
225 /**
226 * Ends a whitespace preserving section.
227 *
228 * @see #startPreserving
229 *
230 * @throws org.xml.sax.SAXException
231 */
232 public void endPreserving() throws org.xml.sax.SAXException
233 {
234
235 // Not sure this is really what we want. -sb
236 m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
237 }
238
239 /**
240 * Receive notification of a processing instruction.
241 *
242 * @param target The processing instruction target.
243 * @param data The processing instruction data, or null if
244 * none was supplied.
245 * @throws org.xml.sax.SAXException Any SAX exception, possibly
246 * wrapping another exception.
247 *
248 * @throws org.xml.sax.SAXException
249 */
250 public void processingInstruction(String target, String data)
251 throws org.xml.sax.SAXException
252 {
253 if (m_inEntityRef)
254 return;
255
256 flushPending();
257
258 if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING))
259 {
260 startNonEscaping();
261 }
262 else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING))
263 {
264 endNonEscaping();
265 }
266 else
267 {
268 try
269 {
270 if (m_elemContext.m_startTagOpen)
271 {
272 closeStartTag();
273 m_elemContext.m_startTagOpen = false;
274 }
275 else if (m_needToCallStartDocument)
276 startDocumentInternal();
277
278 if (shouldIndent())
279 indent();
280
281 final java.io.Writer writer = m_writer;
282 writer.write("<?");
283 writer.write(target);
284
285 if (data.length() > 0
286 && !Character.isSpaceChar(data.charAt(0)))
287 writer.write(' ');
288
289 int indexOfQLT = data.indexOf("?>");
290
291 if (indexOfQLT >= 0)
292 {
293
294 // See XSLT spec on error recovery of "?>" in PIs.
295 if (indexOfQLT > 0)
296 {
297 writer.write(data.substring(0, indexOfQLT));
298 }
299
300 writer.write("? >"); // add space between.
301
302 if ((indexOfQLT + 2) < data.length())
303 {
304 writer.write(data.substring(indexOfQLT + 2));
305 }
306 }
307 else
308 {
309 writer.write(data);
310 }
311
312 writer.write('?');
313 writer.write('>');
314
315 // Always output a newline char if not inside of an
316 // element. The whitespace is not significant in that
317 // case.
318 if (m_elemContext.m_currentElemDepth <= 0)
319 writer.write(m_lineSep, 0, m_lineSepLen);
320
321 m_startNewLine = true;
322 }
323 catch(IOException e)
324 {
325 throw new SAXException(e);
326 }
327 }
328
329 if (m_tracer != null)
330 super.fireEscapingEvent(target, data);
331 }
332
333 /**
334 * Receive notivication of a entityReference.
335 *
336 * @param name The name of the entity.
337 *
338 * @throws org.xml.sax.SAXException
339 */
340 public void entityReference(String name) throws org.xml.sax.SAXException
341 {
342 if (m_elemContext.m_startTagOpen)
343 {
344 closeStartTag();
345 m_elemContext.m_startTagOpen = false;
346 }
347
348 try
349 {
350 if (shouldIndent())
351 indent();
352
353 final java.io.Writer writer = m_writer;
354 writer.write('&');
355 writer.write(name);
356 writer.write(';');
357 }
358 catch(IOException e)
359 {
360 throw new SAXException(e);
361 }
362
363 if (m_tracer != null)
364 super.fireEntityReference(name);
365 }
366
367 /**
368 * This method is used to add an attribute to the currently open element.
369 * The caller has guaranted that this attribute is unique, which means that it
370 * not been seen before and will not be seen again.
371 *
372 * @param name the qualified name of the attribute
373 * @param value the value of the attribute which can contain only
374 * ASCII printable characters characters in the range 32 to 127 inclusive.
375 * @param flags the bit values of this integer give optimization information.
376 */
377 public void addUniqueAttribute(String name, String value, int flags)
378 throws SAXException
379 {
380 if (m_elemContext.m_startTagOpen)
381 {
382
383 try
384 {
385 final String patchedName = patchName(name);
386 final java.io.Writer writer = m_writer;
387 if ((flags & NO_BAD_CHARS) > 0 && m_xmlcharInfo.onlyQuotAmpLtGt)
388 {
389 // "flags" has indicated that the characters
390 // '>' '<' '&' and '"' are not in the value and
391 // m_htmlcharInfo has recorded that there are no other
392 // entities in the range 32 to 127 so we write out the
393 // value directly
394
395 writer.write(' ');
396 writer.write(patchedName);
397 writer.write("=\"");
398 writer.write(value);
399 writer.write('"');
400 }
401 else
402 {
403 writer.write(' ');
404 writer.write(patchedName);
405 writer.write("=\"");
406 writeAttrString(writer, value, this.getEncoding());
407 writer.write('"');
408 }
409 } catch (IOException e) {
410 throw new SAXException(e);
411 }
412 }
413 }
414
415 /**
416 * Add an attribute to the current element.
417 * @param uri the URI associated with the element name
418 * @param localName local part of the attribute name
419 * @param rawName prefix:localName
420 * @param type
421 * @param value the value of the attribute
422 * @param xslAttribute true if this attribute is from an xsl:attribute,
423 * false if declared within the elements opening tag.
424 * @throws SAXException
425 */
426 public void addAttribute(
427 String uri,
428 String localName,
429 String rawName,
430 String type,
431 String value,
432 boolean xslAttribute)
433 throws SAXException
434 {
435 if (m_elemContext.m_startTagOpen)
436 {
437 boolean was_added = addAttributeAlways(uri, localName, rawName, type, value, xslAttribute);
438
439
440 /*
441 * We don't run this block of code if:
442 * 1. The attribute value was only replaced (was_added is false).
443 * 2. The attribute is from an xsl:attribute element (that is handled
444 * in the addAttributeAlways() call just above.
445 * 3. The name starts with "xmlns", i.e. it is a namespace declaration.
446 */
447 if (was_added && !xslAttribute && !rawName.startsWith("xmlns"))
448 {
449 String prefixUsed =
450 ensureAttributesNamespaceIsDeclared(
451 uri,
452 localName,
453 rawName);
454 if (prefixUsed != null
455 && rawName != null
456 && !rawName.startsWith(prefixUsed))
457 {
458 // use a different raw name, with the prefix used in the
459 // generated namespace declaration
460 rawName = prefixUsed + ":" + localName;
461
462 }
463 }
464 addAttributeAlways(uri, localName, rawName, type, value, xslAttribute);
465 }
466 else
467 {
468 /*
469 * The startTag is closed, yet we are adding an attribute?
470 *
471 * Section: 7.1.3 Creating Attributes Adding an attribute to an
472 * element after a PI (for example) has been added to it is an
473 * error. The attributes can be ignored. The spec doesn't explicitly
474 * say this is disallowed, as it does for child elements, but it
475 * makes sense to have the same treatment.
476 *
477 * We choose to ignore the attribute which is added too late.
478 */
479 // Generate a warning of the ignored attributes
480
481 // Create the warning message
482 String msg = Utils.messages.createMessage(
483 MsgKey.ER_ILLEGAL_ATTRIBUTE_POSITION,new Object[]{ localName });
484
485 try {
486 // Prepare to issue the warning message
487 Transformer tran = super.getTransformer();
488 ErrorListener errHandler = tran.getErrorListener();
489
490
491 // Issue the warning message
492 if (null != errHandler && m_sourceLocator != null)
493 errHandler.warning(new TransformerException(msg, m_sourceLocator));
494 else
495 System.out.println(msg);
496 }
497 catch (Exception e){}
498 }
499 }
500
501 /**
502 * @see ExtendedContentHandler#endElement(String)
503 */
504 public void endElement(String elemName) throws SAXException
505 {
506 endElement(null, null, elemName);
507 }
508
509 /**
510 * This method is used to notify the serializer of a namespace mapping (or node)
511 * that applies to the current element whose startElement() call has already been seen.
512 * The official SAX startPrefixMapping(prefix,uri) is to define a mapping for a child
513 * element that is soon to be seen with a startElement() call. The official SAX call
514 * does not apply to the current element, hence the reason for this method.
515 */
516 public void namespaceAfterStartElement(
517 final String prefix,
518 final String uri)
519 throws SAXException
520 {
521
522 // hack for XSLTC with finding URI for default namespace
523 if (m_elemContext.m_elementURI == null)
524 {
525 String prefix1 = getPrefixPart(m_elemContext.m_elementName);
526 if (prefix1 == null && EMPTYSTRING.equals(prefix))
527 {
528 // the elements URI is not known yet, and it
529 // doesn't have a prefix, and we are currently
530 // setting the uri for prefix "", so we have
531 // the uri for the element... lets remember it
532 m_elemContext.m_elementURI = uri;
533 }
534 }
535 startPrefixMapping(prefix,uri,false);
536 return;
537
538 }
539
540 /**
541 * From XSLTC
542 * Declare a prefix to point to a namespace URI. Inform SAX handler
543 * if this is a new prefix mapping.
544 */
545 protected boolean pushNamespace(String prefix, String uri)
546 {
547 try
548 {
549 if (m_prefixMap.pushNamespace(
550 prefix, uri, m_elemContext.m_currentElemDepth))
551 {
552 startPrefixMapping(prefix, uri);
553 return true;
554 }
555 }
556 catch (SAXException e)
557 {
558 // falls through
559 }
560 return false;
561 }
562 /**
563 * Try's to reset the super class and reset this class for
564 * re-use, so that you don't need to create a new serializer
565 * (mostly for performance reasons).
566 *
567 * @return true if the class was successfuly reset.
568 */
569 public boolean reset()
570 {
571 boolean wasReset = false;
572 if (super.reset())
573 {
574 resetToXMLStream();
575 wasReset = true;
576 }
577 return wasReset;
578 }
579
580 /**
581 * Reset all of the fields owned by ToStream class
582 *
583 */
584 private void resetToXMLStream()
585 {
586 this.m_cdataTagOpen = false;
587
588 }
589
590 /**
591 * This method checks for the XML version of output document.
592 * If XML version of output document is not specified, then output
593 * document is of version XML 1.0.
594 * If XML version of output doucment is specified, but it is not either
595 * XML 1.0 or XML 1.1, a warning message is generated, the XML Version of
596 * output document is set to XML 1.0 and processing continues.
597 * @return string (XML version)
598 */
599 private String getXMLVersion()
600 {
601 String xmlVersion = getVersion();
602 if(xmlVersion == null || xmlVersion.equals(XMLVERSION10))
603 {
604 xmlVersion = XMLVERSION10;
605 }
606 else if(xmlVersion.equals(XMLVERSION11))
607 {
608 xmlVersion = XMLVERSION11;
609 }
610 else
611 {
612 String msg = Utils.messages.createMessage(
613 MsgKey.ER_XML_VERSION_NOT_SUPPORTED,new Object[]{ xmlVersion });
614 try
615 {
616 // Prepare to issue the warning message
617 Transformer tran = super.getTransformer();
618 ErrorListener errHandler = tran.getErrorListener();
619 // Issue the warning message
620 if (null != errHandler && m_sourceLocator != null)
621 errHandler.warning(new TransformerException(msg, m_sourceLocator));
622 else
623 System.out.println(msg);
624 }
625 catch (Exception e){}
626 xmlVersion = XMLVERSION10;
627 }
628 return xmlVersion;
629 }
630 }