1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /*
  17. * $Id: TransformerImpl.java,v 1.80 2004/02/23 21:33:15 igorh Exp $
  18. */
  19. package com.sun.org.apache.xalan.internal.xsltc.trax;
  20. import java.io.File;
  21. import java.io.FileOutputStream;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.OutputStream;
  25. import java.io.Reader;
  26. import java.io.Writer;
  27. import java.net.URI;
  28. import java.net.URL;
  29. import java.net.URLConnection;
  30. import java.net.UnknownServiceException;
  31. import java.util.Enumeration;
  32. import java.util.Properties;
  33. import java.util.StringTokenizer;
  34. import java.util.Vector;
  35. import java.lang.reflect.Constructor;
  36. import javax.xml.parsers.DocumentBuilder;
  37. import javax.xml.parsers.DocumentBuilderFactory;
  38. import javax.xml.parsers.ParserConfigurationException;
  39. import javax.xml.transform.ErrorListener;
  40. import javax.xml.transform.OutputKeys;
  41. import javax.xml.transform.Result;
  42. import javax.xml.transform.Source;
  43. import javax.xml.transform.Transformer;
  44. import javax.xml.transform.TransformerException;
  45. import javax.xml.transform.URIResolver;
  46. import javax.xml.transform.dom.DOMResult;
  47. import javax.xml.transform.dom.DOMSource;
  48. import javax.xml.transform.sax.SAXResult;
  49. import javax.xml.transform.sax.SAXSource;
  50. import javax.xml.transform.stream.StreamResult;
  51. import javax.xml.transform.stream.StreamSource;
  52. import com.sun.org.apache.xml.internal.utils.SystemIDResolver;
  53. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  54. import com.sun.org.apache.xalan.internal.xsltc.DOMCache;
  55. import com.sun.org.apache.xalan.internal.xsltc.DOMEnhancedForDTM;
  56. import com.sun.org.apache.xalan.internal.xsltc.StripFilter;
  57. import com.sun.org.apache.xalan.internal.xsltc.Translet;
  58. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  59. import com.sun.org.apache.xml.internal.serializer.OutputPropertiesFactory;
  60. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  61. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
  62. import com.sun.org.apache.xalan.internal.xsltc.dom.DOMWSFilter;
  63. import com.sun.org.apache.xalan.internal.xsltc.dom.SAXImpl;
  64. import com.sun.org.apache.xalan.internal.xsltc.dom.XSLTCDTMManager;
  65. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  66. import com.sun.org.apache.xalan.internal.xsltc.runtime.Hashtable;
  67. import com.sun.org.apache.xalan.internal.xsltc.runtime.output.TransletOutputHandlerFactory;
  68. import com.sun.org.apache.xml.internal.dtm.DTMWSFilter;
  69. import com.sun.org.apache.xml.internal.utils.XMLReaderManager;
  70. import org.xml.sax.ContentHandler;
  71. import org.xml.sax.InputSource;
  72. import org.xml.sax.SAXException;
  73. import org.xml.sax.XMLReader;
  74. import org.xml.sax.ext.LexicalHandler;
  75. /**
  76. * @author Morten Jorgensen
  77. * @author G. Todd Miller
  78. * @author Santiago Pericas-Geertsen
  79. */
  80. public final class TransformerImpl extends Transformer
  81. implements DOMCache, ErrorListener
  82. {
  83. private final static String EMPTY_STRING = "";
  84. private final static String NO_STRING = "no";
  85. private final static String YES_STRING = "yes";
  86. private final static String XML_STRING = "xml";
  87. private final static String LEXICAL_HANDLER_PROPERTY =
  88. "http://xml.org/sax/properties/lexical-handler";
  89. private static final String NAMESPACE_FEATURE =
  90. "http://xml.org/sax/features/namespaces";
  91. /**
  92. * A reference to the translet or null if the identity transform.
  93. */
  94. private AbstractTranslet _translet = null;
  95. /**
  96. * The output method of this transformation.
  97. */
  98. private String _method = null;
  99. /**
  100. * The output encoding of this transformation.
  101. */
  102. private String _encoding = null;
  103. /**
  104. * The systemId set in input source.
  105. */
  106. private String _sourceSystemId = null;
  107. /**
  108. * An error listener for runtime errors.
  109. */
  110. private ErrorListener _errorListener = this;
  111. /**
  112. * A reference to a URI resolver for calls to document().
  113. */
  114. private URIResolver _uriResolver = null;
  115. /**
  116. * Output properties of this transformer instance.
  117. */
  118. private Properties _properties, _propertiesClone;
  119. /**
  120. * A reference to an output handler factory.
  121. */
  122. private TransletOutputHandlerFactory _tohFactory = null;
  123. /**
  124. * A reference to a internal DOM represenation of the input.
  125. */
  126. private DOM _dom = null;
  127. /**
  128. * Number of indent spaces to add when indentation is on.
  129. */
  130. private int _indentNumber;
  131. /**
  132. * A reference to the transformer factory that this templates
  133. * object belongs to.
  134. */
  135. private TransformerFactoryImpl _tfactory = null;
  136. /**
  137. * A reference to the XSLTCDTMManager which is used to build the DOM/DTM
  138. * for this transformer.
  139. */
  140. private XSLTCDTMManager _dtmManager = null;
  141. /**
  142. * A reference to an object that creates and caches XMLReader objects.
  143. */
  144. private XMLReaderManager _readerManager = XMLReaderManager.getInstance();
  145. /**
  146. * A flag indicating whether we use incremental building of the DTM.
  147. */
  148. //private boolean _isIncremental = false;
  149. /**
  150. * A flag indicating whether this transformer implements the identity
  151. * transform.
  152. */
  153. private boolean _isIdentity = false;
  154. /**
  155. * A hashtable to store parameters for the identity transform. These
  156. * are not needed during the transformation, but we must keep track of
  157. * them to be fully complaint with the JAXP API.
  158. */
  159. private Hashtable _parameters = null;
  160. /**
  161. * This class wraps an ErrorListener into a MessageHandler in order to
  162. * capture messages reported via xsl:message.
  163. */
  164. static class MessageHandler
  165. extends com.sun.org.apache.xalan.internal.xsltc.runtime.MessageHandler
  166. {
  167. private ErrorListener _errorListener;
  168. public MessageHandler(ErrorListener errorListener) {
  169. _errorListener = errorListener;
  170. }
  171. public void displayMessage(String msg) {
  172. if(_errorListener == null) {
  173. System.err.println(msg);
  174. }
  175. else {
  176. try {
  177. _errorListener.warning(new TransformerException(msg));
  178. }
  179. catch (TransformerException e) {
  180. // ignored
  181. }
  182. }
  183. }
  184. }
  185. protected TransformerImpl(Properties outputProperties, int indentNumber,
  186. TransformerFactoryImpl tfactory)
  187. {
  188. this(null, outputProperties, indentNumber, tfactory);
  189. _isIdentity = true;
  190. // _properties.put(OutputKeys.METHOD, "xml");
  191. }
  192. protected TransformerImpl(Translet translet, Properties outputProperties,
  193. int indentNumber, TransformerFactoryImpl tfactory)
  194. {
  195. _translet = (AbstractTranslet) translet;
  196. _properties = createOutputProperties(outputProperties);
  197. _propertiesClone = (Properties) _properties.clone();
  198. _indentNumber = indentNumber;
  199. _tfactory = tfactory;
  200. //_isIncremental = tfactory._incremental;
  201. }
  202. /**
  203. * Returns the translet wrapped inside this Transformer or
  204. * null if this is the identity transform.
  205. */
  206. protected AbstractTranslet getTranslet() {
  207. return _translet;
  208. }
  209. public boolean isIdentity() {
  210. return _isIdentity;
  211. }
  212. /**
  213. * Implements JAXP's Transformer.transform()
  214. *
  215. * @param source Contains the input XML document
  216. * @param result Will contain the output from the transformation
  217. * @throws TransformerException
  218. */
  219. public void transform(Source source, Result result)
  220. throws TransformerException
  221. {
  222. if (!_isIdentity) {
  223. if (_translet == null) {
  224. ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_TRANSLET_ERR);
  225. throw new TransformerException(err.toString());
  226. }
  227. // Pass output properties to the translet
  228. transferOutputProperties(_translet);
  229. }
  230. final SerializationHandler toHandler = getOutputHandler(result);
  231. if (toHandler == null) {
  232. ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_HANDLER_ERR);
  233. throw new TransformerException(err.toString());
  234. }
  235. if (_uriResolver != null && !_isIdentity) {
  236. _translet.setDOMCache(this);
  237. }
  238. // Pass output properties to handler if identity
  239. if (_isIdentity) {
  240. transferOutputProperties(toHandler);
  241. }
  242. transform(source, toHandler, _encoding);
  243. if (result instanceof DOMResult) {
  244. ((DOMResult)result).setNode(_tohFactory.getNode());
  245. }
  246. }
  247. /**
  248. * Create an output handler for the transformation output based on
  249. * the type and contents of the TrAX Result object passed to the
  250. * transform() method.
  251. */
  252. public SerializationHandler getOutputHandler(Result result)
  253. throws TransformerException
  254. {
  255. // Get output method using get() to ignore defaults
  256. _method = (String) _properties.get(OutputKeys.METHOD);
  257. // Get encoding using getProperty() to use defaults
  258. _encoding = (String) _properties.getProperty(OutputKeys.ENCODING);
  259. _tohFactory = TransletOutputHandlerFactory.newInstance();
  260. _tohFactory.setEncoding(_encoding);
  261. if (_method != null) {
  262. _tohFactory.setOutputMethod(_method);
  263. }
  264. // Set indentation number in the factory
  265. if (_indentNumber >= 0) {
  266. _tohFactory.setIndentNumber(_indentNumber);
  267. }
  268. // Return the content handler for this Result object
  269. try {
  270. // Result object could be SAXResult, DOMResult, or StreamResult
  271. if (result instanceof SAXResult) {
  272. final SAXResult target = (SAXResult)result;
  273. final ContentHandler handler = target.getHandler();
  274. _tohFactory.setHandler(handler);
  275. /**
  276. * Fix for bug 24414
  277. * If the lexicalHandler is set then we need to get that
  278. * for obtaining the lexical information
  279. */
  280. LexicalHandler lexicalHandler = target.getLexicalHandler();
  281. if (lexicalHandler != null ) {
  282. _tohFactory.setLexicalHandler(lexicalHandler);
  283. }
  284. _tohFactory.setOutputType(TransletOutputHandlerFactory.SAX);
  285. return _tohFactory.getSerializationHandler();
  286. }
  287. else if (result instanceof DOMResult) {
  288. _tohFactory.setNode(((DOMResult) result).getNode());
  289. _tohFactory.setOutputType(TransletOutputHandlerFactory.DOM);
  290. return _tohFactory.getSerializationHandler();
  291. }
  292. else if (result instanceof StreamResult) {
  293. // Get StreamResult
  294. final StreamResult target = (StreamResult) result;
  295. // StreamResult may have been created with a java.io.File,
  296. // java.io.Writer, java.io.OutputStream or just a String
  297. // systemId.
  298. _tohFactory.setOutputType(TransletOutputHandlerFactory.STREAM);
  299. // try to get a Writer from Result object
  300. final Writer writer = target.getWriter();
  301. if (writer != null) {
  302. _tohFactory.setWriter(writer);
  303. return _tohFactory.getSerializationHandler();
  304. }
  305. // or try to get an OutputStream from Result object
  306. final OutputStream ostream = target.getOutputStream();
  307. if (ostream != null) {
  308. _tohFactory.setOutputStream(ostream);
  309. return _tohFactory.getSerializationHandler();
  310. }
  311. // or try to get just a systemId string from Result object
  312. String systemId = result.getSystemId();
  313. if (systemId == null) {
  314. ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_RESULT_ERR);
  315. throw new TransformerException(err.toString());
  316. }
  317. // System Id may be in one of several forms, (1) a uri
  318. // that starts with 'file:', (2) uri that starts with 'http:'
  319. // or (3) just a filename on the local system.
  320. URL url = null;
  321. if (systemId.startsWith("file:")) {
  322. // if StreamResult(File) or setSystemID(File) was used,
  323. // the systemId will be URI encoded as a result of File.toURI(),
  324. // it must be decoded for use by URL
  325. try{
  326. Class clazz = ObjectFactory.findProviderClass("java.net.URI", ObjectFactory.findClassLoader(), true);
  327. Constructor construct = clazz.getConstructor(new Class[] {java.lang.String.class} );
  328. URI uri = (URI) construct.newInstance(new Object[]{systemId}) ;
  329. systemId = "file:";
  330. String host = uri.getHost(); // decoded String
  331. String path = uri.getPath(); //decoded String
  332. if (path == null) {
  333. path = "";
  334. }
  335. // if host (URI authority) then file:// + host + path
  336. // else just path (may be absolute or relative)
  337. if (host != null) {
  338. systemId += "//" + host + path;
  339. } else {
  340. systemId += path;
  341. }
  342. }
  343. catch(ClassNotFoundException e){
  344. // running on J2SE 1.3 which doesn't have URI Class so OK to ignore
  345. //ClassNotFoundException.
  346. }
  347. catch (Exception exception) {
  348. // URI exception which means nothing can be done so OK to ignore
  349. }
  350. url = new URL(systemId);
  351. _tohFactory.setOutputStream(
  352. new FileOutputStream(url.getFile()));
  353. return _tohFactory.getSerializationHandler();
  354. }
  355. else if (systemId.startsWith("http:")) {
  356. url = new URL(systemId);
  357. final URLConnection connection = url.openConnection();
  358. _tohFactory.setOutputStream(connection.getOutputStream());
  359. return _tohFactory.getSerializationHandler();
  360. }
  361. else {
  362. // system id is just a filename
  363. url = new File(systemId).toURL();
  364. _tohFactory.setOutputStream(
  365. new FileOutputStream(url.getFile()));
  366. return _tohFactory.getSerializationHandler();
  367. }
  368. }
  369. }
  370. // If we cannot write to the location specified by the SystemId
  371. catch (UnknownServiceException e) {
  372. throw new TransformerException(e);
  373. }
  374. catch (ParserConfigurationException e) {
  375. throw new TransformerException(e);
  376. }
  377. // If we cannot create the file specified by the SystemId
  378. catch (IOException e) {
  379. throw new TransformerException(e);
  380. }
  381. return null;
  382. }
  383. /**
  384. * Set the internal DOM that will be used for the next transformation
  385. */
  386. protected void setDOM(DOM dom) {
  387. _dom = dom;
  388. }
  389. /**
  390. * Builds an internal DOM from a TrAX Source object
  391. */
  392. private DOM getDOM(Source source) throws TransformerException {
  393. try {
  394. DOM dom = null;
  395. if (source != null) {
  396. DTMWSFilter wsfilter;
  397. if (_translet != null && _translet instanceof StripFilter) {
  398. wsfilter = new DOMWSFilter(_translet);
  399. } else {
  400. wsfilter = null;
  401. }
  402. boolean hasIdCall = (_translet != null) ? _translet.hasIdCall()
  403. : false;
  404. if (_dtmManager == null) {
  405. _dtmManager =
  406. (XSLTCDTMManager)_tfactory.getDTMManagerClass()
  407. .newInstance();
  408. }
  409. dom = (DOM)_dtmManager.getDTM(source, false, wsfilter, true,
  410. false, false, 0, hasIdCall);
  411. } else if (_dom != null) {
  412. dom = _dom;
  413. _dom = null; // use only once, so reset to 'null'
  414. } else {
  415. return null;
  416. }
  417. if (!_isIdentity) {
  418. // Give the translet the opportunity to make a prepass of
  419. // the document, in case it can extract useful information early
  420. _translet.prepassDocument(dom);
  421. }
  422. return dom;
  423. }
  424. catch (Exception e) {
  425. if (_errorListener != null) {
  426. postErrorToListener(e.getMessage());
  427. }
  428. throw new TransformerException(e);
  429. }
  430. }
  431. /**
  432. * Returns the {@link com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl}
  433. * object that create this <code>Transformer</code>.
  434. */
  435. protected TransformerFactoryImpl getTransformerFactory() {
  436. return _tfactory;
  437. }
  438. /**
  439. * Returns the {@link com.sun.org.apache.xalan.internal.xsltc.runtime.output.TransletOutputHandlerFactory}
  440. * object that create the <code>TransletOutputHandler</code>.
  441. */
  442. protected TransletOutputHandlerFactory getTransletOutputHandlerFactory() {
  443. return _tohFactory;
  444. }
  445. private void transformIdentity(Source source, SerializationHandler handler)
  446. throws Exception
  447. {
  448. // Get systemId from source
  449. if (source != null) {
  450. _sourceSystemId = source.getSystemId();
  451. }
  452. if (source instanceof StreamSource) {
  453. final StreamSource stream = (StreamSource) source;
  454. final InputStream streamInput = stream.getInputStream();
  455. final Reader streamReader = stream.getReader();
  456. final XMLReader reader = _readerManager.getXMLReader();
  457. try {
  458. // Hook up reader and output handler
  459. try {
  460. reader.setProperty(LEXICAL_HANDLER_PROPERTY, handler);
  461. }
  462. catch (SAXException e) {
  463. // Falls through
  464. }
  465. reader.setContentHandler(handler);
  466. // Create input source from source
  467. InputSource input;
  468. if (streamInput != null) {
  469. input = new InputSource(streamInput);
  470. input.setSystemId(_sourceSystemId);
  471. }
  472. else if (streamReader != null) {
  473. input = new InputSource(streamReader);
  474. input.setSystemId(_sourceSystemId);
  475. }
  476. else if (_sourceSystemId != null) {
  477. input = new InputSource(_sourceSystemId);
  478. }
  479. else {
  480. ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_SOURCE_ERR);
  481. throw new TransformerException(err.toString());
  482. }
  483. // Start pushing SAX events
  484. reader.parse(input);
  485. } finally {
  486. _readerManager.releaseXMLReader(reader);
  487. }
  488. } else if (source instanceof SAXSource) {
  489. final SAXSource sax = (SAXSource) source;
  490. XMLReader reader = sax.getXMLReader();
  491. final InputSource input = sax.getInputSource();
  492. boolean userReader = true;
  493. try {
  494. // Create a reader if not set by user
  495. if (reader == null) {
  496. reader = _readerManager.getXMLReader();
  497. userReader = false;
  498. }
  499. // Hook up reader and output handler
  500. try {
  501. reader.setProperty(LEXICAL_HANDLER_PROPERTY, handler);
  502. }
  503. catch (SAXException e) {
  504. // Falls through
  505. }
  506. reader.setContentHandler(handler);
  507. // Start pushing SAX events
  508. reader.parse(input);
  509. } finally {
  510. if (!userReader) {
  511. _readerManager.releaseXMLReader(reader);
  512. }
  513. }
  514. } else if (source instanceof DOMSource) {
  515. final DOMSource domsrc = (DOMSource) source;
  516. new DOM2TO(domsrc.getNode(), handler).parse();
  517. } else if (source instanceof XSLTCSource) {
  518. final DOM dom = ((XSLTCSource) source).getDOM(null, _translet);
  519. ((SAXImpl)dom).copy(handler);
  520. } else {
  521. ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_NO_SOURCE_ERR);
  522. throw new TransformerException(err.toString());
  523. }
  524. }
  525. /**
  526. * Internal transformation method - uses the internal APIs of XSLTC
  527. */
  528. private void transform(Source source, SerializationHandler handler,
  529. String encoding) throws TransformerException
  530. {
  531. try {
  532. /*
  533. * According to JAXP1.2, new SAXSource()/StreamSource()
  534. * should create an empty input tree, with a default root node.
  535. * new DOMSource()creates an empty document using DocumentBuilder.
  536. * newDocument(); Use DocumentBuilder.newDocument() for all 3
  537. * situations, since there is no clear spec. how to create
  538. * an empty tree when both SAXSource() and StreamSource() are used.
  539. */
  540. if ((source instanceof StreamSource && source.getSystemId()==null
  541. && ((StreamSource)source).getInputStream()==null &&
  542. ((StreamSource)source).getReader()==null)||
  543. (source instanceof SAXSource &&
  544. ((SAXSource)source).getInputSource()==null &&
  545. ((SAXSource)source).getXMLReader()==null )||
  546. (source instanceof DOMSource &&
  547. ((DOMSource)source).getNode()==null)){
  548. DocumentBuilderFactory builderF =
  549. DocumentBuilderFactory.newInstance();
  550. DocumentBuilder builder =
  551. builderF.newDocumentBuilder();
  552. String systemID = source.getSystemId();
  553. source = new DOMSource(builder.newDocument());
  554. // Copy system ID from original, empty Source to new
  555. if (systemID != null) {
  556. source.setSystemId(systemID);
  557. }
  558. }
  559. if (_isIdentity) {
  560. transformIdentity(source, handler);
  561. } else {
  562. _translet.transform(getDOM(source), handler);
  563. }
  564. } catch (TransletException e) {
  565. if (_errorListener != null) postErrorToListener(e.getMessage());
  566. throw new TransformerException(e);
  567. } catch (RuntimeException e) {
  568. if (_errorListener != null) postErrorToListener(e.getMessage());
  569. throw new TransformerException(e);
  570. } catch (Exception e) {
  571. if (_errorListener != null) postErrorToListener(e.getMessage());
  572. throw new TransformerException(e);
  573. } finally {
  574. _dtmManager = null;
  575. }
  576. }
  577. /**
  578. * Implements JAXP's Transformer.getErrorListener()
  579. * Get the error event handler in effect for the transformation.
  580. *
  581. * @return The error event handler currently in effect
  582. */
  583. public ErrorListener getErrorListener() {
  584. return _errorListener;
  585. }
  586. /**
  587. * Implements JAXP's Transformer.setErrorListener()
  588. * Set the error event listener in effect for the transformation.
  589. * Register a message handler in the translet in order to forward
  590. * xsl:messages to error listener.
  591. *
  592. * @param listener The error event listener to use
  593. * @throws IllegalArgumentException
  594. */
  595. public void setErrorListener(ErrorListener listener)
  596. throws IllegalArgumentException {
  597. if (listener == null) {
  598. ErrorMsg err = new ErrorMsg(ErrorMsg.ERROR_LISTENER_NULL_ERR,
  599. "Transformer");
  600. throw new IllegalArgumentException(err.toString());
  601. }
  602. _errorListener = listener;
  603. // Register a message handler to report xsl:messages
  604. if (_translet != null)
  605. _translet.setMessageHandler(new MessageHandler(_errorListener));
  606. }
  607. /**
  608. * Inform TrAX error listener of an error
  609. */
  610. private void postErrorToListener(String message) {
  611. try {
  612. _errorListener.error(new TransformerException(message));
  613. }
  614. catch (TransformerException e) {
  615. // ignored - transformation cannot be continued
  616. }
  617. }
  618. /**
  619. * Inform TrAX error listener of a warning
  620. */
  621. private void postWarningToListener(String message) {
  622. try {
  623. _errorListener.warning(new TransformerException(message));
  624. }
  625. catch (TransformerException e) {
  626. // ignored - transformation cannot be continued
  627. }
  628. }
  629. /**
  630. * The translet stores all CDATA sections set in the <xsl:output> element
  631. * in a Hashtable. This method will re-construct the whitespace separated
  632. * list of elements given in the <xsl:output> element.
  633. */
  634. private String makeCDATAString(Hashtable cdata) {
  635. // Return a 'null' string if no CDATA section elements were specified
  636. if (cdata == null) return null;
  637. StringBuffer result = new StringBuffer();
  638. // Get an enumeration of all the elements in the hashtable
  639. Enumeration elements = cdata.keys();
  640. if (elements.hasMoreElements()) {
  641. result.append((String)elements.nextElement());
  642. while (elements.hasMoreElements()) {
  643. String element = (String)elements.nextElement();
  644. result.append(' ');
  645. result.append(element);
  646. }
  647. }
  648. return(result.toString());
  649. }
  650. /**
  651. * Implements JAXP's Transformer.getOutputProperties().
  652. * Returns a copy of the output properties for the transformation. This is
  653. * a set of layered properties. The first layer contains properties set by
  654. * calls to setOutputProperty() and setOutputProperties() on this class,
  655. * and the output settings defined in the stylesheet's <xsl:output>
  656. * element makes up the second level, while the default XSLT output
  657. * settings are returned on the third level.
  658. *
  659. * @return Properties in effect for this Transformer
  660. */
  661. public Properties getOutputProperties() {
  662. return (Properties) _properties.clone();
  663. }
  664. /**
  665. * Implements JAXP's Transformer.getOutputProperty().
  666. * Get an output property that is in effect for the transformation. The
  667. * property specified may be a property that was set with setOutputProperty,
  668. * or it may be a property specified in the stylesheet.
  669. *
  670. * @param name A non-null string that contains the name of the property
  671. * @throws IllegalArgumentException if the property name is not known
  672. */
  673. public String getOutputProperty(String name)
  674. throws IllegalArgumentException
  675. {
  676. if (!validOutputProperty(name)) {
  677. ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_UNKNOWN_PROP_ERR, name);
  678. throw new IllegalArgumentException(err.toString());
  679. }
  680. return _properties.getProperty(name);
  681. }
  682. /**
  683. * Implements JAXP's Transformer.setOutputProperties().
  684. * Set the output properties for the transformation. These properties
  685. * will override properties set in the Templates with xsl:output.
  686. * Unrecognised properties will be quitely ignored.
  687. *
  688. * @param properties The properties to use for the Transformer
  689. * @throws IllegalArgumentException Never, errors are ignored
  690. */
  691. public void setOutputProperties(Properties properties)
  692. throws IllegalArgumentException
  693. {
  694. if (properties != null) {
  695. final Enumeration names = properties.propertyNames();
  696. while (names.hasMoreElements()) {
  697. final String name = (String) names.nextElement();
  698. // Ignore lower layer properties
  699. if (isDefaultProperty(name, properties)) continue;
  700. if (validOutputProperty(name)) {
  701. _properties.setProperty(name, properties.getProperty(name));
  702. }
  703. else {
  704. ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_UNKNOWN_PROP_ERR, name);
  705. throw new IllegalArgumentException(err.toString());
  706. }
  707. }
  708. }
  709. else {
  710. _properties = _propertiesClone;
  711. }
  712. }
  713. /**
  714. * Implements JAXP's Transformer.setOutputProperty().
  715. * Get an output property that is in effect for the transformation. The
  716. * property specified may be a property that was set with
  717. * setOutputProperty(), or it may be a property specified in the stylesheet.
  718. *
  719. * @param name The name of the property to set
  720. * @param value The value to assign to the property
  721. * @throws IllegalArgumentException Never, errors are ignored
  722. */
  723. public void setOutputProperty(String name, String value)
  724. throws IllegalArgumentException
  725. {
  726. if (!validOutputProperty(name)) {
  727. ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_UNKNOWN_PROP_ERR, name);
  728. throw new IllegalArgumentException(err.toString());
  729. }
  730. _properties.setProperty(name, value);
  731. }
  732. /**
  733. * Internal method to pass any properties to the translet prior to
  734. * initiating the transformation
  735. */
  736. private void transferOutputProperties(AbstractTranslet translet)
  737. {
  738. // Return right now if no properties are set
  739. if (_properties == null) return;
  740. // Get a list of all the defined properties
  741. Enumeration names = _properties.propertyNames();
  742. while (names.hasMoreElements()) {
  743. // Note the use of get() instead of getProperty()
  744. String name = (String) names.nextElement();
  745. String value = (String) _properties.get(name);
  746. // Ignore default properties
  747. if (value == null) continue;
  748. // Pass property value to translet - override previous setting
  749. if (name.equals(OutputKeys.ENCODING)) {
  750. translet._encoding = value;
  751. }
  752. else if (name.equals(OutputKeys.METHOD)) {
  753. translet._method = value;
  754. }
  755. else if (name.equals(OutputKeys.DOCTYPE_PUBLIC)) {
  756. translet._doctypePublic = value;
  757. }
  758. else if (name.equals(OutputKeys.DOCTYPE_SYSTEM)) {
  759. translet._doctypeSystem = value;
  760. }
  761. else if (name.equals(OutputKeys.MEDIA_TYPE)) {
  762. translet._mediaType = value;
  763. }
  764. else if (name.equals(OutputKeys.STANDALONE)) {
  765. translet._standalone = value;
  766. }
  767. else if (name.equals(OutputKeys.VERSION)) {
  768. translet._version = value;
  769. }
  770. else if (name.equals(OutputKeys.OMIT_XML_DECLARATION)) {
  771. translet._omitHeader =
  772. (value != null && value.toLowerCase().equals("yes"));
  773. }
  774. else if (name.equals(OutputKeys.INDENT)) {
  775. translet._indent =
  776. (value != null && value.toLowerCase().equals("yes"));
  777. }
  778. else if (name.equals(OutputKeys.CDATA_SECTION_ELEMENTS)) {
  779. if (value != null) {
  780. translet._cdata = null; // clear previous setting
  781. StringTokenizer e = new StringTokenizer(value);
  782. while (e.hasMoreTokens()) {
  783. translet.addCdataElement(e.nextToken());
  784. }
  785. }
  786. }
  787. }
  788. }
  789. /**
  790. * This method is used to pass any properties to the output handler
  791. * when running the identity transform.
  792. */
  793. public void transferOutputProperties(SerializationHandler handler)
  794. {
  795. // Return right now if no properties are set
  796. if (_properties == null) return;
  797. String doctypePublic = null;
  798. String doctypeSystem = null;
  799. // Get a list of all the defined properties
  800. Enumeration names = _properties.propertyNames();
  801. while (names.hasMoreElements()) {
  802. // Note the use of get() instead of getProperty()
  803. String name = (String) names.nextElement();
  804. String value = (String) _properties.get(name);
  805. // Ignore default properties
  806. if (value == null) continue;
  807. // Pass property value to translet - override previous setting
  808. if (name.equals(OutputKeys.DOCTYPE_PUBLIC)) {
  809. doctypePublic = value;
  810. }
  811. else if (name.equals(OutputKeys.DOCTYPE_SYSTEM)) {
  812. doctypeSystem = value;
  813. }
  814. else if (name.equals(OutputKeys.MEDIA_TYPE)) {
  815. handler.setMediaType(value);
  816. }
  817. else if (name.equals(OutputKeys.STANDALONE)) {
  818. handler.setStandalone(value);
  819. }
  820. else if (name.equals(OutputKeys.VERSION)) {
  821. handler.setVersion(value);
  822. }
  823. else if (name.equals(OutputKeys.OMIT_XML_DECLARATION)) {
  824. handler.setOmitXMLDeclaration(
  825. value != null && value.toLowerCase().equals("yes"));
  826. }
  827. else if (name.equals(OutputKeys.INDENT)) {
  828. handler.setIndent(
  829. value != null && value.toLowerCase().equals("yes"));
  830. }
  831. else if (name.equals(OutputKeys.CDATA_SECTION_ELEMENTS)) {
  832. if (value != null) {
  833. StringTokenizer e = new StringTokenizer(value);
  834. Vector uriAndLocalNames = null;
  835. while (e.hasMoreTokens()) {
  836. final String token = e.nextToken();
  837. // look for the last colon, as the String may be
  838. // something like "http://abc.com:local"
  839. int lastcolon = token.lastIndexOf(':');
  840. String uri;
  841. String localName;
  842. if (lastcolon > 0) {
  843. uri = token.substring(0, lastcolon);
  844. localName = token.substring(lastcolon+1);
  845. } else {
  846. // no colon at all, lets hope this is the
  847. // local name itself then
  848. uri = null;
  849. localName = token;
  850. }
  851. if (uriAndLocalNames == null) {
  852. uriAndLocalNames = new Vector();
  853. }
  854. // add the uri/localName as a pair, in that order
  855. uriAndLocalNames.addElement(uri);
  856. uriAndLocalNames.addElement(localName);
  857. }
  858. handler.setCdataSectionElements(uriAndLocalNames);
  859. }
  860. }
  861. }
  862. // Call setDoctype() if needed
  863. if (doctypePublic != null || doctypeSystem != null) {
  864. handler.setDoctype(doctypeSystem, doctypePublic);
  865. }
  866. }
  867. /**
  868. * Internal method to create the initial set of properties. There
  869. * are two layers of properties: the default layer and the base layer.
  870. * The latter contains properties defined in the stylesheet or by
  871. * the user using this API.
  872. */
  873. private Properties createOutputProperties(Properties outputProperties) {
  874. final Properties defaults = new Properties();
  875. setDefaults(defaults, "xml");
  876. // Copy propeties set in stylesheet to base
  877. final Properties base = new Properties(defaults);
  878. if (outputProperties != null) {
  879. final Enumeration names = outputProperties.propertyNames();
  880. while (names.hasMoreElements()) {
  881. final String name = (String) names.nextElement();
  882. base.setProperty(name, outputProperties.getProperty(name));
  883. }
  884. }
  885. else {
  886. base.setProperty(OutputKeys.ENCODING, _translet._encoding);
  887. if (_translet._method != null)
  888. base.setProperty(OutputKeys.METHOD, _translet._method);
  889. }
  890. // Update defaults based on output method
  891. final String method = base.getProperty(OutputKeys.METHOD);
  892. if (method != null) {
  893. if (method.equals("html")) {
  894. setDefaults(defaults,"html");
  895. }
  896. else if (method.equals("text")) {
  897. setDefaults(defaults,"text");
  898. }
  899. }
  900. return base;
  901. }
  902. /**
  903. * Internal method to get the default properties from the
  904. * serializer factory and set them on the property object.
  905. * @param props a java.util.Property object on which the properties are set.
  906. * @param method The output method type, one of "xml", "text", "html" ...
  907. */
  908. private void setDefaults(Properties props, String method)
  909. {
  910. final Properties method_props =
  911. OutputPropertiesFactory.getDefaultMethodProperties(method);
  912. {
  913. final Enumeration names = method_props.propertyNames();
  914. while (names.hasMoreElements())
  915. {
  916. final String name = (String)names.nextElement();
  917. props.setProperty(name, method_props.getProperty(name));
  918. }
  919. }
  920. }
  921. /**
  922. * Verifies if a given output property name is a property defined in
  923. * the JAXP 1.1 / TrAX spec
  924. */
  925. private boolean validOutputProperty(String name) {
  926. return (name.equals(OutputKeys.ENCODING) ||
  927. name.equals(OutputKeys.METHOD) ||
  928. name.equals(OutputKeys.INDENT) ||
  929. name.equals(OutputKeys.DOCTYPE_PUBLIC) ||
  930. name.equals(OutputKeys.DOCTYPE_SYSTEM) ||
  931. name.equals(OutputKeys.CDATA_SECTION_ELEMENTS) ||
  932. name.equals(OutputKeys.MEDIA_TYPE) ||
  933. name.equals(OutputKeys.OMIT_XML_DECLARATION) ||
  934. name.equals(OutputKeys.STANDALONE) ||
  935. name.equals(OutputKeys.VERSION) ||
  936. name.charAt(0) == '{');
  937. }
  938. /**
  939. * Checks if a given output property is default (2nd layer only)
  940. */
  941. private boolean isDefaultProperty(String name, Properties properties) {
  942. return (properties.get(name) == null);
  943. }
  944. /**
  945. * Implements JAXP's Transformer.setParameter()
  946. * Add a parameter for the transformation. The parameter is simply passed
  947. * on to the translet - no validation is performed - so any unused
  948. * parameters are quitely ignored by the translet.
  949. *
  950. * @param name The name of the parameter
  951. * @param value The value to assign to the parameter
  952. */
  953. public void setParameter(String name, Object value) {
  954. if (value == null) {
  955. ErrorMsg err = new ErrorMsg(ErrorMsg.JAXP_INVALID_SET_PARAM_VALUE, name);
  956. throw new IllegalArgumentException(err.toString());
  957. }
  958. if (_isIdentity) {
  959. if (_parameters == null) {
  960. _parameters = new Hashtable();
  961. }
  962. _parameters.put(name, value);
  963. }
  964. else {
  965. _translet.addParameter(name, value);
  966. }
  967. }
  968. /**
  969. * Implements JAXP's Transformer.clearParameters()
  970. * Clear all parameters set with setParameter. Clears the translet's
  971. * parameter stack.
  972. */
  973. public void clearParameters() {
  974. if (_isIdentity && _parameters != null) {
  975. _parameters.clear();
  976. }
  977. else {
  978. _translet.clearParameters();
  979. }
  980. }
  981. /**
  982. * Implements JAXP's Transformer.getParameter()
  983. * Returns the value of a given parameter. Note that the translet will not
  984. * keep values for parameters that were not defined in the stylesheet.
  985. *
  986. * @param name The name of the parameter
  987. * @return An object that contains the value assigned to the parameter
  988. */
  989. public final Object getParameter(String name) {
  990. if (_isIdentity) {
  991. return (_parameters != null) ? _parameters.get(name) : null;
  992. }
  993. else {
  994. return _translet.getParameter(name);
  995. }
  996. }
  997. /**
  998. * Implements JAXP's Transformer.getURIResolver()
  999. * Set the object currently used to resolve URIs used in document().
  1000. *
  1001. * @return The URLResolver object currently in use
  1002. */
  1003. public URIResolver getURIResolver() {
  1004. return _uriResolver;
  1005. }
  1006. /**
  1007. * Implements JAXP's Transformer.setURIResolver()
  1008. * Set an object that will be used to resolve URIs used in document().
  1009. *
  1010. * @param resolver The URIResolver to use in document()
  1011. */
  1012. public void setURIResolver(URIResolver resolver) {
  1013. _uriResolver = resolver;
  1014. }
  1015. /**
  1016. * This class should only be used as a DOMCache for the translet if the
  1017. * URIResolver has been set.
  1018. *
  1019. * The method implements XSLTC's DOMCache interface, which is used to
  1020. * plug in an external document loader into a translet. This method acts
  1021. * as an adapter between TrAX's URIResolver interface and XSLTC's
  1022. * DOMCache interface. This approach is simple, but removes the
  1023. * possibility of using external document caches with XSLTC.
  1024. *
  1025. * @param baseURI The base URI used by the document call.
  1026. * @param href The href argument passed to the document function.
  1027. * @param translet A reference to the translet requesting the document
  1028. */
  1029. public DOM retrieveDocument(String baseURI, String href, Translet translet) {
  1030. try {
  1031. // Argument to document function was: document('');
  1032. if (href.length() == 0) {
  1033. href = new String(baseURI);
  1034. }
  1035. /*
  1036. * Fix for bug 24188
  1037. * Incase the _uriResolver.resolve(href,base) is null
  1038. * try to still retrieve the document before returning null
  1039. * and throwing the FileNotFoundException in
  1040. * com.sun.org.apache.xalan.internal.xsltc.dom.LoadDocument
  1041. *
  1042. */
  1043. Source resolvedSource = _uriResolver.resolve(href, baseURI);
  1044. if (resolvedSource == null) {
  1045. StreamSource streamSource = new StreamSource(
  1046. SystemIDResolver.getAbsoluteURI(href, baseURI));
  1047. return getDOM(streamSource) ;
  1048. }
  1049. return getDOM(resolvedSource);
  1050. }
  1051. catch (TransformerException e) {
  1052. if (_errorListener != null)
  1053. postErrorToListener("File not found: " + e.getMessage());
  1054. return(null);
  1055. }
  1056. }
  1057. /**
  1058. * Receive notification of a recoverable error.
  1059. * The transformer must continue to provide normal parsing events after
  1060. * invoking this method. It should still be possible for the application
  1061. * to process the document through to the end.
  1062. *
  1063. * @param exception The warning information encapsulated in a transformer
  1064. * exception.
  1065. * @throws TransformerException if the application chooses to discontinue
  1066. * the transformation (always does in our case).
  1067. */
  1068. public void error(TransformerException e)
  1069. throws TransformerException
  1070. {
  1071. Throwable wrapped = e.getException();
  1072. if (wrapped != null) {
  1073. System.err.println(new ErrorMsg(ErrorMsg.ERROR_PLUS_WRAPPED_MSG,
  1074. e.getMessageAndLocation(),
  1075. wrapped.getMessage()));
  1076. } else {
  1077. System.err.println(new ErrorMsg(ErrorMsg.ERROR_MSG,
  1078. e.getMessageAndLocation()));
  1079. }
  1080. throw e;
  1081. }
  1082. /**
  1083. * Receive notification of a non-recoverable error.
  1084. * The application must assume that the transformation cannot continue
  1085. * after the Transformer has invoked this method, and should continue
  1086. * (if at all) only to collect addition error messages. In fact,
  1087. * Transformers are free to stop reporting events once this method has
  1088. * been invoked.
  1089. *
  1090. * @param exception The warning information encapsulated in a transformer
  1091. * exception.
  1092. * @throws TransformerException if the application chooses to discontinue
  1093. * the transformation (always does in our case).
  1094. */
  1095. public void fatalError(TransformerException e)
  1096. throws TransformerException
  1097. {
  1098. Throwable wrapped = e.getException();
  1099. if (wrapped != null) {
  1100. System.err.println(new ErrorMsg(ErrorMsg.FATAL_ERR_PLUS_WRAPPED_MSG,
  1101. e.getMessageAndLocation(),
  1102. wrapped.getMessage()));
  1103. } else {
  1104. System.err.println(new ErrorMsg(ErrorMsg.FATAL_ERR_MSG,
  1105. e.getMessageAndLocation()));
  1106. }
  1107. throw e;
  1108. }
  1109. /**
  1110. * Receive notification of a warning.
  1111. * Transformers can use this method to report conditions that are not
  1112. * errors or fatal errors. The default behaviour is to take no action.
  1113. * After invoking this method, the Transformer must continue with the
  1114. * transformation. It should still be possible for the application to
  1115. * process the document through to the end.
  1116. *
  1117. * @param exception The warning information encapsulated in a transformer
  1118. * exception.
  1119. * @throws TransformerException if the application chooses to discontinue
  1120. * the transformation (never does in our case).
  1121. */
  1122. public void warning(TransformerException e)
  1123. throws TransformerException
  1124. {
  1125. Throwable wrapped = e.getException();
  1126. if (wrapped != null) {
  1127. System.err.println(new ErrorMsg(ErrorMsg.WARNING_PLUS_WRAPPED_MSG,
  1128. e.getMessageAndLocation(),
  1129. wrapped.getMessage()));
  1130. } else {
  1131. System.err.println(new ErrorMsg(ErrorMsg.WARNING_MSG,
  1132. e.getMessageAndLocation()));
  1133. }
  1134. }
  1135. /**
  1136. * This method resets the Transformer to its original configuration
  1137. * Transformer code is reset to the same state it was when it was
  1138. * created
  1139. * @since 1.5
  1140. */
  1141. public void reset() {
  1142. _method = null;
  1143. _encoding = null;
  1144. _sourceSystemId = null;
  1145. _errorListener = this;
  1146. _uriResolver = null;
  1147. _dom = null;
  1148. _parameters = null;
  1149. _indentNumber = 0;
  1150. setOutputProperties (null);
  1151. }
  1152. }