Home | History | Annotate | Download | only in serializer
      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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    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