1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 1999 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xalan" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, Lotus
  53. * Development Corporation., http://www.lotus.com. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package org.apache.xalan.templates;
  58. import java.io.BufferedInputStream;
  59. import java.io.InputStream;
  60. import java.io.IOException;
  61. import java.util.Vector;
  62. import java.util.Hashtable;
  63. import java.util.Properties;
  64. import java.util.Enumeration;
  65. import java.lang.Cloneable;
  66. import java.security.AccessController;
  67. import java.security.PrivilegedAction;
  68. import org.w3c.dom.Document;
  69. import org.apache.xml.utils.QName;
  70. import org.apache.xml.utils.FastStringBuffer;
  71. import org.apache.xml.utils.WrappedRuntimeException;
  72. import org.apache.xalan.serialize.Method;
  73. import org.apache.xalan.extensions.ExtensionHandler;
  74. import org.apache.xalan.res.XSLTErrorResources;
  75. import org.apache.xalan.res.XSLMessages;
  76. import javax.xml.transform.TransformerException;
  77. import javax.xml.transform.OutputKeys;
  78. /**
  79. * This class provides information from xsl:output elements. It is mainly
  80. * a wrapper for {@link java.util.Properties}, but can not extend that class
  81. * because it must be part of the {@link org.apache.xalan.templates.ElemTemplateElement}
  82. * heararchy.
  83. * <p>An OutputProperties list can contain another OutputProperties list as
  84. * its "defaults"; this second property list is searched if the property key
  85. * is not found in the original property list.</p>
  86. * @see <a href="http://www.w3.org/TR/xslt#dtd">XSLT DTD</a>
  87. * @see <a href="http://www.w3.org/TR/xslt#output">xsl:output in XSLT Specification</a>
  88. * @
  89. */
  90. public class OutputProperties extends ElemTemplateElement
  91. implements Cloneable
  92. {
  93. /**
  94. * Creates an empty OutputProperties with no default values.
  95. */
  96. public OutputProperties()
  97. {
  98. this(Method.XML);
  99. }
  100. /**
  101. * Creates an empty OutputProperties with the specified defaults.
  102. *
  103. * @param defaults the defaults.
  104. */
  105. public OutputProperties(Properties defaults)
  106. {
  107. m_properties = new Properties(defaults);
  108. }
  109. /**
  110. * Creates an empty OutputProperties with the defaults specified by
  111. * a property file. The method argument is used to construct a string of
  112. * the form output_[method].properties (for instance, output_html.properties).
  113. * The output_xml.properties file is always used as the base.
  114. * <p>At the moment, anything other than 'text', 'xml', and 'html', will
  115. * use the output_xml.properties file.</p>
  116. *
  117. * @param method non-null reference to method name.
  118. */
  119. public OutputProperties(String method)
  120. {
  121. m_properties = new Properties(getDefaultMethodProperties(method));
  122. }
  123. static final String S_XSLT_PREFIX = "xslt.output.";
  124. static final int S_XSLT_PREFIX_LEN = S_XSLT_PREFIX.length();
  125. static final String S_XALAN_PREFIX = "org.apache.xslt.";
  126. static final int S_XALAN_PREFIX_LEN = S_XALAN_PREFIX.length();
  127. /** Built-in extensions namespace, reexpressed in {namespaceURI} syntax
  128. * suitable for prepending to a localname to produce a "universal
  129. * name".
  130. */
  131. static final String S_BUILTIN_EXTENSIONS_UNIVERSAL=
  132. "{"+Constants.S_BUILTIN_EXTENSIONS_URL+"}";
  133. /**
  134. * The old built-in extension namespace
  135. */
  136. static final String S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL=
  137. "{"+Constants.S_BUILTIN_OLD_EXTENSIONS_URL+"}";
  138. static final int S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN = S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL.length();
  139. /**
  140. * Fix up a string in an output properties file according to
  141. * the rules of {@link #loadPropertiesFile}.
  142. *
  143. * @param s non-null reference to string that may need to be fixed up.
  144. * @return A new string if fixup occured, otherwise the s argument.
  145. */
  146. static private String fixupPropertyString(String s, boolean doClipping)
  147. {
  148. int index;
  149. if (doClipping && s.startsWith(S_XSLT_PREFIX))
  150. {
  151. s = s.substring(S_XSLT_PREFIX_LEN);
  152. }
  153. if (s.startsWith(S_XALAN_PREFIX))
  154. {
  155. s = S_BUILTIN_EXTENSIONS_UNIVERSAL + s.substring(S_XALAN_PREFIX_LEN);
  156. }
  157. if ((index = s.indexOf("\\u003a")) > 0)
  158. {
  159. String temp = s.substring(index+6);
  160. s = s.substring(0, index) + ":" + temp;
  161. }
  162. return s;
  163. }
  164. /**
  165. * Load the properties file from a resource stream. If a
  166. * key name such as "org.apache.xslt.xxx", fix up the start of
  167. * string to be a curly namespace. If a key name starts with
  168. * "xslt.output.xxx", clip off "xslt.output.". If a key name *or* a
  169. * key value is discovered, check for \u003a in the text, and
  170. * fix it up to be ":", since earlier versions of the JDK do not
  171. * handle the escape sequence (at least in key names).
  172. *
  173. * @param resourceName non-null reference to resource name.
  174. * @param defaults Default properties, which may be null.
  175. */
  176. static private Properties loadPropertiesFile(final String resourceName, Properties defaults)
  177. throws IOException
  178. {
  179. // This static method should eventually be moved to a thread-specific class
  180. // so that we can cache the ContextClassLoader and bottleneck all properties file
  181. // loading throughout Xalan.
  182. Properties props = new Properties(defaults);
  183. InputStream is = null;
  184. BufferedInputStream bis = null;
  185. try {
  186. try {
  187. // Using doPrivileged to be able to read property file without opening
  188. // up secured container permissions like J2EE container
  189. is =(InputStream)AccessController.doPrivileged( new PrivilegedAction() {
  190. public Object run() {
  191. try {
  192. java.lang.reflect.Method getCCL = Thread.class.getMethod(
  193. "getContextClassLoader", NO_CLASSES);
  194. if (getCCL != null) {
  195. ClassLoader contextClassLoader = (ClassLoader)
  196. getCCL.invoke(Thread.currentThread(), NO_OBJS);
  197. return ( contextClassLoader.getResourceAsStream (
  198. "org/apache/xalan/templates/" + resourceName) );
  199. }
  200. }
  201. catch ( Exception e ) { }
  202. return null;
  203. }
  204. });
  205. }
  206. catch (Exception e) {}
  207. if ( is == null ) {
  208. is = (InputStream)AccessController.doPrivileged( new PrivilegedAction(){
  209. public Object run() {
  210. return OutputProperties.class.getResourceAsStream(resourceName);
  211. }
  212. });
  213. }
  214. bis = new BufferedInputStream(is);
  215. props.load(bis);
  216. }
  217. catch (IOException ioe) {
  218. if ( defaults == null ) {
  219. throw ioe;
  220. }
  221. else {
  222. throw new WrappedRuntimeException(XSLMessages.createMessage(XSLTErrorResources.ER_COULD_NOT_LOAD_RESOURCE, new Object[]{resourceName}), ioe); //"Could not load '"+resourceName+"' (check CLASSPATH), now using just the defaults ", ioe);
  223. }
  224. }
  225. catch (SecurityException se) {
  226. // Repeat IOException handling for sandbox/applet case -sc
  227. if ( defaults == null ) {
  228. throw se;
  229. }
  230. else {
  231. throw new WrappedRuntimeException(XSLMessages.createMessage(XSLTErrorResources.ER_COULD_NOT_LOAD_RESOURCE, new Object[]{resourceName}), se); //"Could not load '"+resourceName+"' (check CLASSPATH, applet security), now using just the defaults ", se);
  232. }
  233. }
  234. finally {
  235. if ( bis != null ) {
  236. bis.close();
  237. }
  238. if (is != null ) {
  239. is.close();
  240. }
  241. }
  242. // Note that we're working at the HashTable level here,
  243. // and not at the Properties level! This is important
  244. // because we don't want to modify the default properties.
  245. // NB: If fixupPropertyString ends up changing the property
  246. // name or value, we need to remove the old key and re-add
  247. // with the new key and value. However, then our Enumeration
  248. // could lose its place in the HashTable. So, we first
  249. // clone the HashTable and enumerate over that since the
  250. // clone will not change. When we migrate to Collections,
  251. // this code should be revisited and cleaned up to use
  252. // an Iterator which may (or may not) alleviate the need for
  253. // the clone. Many thanks to Padraig O'hIceadha
  254. // <padraig@gradient.ie> for finding this problem. Bugzilla 2000.
  255. Enumeration keys = ((Properties) props.clone()).keys();
  256. while(keys.hasMoreElements())
  257. {
  258. String key = (String)keys.nextElement();
  259. // Now check if the given key was specified as a
  260. // System property. If so, the system property
  261. // overides the default value in the propery file.
  262. String value = null;
  263. try {
  264. value = System.getProperty(key);
  265. }
  266. catch (SecurityException se) {
  267. // No-op for sandbox/applet case, leave null -sc
  268. }
  269. if (value == null)
  270. value = (String)props.get(key);
  271. String newKey = fixupPropertyString(key, true);
  272. String newValue = null;
  273. try {
  274. newValue = System.getProperty(newKey);
  275. }
  276. catch (SecurityException se) {
  277. // No-op for sandbox/applet case, leave null -sc
  278. }
  279. if (newValue == null)
  280. newValue = fixupPropertyString(value, false);
  281. else
  282. newValue = fixupPropertyString(newValue, false);
  283. if(key != newKey || value != newValue)
  284. {
  285. props.remove(key);
  286. props.put(newKey, newValue);
  287. }
  288. }
  289. return props;
  290. }
  291. /**
  292. * Creates an empty OutputProperties with the defaults specified by
  293. * a property file. The method argument is used to construct a string of
  294. * the form output_[method].properties (for instance, output_html.properties).
  295. * The output_xml.properties file is always used as the base.
  296. * <p>At the moment, anything other than 'text', 'xml', and 'html', will
  297. * use the output_xml.properties file.</p>
  298. *
  299. * @param method non-null reference to method name.
  300. *
  301. * @return Properties object that holds the defaults for the given method.
  302. */
  303. static public Properties getDefaultMethodProperties(String method)
  304. {
  305. String fileName = null;
  306. Properties defaultProperties = null;
  307. // According to this article : Double-check locking does not work
  308. // http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-toolbox.html
  309. try
  310. {
  311. synchronized (m_synch_object)
  312. {
  313. if (null == m_xml_properties) // double check
  314. {
  315. fileName = "output_xml.properties";
  316. m_xml_properties = loadPropertiesFile(fileName, null);
  317. }
  318. }
  319. if (method.equals(Method.XML))
  320. {
  321. defaultProperties = m_xml_properties;
  322. }
  323. else if (method.equals(Method.HTML))
  324. {
  325. if (null == m_html_properties) // double check
  326. {
  327. fileName = "output_html.properties";
  328. m_html_properties = loadPropertiesFile(fileName,
  329. m_xml_properties);
  330. }
  331. defaultProperties = m_html_properties;
  332. }
  333. else if (method.equals(Method.Text))
  334. {
  335. if (null == m_text_properties) // double check
  336. {
  337. fileName = "output_text.properties";
  338. m_text_properties = loadPropertiesFile(fileName,
  339. m_xml_properties);
  340. if(null == m_text_properties.getProperty(OutputKeys.ENCODING))
  341. {
  342. String mimeEncoding = org.apache.xalan.serialize.Encodings.getMimeEncoding(null);
  343. m_text_properties.put(OutputKeys.ENCODING, mimeEncoding);
  344. }
  345. }
  346. defaultProperties = m_text_properties;
  347. }
  348. else
  349. {
  350. // TODO: Calculate res file from name.
  351. defaultProperties = m_xml_properties;
  352. }
  353. }
  354. catch (IOException ioe)
  355. {
  356. throw new WrappedRuntimeException(
  357. "Output method is "+method+" could not load "+fileName+" (check CLASSPATH)",
  358. ioe);
  359. }
  360. return defaultProperties;
  361. }
  362. /**
  363. * Clone this OutputProperties, including a clone of the wrapped Properties
  364. * reference.
  365. *
  366. * @return A new OutputProperties reference, mutation of which should not
  367. * effect this object.
  368. */
  369. public Object clone()
  370. {
  371. try
  372. {
  373. OutputProperties cloned = (OutputProperties) super.clone();
  374. cloned.m_properties = (Properties) cloned.m_properties.clone();
  375. return cloned;
  376. }
  377. catch (CloneNotSupportedException e)
  378. {
  379. return null;
  380. }
  381. }
  382. /**
  383. * Set an output property.
  384. *
  385. * @param key the key to be placed into the property list.
  386. * @param value the value corresponding to <tt>key</tt>.
  387. * @see javax.xml.transform.OutputKeys
  388. */
  389. public void setProperty(QName key, String value)
  390. {
  391. setProperty(key.toNamespacedString(), value);
  392. }
  393. /**
  394. * Set an output property.
  395. *
  396. * @param key the key to be placed into the property list.
  397. * @param value the value corresponding to <tt>key</tt>.
  398. * @see javax.xml.transform.OutputKeys
  399. */
  400. public void setProperty(String key, String value)
  401. {
  402. if(key.equals(OutputKeys.METHOD))
  403. {
  404. setMethodDefaults(value);
  405. }
  406. if (key.startsWith(S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL))
  407. key = S_BUILTIN_EXTENSIONS_UNIVERSAL + key.substring(S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN);
  408. m_properties.put(key, value);
  409. }
  410. /**
  411. * Searches for the property with the specified key in the property list.
  412. * If the key is not found in this property list, the default property list,
  413. * and its defaults, recursively, are then checked. The method returns
  414. * <code>null</code> if the property is not found.
  415. *
  416. * @param key the property key.
  417. * @return the value in this property list with the specified key value.
  418. */
  419. public String getProperty(QName key)
  420. {
  421. return m_properties.getProperty(key.toNamespacedString());
  422. }
  423. /**
  424. * Searches for the property with the specified key in the property list.
  425. * If the key is not found in this property list, the default property list,
  426. * and its defaults, recursively, are then checked. The method returns
  427. * <code>null</code> if the property is not found.
  428. *
  429. * @param key the property key.
  430. * @return the value in this property list with the specified key value.
  431. */
  432. public String getProperty(String key)
  433. {
  434. if (key.startsWith(S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL))
  435. key = S_BUILTIN_EXTENSIONS_UNIVERSAL + key.substring(S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN);
  436. return m_properties.getProperty(key);
  437. }
  438. /**
  439. * Set an output property.
  440. *
  441. * @param key the key to be placed into the property list.
  442. * @param value the value corresponding to <tt>key</tt>.
  443. * @see javax.xml.transform.OutputKeys
  444. */
  445. public void setBooleanProperty(QName key, boolean value)
  446. {
  447. m_properties.put(key.toNamespacedString(), value ? "yes" : "no");
  448. }
  449. /**
  450. * Set an output property.
  451. *
  452. * @param key the key to be placed into the property list.
  453. * @param value the value corresponding to <tt>key</tt>.
  454. * @see javax.xml.transform.OutputKeys
  455. */
  456. public void setBooleanProperty(String key, boolean value)
  457. {
  458. m_properties.put(key, value ? "yes" : "no");
  459. }
  460. /**
  461. * Searches for the boolean property with the specified key in the property list.
  462. * If the key is not found in this property list, the default property list,
  463. * and its defaults, recursively, are then checked. The method returns
  464. * <code>false</code> if the property is not found, or if the value is other
  465. * than "yes".
  466. *
  467. * @param key the property key.
  468. * @return the value in this property list as a boolean value, or false
  469. * if null or not "yes".
  470. */
  471. public boolean getBooleanProperty(QName key)
  472. {
  473. return getBooleanProperty(key.toNamespacedString());
  474. }
  475. /**
  476. * Searches for the boolean property with the specified key in the property list.
  477. * If the key is not found in this property list, the default property list,
  478. * and its defaults, recursively, are then checked. The method returns
  479. * <code>false</code> if the property is not found, or if the value is other
  480. * than "yes".
  481. *
  482. * @param key the property key.
  483. * @return the value in this property list as a boolean value, or false
  484. * if null or not "yes".
  485. */
  486. public boolean getBooleanProperty(String key)
  487. {
  488. return getBooleanProperty(key, m_properties);
  489. }
  490. /**
  491. * Searches for the boolean property with the specified key in the property list.
  492. * If the key is not found in this property list, the default property list,
  493. * and its defaults, recursively, are then checked. The method returns
  494. * <code>false</code> if the property is not found, or if the value is other
  495. * than "yes".
  496. *
  497. * @param key the property key.
  498. * @param props the list of properties that will be searched.
  499. * @return the value in this property list as a boolean value, or false
  500. * if null or not "yes".
  501. */
  502. public static boolean getBooleanProperty(String key, Properties props)
  503. {
  504. String s = props.getProperty(key);
  505. if (null == s ||!s.equals("yes"))
  506. return false;
  507. else
  508. return true;
  509. }
  510. /**
  511. * Set an output property.
  512. *
  513. * @param key the key to be placed into the property list.
  514. * @param value the value corresponding to <tt>key</tt>.
  515. * @see javax.xml.transform.OutputKeys
  516. */
  517. public void setIntProperty(QName key, int value)
  518. {
  519. setIntProperty(key.toNamespacedString(), value);
  520. }
  521. /**
  522. * Set an output property.
  523. *
  524. * @param key the key to be placed into the property list.
  525. * @param value the value corresponding to <tt>key</tt>.
  526. * @see javax.xml.transform.OutputKeys
  527. */
  528. public void setIntProperty(String key, int value)
  529. {
  530. m_properties.put(key, Integer.toString(value));
  531. }
  532. /**
  533. * Searches for the int property with the specified key in the property list.
  534. * If the key is not found in this property list, the default property list,
  535. * and its defaults, recursively, are then checked. The method returns
  536. * <code>false</code> if the property is not found, or if the value is other
  537. * than "yes".
  538. *
  539. * @param key the property key.
  540. * @return the value in this property list as a int value, or false
  541. * if null or not a number.
  542. */
  543. public int getIntProperty(QName key)
  544. {
  545. return getIntProperty(key.toNamespacedString());
  546. }
  547. /**
  548. * Searches for the int property with the specified key in the property list.
  549. * If the key is not found in this property list, the default property list,
  550. * and its defaults, recursively, are then checked. The method returns
  551. * <code>false</code> if the property is not found, or if the value is other
  552. * than "yes".
  553. *
  554. * @param key the property key.
  555. * @return the value in this property list as a int value, or false
  556. * if null or not a number.
  557. */
  558. public int getIntProperty(String key)
  559. {
  560. return getIntProperty(key, m_properties);
  561. }
  562. /**
  563. * Searches for the int property with the specified key in the property list.
  564. * If the key is not found in this property list, the default property list,
  565. * and its defaults, recursively, are then checked. The method returns
  566. * <code>false</code> if the property is not found, or if the value is other
  567. * than "yes".
  568. *
  569. * @param key the property key.
  570. * @param props the list of properties that will be searched.
  571. * @return the value in this property list as a int value, or 0
  572. * if null or not a number.
  573. */
  574. public static int getIntProperty(String key, Properties props)
  575. {
  576. String s = props.getProperty(key);
  577. if (null == s)
  578. return 0;
  579. else
  580. return Integer.parseInt(s);
  581. }
  582. /**
  583. * Set an output property with a QName value. The QName will be turned
  584. * into a string with the namespace in curly brackets.
  585. *
  586. * @param key the key to be placed into the property list.
  587. * @param value the value corresponding to <tt>key</tt>.
  588. * @see javax.xml.transform.OutputKeys
  589. */
  590. public void setQNameProperty(QName key, QName value)
  591. {
  592. setQNameProperty(key.toNamespacedString(), value);
  593. }
  594. /**
  595. * Reset the default properties based on the method.
  596. *
  597. * @param method the method value.
  598. * @see javax.xml.transform.OutputKeys
  599. */
  600. public void setMethodDefaults(String method)
  601. {
  602. String defaultMethod = m_properties.getProperty(OutputKeys.METHOD);
  603. if((null == defaultMethod) || !defaultMethod.equals(method))
  604. {
  605. Properties savedProps = m_properties;
  606. Properties newDefaults = getDefaultMethodProperties(method);
  607. m_properties = new Properties(newDefaults);
  608. copyFrom(savedProps, false);
  609. }
  610. }
  611. /**
  612. * Set an output property with a QName value. The QName will be turned
  613. * into a string with the namespace in curly brackets.
  614. *
  615. * @param key the key to be placed into the property list.
  616. * @param value the value corresponding to <tt>key</tt>.
  617. * @see javax.xml.transform.OutputKeys
  618. */
  619. public void setQNameProperty(String key, QName value)
  620. {
  621. setProperty(key, value.toNamespacedString());
  622. }
  623. /**
  624. * Searches for the qname property with the specified key in the property list.
  625. * If the key is not found in this property list, the default property list,
  626. * and its defaults, recursively, are then checked. The method returns
  627. * <code>null</code> if the property is not found.
  628. *
  629. * @param key the property key.
  630. * @return the value in this property list as a QName value, or false
  631. * if null or not "yes".
  632. */
  633. public QName getQNameProperty(QName key)
  634. {
  635. return getQNameProperty(key.toNamespacedString());
  636. }
  637. /**
  638. * Searches for the qname property with the specified key in the property list.
  639. * If the key is not found in this property list, the default property list,
  640. * and its defaults, recursively, are then checked. The method returns
  641. * <code>null</code> if the property is not found.
  642. *
  643. * @param key the property key.
  644. * @return the value in this property list as a QName value, or false
  645. * if null or not "yes".
  646. */
  647. public QName getQNameProperty(String key)
  648. {
  649. return getQNameProperty(key, m_properties);
  650. }
  651. /**
  652. * Searches for the qname property with the specified key in the property list.
  653. * If the key is not found in this property list, the default property list,
  654. * and its defaults, recursively, are then checked. The method returns
  655. * <code>null</code> if the property is not found.
  656. *
  657. * @param key the property key.
  658. * @param props the list of properties to search in.
  659. * @return the value in this property list as a QName value, or false
  660. * if null or not "yes".
  661. */
  662. public static QName getQNameProperty(String key, Properties props)
  663. {
  664. String s = props.getProperty(key);
  665. if (null != s)
  666. return QName.getQNameFromString(s);
  667. else
  668. return null;
  669. }
  670. /**
  671. * Set an output property with a QName list value. The QNames will be turned
  672. * into strings with the namespace in curly brackets.
  673. *
  674. * @param key the key to be placed into the property list.
  675. * @param v non-null list of QNames corresponding to <tt>key</tt>.
  676. * @see javax.xml.transform.OutputKeys
  677. */
  678. public void setQNameProperties(QName key, Vector v)
  679. {
  680. setQNameProperties(key.toNamespacedString(), v);
  681. }
  682. /**
  683. * Set an output property with a QName list value. The QNames will be turned
  684. * into strings with the namespace in curly brackets.
  685. *
  686. * @param key the key to be placed into the property list.
  687. * @param v non-null list of QNames corresponding to <tt>key</tt>.
  688. * @see javax.xml.transform.OutputKeys
  689. */
  690. public void setQNameProperties(String key, Vector v)
  691. {
  692. int s = v.size();
  693. // Just an initial guess at reasonable tuning parameters
  694. FastStringBuffer fsb = new FastStringBuffer(9,9);
  695. for (int i = 0; i < s; i++)
  696. {
  697. QName qname = (QName) v.elementAt(i);
  698. fsb.append(qname.toNamespacedString());
  699. // Don't append space after last value
  700. if (i < s-1)
  701. fsb.append(' ');
  702. }
  703. m_properties.put(key, fsb.toString());
  704. }
  705. /**
  706. * Searches for the list of qname properties with the specified key in
  707. * the property list.
  708. * If the key is not found in this property list, the default property list,
  709. * and its defaults, recursively, are then checked. The method returns
  710. * <code>null</code> if the property is not found.
  711. *
  712. * @param key the property key.
  713. * @return the value in this property list as a vector of QNames, or false
  714. * if null or not "yes".
  715. */
  716. public Vector getQNameProperties(QName key)
  717. {
  718. return getQNameProperties(key.toNamespacedString());
  719. }
  720. /**
  721. * Searches for the list of qname properties with the specified key in
  722. * the property list.
  723. * If the key is not found in this property list, the default property list,
  724. * and its defaults, recursively, are then checked. The method returns
  725. * <code>null</code> if the property is not found.
  726. *
  727. * @param key the property key.
  728. * @return the value in this property list as a vector of QNames, or false
  729. * if null or not "yes".
  730. */
  731. public Vector getQNameProperties(String key)
  732. {
  733. return getQNameProperties(key, m_properties);
  734. }
  735. /**
  736. * Searches for the list of qname properties with the specified key in
  737. * the property list.
  738. * If the key is not found in this property list, the default property list,
  739. * and its defaults, recursively, are then checked. The method returns
  740. * <code>null</code> if the property is not found.
  741. *
  742. * @param key the property key.
  743. * @param props the list of properties to search in.
  744. * @return the value in this property list as a vector of QNames, or false
  745. * if null or not "yes".
  746. */
  747. public static Vector getQNameProperties(String key, Properties props)
  748. {
  749. String s = props.getProperty(key);
  750. if (null != s)
  751. {
  752. Vector v = new Vector();
  753. int l = s.length();
  754. boolean inCurly = false;
  755. FastStringBuffer buf = new FastStringBuffer();
  756. // parse through string, breaking on whitespaces. I do this instead
  757. // of a tokenizer so I can track whitespace inside of curly brackets,
  758. // which theoretically shouldn't happen if they contain legal URLs.
  759. for (int i = 0; i < l; i++)
  760. {
  761. char c = s.charAt(i);
  762. if (Character.isWhitespace(c))
  763. {
  764. if (!inCurly)
  765. {
  766. if (buf.length() > 0)
  767. {
  768. QName qname = QName.getQNameFromString(buf.toString());
  769. v.addElement(qname);
  770. buf.reset();
  771. }
  772. continue;
  773. }
  774. }
  775. else if ('{' == c)
  776. inCurly = true;
  777. else if ('}' == c)
  778. inCurly = false;
  779. buf.append(c);
  780. }
  781. if (buf.length() > 0)
  782. {
  783. QName qname = QName.getQNameFromString(buf.toString());
  784. v.addElement(qname);
  785. buf.reset();
  786. }
  787. return v;
  788. }
  789. else
  790. return null;
  791. }
  792. /**
  793. * This function is called to recompose all of the output format extended elements.
  794. *
  795. * @param root non-null reference to the stylesheet root object.
  796. */
  797. public void recompose(StylesheetRoot root)
  798. throws TransformerException
  799. {
  800. root.recomposeOutput(this);
  801. }
  802. /**
  803. * This function is called after everything else has been
  804. * recomposed, and allows the template to set remaining
  805. * values that may be based on some other property that
  806. * depends on recomposition.
  807. */
  808. public void compose(StylesheetRoot sroot) throws TransformerException
  809. {
  810. super.compose(sroot);
  811. m_propertiesLevels = null;
  812. }
  813. /**
  814. * Get the Properties object that this class wraps.
  815. *
  816. * @return non-null reference to Properties object.
  817. */
  818. public Properties getProperties()
  819. {
  820. return m_properties;
  821. }
  822. /**
  823. * Copy the keys and values from the source to this object. This will
  824. * not copy the default values. This is meant to be used by going from
  825. * a higher precedence object to a lower precedence object, so that if a
  826. * key already exists, this method will not reset it.
  827. *
  828. * @param src non-null reference to the source properties.
  829. */
  830. public void copyFrom(Properties src)
  831. {
  832. copyFrom(src, true);
  833. }
  834. /**
  835. * Copy the keys and values from the source to this object. This will
  836. * not copy the default values. This is meant to be used by going from
  837. * a higher precedence object to a lower precedence object, so that if a
  838. * key already exists, this method will not reset it.
  839. *
  840. * @param src non-null reference to the source properties.
  841. * @param shouldResetDefaults true if the defaults should be reset based on
  842. * the method property.
  843. */
  844. public void copyFrom(Properties src, boolean shouldResetDefaults)
  845. {
  846. Enumeration enum = src.keys();
  847. while (enum.hasMoreElements())
  848. {
  849. String key = (String) enum.nextElement();
  850. if (!isLegalPropertyKey(key))
  851. throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{key})); //"output property not recognized: "
  852. Object oldValue = m_properties.get(key);
  853. if (null == oldValue)
  854. {
  855. String val = (String) src.get(key);
  856. if(shouldResetDefaults && key.equals(OutputKeys.METHOD))
  857. {
  858. setMethodDefaults(val);
  859. }
  860. m_properties.put(key, val);
  861. }
  862. else if (key.equals(OutputKeys.CDATA_SECTION_ELEMENTS))
  863. {
  864. m_properties.put(key, (String) oldValue + " " + (String) src.get(key));
  865. }
  866. }
  867. }
  868. /**
  869. * Copy the keys and values from the source to this object. This will
  870. * not copy the default values. This is meant to be used by going from
  871. * a higher precedence object to a lower precedence object, so that if a
  872. * key already exists, this method will not reset it.
  873. *
  874. * @param opsrc non-null reference to an OutputProperties.
  875. */
  876. public void copyFrom(OutputProperties opsrc)
  877. throws TransformerException
  878. {
  879. checkDuplicates(opsrc);
  880. copyFrom(opsrc.getProperties());
  881. }
  882. /**
  883. * Check to see if a set of properties is at the same import level as the
  884. * last set of properties set that was passed as an argument to this method.
  885. * This operation assumes that the OutputProperties are being called
  886. * from most important to least important, in document order.
  887. *
  888. * @param newProps non-null reference to OutputProperties that is about to
  889. * be added to this set.
  890. */
  891. private void checkDuplicates(OutputProperties newProps)
  892. throws TransformerException
  893. {
  894. if (null == m_propertiesLevels)
  895. m_propertiesLevels = new Hashtable();
  896. // This operation assumes that the OutputProperties are being called
  897. // from most important to least important, in reverse document order.
  898. int newPrecedence = newProps.getStylesheetComposed().getImportCountComposed();
  899. Properties p = newProps.getProperties();
  900. Enumeration enum = p.keys();
  901. while (enum.hasMoreElements())
  902. {
  903. String key = (String) enum.nextElement();
  904. if (key.equals(OutputKeys.CDATA_SECTION_ELEMENTS))
  905. continue;
  906. // Do we already have this property? Call hashtable operation,
  907. // since we don't want to look at default properties.
  908. Integer oldPrecedence = (Integer) m_propertiesLevels.get(key);
  909. if (null == oldPrecedence)
  910. {
  911. m_propertiesLevels.put(key, new Integer(newPrecedence));
  912. }
  913. else if (newPrecedence >= oldPrecedence.intValue())
  914. {
  915. String oldValue = (String) this.m_properties.get(key);
  916. String newValue = (String) newProps.m_properties.get(key);
  917. if ( ((oldValue == null) && (newValue != null)) || !oldValue.equals(newValue) )
  918. {
  919. String msg = key + " can not be multiply defined at the same "
  920. + "import level! Old value = "
  921. + oldValue + "; New value = " + newValue;
  922. throw new TransformerException(msg, newProps);
  923. }
  924. }
  925. }
  926. }
  927. /**
  928. * Report if the key given as an argument is a legal xsl:output key.
  929. *
  930. * @param key non-null reference to key name.
  931. *
  932. * @return true if key is legal.
  933. */
  934. public boolean isLegalPropertyKey(String key)
  935. {
  936. return (key.equals(OutputKeys.CDATA_SECTION_ELEMENTS)
  937. || key.equals(OutputKeys.DOCTYPE_PUBLIC)
  938. || key.equals(OutputKeys.DOCTYPE_SYSTEM)
  939. || key.equals(OutputKeys.ENCODING)
  940. || key.equals(OutputKeys.INDENT)
  941. || key.equals(OutputKeys.MEDIA_TYPE)
  942. || key.equals(OutputKeys.METHOD)
  943. || key.equals(OutputKeys.OMIT_XML_DECLARATION)
  944. || key.equals(OutputKeys.STANDALONE)
  945. || key.equals(OutputKeys.VERSION)
  946. || (key.length() > 0) && (key.charAt(0) == '{'));
  947. }
  948. /**
  949. * This ugly field is used during recomposition to track the import precedence
  950. * at which each attribute was first specified, so we can flag errors about values being
  951. * set multiple time at the same precedence level. Note that this field is only used
  952. * during recomposition, with the OutputProperties object owned by the
  953. * {@link org.apache.xalan.templates.StylesheetRoot} object.
  954. */
  955. private transient Hashtable m_propertiesLevels;
  956. /** The output properties.
  957. * @serial */
  958. private Properties m_properties = null;
  959. // Some special Xalan keys.
  960. /** The number of whitespaces to indent by, if indent="yes". */
  961. public static String S_KEY_INDENT_AMOUNT =
  962. S_BUILTIN_EXTENSIONS_UNIVERSAL+"indent-amount";
  963. /**
  964. * Fully qualified name of class with a default constructor that
  965. * implements the ContentHandler interface, where the result tree events
  966. * will be sent to.
  967. */
  968. public static String S_KEY_CONTENT_HANDLER =
  969. S_BUILTIN_EXTENSIONS_UNIVERSAL+"content-handler";
  970. /** File name of file that specifies character to entity reference mappings. */
  971. public static String S_KEY_ENTITIES =
  972. S_BUILTIN_EXTENSIONS_UNIVERSAL+"entities";
  973. /** Use a value of "yes" if the href values for HTML serialization should
  974. * use %xx escaping. */
  975. public static String S_USE_URL_ESCAPING =
  976. S_BUILTIN_EXTENSIONS_UNIVERSAL+"use-url-escaping";
  977. /** Use a value of "yes" if the META tag should be omitted where it would
  978. * otherwise be supplied.
  979. */
  980. public static String S_OMIT_META_TAG =
  981. S_BUILTIN_EXTENSIONS_UNIVERSAL+"omit-meta-tag";
  982. /** The default properties of all output files. */
  983. private static Properties m_xml_properties = null;
  984. /** The default properties when method="html". */
  985. private static Properties m_html_properties = null;
  986. /** The default properties when method="text". */
  987. private static Properties m_text_properties = null;
  988. /** Synchronization object for lazy initialization of the above tables. */
  989. private static Integer m_synch_object = new Integer(1);
  990. /** a zero length Class array used in loadPropertiesFile() */
  991. private static final Class[] NO_CLASSES = new Class[0];
  992. /** a zero length Object array used in loadPropertiesFile() */
  993. private static final Object[] NO_OBJS = new Object[0];
  994. }