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: TransformerIdentityImpl.java 575747 2007-09-14 16:28:37Z kcormier $
020 */
021 package org.apache.xalan.transformer;
022
023 import java.io.IOException;
024 import java.util.Hashtable;
025 import java.util.Properties;
026
027 import javax.xml.XMLConstants;
028 import javax.xml.parsers.DocumentBuilder;
029 import javax.xml.parsers.DocumentBuilderFactory;
030 import javax.xml.parsers.ParserConfigurationException;
031 import javax.xml.transform.ErrorListener;
032 import javax.xml.transform.OutputKeys;
033 import javax.xml.transform.Result;
034 import javax.xml.transform.Source;
035 import javax.xml.transform.Transformer;
036 import javax.xml.transform.TransformerException;
037 import javax.xml.transform.URIResolver;
038 import javax.xml.transform.dom.DOMResult;
039 import javax.xml.transform.dom.DOMSource;
040 import javax.xml.transform.sax.SAXResult;
041 import javax.xml.transform.sax.SAXSource;
042 import javax.xml.transform.sax.TransformerHandler;
043 import javax.xml.transform.stream.StreamSource;
044 import javax.xml.transform.stream.StreamResult;
045
046 import org.apache.xalan.res.XSLMessages;
047 import org.apache.xalan.res.XSLTErrorResources;
048 import org.apache.xalan.templates.OutputProperties;
049 import org.apache.xml.serializer.Serializer;
050 import org.apache.xml.serializer.SerializerFactory;
051 import org.apache.xml.serializer.Method;
052 import org.apache.xml.utils.DOMBuilder;
053 import org.apache.xml.utils.XMLReaderManager;
054
055 import org.w3c.dom.Document;
056 import org.w3c.dom.DocumentFragment;
057 import org.w3c.dom.Node;
058
059 import org.xml.sax.Attributes;
060 import org.xml.sax.ContentHandler;
061 import org.xml.sax.DTDHandler;
062 import org.xml.sax.InputSource;
063 import org.xml.sax.Locator;
064 import org.xml.sax.SAXException;
065 import org.xml.sax.XMLReader;
066 import org.xml.sax.ext.DeclHandler;
067 import org.xml.sax.ext.LexicalHandler;
068
069 /**
070 * This class implements an identity transformer for
071 * {@link javax.xml.transform.sax.SAXTransformerFactory#newTransformerHandler()}
072 * and {@link javax.xml.transform.TransformerFactory#newTransformer()}. It
073 * simply feeds SAX events directly to a serializer ContentHandler, if the
074 * result is a stream. If the result is a DOM, it will send the events to
075 * {@link org.apache.xml.utils.DOMBuilder}. If the result is another
076 * content handler, it will simply pass the events on.
077 */
078 public class TransformerIdentityImpl extends Transformer
079 implements TransformerHandler, DeclHandler
080 {
081
082 /**
083 * Constructor TransformerIdentityImpl creates an identity transform.
084 *
085 */
086 public TransformerIdentityImpl(boolean isSecureProcessing)
087 {
088 m_outputFormat = new OutputProperties(Method.XML);
089 m_isSecureProcessing = isSecureProcessing;
090 }
091
092 /**
093 * Constructor TransformerIdentityImpl creates an identity transform.
094 *
095 */
096 public TransformerIdentityImpl()
097 {
098 this(false);
099 }
100
101 /**
102 * Enables the user of the TransformerHandler to set the
103 * to set the Result for the transformation.
104 *
105 * @param result A Result instance, should not be null.
106 *
107 * @throws IllegalArgumentException if result is invalid for some reason.
108 */
109 public void setResult(Result result) throws IllegalArgumentException
110 {
111 if(null == result)
112 throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_RESULT_NULL, null)); //"Result should not be null");
113 m_result = result;
114 }
115
116 /**
117 * Set the base ID (URI or system ID) from where relative
118 * URLs will be resolved.
119 * @param systemID Base URI for the source tree.
120 */
121 public void setSystemId(String systemID)
122 {
123 m_systemID = systemID;
124 }
125
126 /**
127 * Get the base ID (URI or system ID) from where relative
128 * URLs will be resolved.
129 * @return The systemID that was set with {@link #setSystemId}.
130 */
131 public String getSystemId()
132 {
133 return m_systemID;
134 }
135
136 /**
137 * Get the Transformer associated with this handler, which
138 * is needed in order to set parameters and output properties.
139 *
140 * @return non-null reference to the transformer.
141 */
142 public Transformer getTransformer()
143 {
144 return this;
145 }
146
147 /**
148 * Reset the status of the transformer.
149 */
150 public void reset()
151 {
152 m_flushedStartDoc = false;
153 m_foundFirstElement = false;
154 m_outputStream = null;
155 clearParameters();
156 m_result = null;
157 m_resultContentHandler = null;
158 m_resultDeclHandler = null;
159 m_resultDTDHandler = null;
160 m_resultLexicalHandler = null;
161 m_serializer = null;
162 m_systemID = null;
163 m_URIResolver = null;
164 m_outputFormat = new OutputProperties(Method.XML);
165 }
166
167 /**
168 * Create a result ContentHandler from a Result object, based
169 * on the current OutputProperties.
170 *
171 * @param outputTarget Where the transform result should go,
172 * should not be null.
173 *
174 * @return A valid ContentHandler that will create the
175 * result tree when it is fed SAX events.
176 *
177 * @throws TransformerException
178 */
179 private void createResultContentHandler(Result outputTarget)
180 throws TransformerException
181 {
182
183 if (outputTarget instanceof SAXResult)
184 {
185 SAXResult saxResult = (SAXResult) outputTarget;
186
187 m_resultContentHandler = saxResult.getHandler();
188 m_resultLexicalHandler = saxResult.getLexicalHandler();
189
190 if (m_resultContentHandler instanceof Serializer)
191 {
192
193 // Dubious but needed, I think.
194 m_serializer = (Serializer) m_resultContentHandler;
195 }
196 }
197 else if (outputTarget instanceof DOMResult)
198 {
199 DOMResult domResult = (DOMResult) outputTarget;
200 Node outputNode = domResult.getNode();
201 Node nextSibling = domResult.getNextSibling();
202 Document doc;
203 short type;
204
205 if (null != outputNode)
206 {
207 type = outputNode.getNodeType();
208 doc = (Node.DOCUMENT_NODE == type)
209 ? (Document) outputNode : outputNode.getOwnerDocument();
210 }
211 else
212 {
213 try
214 {
215 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
216
217 dbf.setNamespaceAware(true);
218
219 if (m_isSecureProcessing)
220 {
221 try
222 {
223 dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
224 }
225 catch (ParserConfigurationException pce) {}
226 }
227
228 DocumentBuilder db = dbf.newDocumentBuilder();
229
230 doc = db.newDocument();
231 }
232 catch (ParserConfigurationException pce)
233 {
234 throw new TransformerException(pce);
235 }
236
237 outputNode = doc;
238 type = outputNode.getNodeType();
239
240 ((DOMResult) outputTarget).setNode(outputNode);
241 }
242
243 DOMBuilder domBuilder =
244 (Node.DOCUMENT_FRAGMENT_NODE == type)
245 ? new DOMBuilder(doc, (DocumentFragment) outputNode)
246 : new DOMBuilder(doc, outputNode);
247
248 if (nextSibling != null)
249 domBuilder.setNextSibling(nextSibling);
250
251 m_resultContentHandler = domBuilder;
252 m_resultLexicalHandler = domBuilder;
253 }
254 else if (outputTarget instanceof StreamResult)
255 {
256 StreamResult sresult = (StreamResult) outputTarget;
257
258 try
259 {
260 Serializer serializer =
261 SerializerFactory.getSerializer(m_outputFormat.getProperties());
262
263 m_serializer = serializer;
264
265 if (null != sresult.getWriter())
266 serializer.setWriter(sresult.getWriter());
267 else if (null != sresult.getOutputStream())
268 serializer.setOutputStream(sresult.getOutputStream());
269 else if (null != sresult.getSystemId())
270 {
271 String fileURL = sresult.getSystemId();
272
273 if (fileURL.startsWith("file:///")) {
274 if (fileURL.substring(8).indexOf(":") >0) {
275 fileURL = fileURL.substring(8);
276 } else {
277 fileURL = fileURL.substring(7);
278 }
279 } else if (fileURL.startsWith("file:/")) {
280 if (fileURL.substring(6).indexOf(":") >0) {
281 fileURL = fileURL.substring(6);
282 } else {
283 fileURL = fileURL.substring(5);
284 }
285 }
286
287 m_outputStream = new java.io.FileOutputStream(fileURL);
288 serializer.setOutputStream(m_outputStream);
289 }
290 else
291 throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_NO_OUTPUT_SPECIFIED, null)); //"No output specified!");
292
293 m_resultContentHandler = serializer.asContentHandler();
294 }
295 catch (IOException ioe)
296 {
297 throw new TransformerException(ioe);
298 }
299 }
300 else
301 {
302 throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_CANNOT_TRANSFORM_TO_RESULT_TYPE, new Object[]{outputTarget.getClass().getName()})); //"Can't transform to a Result of type "
303 // + outputTarget.getClass().getName()
304 // + "!");
305 }
306
307 if (m_resultContentHandler instanceof DTDHandler)
308 m_resultDTDHandler = (DTDHandler) m_resultContentHandler;
309
310 if (m_resultContentHandler instanceof DeclHandler)
311 m_resultDeclHandler = (DeclHandler) m_resultContentHandler;
312
313 if (m_resultContentHandler instanceof LexicalHandler)
314 m_resultLexicalHandler = (LexicalHandler) m_resultContentHandler;
315 }
316
317 /**
318 * Process the source tree to the output result.
319 * @param source The input for the source tree.
320 *
321 * @param outputTarget The output target.
322 *
323 * @throws TransformerException If an unrecoverable error occurs
324 * during the course of the transformation.
325 */
326 public void transform(Source source, Result outputTarget)
327 throws TransformerException
328 {
329
330 createResultContentHandler(outputTarget);
331
332 /*
333 * According to JAXP1.2, new SAXSource()/StreamSource()
334 * should create an empty input tree, with a default root node.
335 * new DOMSource()creates an empty document using DocumentBuilder.
336 * newDocument(); Use DocumentBuilder.newDocument() for all 3 situations,
337 * since there is no clear spec. how to create an empty tree when
338 * both SAXSource() and StreamSource() are used.
339 */
340 if ((source instanceof StreamSource && source.getSystemId()==null &&
341 ((StreamSource)source).getInputStream()==null &&
342 ((StreamSource)source).getReader()==null)||
343 (source instanceof SAXSource &&
344 ((SAXSource)source).getInputSource()==null &&
345 ((SAXSource)source).getXMLReader()==null )||
346 (source instanceof DOMSource && ((DOMSource)source).getNode()==null)){
347 try {
348 DocumentBuilderFactory builderF = DocumentBuilderFactory.newInstance();
349 DocumentBuilder builder = builderF.newDocumentBuilder();
350 String systemID = source.getSystemId();
351 source = new DOMSource(builder.newDocument());
352
353 // Copy system ID from original, empty Source to new Source
354 if (systemID != null) {
355 source.setSystemId(systemID);
356 }
357 } catch (ParserConfigurationException e){
358 throw new TransformerException(e.getMessage());
359 }
360 }
361
362 try
363 {
364 if (source instanceof DOMSource)
365 {
366 DOMSource dsource = (DOMSource) source;
367
368 m_systemID = dsource.getSystemId();
369
370 Node dNode = dsource.getNode();
371
372 if (null != dNode)
373 {
374 try
375 {
376 if(dNode.getNodeType() == Node.ATTRIBUTE_NODE)
377 this.startDocument();
378 try
379 {
380 if(dNode.getNodeType() == Node.ATTRIBUTE_NODE)
381 {
382 String data = dNode.getNodeValue();
383 char[] chars = data.toCharArray();
384 characters(chars, 0, chars.length);
385 }
386 else
387 {
388 org.apache.xml.serializer.TreeWalker walker;
389 walker = new org.apache.xml.serializer.TreeWalker(this, m_systemID);
390 walker.traverse(dNode);
391 }
392 }
393 finally
394 {
395 if(dNode.getNodeType() == Node.ATTRIBUTE_NODE)
396 this.endDocument();
397 }
398 }
399 catch (SAXException se)
400 {
401 throw new TransformerException(se);
402 }
403
404 return;
405 }
406 else
407 {
408 String messageStr = XSLMessages.createMessage(
409 XSLTErrorResources.ER_ILLEGAL_DOMSOURCE_INPUT, null);
410
411 throw new IllegalArgumentException(messageStr);
412 }
413 }
414
415 InputSource xmlSource = SAXSource.sourceToInputSource(source);
416
417 if (null == xmlSource)
418 {
419 throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_CANNOT_TRANSFORM_SOURCE_TYPE, new Object[]{source.getClass().getName()})); //"Can't transform a Source of type "
420 //+ source.getClass().getName() + "!");
421 }
422
423 if (null != xmlSource.getSystemId())
424 m_systemID = xmlSource.getSystemId();
425
426 XMLReader reader = null;
427 boolean managedReader = false;
428
429 try
430 {
431 if (source instanceof SAXSource) {
432 reader = ((SAXSource) source).getXMLReader();
433 }
434
435 if (null == reader) {
436 try {
437 reader = XMLReaderManager.getInstance().getXMLReader();
438 managedReader = true;
439 } catch (SAXException se) {
440 throw new TransformerException(se);
441 }
442 } else {
443 try {
444 reader.setFeature("http://xml.org/sax/features/namespace-prefixes",
445 true);
446 } catch (org.xml.sax.SAXException se) {
447 // We don't care.
448 }
449 }
450
451 // Get the input content handler, which will handle the
452 // parse events and create the source tree.
453 ContentHandler inputHandler = this;
454
455 reader.setContentHandler(inputHandler);
456
457 if (inputHandler instanceof org.xml.sax.DTDHandler)
458 reader.setDTDHandler((org.xml.sax.DTDHandler) inputHandler);
459
460 try
461 {
462 if (inputHandler instanceof org.xml.sax.ext.LexicalHandler)
463 reader.setProperty("http://xml.org/sax/properties/lexical-handler",
464 inputHandler);
465
466 if (inputHandler instanceof org.xml.sax.ext.DeclHandler)
467 reader.setProperty(
468 "http://xml.org/sax/properties/declaration-handler",
469 inputHandler);
470 }
471 catch (org.xml.sax.SAXException se){}
472
473 try
474 {
475 if (inputHandler instanceof org.xml.sax.ext.LexicalHandler)
476 reader.setProperty("http://xml.org/sax/handlers/LexicalHandler",
477 inputHandler);
478
479 if (inputHandler instanceof org.xml.sax.ext.DeclHandler)
480 reader.setProperty("http://xml.org/sax/handlers/DeclHandler",
481 inputHandler);
482 }
483 catch (org.xml.sax.SAXNotRecognizedException snre){}
484
485 reader.parse(xmlSource);
486 }
487 catch (org.apache.xml.utils.WrappedRuntimeException wre)
488 {
489 Throwable throwable = wre.getException();
490
491 while (throwable
492 instanceof org.apache.xml.utils.WrappedRuntimeException)
493 {
494 throwable =
495 ((org.apache.xml.utils.WrappedRuntimeException) throwable).getException();
496 }
497
498 throw new TransformerException(wre.getException());
499 }
500 catch (org.xml.sax.SAXException se)
501 {
502 throw new TransformerException(se);
503 }
504 catch (IOException ioe)
505 {
506 throw new TransformerException(ioe);
507 } finally {
508 if (managedReader) {
509 XMLReaderManager.getInstance().releaseXMLReader(reader);
510 }
511 }
512 }
513 finally
514 {
515 if(null != m_outputStream)
516 {
517 try
518 {
519 m_outputStream.close();
520 }
521 catch(IOException ioe){}
522 m_outputStream = null;
523 }
524 }
525 }
526
527 /**
528 * Add a parameter for the transformation.
529 *
530 * <p>Pass a qualified name as a two-part string, the namespace URI
531 * enclosed in curly braces ({}), followed by the local name. If the
532 * name has a null URL, the String only contain the local name. An
533 * application can safely check for a non-null URI by testing to see if the first
534 * character of the name is a '{' character.</p>
535 * <p>For example, if a URI and local name were obtained from an element
536 * defined with <xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/>,
537 * then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo". Note that
538 * no prefix is used.</p>
539 *
540 * @param name The name of the parameter, which may begin with a namespace URI
541 * in curly braces ({}).
542 * @param value The value object. This can be any valid Java object. It is
543 * up to the processor to provide the proper object coersion or to simply
544 * pass the object on for use in an extension.
545 */
546 public void setParameter(String name, Object value)
547 {
548 if (value == null) {
549 throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_SET_PARAM_VALUE, new Object[]{name}));
550 }
551
552 if (null == m_params)
553 {
554 m_params = new Hashtable();
555 }
556
557 m_params.put(name, value);
558 }
559
560 /**
561 * Get a parameter that was explicitly set with setParameter
562 * or setParameters.
563 *
564 * <p>This method does not return a default parameter value, which
565 * cannot be determined until the node context is evaluated during
566 * the transformation process.
567 *
568 *
569 * @param name Name of the parameter.
570 * @return A parameter that has been set with setParameter.
571 */
572 public Object getParameter(String name)
573 {
574
575 if (null == m_params)
576 return null;
577
578 return m_params.get(name);
579 }
580
581 /**
582 * Clear all parameters set with setParameter.
583 */
584 public void clearParameters()
585 {
586
587 if (null == m_params)
588 return;
589
590 m_params.clear();
591 }
592
593 /**
594 * Set an object that will be used to resolve URIs used in
595 * document().
596 *
597 * <p>If the resolver argument is null, the URIResolver value will
598 * be cleared, and the default behavior will be used.</p>
599 *
600 * @param resolver An object that implements the URIResolver interface,
601 * or null.
602 */
603 public void setURIResolver(URIResolver resolver)
604 {
605 m_URIResolver = resolver;
606 }
607
608 /**
609 * Get an object that will be used to resolve URIs used in
610 * document(), etc.
611 *
612 * @return An object that implements the URIResolver interface,
613 * or null.
614 */
615 public URIResolver getURIResolver()
616 {
617 return m_URIResolver;
618 }
619
620 /**
621 * Set the output properties for the transformation. These
622 * properties will override properties set in the Templates
623 * with xsl:output.
624 *
625 * <p>If argument to this function is null, any properties
626 * previously set are removed, and the value will revert to the value
627 * defined in the templates object.</p>
628 *
629 * <p>Pass a qualified property key name as a two-part string, the namespace URI
630 * enclosed in curly braces ({}), followed by the local name. If the
631 * name has a null URL, the String only contain the local name. An
632 * application can safely check for a non-null URI by testing to see if the first
633 * character of the name is a '{' character.</p>
634 * <p>For example, if a URI and local name were obtained from an element
635 * defined with <xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/>,
636 * then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo". Note that
637 * no prefix is used.</p>
638 *
639 * @param oformat A set of output properties that will be
640 * used to override any of the same properties in affect
641 * for the transformation.
642 *
643 * @see javax.xml.transform.OutputKeys
644 * @see java.util.Properties
645 *
646 * @throws IllegalArgumentException if any of the argument keys are not
647 * recognized and are not namespace qualified.
648 */
649 public void setOutputProperties(Properties oformat)
650 throws IllegalArgumentException
651 {
652
653 if (null != oformat)
654 {
655
656 // See if an *explicit* method was set.
657 String method = (String) oformat.get(OutputKeys.METHOD);
658
659 if (null != method)
660 m_outputFormat = new OutputProperties(method);
661 else
662 m_outputFormat = new OutputProperties();
663
664 m_outputFormat.copyFrom(oformat);
665 }
666 else {
667 // if oformat is null JAXP says that any props previously set are removed
668 // and we are to revert back to those in the templates object (i.e. Stylesheet).
669 m_outputFormat = null;
670 }
671 }
672
673 /**
674 * Get a copy of the output properties for the transformation.
675 *
676 * <p>The properties returned should contain properties set by the user,
677 * and properties set by the stylesheet, and these properties
678 * are "defaulted" by default properties specified by <a href="http://www.w3.org/TR/xslt#output">section 16 of the
679 * XSL Transformations (XSLT) W3C Recommendation</a>. The properties that
680 * were specifically set by the user or the stylesheet should be in the base
681 * Properties list, while the XSLT default properties that were not
682 * specifically set should be the default Properties list. Thus,
683 * getOutputProperties().getProperty(String key) will obtain any
684 * property in that was set by {@link #setOutputProperty},
685 * {@link #setOutputProperties}, in the stylesheet, <em>or</em> the default
686 * properties, while
687 * getOutputProperties().get(String key) will only retrieve properties
688 * that were explicitly set by {@link #setOutputProperty},
689 * {@link #setOutputProperties}, or in the stylesheet.</p>
690 *
691 * <p>Note that mutation of the Properties object returned will not
692 * effect the properties that the transformation contains.</p>
693 *
694 * <p>If any of the argument keys are not recognized and are not
695 * namespace qualified, the property will be ignored. In other words the
696 * behaviour is not orthogonal with setOutputProperties.</p>
697 *
698 * @return A copy of the set of output properties in effect
699 * for the next transformation.
700 *
701 * @see javax.xml.transform.OutputKeys
702 * @see java.util.Properties
703 */
704 public Properties getOutputProperties()
705 {
706 return (Properties) m_outputFormat.getProperties().clone();
707 }
708
709 /**
710 * Set an output property that will be in effect for the
711 * transformation.
712 *
713 * <p>Pass a qualified property name as a two-part string, the namespace URI
714 * enclosed in curly braces ({}), followed by the local name. If the
715 * name has a null URL, the String only contain the local name. An
716 * application can safely check for a non-null URI by testing to see if the first
717 * character of the name is a '{' character.</p>
718 * <p>For example, if a URI and local name were obtained from an element
719 * defined with <xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/>,
720 * then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo". Note that
721 * no prefix is used.</p>
722 *
723 * <p>The Properties object that was passed to {@link #setOutputProperties} won't
724 * be effected by calling this method.</p>
725 *
726 * @param name A non-null String that specifies an output
727 * property name, which may be namespace qualified.
728 * @param value The non-null string value of the output property.
729 *
730 * @throws IllegalArgumentException If the property is not supported, and is
731 * not qualified with a namespace.
732 *
733 * @see javax.xml.transform.OutputKeys
734 */
735 public void setOutputProperty(String name, String value)
736 throws IllegalArgumentException
737 {
738
739 if (!OutputProperties.isLegalPropertyKey(name))
740 throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{name})); //"output property not recognized: "
741 //+ name);
742
743 m_outputFormat.setProperty(name, value);
744 }
745
746 /**
747 * Get an output property that is in effect for the
748 * transformation. The property specified may be a property
749 * that was set with setOutputProperty, or it may be a
750 * property specified in the stylesheet.
751 *
752 * @param name A non-null String that specifies an output
753 * property name, which may be namespace qualified.
754 *
755 * @return The string value of the output property, or null
756 * if no property was found.
757 *
758 * @throws IllegalArgumentException If the property is not supported.
759 *
760 * @see javax.xml.transform.OutputKeys
761 */
762 public String getOutputProperty(String name) throws IllegalArgumentException
763 {
764
765 String value = null;
766 OutputProperties props = m_outputFormat;
767
768 value = props.getProperty(name);
769
770 if (null == value)
771 {
772 if (!OutputProperties.isLegalPropertyKey(name))
773 throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{name})); //"output property not recognized: "
774 // + name);
775 }
776
777 return value;
778 }
779
780 /**
781 * Set the error event listener in effect for the transformation.
782 *
783 * @param listener The new error listener.
784 * @throws IllegalArgumentException if listener is null.
785 */
786 public void setErrorListener(ErrorListener listener)
787 throws IllegalArgumentException
788 {
789 if (listener == null)
790 throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_NULL_ERROR_HANDLER, null));
791 else
792 m_errorListener = listener;
793 }
794
795 /**
796 * Get the error event handler in effect for the transformation.
797 *
798 * @return The current error handler, which should never be null.
799 */
800 public ErrorListener getErrorListener()
801 {
802 return m_errorListener;
803 }
804
805 ////////////////////////////////////////////////////////////////////
806 // Default implementation of DTDHandler interface.
807 ////////////////////////////////////////////////////////////////////
808
809 /**
810 * Receive notification of a notation declaration.
811 *
812 * <p>By default, do nothing. Application writers may override this
813 * method in a subclass if they wish to keep track of the notations
814 * declared in a document.</p>
815 *
816 * @param name The notation name.
817 * @param publicId The notation public identifier, or null if not
818 * available.
819 * @param systemId The notation system identifier.
820 * @throws org.xml.sax.SAXException Any SAX exception, possibly
821 * wrapping another exception.
822 * @see org.xml.sax.DTDHandler#notationDecl
823 *
824 * @throws SAXException
825 */
826 public void notationDecl(String name, String publicId, String systemId)
827 throws SAXException
828 {
829 if (null != m_resultDTDHandler)
830 m_resultDTDHandler.notationDecl(name, publicId, systemId);
831 }
832
833 /**
834 * Receive notification of an unparsed entity declaration.
835 *
836 * <p>By default, do nothing. Application writers may override this
837 * method in a subclass to keep track of the unparsed entities
838 * declared in a document.</p>
839 *
840 * @param name The entity name.
841 * @param publicId The entity public identifier, or null if not
842 * available.
843 * @param systemId The entity system identifier.
844 * @param notationName The name of the associated notation.
845 * @throws org.xml.sax.SAXException Any SAX exception, possibly
846 * wrapping another exception.
847 * @see org.xml.sax.DTDHandler#unparsedEntityDecl
848 *
849 * @throws SAXException
850 */
851 public void unparsedEntityDecl(
852 String name, String publicId, String systemId, String notationName)
853 throws SAXException
854 {
855
856 if (null != m_resultDTDHandler)
857 m_resultDTDHandler.unparsedEntityDecl(name, publicId, systemId,
858 notationName);
859 }
860
861 ////////////////////////////////////////////////////////////////////
862 // Default implementation of ContentHandler interface.
863 ////////////////////////////////////////////////////////////////////
864
865 /**
866 * Receive a Locator object for document events.
867 *
868 * <p>By default, do nothing. Application writers may override this
869 * method in a subclass if they wish to store the locator for use
870 * with other document events.</p>
871 *
872 * @param locator A locator for all SAX document events.
873 * @see org.xml.sax.ContentHandler#setDocumentLocator
874 * @see org.xml.sax.Locator
875 */
876 public void setDocumentLocator(Locator locator)
877 {
878 try
879 {
880 if (null == m_resultContentHandler)
881 createResultContentHandler(m_result);
882 }
883 catch (TransformerException te)
884 {
885 throw new org.apache.xml.utils.WrappedRuntimeException(te);
886 }
887
888 m_resultContentHandler.setDocumentLocator(locator);
889 }
890
891 /**
892 * Receive notification of the beginning of the document.
893 *
894 * <p>By default, do nothing. Application writers may override this
895 * method in a subclass to take specific actions at the beginning
896 * of a document (such as allocating the root node of a tree or
897 * creating an output file).</p>
898 *
899 * @throws org.xml.sax.SAXException Any SAX exception, possibly
900 * wrapping another exception.
901 * @see org.xml.sax.ContentHandler#startDocument
902 *
903 * @throws SAXException
904 */
905 public void startDocument() throws SAXException
906 {
907
908 try
909 {
910 if (null == m_resultContentHandler)
911 createResultContentHandler(m_result);
912 }
913 catch (TransformerException te)
914 {
915 throw new SAXException(te.getMessage(), te);
916 }
917
918 // Reset for multiple transforms with this transformer.
919 m_flushedStartDoc = false;
920 m_foundFirstElement = false;
921 }
922
923 boolean m_flushedStartDoc = false;
924
925 protected final void flushStartDoc()
926 throws SAXException
927 {
928 if(!m_flushedStartDoc)
929 {
930 if (m_resultContentHandler == null)
931 {
932 try
933 {
934 createResultContentHandler(m_result);
935 }
936 catch(TransformerException te)
937 {
938 throw new SAXException(te);
939 }
940 }
941 m_resultContentHandler.startDocument();
942 m_flushedStartDoc = true;
943 }
944 }
945
946 /**
947 * Receive notification of the end of the document.
948 *
949 * <p>By default, do nothing. Application writers may override this
950 * method in a subclass to take specific actions at the end
951 * of a document (such as finalising a tree or closing an output
952 * file).</p>
953 *
954 * @throws org.xml.sax.SAXException Any SAX exception, possibly
955 * wrapping another exception.
956 * @see org.xml.sax.ContentHandler#endDocument
957 *
958 * @throws SAXException
959 */
960 public void endDocument() throws SAXException
961 {
962 flushStartDoc();
963 m_resultContentHandler.endDocument();
964 }
965
966 /**
967 * Receive notification of the start of a Namespace mapping.
968 *
969 * <p>By default, do nothing. Application writers may override this
970 * method in a subclass to take specific actions at the start of
971 * each Namespace prefix scope (such as storing the prefix mapping).</p>
972 *
973 * @param prefix The Namespace prefix being declared.
974 * @param uri The Namespace URI mapped to the prefix.
975 * @throws org.xml.sax.SAXException Any SAX exception, possibly
976 * wrapping another exception.
977 * @see org.xml.sax.ContentHandler#startPrefixMapping
978 *
979 * @throws SAXException
980 */
981 public void startPrefixMapping(String prefix, String uri)
982 throws SAXException
983 {
984 flushStartDoc();
985 m_resultContentHandler.startPrefixMapping(prefix, uri);
986 }
987
988 /**
989 * Receive notification of the end of a Namespace mapping.
990 *
991 * <p>By default, do nothing. Application writers may override this
992 * method in a subclass to take specific actions at the end of
993 * each prefix mapping.</p>
994 *
995 * @param prefix The Namespace prefix being declared.
996 * @throws org.xml.sax.SAXException Any SAX exception, possibly
997 * wrapping another exception.
998 * @see org.xml.sax.ContentHandler#endPrefixMapping
999 *
1000 * @throws SAXException
1001 */
1002 public void endPrefixMapping(String prefix) throws SAXException
1003 {
1004 flushStartDoc();
1005 m_resultContentHandler.endPrefixMapping(prefix);
1006 }
1007
1008 /**
1009 * Receive notification of the start of an element.
1010 *
1011 * <p>By default, do nothing. Application writers may override this
1012 * method in a subclass to take specific actions at the start of
1013 * each element (such as allocating a new tree node or writing
1014 * output to a file).</p>
1015 *
1016 * @param uri The Namespace URI, or the empty string if the
1017 * element has no Namespace URI or if Namespace
1018 * processing is not being performed.
1019 * @param localName The local name (without prefix), or the
1020 * empty string if Namespace processing is not being
1021 * performed.
1022 * @param qName The qualified name (with prefix), or the
1023 * empty string if qualified names are not available.
1024 * @param attributes The specified or defaulted attributes.
1025 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1026 * wrapping another exception.
1027 * @see org.xml.sax.ContentHandler#startElement
1028 *
1029 * @throws SAXException
1030 */
1031 public void startElement(
1032 String uri, String localName, String qName, Attributes attributes)
1033 throws SAXException
1034 {
1035
1036 if (!m_foundFirstElement && null != m_serializer)
1037 {
1038 m_foundFirstElement = true;
1039
1040 Serializer newSerializer;
1041
1042 try
1043 {
1044 newSerializer = SerializerSwitcher.switchSerializerIfHTML(uri,
1045 localName, m_outputFormat.getProperties(), m_serializer);
1046 }
1047 catch (TransformerException te)
1048 {
1049 throw new SAXException(te);
1050 }
1051
1052 if (newSerializer != m_serializer)
1053 {
1054 try
1055 {
1056 m_resultContentHandler = newSerializer.asContentHandler();
1057 }
1058 catch (IOException ioe) // why?
1059 {
1060 throw new SAXException(ioe);
1061 }
1062
1063 if (m_resultContentHandler instanceof DTDHandler)
1064 m_resultDTDHandler = (DTDHandler) m_resultContentHandler;
1065
1066 if (m_resultContentHandler instanceof LexicalHandler)
1067 m_resultLexicalHandler = (LexicalHandler) m_resultContentHandler;
1068
1069 m_serializer = newSerializer;
1070 }
1071 }
1072 flushStartDoc();
1073 m_resultContentHandler.startElement(uri, localName, qName, attributes);
1074 }
1075
1076 /**
1077 * Receive notification of the end of an element.
1078 *
1079 * <p>By default, do nothing. Application writers may override this
1080 * method in a subclass to take specific actions at the end of
1081 * each element (such as finalising a tree node or writing
1082 * output to a file).</p>
1083 *
1084 * @param uri The Namespace URI, or the empty string if the
1085 * element has no Namespace URI or if Namespace
1086 * processing is not being performed.
1087 * @param localName The local name (without prefix), or the
1088 * empty string if Namespace processing is not being
1089 * performed.
1090 * @param qName The qualified name (with prefix), or the
1091 * empty string if qualified names are not available.
1092 *
1093 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1094 * wrapping another exception.
1095 * @see org.xml.sax.ContentHandler#endElement
1096 *
1097 * @throws SAXException
1098 */
1099 public void endElement(String uri, String localName, String qName)
1100 throws SAXException
1101 {
1102 m_resultContentHandler.endElement(uri, localName, qName);
1103 }
1104
1105 /**
1106 * Receive notification of character data inside an element.
1107 *
1108 * <p>By default, do nothing. Application writers may override this
1109 * method to take specific actions for each chunk of character data
1110 * (such as adding the data to a node or buffer, or printing it to
1111 * a file).</p>
1112 *
1113 * @param ch The characters.
1114 * @param start The start position in the character array.
1115 * @param length The number of characters to use from the
1116 * character array.
1117 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1118 * wrapping another exception.
1119 * @see org.xml.sax.ContentHandler#characters
1120 *
1121 * @throws SAXException
1122 */
1123 public void characters(char ch[], int start, int length) throws SAXException
1124 {
1125 flushStartDoc();
1126 m_resultContentHandler.characters(ch, start, length);
1127 }
1128
1129 /**
1130 * Receive notification of ignorable whitespace in element content.
1131 *
1132 * <p>By default, do nothing. Application writers may override this
1133 * method to take specific actions for each chunk of ignorable
1134 * whitespace (such as adding data to a node or buffer, or printing
1135 * it to a file).</p>
1136 *
1137 * @param ch The whitespace characters.
1138 * @param start The start position in the character array.
1139 * @param length The number of characters to use from the
1140 * character array.
1141 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1142 * wrapping another exception.
1143 * @see org.xml.sax.ContentHandler#ignorableWhitespace
1144 *
1145 * @throws SAXException
1146 */
1147 public void ignorableWhitespace(char ch[], int start, int length)
1148 throws SAXException
1149 {
1150 m_resultContentHandler.ignorableWhitespace(ch, start, length);
1151 }
1152
1153 /**
1154 * Receive notification of a processing instruction.
1155 *
1156 * <p>By default, do nothing. Application writers may override this
1157 * method in a subclass to take specific actions for each
1158 * processing instruction, such as setting status variables or
1159 * invoking other methods.</p>
1160 *
1161 * @param target The processing instruction target.
1162 * @param data The processing instruction data, or null if
1163 * none is supplied.
1164 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1165 * wrapping another exception.
1166 * @see org.xml.sax.ContentHandler#processingInstruction
1167 *
1168 * @throws SAXException
1169 */
1170 public void processingInstruction(String target, String data)
1171 throws SAXException
1172 {
1173 flushStartDoc();
1174 m_resultContentHandler.processingInstruction(target, data);
1175 }
1176
1177 /**
1178 * Receive notification of a skipped entity.
1179 *
1180 * <p>By default, do nothing. Application writers may override this
1181 * method in a subclass to take specific actions for each
1182 * processing instruction, such as setting status variables or
1183 * invoking other methods.</p>
1184 *
1185 * @param name The name of the skipped entity.
1186 * @throws org.xml.sax.SAXException Any SAX exception, possibly
1187 * wrapping another exception.
1188 * @see org.xml.sax.ContentHandler#processingInstruction
1189 *
1190 * @throws SAXException
1191 */
1192 public void skippedEntity(String name) throws SAXException
1193 {
1194 flushStartDoc();
1195 m_resultContentHandler.skippedEntity(name);
1196 }
1197
1198 /**
1199 * Report the start of DTD declarations, if any.
1200 *
1201 * <p>Any declarations are assumed to be in the internal subset
1202 * unless otherwise indicated by a {@link #startEntity startEntity}
1203 * event.</p>
1204 *
1205 * <p>Note that the start/endDTD events will appear within
1206 * the start/endDocument events from ContentHandler and
1207 * before the first startElement event.</p>
1208 *
1209 * @param name The document type name.
1210 * @param publicId The declared public identifier for the
1211 * external DTD subset, or null if none was declared.
1212 * @param systemId The declared system identifier for the
1213 * external DTD subset, or null if none was declared.
1214 * @throws SAXException The application may raise an
1215 * exception.
1216 * @see #endDTD
1217 * @see #startEntity
1218 */
1219 public void startDTD(String name, String publicId, String systemId)
1220 throws SAXException
1221 {
1222 flushStartDoc();
1223 if (null != m_resultLexicalHandler)
1224 m_resultLexicalHandler.startDTD(name, publicId, systemId);
1225 }
1226
1227 /**
1228 * Report the end of DTD declarations.
1229 *
1230 * @throws SAXException The application may raise an exception.
1231 * @see #startDTD
1232 */
1233 public void endDTD() throws SAXException
1234 {
1235 if (null != m_resultLexicalHandler)
1236 m_resultLexicalHandler.endDTD();
1237 }
1238
1239 /**
1240 * Report the beginning of an entity in content.
1241 *
1242 * <p><strong>NOTE:</entity> entity references in attribute
1243 * values -- and the start and end of the document entity --
1244 * are never reported.</p>
1245 *
1246 * <p>The start and end of the external DTD subset are reported
1247 * using the pseudo-name "[dtd]". All other events must be
1248 * properly nested within start/end entity events.</p>
1249 *
1250 * <p>Note that skipped entities will be reported through the
1251 * {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity}
1252 * event, which is part of the ContentHandler interface.</p>
1253 *
1254 * @param name The name of the entity. If it is a parameter
1255 * entity, the name will begin with '%'.
1256 * @throws SAXException The application may raise an exception.
1257 * @see #endEntity
1258 * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
1259 * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
1260 */
1261 public void startEntity(String name) throws SAXException
1262 {
1263 if (null != m_resultLexicalHandler)
1264 m_resultLexicalHandler.startEntity(name);
1265 }
1266
1267 /**
1268 * Report the end of an entity.
1269 *
1270 * @param name The name of the entity that is ending.
1271 * @throws SAXException The application may raise an exception.
1272 * @see #startEntity
1273 */
1274 public void endEntity(String name) throws SAXException
1275 {
1276 if (null != m_resultLexicalHandler)
1277 m_resultLexicalHandler.endEntity(name);
1278 }
1279
1280 /**
1281 * Report the start of a CDATA section.
1282 *
1283 * <p>The contents of the CDATA section will be reported through
1284 * the regular {@link org.xml.sax.ContentHandler#characters
1285 * characters} event.</p>
1286 *
1287 * @throws SAXException The application may raise an exception.
1288 * @see #endCDATA
1289 */
1290 public void startCDATA() throws SAXException
1291 {
1292 if (null != m_resultLexicalHandler)
1293 m_resultLexicalHandler.startCDATA();
1294 }
1295
1296 /**
1297 * Report the end of a CDATA section.
1298 *
1299 * @throws SAXException The application may raise an exception.
1300 * @see #startCDATA
1301 */
1302 public void endCDATA() throws SAXException
1303 {
1304 if (null != m_resultLexicalHandler)
1305 m_resultLexicalHandler.endCDATA();
1306 }
1307
1308 /**
1309 * Report an XML comment anywhere in the document.
1310 *
1311 * <p>This callback will be used for comments inside or outside the
1312 * document element, including comments in the external DTD
1313 * subset (if read).</p>
1314 *
1315 * @param ch An array holding the characters in the comment.
1316 * @param start The starting position in the array.
1317 * @param length The number of characters to use from the array.
1318 * @throws SAXException The application may raise an exception.
1319 */
1320 public void comment(char ch[], int start, int length) throws SAXException
1321 {
1322 flushStartDoc();
1323 if (null != m_resultLexicalHandler)
1324 m_resultLexicalHandler.comment(ch, start, length);
1325 }
1326
1327 // Implement DeclHandler
1328
1329 /**
1330 * Report an element type declaration.
1331 *
1332 * <p>The content model will consist of the string "EMPTY", the
1333 * string "ANY", or a parenthesised group, optionally followed
1334 * by an occurrence indicator. The model will be normalized so
1335 * that all whitespace is removed,and will include the enclosing
1336 * parentheses.</p>
1337 *
1338 * @param name The element type name.
1339 * @param model The content model as a normalized string.
1340 * @exception SAXException The application may raise an exception.
1341 */
1342 public void elementDecl (String name, String model)
1343 throws SAXException
1344 {
1345 if (null != m_resultDeclHandler)
1346 m_resultDeclHandler.elementDecl(name, model);
1347 }
1348
1349
1350 /**
1351 * Report an attribute type declaration.
1352 *
1353 * <p>Only the effective (first) declaration for an attribute will
1354 * be reported. The type will be one of the strings "CDATA",
1355 * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
1356 * "ENTITIES", or "NOTATION", or a parenthesized token group with
1357 * the separator "|" and all whitespace removed.</p>
1358 *
1359 * @param eName The name of the associated element.
1360 * @param aName The name of the attribute.
1361 * @param type A string representing the attribute type.
1362 * @param valueDefault A string representing the attribute default
1363 * ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
1364 * none of these applies.
1365 * @param value A string representing the attribute's default value,
1366 * or null if there is none.
1367 * @exception SAXException The application may raise an exception.
1368 */
1369 public void attributeDecl (String eName,
1370 String aName,
1371 String type,
1372 String valueDefault,
1373 String value)
1374 throws SAXException
1375 {
1376 if (null != m_resultDeclHandler)
1377 m_resultDeclHandler.attributeDecl(eName, aName, type, valueDefault, value);
1378 }
1379
1380
1381 /**
1382 * Report an internal entity declaration.
1383 *
1384 * <p>Only the effective (first) declaration for each entity
1385 * will be reported.</p>
1386 *
1387 * @param name The name of the entity. If it is a parameter
1388 * entity, the name will begin with '%'.
1389 * @param value The replacement text of the entity.
1390 * @exception SAXException The application may raise an exception.
1391 * @see #externalEntityDecl
1392 * @see org.xml.sax.DTDHandler#unparsedEntityDecl
1393 */
1394 public void internalEntityDecl (String name, String value)
1395 throws SAXException
1396 {
1397 if (null != m_resultDeclHandler)
1398 m_resultDeclHandler.internalEntityDecl(name, value);
1399 }
1400
1401
1402 /**
1403 * Report a parsed external entity declaration.
1404 *
1405 * <p>Only the effective (first) declaration for each entity
1406 * will be reported.</p>
1407 *
1408 * @param name The name of the entity. If it is a parameter
1409 * entity, the name will begin with '%'.
1410 * @param publicId The declared public identifier of the entity, or
1411 * null if none was declared.
1412 * @param systemId The declared system identifier of the entity.
1413 * @exception SAXException The application may raise an exception.
1414 * @see #internalEntityDecl
1415 * @see org.xml.sax.DTDHandler#unparsedEntityDecl
1416 */
1417 public void externalEntityDecl (String name, String publicId,
1418 String systemId)
1419 throws SAXException
1420 {
1421 if (null != m_resultDeclHandler)
1422 m_resultDeclHandler.externalEntityDecl(name, publicId, systemId);
1423 }
1424
1425 /**
1426 * This is null unless we own the stream.
1427 */
1428 private java.io.FileOutputStream m_outputStream = null;
1429
1430 /** The content handler where result events will be sent. */
1431 private ContentHandler m_resultContentHandler;
1432
1433 /** The lexical handler where result events will be sent. */
1434 private LexicalHandler m_resultLexicalHandler;
1435
1436 /** The DTD handler where result events will be sent. */
1437 private DTDHandler m_resultDTDHandler;
1438
1439 /** The Decl handler where result events will be sent. */
1440 private DeclHandler m_resultDeclHandler;
1441
1442 /** The Serializer, which may or may not be null. */
1443 private Serializer m_serializer;
1444
1445 /** The Result object. */
1446 private Result m_result;
1447
1448 /**
1449 * The system ID, which is unused, but must be returned to fullfill the
1450 * TransformerHandler interface.
1451 */
1452 private String m_systemID;
1453
1454 /**
1455 * The parameters, which is unused, but must be returned to fullfill the
1456 * Transformer interface.
1457 */
1458 private Hashtable m_params;
1459
1460 /** The error listener for TrAX errors and warnings. */
1461 private ErrorListener m_errorListener =
1462 new org.apache.xml.utils.DefaultErrorHandler(false);
1463
1464 /**
1465 * The URIResolver, which is unused, but must be returned to fullfill the
1466 * TransformerHandler interface.
1467 */
1468 URIResolver m_URIResolver;
1469
1470 /** The output properties. */
1471 private OutputProperties m_outputFormat;
1472
1473 /** Flag to set if we've found the first element, so we can tell if we have
1474 * to check to see if we should create an HTML serializer. */
1475 boolean m_foundFirstElement;
1476
1477 /**
1478 * State of the secure processing feature.
1479 */
1480 private boolean m_isSecureProcessing = false;
1481 }