1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the  "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id: CharInfo.java 468654 2006-10-28 07:09:23Z minchau $
20  */
21 package org.apache.xml.serializer;
22 
23 import java.io.BufferedReader;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.UnsupportedEncodingException;
27 import java.net.URL;
28 import java.util.Enumeration;
29 import java.util.HashMap;
30 import java.util.Hashtable;
31 import java.util.PropertyResourceBundle;
32 import java.util.ResourceBundle;
33 import java.security.AccessController;
34 import java.security.PrivilegedAction;
35 
36 import javax.xml.transform.TransformerException;
37 
38 import org.apache.xml.serializer.utils.MsgKey;
39 import org.apache.xml.serializer.utils.SystemIDResolver;
40 import org.apache.xml.serializer.utils.Utils;
41 import org.apache.xml.serializer.utils.WrappedRuntimeException;
42 
43 /**
44  * This class provides services that tell if a character should have
45  * special treatement, such as entity reference substitution or normalization
46  * of a newline character.  It also provides character to entity reference
47  * lookup.
48  *
49  * DEVELOPERS: See Known Issue in the constructor.
50  *
51  * @xsl.usage internal
52  */
53 final class CharInfo
54 {
55     /** Given a character, lookup a String to output (e.g. a decorated entity reference). */
56     private HashMap m_charToString;
57 
58     /**
59      * The name of the HTML entities file.
60      * If specified, the file will be resource loaded with the default class loader.
61      */
62     public static final String HTML_ENTITIES_RESOURCE =
63                 SerializerBase.PKG_NAME+".HTMLEntities";
64 
65     /**
66      * The name of the XML entities file.
67      * If specified, the file will be resource loaded with the default class loader.
68      */
69     public static final String XML_ENTITIES_RESOURCE =
70                 SerializerBase.PKG_NAME+".XMLEntities";
71 
72     /** The horizontal tab character, which the parser should always normalize. */
73     static final char S_HORIZONAL_TAB = 0x09;
74 
75     /** The linefeed character, which the parser should always normalize. */
76     static final char S_LINEFEED = 0x0A;
77 
78     /** The carriage return character, which the parser should always normalize. */
79     static final char S_CARRIAGERETURN = 0x0D;
80     static final char S_SPACE = 0x20;
81     static final char S_QUOTE = 0x22;
82     static final char S_LT = 0x3C;
83     static final char S_GT = 0x3E;
84     static final char S_NEL = 0x85;
85     static final char S_LINE_SEPARATOR = 0x2028;
86 
87     /** This flag is an optimization for HTML entities. It false if entities
88      * other than quot (34), amp (38), lt (60) and gt (62) are defined
89      * in the range 0 to 127.
90      * @xsl.usage internal
91      */
92     boolean onlyQuotAmpLtGt;
93 
94     /** Copy the first 0,1 ... ASCII_MAX values into an array */
95     static final int ASCII_MAX = 128;
96 
97     /** Array of values is faster access than a set of bits
98      * to quickly check ASCII characters in attribute values,
99      * the value is true if the character in an attribute value
100      * should be mapped to a String.
101      */
102     private final boolean[] shouldMapAttrChar_ASCII;
103 
104     /** Array of values is faster access than a set of bits
105      * to quickly check ASCII characters in text nodes,
106      * the value is true if the character in a text node
107      * should be mapped to a String.
108      */
109     private final boolean[] shouldMapTextChar_ASCII;
110 
111     /** An array of bits to record if the character is in the set.
112      * Although information in this array is complete, the
113      * isSpecialAttrASCII array is used first because access to its values
114      * is common and faster.
115      */
116     private final int array_of_bits[];
117 
118 
119     // 5 for 32 bit words,  6 for 64 bit words ...
120     /*
121      * This constant is used to shift an integer to quickly
122      * calculate which element its bit is stored in.
123      * 5 for 32 bit words (int) ,  6 for 64 bit words (long)
124      */
125     private static final int SHIFT_PER_WORD = 5;
126 
127     /*
128      * A mask to get the low order bits which are used to
129      * calculate the value of the bit within a given word,
130      * that will represent the presence of the integer in the
131      * set.
132      *
133      * 0x1F for 32 bit words (int),
134      * or 0x3F for 64 bit words (long)
135      */
136     private static final int LOW_ORDER_BITMASK = 0x1f;
137 
138     /*
139      * This is used for optimizing the lookup of bits representing
140      * the integers in the set. It is the index of the first element
141      * in the array array_of_bits[] that is not used.
142      */
143     private int firstWordNotUsed;
144 
145 
146     /**
147      * A base constructor just to explicitly create the fields,
148      * with the exception of m_charToString which is handled
149      * by the constructor that delegates base construction to this one.
150      * <p>
151      * m_charToString is not created here only for performance reasons,
152      * to avoid creating a Hashtable that will be replaced when
153      * making a mutable copy, {@link #mutableCopyOf(CharInfo)}.
154      *
155      */
CharInfo()156     private CharInfo()
157     {
158     	this.array_of_bits = createEmptySetOfIntegers(65535);
159     	this.firstWordNotUsed = 0;
160     	this.shouldMapAttrChar_ASCII = new boolean[ASCII_MAX];
161     	this.shouldMapTextChar_ASCII = new boolean[ASCII_MAX];
162     	this.m_charKey = new CharKey();
163 
164     	// Not set here, but in a constructor that uses this one
165     	// this.m_charToString =  new Hashtable();
166 
167     	this.onlyQuotAmpLtGt = true;
168 
169 
170     	return;
171     }
172 
CharInfo(String entitiesResource, String method, boolean internal)173     private CharInfo(String entitiesResource, String method, boolean internal)
174     {
175     	// call the default constructor to create the fields
176     	this();
177     	m_charToString = new HashMap();
178 
179         ResourceBundle entities = null;
180         boolean noExtraEntities = true;
181 
182         // Make various attempts to interpret the parameter as a properties
183         // file or resource file, as follows:
184         //
185         //   1) attempt to load .properties file using ResourceBundle
186         //   2) try using the class loader to find the specified file a resource
187         //      file
188         //   3) try treating the resource a URI
189 
190         if (internal) {
191             try {
192                 // Load entity property files by using PropertyResourceBundle,
193                 // cause of security issure for applets
194                 entities = PropertyResourceBundle.getBundle(entitiesResource);
195             } catch (Exception e) {}
196         }
197 
198         if (entities != null) {
199             Enumeration keys = entities.getKeys();
200             while (keys.hasMoreElements()){
201                 String name = (String) keys.nextElement();
202                 String value = entities.getString(name);
203                 int code = Integer.parseInt(value);
204                 boolean extra = defineEntity(name, (char) code);
205                 if (extra)
206                     noExtraEntities = false;
207             }
208         } else {
209             InputStream is = null;
210 
211             // Load user specified resource file by using URL loading, it
212             // requires a valid URI as parameter
213             try {
214                 if (internal) {
215                     is = CharInfo.class.getResourceAsStream(entitiesResource);
216                 } else {
217                     ClassLoader cl = ObjectFactory.findClassLoader();
218                     if (cl == null) {
219                         is = ClassLoader.getSystemResourceAsStream(entitiesResource);
220                     } else {
221                         is = cl.getResourceAsStream(entitiesResource);
222                     }
223 
224                     if (is == null) {
225                         try {
226                             URL url = new URL(entitiesResource);
227                             is = url.openStream();
228                         } catch (Exception e) {}
229                     }
230                 }
231 
232                 if (is == null) {
233                     throw new RuntimeException(
234                         Utils.messages.createMessage(
235                             MsgKey.ER_RESOURCE_COULD_NOT_FIND,
236                             new Object[] {entitiesResource, entitiesResource}));
237                 }
238 
239                 // Fix Bugzilla#4000: force reading in UTF-8
240                 //  This creates the de facto standard that Xalan's resource
241                 //  files must be encoded in UTF-8. This should work in all
242                 // JVMs.
243                 //
244                 // %REVIEW% KNOWN ISSUE: IT FAILS IN MICROSOFT VJ++, which
245                 // didn't implement the UTF-8 encoding. Theoretically, we should
246                 // simply let it fail in that case, since the JVM is obviously
247                 // broken if it doesn't support such a basic standard.  But
248                 // since there are still some users attempting to use VJ++ for
249                 // development, we have dropped in a fallback which makes a
250                 // second attempt using the platform's default encoding. In VJ++
251                 // this is apparently ASCII, which is subset of UTF-8... and
252                 // since the strings we'll be reading here are also primarily
253                 // limited to the 7-bit ASCII range (at least, in English
254                 // versions of Xalan), this should work well enough to keep us
255                 // on the air until we're ready to officially decommit from
256                 // VJ++.
257 
258                 BufferedReader reader;
259                 try {
260                     reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
261                 } catch (UnsupportedEncodingException e) {
262                     reader = new BufferedReader(new InputStreamReader(is));
263                 }
264 
265                 String line = reader.readLine();
266 
267                 while (line != null) {
268                     if (line.length() == 0 || line.charAt(0) == '#') {
269                         line = reader.readLine();
270 
271                         continue;
272                     }
273 
274                     int index = line.indexOf(' ');
275 
276                     if (index > 1) {
277                         String name = line.substring(0, index);
278 
279                         ++index;
280 
281                         if (index < line.length()) {
282                             String value = line.substring(index);
283                             index = value.indexOf(' ');
284 
285                             if (index > 0) {
286                                 value = value.substring(0, index);
287                             }
288 
289                             int code = Integer.parseInt(value);
290 
291                             boolean extra = defineEntity(name, (char) code);
292                             if (extra)
293                                 noExtraEntities = false;
294                         }
295                     }
296 
297                     line = reader.readLine();
298                 }
299 
300                 is.close();
301             } catch (Exception e) {
302                 throw new RuntimeException(
303                     Utils.messages.createMessage(
304                         MsgKey.ER_RESOURCE_COULD_NOT_LOAD,
305                         new Object[] { entitiesResource,
306                                        e.toString(),
307                                        entitiesResource,
308                                        e.toString()}));
309             } finally {
310                 if (is != null) {
311                     try {
312                         is.close();
313                     } catch (Exception except) {}
314                 }
315             }
316         }
317 
318         onlyQuotAmpLtGt = noExtraEntities;
319 
320         /* Now that we've used get(ch) just above to initialize the
321          * two arrays we will change by adding a tab to the set of
322          * special chars for XML (but not HTML!).
323          * We do this because a tab is always a
324          * special character in an XML attribute,
325          * but only a special character in XML text
326          * if it has an entity defined for it.
327          * This is the reason for this delay.
328          */
329         if (Method.XML.equals(method))
330         {
331             // We choose not to escape the quotation mark as &quot; in text nodes
332             shouldMapTextChar_ASCII[S_QUOTE] = false;
333         }
334 
335         if (Method.HTML.equals(method)) {
336         	// The XSLT 1.0 recommendation says
337         	// "The html output method should not escape < characters occurring in attribute values."
338         	// So we don't escape '<' in an attribute for HTML
339         	shouldMapAttrChar_ASCII['<'] = false;
340 
341         	// We choose not to escape the quotation mark as &quot; in text nodes.
342             shouldMapTextChar_ASCII[S_QUOTE] = false;
343         }
344     }
345 
346     /**
347      * Defines a new character reference. The reference's name and value are
348      * supplied. Nothing happens if the character reference is already defined.
349      * <p>Unlike internal entities, character references are a string to single
350      * character mapping. They are used to map non-ASCII characters both on
351      * parsing and printing, primarily for HTML documents. '&amp;lt;' is an
352      * example of a character reference.</p>
353      *
354      * @param name The entity's name
355      * @param value The entity's value
356      * @return true if the mapping is not one of:
357      * <ul>
358      * <li> '<' to "&lt;"
359      * <li> '>' to "&gt;"
360      * <li> '&' to "&amp;"
361      * <li> '"' to "&quot;"
362      * </ul>
363      */
defineEntity(String name, char value)364     private boolean defineEntity(String name, char value)
365     {
366         StringBuffer sb = new StringBuffer("&");
367         sb.append(name);
368         sb.append(';');
369         String entityString = sb.toString();
370 
371         boolean extra = defineChar2StringMapping(entityString, value);
372         return extra;
373     }
374 
375     /**
376      * A utility object, just used to map characters to output Strings,
377      * needed because a HashMap needs to map an object as a key, not a
378      * Java primitive type, like a char, so this object gets around that
379      * and it is reusable.
380      */
381     private final CharKey m_charKey;
382 
383     /**
384      * Map a character to a String. For example given
385      * the character '>' this method would return the fully decorated
386      * entity name "&lt;".
387      * Strings for entity references are loaded from a properties file,
388      * but additional mappings defined through calls to defineChar2String()
389      * are possible. Such entity reference mappings could be over-ridden.
390      *
391      * This is reusing a stored key object, in an effort to avoid
392      * heap activity. Unfortunately, that introduces a threading risk.
393      * Simplest fix for now is to make it a synchronized method, or to give
394      * up the reuse; I see very little performance difference between them.
395      * Long-term solution would be to replace the hashtable with a sparse array
396      * keyed directly from the character's integer value; see DTM's
397      * string pool for a related solution.
398      *
399      * @param value The character that should be resolved to
400      * a String, e.g. resolve '>' to  "&lt;".
401      *
402      * @return The String that the character is mapped to, or null if not found.
403      * @xsl.usage internal
404      */
getOutputStringForChar(char value)405     String getOutputStringForChar(char value)
406     {
407         // CharKey m_charKey = new CharKey(); //Alternative to synchronized
408         m_charKey.setChar(value);
409         return (String) m_charToString.get(m_charKey);
410     }
411 
412     /**
413      * Tell if the character argument that is from
414      * an attribute value has a mapping to a String.
415      *
416      * @param value the value of a character that is in an attribute value
417      * @return true if the character should have any special treatment,
418      * such as when writing out entity references.
419      * @xsl.usage internal
420      */
shouldMapAttrChar(int value)421     final boolean shouldMapAttrChar(int value)
422     {
423         // for performance try the values in the boolean array first,
424         // this is faster access than the BitSet for common ASCII values
425 
426         if (value < ASCII_MAX)
427             return shouldMapAttrChar_ASCII[value];
428 
429         // rather than java.util.BitSet, our private
430         // implementation is faster (and less general).
431         return get(value);
432     }
433 
434     /**
435      * Tell if the character argument that is from a
436      * text node has a mapping to a String, for example
437      * to map '<' to "&lt;".
438      *
439      * @param value the value of a character that is in a text node
440      * @return true if the character has a mapping to a String,
441      * such as when writing out entity references.
442      * @xsl.usage internal
443      */
shouldMapTextChar(int value)444     final boolean shouldMapTextChar(int value)
445     {
446         // for performance try the values in the boolean array first,
447         // this is faster access than the BitSet for common ASCII values
448 
449         if (value < ASCII_MAX)
450             return shouldMapTextChar_ASCII[value];
451 
452         // rather than java.util.BitSet, our private
453         // implementation is faster (and less general).
454         return get(value);
455     }
456 
457 
458 
getCharInfoBasedOnPrivilege( final String entitiesFileName, final String method, final boolean internal)459     private static CharInfo getCharInfoBasedOnPrivilege(
460         final String entitiesFileName, final String method,
461         final boolean internal){
462             return (CharInfo) AccessController.doPrivileged(
463                 new PrivilegedAction() {
464                         public Object run() {
465                             return new CharInfo(entitiesFileName,
466                               method, internal);}
467             });
468     }
469 
470     /**
471      * Factory that reads in a resource file that describes the mapping of
472      * characters to entity references.
473      *
474      * Resource files must be encoded in UTF-8 and have a format like:
475      * <pre>
476      * # First char # is a comment
477      * Entity numericValue
478      * quot 34
479      * amp 38
480      * </pre>
481      * (Note: Why don't we just switch to .properties files? Oct-01 -sc)
482      *
483      * @param entitiesResource Name of entities resource file that should
484      * be loaded, which describes that mapping of characters to entity references.
485      * @param method the output method type, which should be one of "xml", "html", "text"...
486      *
487      * @xsl.usage internal
488      */
489     static CharInfo getCharInfo(String entitiesFileName, String method)
490     {
491         CharInfo charInfo = (CharInfo) m_getCharInfoCache.get(entitiesFileName);
492         if (charInfo != null) {
493         	return mutableCopyOf(charInfo);
494         }
495 
496         // try to load it internally - cache
497         try {
498             charInfo = getCharInfoBasedOnPrivilege(entitiesFileName,
499                                         method, true);
500             // Put the common copy of charInfo in the cache, but return
501             // a copy of it.
502             m_getCharInfoCache.put(entitiesFileName, charInfo);
503             return mutableCopyOf(charInfo);
504         } catch (Exception e) {}
505 
506         // try to load it externally - do not cache
507         try {
508             return getCharInfoBasedOnPrivilege(entitiesFileName,
509                                 method, false);
510         } catch (Exception e) {}
511 
512         String absoluteEntitiesFileName;
513 
514         if (entitiesFileName.indexOf(':') < 0) {
515             absoluteEntitiesFileName =
516                 SystemIDResolver.getAbsoluteURIFromRelative(entitiesFileName);
517         } else {
518             try {
519                 absoluteEntitiesFileName =
520                     SystemIDResolver.getAbsoluteURI(entitiesFileName, null);
521             } catch (TransformerException te) {
522                 throw new WrappedRuntimeException(te);
523             }
524         }
525 
526         return getCharInfoBasedOnPrivilege(entitiesFileName,
527                                 method, false);
528     }
529 
530     /**
531      * Create a mutable copy of the cached one.
532      * @param charInfo The cached one.
533      * @return
534      */
535     private static CharInfo mutableCopyOf(CharInfo charInfo) {
536     	CharInfo copy = new CharInfo();
537 
538     	int max = charInfo.array_of_bits.length;
539     	System.arraycopy(charInfo.array_of_bits,0,copy.array_of_bits,0,max);
540 
541     	copy.firstWordNotUsed = charInfo.firstWordNotUsed;
542 
543     	max = charInfo.shouldMapAttrChar_ASCII.length;
544     	System.arraycopy(charInfo.shouldMapAttrChar_ASCII,0,copy.shouldMapAttrChar_ASCII,0,max);
545 
546     	max = charInfo.shouldMapTextChar_ASCII.length;
547     	System.arraycopy(charInfo.shouldMapTextChar_ASCII,0,copy.shouldMapTextChar_ASCII,0,max);
548 
549     	// utility field copy.m_charKey is already created in the default constructor
550 
551     	copy.m_charToString = (HashMap) charInfo.m_charToString.clone();
552 
553     	copy.onlyQuotAmpLtGt = charInfo.onlyQuotAmpLtGt;
554 
555 		return copy;
556 	}
557 
558 	/**
559 	 * Table of user-specified char infos.
560 	 * The table maps entify file names (the name of the
561 	 * property file without the .properties extension)
562 	 * to CharInfo objects populated with entities defined in
563 	 * corresponding property file.
564 	 */
565     private static Hashtable m_getCharInfoCache = new Hashtable();
566 
567     /**
568      * Returns the array element holding the bit value for the
569      * given integer
570      * @param i the integer that might be in the set of integers
571      *
572      */
573     private static int arrayIndex(int i) {
574         return (i >> SHIFT_PER_WORD);
575     }
576 
577     /**
578      * For a given integer in the set it returns the single bit
579      * value used within a given word that represents whether
580      * the integer is in the set or not.
581      */
582     private static int bit(int i) {
583         int ret = (1 << (i & LOW_ORDER_BITMASK));
584         return ret;
585     }
586 
587     /**
588      * Creates a new empty set of integers (characters)
589      * @param max the maximum integer to be in the set.
590      */
591     private int[] createEmptySetOfIntegers(int max) {
592         firstWordNotUsed = 0; // an optimization
593 
594         int[] arr = new int[arrayIndex(max - 1) + 1];
595             return arr;
596 
597     }
598 
599     /**
600      * Adds the integer (character) to the set of integers.
601      * @param i the integer to add to the set, valid values are
602      * 0, 1, 2 ... up to the maximum that was specified at
603      * the creation of the set.
604      */
605     private final void set(int i) {
606         setASCIItextDirty(i);
607         setASCIIattrDirty(i);
608 
609         int j = (i >> SHIFT_PER_WORD); // this word is used
610         int k = j + 1;
611 
612         if(firstWordNotUsed < k) // for optimization purposes.
613             firstWordNotUsed = k;
614 
615         array_of_bits[j] |= (1 << (i & LOW_ORDER_BITMASK));
616     }
617 
618 
619     /**
620      * Return true if the integer (character)is in the set of integers.
621      *
622      * This implementation uses an array of integers with 32 bits per
623      * integer.  If a bit is set to 1 the corresponding integer is
624      * in the set of integers.
625      *
626      * @param i an integer that is tested to see if it is the
627      * set of integers, or not.
628      */
629     private final boolean get(int i) {
630 
631         boolean in_the_set = false;
632         int j = (i >> SHIFT_PER_WORD); // wordIndex(i)
633         // an optimization here, ... a quick test to see
634         // if this integer is beyond any of the words in use
635         if(j < firstWordNotUsed)
636             in_the_set = (array_of_bits[j] &
637                           (1 << (i & LOW_ORDER_BITMASK))
638             ) != 0;  // 0L for 64 bit words
639         return in_the_set;
640     }
641 
642     /**
643      * This method returns true if there are some non-standard mappings to
644      * entities other than quot, amp, lt, gt, and its only purpose is for
645      * performance.
646      * @param charToMap The value of the character that is mapped to a String
647      * @param outputString The String to which the character is mapped, usually
648      * an entity reference such as "&lt;".
649      * @return true if the mapping is not one of:
650      * <ul>
651      * <li> '<' to "&lt;"
652      * <li> '>' to "&gt;"
653      * <li> '&' to "&amp;"
654      * <li> '"' to "&quot;"
655      * </ul>
656      */
657     private boolean extraEntity(String outputString, int charToMap)
658     {
659         boolean extra = false;
660         if (charToMap < ASCII_MAX)
661         {
662             switch (charToMap)
663             {
664                 case '"' : // quot
665                 	if (!outputString.equals("&quot;"))
666                 		extra = true;
667                 	break;
668                 case '&' : // amp
669                 	if (!outputString.equals("&amp;"))
670                 		extra = true;
671                 	break;
672                 case '<' : // lt
673                 	if (!outputString.equals("&lt;"))
674                 		extra = true;
675                 	break;
676                 case '>' : // gt
677                 	if (!outputString.equals("&gt;"))
678                 		extra = true;
679                     break;
680                 default : // other entity in range 0 to 127
681                     extra = true;
682             }
683         }
684         return extra;
685     }
686 
687     /**
688      * If the character is in the ASCII range then
689      * mark it as needing replacement with
690      * a String on output if it occurs in a text node.
691      * @param ch
692      */
693     private void setASCIItextDirty(int j)
694     {
695         if (0 <= j && j < ASCII_MAX)
696         {
697             shouldMapTextChar_ASCII[j] = true;
698         }
699     }
700 
701     /**
702      * If the character is in the ASCII range then
703      * mark it as needing replacement with
704      * a String on output if it occurs in a attribute value.
705      * @param ch
706      */
707     private void setASCIIattrDirty(int j)
708     {
709         if (0 <= j && j < ASCII_MAX)
710         {
711             shouldMapAttrChar_ASCII[j] = true;
712         }
713     }
714 
715 
716     /**
717      * Call this method to register a char to String mapping, for example
718      * to map '<' to "&lt;".
719      * @param outputString The String to map to.
720      * @param inputChar The char to map from.
721      * @return true if the mapping is not one of:
722      * <ul>
723      * <li> '<' to "&lt;"
724      * <li> '>' to "&gt;"
725      * <li> '&' to "&amp;"
726      * <li> '"' to "&quot;"
727      * </ul>
728      */
729     boolean defineChar2StringMapping(String outputString, char inputChar)
730     {
731         CharKey character = new CharKey(inputChar);
732         m_charToString.put(character, outputString);
733         set(inputChar);  // mark the character has having a mapping to a String
734 
735         boolean extraMapping = extraEntity(outputString, inputChar);
736         return extraMapping;
737 
738     }
739 
740     /**
741      * Simple class for fast lookup of char values, when used with
742      * hashtables.  You can set the char, then use it as a key.
743      *
744      * @xsl.usage internal
745      */
746     private static class CharKey extends Object
747     {
748 
749       /** String value          */
750       private char m_char;
751 
752       /**
753        * Constructor CharKey
754        *
755        * @param key char value of this object.
756        */
757       public CharKey(char key)
758       {
759         m_char = key;
760       }
761 
762       /**
763        * Default constructor for a CharKey.
764        *
765        * @param key char value of this object.
766        */
767       public CharKey()
768       {
769       }
770 
771       /**
772        * Get the hash value of the character.
773        *
774        * @return hash value of the character.
775        */
776       public final void setChar(char c)
777       {
778         m_char = c;
779       }
780 
781 
782 
783       /**
784        * Get the hash value of the character.
785        *
786        * @return hash value of the character.
787        */
788       public final int hashCode()
789       {
790         return (int)m_char;
791       }
792 
793       /**
794        * Override of equals() for this object
795        *
796        * @param obj to compare to
797        *
798        * @return True if this object equals this string value
799        */
800       public final boolean equals(Object obj)
801       {
802         return ((CharKey)obj).m_char == m_char;
803       }
804     }
805 
806 
807 }
808