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