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.serialize;
  58. import java.io.InputStream;
  59. import java.io.Writer;
  60. import java.io.OutputStream;
  61. import java.io.OutputStreamWriter;
  62. import java.io.UnsupportedEncodingException;
  63. import java.net.URL;
  64. import java.util.Enumeration;
  65. import java.util.Properties;
  66. /**
  67. * Provides information about encodings. Depends on the Java runtime
  68. * to provides writers for the different encodings, but can be used
  69. * to override encoding names and provide the last printable character
  70. * for each encoding.
  71. *
  72. * @version $Revision: 1.12 $ $Date: 2002/10/20 02:11:04 $
  73. * @author <a href="mailto:arkin@intalio.com">Assaf Arkin</a>
  74. */
  75. public class Encodings extends Object
  76. {
  77. /**
  78. * The last printable character for unknown encodings.
  79. */
  80. static final int m_defaultLastPrintable = 0x7F;
  81. /**
  82. * Standard filename for properties file with encodings data.
  83. */
  84. static final String ENCODINGS_FILE = "org/apache/xalan/serialize/Encodings.properties";
  85. /** a zero length Class array used in loadPropertyFile() */
  86. private static final Class[] NO_CLASSES = new Class[0];
  87. /** a zero length Object array used in loadPropertyFile() */
  88. private static final Object[] NO_OBJS = new Object[0];
  89. /**
  90. * Returns a writer for the specified encoding based on
  91. * an output stream.
  92. *
  93. * @param output The output stream
  94. * @param encoding The encoding
  95. * @return A suitable writer
  96. * @throws UnsupportedEncodingException There is no convertor
  97. * to support this encoding
  98. */
  99. public static Writer getWriter(OutputStream output, String encoding)
  100. throws UnsupportedEncodingException
  101. {
  102. for (int i = 0; i < _encodings.length; ++i)
  103. {
  104. if (_encodings[i].name.equalsIgnoreCase(encoding))
  105. {
  106. try
  107. {
  108. return new OutputStreamWriter(output, _encodings[i].javaName);
  109. }
  110. catch( java.lang.IllegalArgumentException iae) // java 1.1.8
  111. {
  112. // keep trying
  113. }
  114. catch (UnsupportedEncodingException usee)
  115. {
  116. // keep trying
  117. }
  118. }
  119. }
  120. try
  121. {
  122. return new OutputStreamWriter(output, encoding);
  123. }
  124. catch( java.lang.IllegalArgumentException iae) // java 1.1.8
  125. {
  126. throw new UnsupportedEncodingException(encoding);
  127. }
  128. }
  129. /**
  130. * Returns an opaque CharToByte converter for the specified encoding.
  131. *
  132. * @param encoding The encoding
  133. * @return An object which should be a sun.io.CharToByteConverter, or null.
  134. */
  135. public static Object getCharToByteConverter(String encoding)
  136. {
  137. Class charToByteConverterClass = null;
  138. java.lang.reflect.Method getConverterMethod = null;
  139. try
  140. {
  141. charToByteConverterClass = Class.forName("sun.io.CharToByteConverter");
  142. Class argTypes[] = new Class[1];
  143. argTypes[0] = String.class;
  144. getConverterMethod
  145. = charToByteConverterClass.getMethod("getConverter", argTypes);
  146. }
  147. catch(Exception e)
  148. {
  149. System.err.println("Warning: Could not get charToByteConverterClass!");
  150. return null;
  151. }
  152. Object args[] = new Object[1];
  153. for (int i = 0; i < _encodings.length; ++i)
  154. {
  155. if (_encodings[i].name.equalsIgnoreCase(encoding))
  156. {
  157. try
  158. {
  159. args[0] = _encodings[i].javaName;
  160. Object converter = getConverterMethod.invoke(null, args);
  161. if(null != converter)
  162. return converter;
  163. }
  164. catch( Exception iae)
  165. {
  166. // keep trying
  167. }
  168. }
  169. }
  170. return null;
  171. }
  172. /**
  173. * Returns the last printable character for the specified
  174. * encoding.
  175. *
  176. * @param encoding The encoding
  177. * @return The last printable character
  178. */
  179. public static int getLastPrintable(String encoding)
  180. {
  181. for (int i = 0; i < _encodings.length; ++i)
  182. {
  183. if (_encodings[i].name.equalsIgnoreCase(encoding)
  184. || _encodings[i].javaName.equalsIgnoreCase(encoding))
  185. return _encodings[i].lastPrintable;
  186. }
  187. return m_defaultLastPrintable;
  188. }
  189. /**
  190. * Returns the last printable character for an unspecified
  191. * encoding.
  192. *
  193. * @return the default size
  194. */
  195. public static int getLastPrintable()
  196. {
  197. return m_defaultLastPrintable;
  198. }
  199. /** The default encoding, ISO style, ISO style. */
  200. public static final String DEFAULT_MIME_ENCODING = "UTF-8";
  201. /**
  202. * Get the proper mime encoding. From the XSLT recommendation: "The encoding
  203. * attribute specifies the preferred encoding to use for outputting the result
  204. * tree. XSLT processors are required to respect values of UTF-8 and UTF-16.
  205. * For other values, if the XSLT processor does not support the specified
  206. * encoding it may signal an error; if it does not signal an error it should
  207. * use UTF-8 or UTF-16 instead. The XSLT processor must not use an encoding
  208. * whose name does not match the EncName production of the XML Recommendation
  209. * [XML]. If no encoding attribute is specified, then the XSLT processor should
  210. * use either UTF-8 or UTF-16."
  211. *
  212. * @param encoding Reference to java-style encoding string, which may be null,
  213. * in which case a default will be found.
  214. *
  215. * @return The ISO-style encoding string, or null if failure.
  216. */
  217. public static String getMimeEncoding(String encoding)
  218. {
  219. if (null == encoding)
  220. {
  221. try
  222. {
  223. // Get the default system character encoding. This may be
  224. // incorrect if they passed in a writer, but right now there
  225. // seems to be no way to get the encoding from a writer.
  226. encoding = System.getProperty("file.encoding", "UTF8");
  227. if (null != encoding)
  228. {
  229. /*
  230. * See if the mime type is equal to UTF8. If you don't
  231. * do that, then convertJava2MimeEncoding will convert
  232. * 8859_1 to "ISO-8859-1", which is not what we want,
  233. * I think, and I don't think I want to alter the tables
  234. * to convert everything to UTF-8.
  235. */
  236. String jencoding =
  237. (encoding.equalsIgnoreCase("Cp1252") || encoding.equalsIgnoreCase(
  238. "ISO8859_1") || encoding.equalsIgnoreCase("8859_1")
  239. || encoding.equalsIgnoreCase("UTF8")) ? DEFAULT_MIME_ENCODING
  240. : convertJava2MimeEncoding(
  241. encoding);
  242. encoding = (null != jencoding) ? jencoding : DEFAULT_MIME_ENCODING;
  243. }
  244. else
  245. {
  246. encoding = DEFAULT_MIME_ENCODING;
  247. }
  248. }
  249. catch (SecurityException se)
  250. {
  251. encoding = DEFAULT_MIME_ENCODING;
  252. }
  253. }
  254. else
  255. {
  256. encoding = convertJava2MimeEncoding(encoding);
  257. }
  258. return encoding;
  259. }
  260. /**
  261. * Try the best we can to convert a Java encoding to a XML-style encoding.
  262. *
  263. * @param encoding non-null reference to encoding string, java style.
  264. *
  265. * @return ISO-style encoding string.
  266. */
  267. public static String convertJava2MimeEncoding(String encoding)
  268. {
  269. for (int i = 0; i < _encodings.length; ++i)
  270. {
  271. if (_encodings[i].javaName.equalsIgnoreCase(encoding))
  272. {
  273. return _encodings[i].name;
  274. }
  275. }
  276. return encoding;
  277. }
  278. /**
  279. * Try the best we can to convert a Java encoding to a XML-style encoding.
  280. *
  281. * @param encoding non-null reference to encoding string, java style.
  282. *
  283. * @return ISO-style encoding string.
  284. */
  285. public static String convertMime2JavaEncoding(String encoding)
  286. {
  287. for (int i = 0; i < _encodings.length; ++i)
  288. {
  289. if (_encodings[i].name.equalsIgnoreCase(encoding))
  290. {
  291. return _encodings[i].javaName;
  292. }
  293. }
  294. return encoding;
  295. }
  296. /**
  297. * Load a list of all the supported encodings.
  298. *
  299. * System property "org.apache.xalan.serialize.encodings"
  300. * formatted using URL syntax may define an external encodings list.
  301. * Thanks to Sergey Ushakov for the code contribution!
  302. */
  303. private static EncodingInfo[] loadEncodingInfo()
  304. {
  305. URL url = null;
  306. try {
  307. String urlString = null;
  308. try {
  309. urlString = System.getProperty("org.apache.xalan.serialize.encodings", "");
  310. }
  311. catch (SecurityException e) {}
  312. if (urlString != null && urlString.length() > 0)
  313. url = new URL (urlString);
  314. if (url == null) {
  315. ClassLoader cl = null;
  316. try{
  317. java.lang.reflect.Method getCCL = Thread.class.getMethod("getContextClassLoader", NO_CLASSES);
  318. if (getCCL != null) {
  319. cl = (ClassLoader) getCCL.invoke(Thread.currentThread(), NO_OBJS);
  320. }
  321. }
  322. catch (Exception e) {}
  323. if (cl != null) {
  324. url = cl.getResource(ENCODINGS_FILE);
  325. }
  326. }
  327. if (url == null)
  328. url = ClassLoader.getSystemResource(ENCODINGS_FILE);
  329. Properties props = new Properties ();
  330. if (url != null) {
  331. InputStream is = url.openStream();
  332. props.load(is);
  333. is.close();
  334. }
  335. else {
  336. // Seems to be no real need to force failure here, let the system
  337. // do its best... The issue is not really very critical, and the
  338. // output will be in any case _correct_ though maybe not always
  339. // human-friendly... :)
  340. // But maybe report/log the resource problem?
  341. // Any standard ways to report/log errors in Xalan (in static context)?
  342. }
  343. int totalEntries = props.size();
  344. EncodingInfo[] ret = new EncodingInfo[totalEntries];
  345. Enumeration keys = props.keys();
  346. for (int i = 0; i < totalEntries; ++i) {
  347. String javaName = (String) keys.nextElement();
  348. String val = props.getProperty(javaName);
  349. int pos = val.indexOf(' ');
  350. String mimeName;
  351. int lastPrintable;
  352. if (pos < 0)
  353. {
  354. // Maybe report/log this problem?
  355. // "Last printable character not defined for encoding " +
  356. // mimeName + " (" + val + ")" ...
  357. mimeName = val;
  358. lastPrintable = 0x00FF;
  359. }
  360. else
  361. {
  362. mimeName = val.substring(0, pos);
  363. lastPrintable =
  364. Integer.decode(val.substring(pos).trim()).intValue();
  365. }
  366. ret [i] = new EncodingInfo (mimeName, javaName, lastPrintable);
  367. }
  368. return ret;
  369. } catch (java.net.MalformedURLException mue) {
  370. throw new org.apache.xml.utils.WrappedRuntimeException(mue);
  371. }
  372. catch (java.io.IOException ioe) {
  373. throw new org.apache.xml.utils.WrappedRuntimeException(ioe);
  374. }
  375. }
  376. private static final EncodingInfo[] _encodings = loadEncodingInfo();
  377. }