001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: NumeratorFormatter.java 1225439 2011-12-29 05:22:32Z mrglavas $
020     */
021    package org.apache.xalan.transformer;
022    
023    import java.util.Locale;
024    import java.util.NoSuchElementException;
025    
026    import org.w3c.dom.Element;
027    
028    /**
029     * Converts enumerated numbers into strings, using the XSL conversion attributes.
030     * Having this in a class helps avoid being forced to extract the attributes repeatedly.
031     * @xsl.usage internal
032     */
033    class NumeratorFormatter
034    {
035    
036      /** The owning xsl:number element.          */
037      protected Element m_xslNumberElement;
038    
039      /** An instance of a Tokenizer          */
040      NumberFormatStringTokenizer m_formatTokenizer;
041    
042      /** Locale we need to format in          */
043      Locale m_locale;
044    
045      /** An instance of a NumberFormat         */
046      java.text.NumberFormat m_formatter;
047    
048      /** An instance of a transformer          */
049      TransformerImpl m_processor;
050    
051      /**
052       * Table to help in converting decimals to roman numerals.
053       * @see org.apache.xalan.transformer.DecimalToRoman
054       */
055      private final static DecimalToRoman m_romanConvertTable[] = {
056        new DecimalToRoman(1000, "M", 900, "CM"),
057        new DecimalToRoman(500, "D", 400, "CD"),
058        new DecimalToRoman(100L, "C", 90L, "XC"),
059        new DecimalToRoman(50L, "L", 40L, "XL"),
060        new DecimalToRoman(10L, "X", 9L, "IX"),
061        new DecimalToRoman(5L, "V", 4L, "IV"),
062        new DecimalToRoman(1L, "I", 1L, "I") };
063    
064      /**
065       * Chars for converting integers into alpha counts.
066       * @see TransformerImpl#int2alphaCount
067       */
068      private final static char[] m_alphaCountTable = { 'Z',  // z for zero
069                                                        'A', 'B', 'C', 'D', 'E',
070                                                        'F', 'G', 'H', 'I', 'J',
071                                                        'K', 'L', 'M', 'N', 'O',
072                                                        'P', 'Q', 'R', 'S', 'T',
073                                                        'U', 'V', 'W', 'X', 'Y' };
074    
075      /**
076       * Construct a NumeratorFormatter using an element
077       * that contains XSL number conversion attributes -
078       * format, letter-value, xml:lang, digit-group-sep,
079       * n-digits-per-group, and sequence-src.
080       *
081       * @param xslNumberElement The given xsl:number element
082       * @param processor a non-null transformer instance
083       */
084      NumeratorFormatter(Element xslNumberElement, TransformerImpl processor)
085      {
086        m_xslNumberElement = xslNumberElement;
087        m_processor = processor;
088      }  // end NumeratorFormatter(Element) constructor
089    
090      /**
091       * Convert a long integer into alphabetic counting, in other words
092       * count using the sequence A B C ... Z AA AB AC.... etc.
093       * 
094       * @param val Value to convert -- must be greater than zero.
095       * @param table a table containing one character for each digit in the radix
096       * @return String representing alpha count of number.
097       * @see org.apache.xalan.transformer.DecimalToRoman
098       *
099       * Note that the radix of the conversion is inferred from the size
100       * of the table.
101       */
102      protected String int2alphaCount(int val, char[] table)
103      {
104    
105        int radix = table.length;
106    
107        // Create a buffer to hold the result
108        // TODO:  size of the table can be detereined by computing
109        // logs of the radix.  For now, we fake it.
110        char buf[] = new char[100];
111    
112        // next character to set in the buffer
113        int charPos = buf.length - 1;  // work backward through buf[]
114    
115        // index in table of the last character that we stored
116        int lookupIndex = 1;  // start off with anything other than zero to make correction work
117    
118        //                                          Correction number
119        //
120        //  Correction can take on exactly two values:
121        //
122        //          0       if the next character is to be emitted is usual
123        //
124        //      radix - 1
125        //                  if the next char to be emitted should be one less than
126        //                  you would expect
127        //
128        // For example, consider radix 10, where 1="A" and 10="J"
129        //
130        // In this scheme, we count: A, B, C ...   H, I, J (not A0 and certainly
131        // not AJ), A1
132        //
133        // So, how do we keep from emitting AJ for 10?  After correctly emitting the
134        // J, lookupIndex is zero.  We now compute a correction number of 9 (radix-1).
135        // In the following line, we'll compute (val+correction) % radix, which is,
136        // (val+9)/10.  By this time, val is 1, so we compute (1+9) % 10, which
137        // is 10 % 10 or zero.  So, we'll prepare to emit "JJ", but then we'll
138        // later suppress the leading J as representing zero (in the mod system,
139        // it can represent either 10 or zero).  In summary, the correction value of
140        // "radix-1" acts like "-1" when run through the mod operator, but with the
141        // desireable characteristic that it never produces a negative number.
142        int correction = 0;
143    
144        // TODO:  throw error on out of range input
145        do
146        {
147    
148          // most of the correction calculation is explained above,  the reason for the
149          // term after the "|| " is that it correctly propagates carries across
150          // multiple columns.
151          correction =
152            ((lookupIndex == 0) || (correction != 0 && lookupIndex == radix - 1))
153            ? (radix - 1) : 0;
154    
155          // index in "table" of the next char to emit
156          lookupIndex = (val + correction) % radix;
157    
158          // shift input by one "column"
159          val = (val / radix);
160    
161          // if the next value we'd put out would be a leading zero, we're done.
162          if (lookupIndex == 0 && val == 0)
163            break;
164    
165          // put out the next character of output
166          buf[charPos--] = table[lookupIndex];
167        }
168        while (val > 0);
169    
170        return new String(buf, charPos + 1, (buf.length - charPos - 1));
171      }
172    
173      /**
174       * Convert a long integer into roman numerals.
175       * @param val Value to convert.
176       * @param prefixesAreOK true_ to enable prefix notation (e.g. 4 = "IV"),
177       * false_ to disable prefix notation (e.g. 4 = "IIII").
178       * @return Roman numeral string.
179       * @see DecimalToRoman
180       * @see m_romanConvertTable
181       */
182      String long2roman(long val, boolean prefixesAreOK)
183      {
184    
185        if (val <= 0)
186        {
187          return "#E(" + val + ")";
188        }
189    
190        final String roman;
191        int place = 0;
192    
193        if (val <= 3999L)
194        {
195          StringBuffer romanBuffer = new StringBuffer();
196          do
197          {
198            while (val >= m_romanConvertTable[place].m_postValue)
199            {
200              romanBuffer.append(m_romanConvertTable[place].m_postLetter);
201              val -= m_romanConvertTable[place].m_postValue;
202            }
203    
204            if (prefixesAreOK)
205            {
206              if (val >= m_romanConvertTable[place].m_preValue)
207              {
208                romanBuffer.append(m_romanConvertTable[place].m_preLetter);
209                val -= m_romanConvertTable[place].m_preValue;
210              }
211            }
212    
213            place++;
214          }
215          while (val > 0);
216          roman = romanBuffer.toString();
217        }
218        else
219        {
220          roman = "#error";
221        }
222    
223        return roman;
224      }  // end long2roman
225    
226      /**
227       * This class returns tokens using non-alphanumberic
228       * characters as delimiters.
229       */
230      static class NumberFormatStringTokenizer
231      {
232    
233        /** Field holding the current position in the string      */
234        private int currentPosition;
235    
236        /** The total length of the string          */
237        private int maxPosition;
238    
239        /** The string to tokenize          */
240        private String str;
241    
242        /**
243         * Construct a NumberFormatStringTokenizer.
244         *
245         * @param str The string to tokenize
246         */
247        NumberFormatStringTokenizer(String str)
248        {
249          this.str = str;
250          maxPosition = str.length();
251        }
252        
253        /**
254         * Reset tokenizer so that nextToken() starts from the beginning. 
255         *
256         */
257        void reset()
258        {
259          currentPosition = 0;
260        }
261    
262        /**
263         * Returns the next token from this string tokenizer.
264         *
265         * @return     the next token from this string tokenizer.
266         * @throws  NoSuchElementException  if there are no more tokens in this
267         *               tokenizer's string.
268         */
269        String nextToken()
270        {
271    
272          if (currentPosition >= maxPosition)
273          {
274            throw new NoSuchElementException();
275          }
276    
277          int start = currentPosition;
278    
279          while ((currentPosition < maxPosition)
280                 && Character.isLetterOrDigit(str.charAt(currentPosition)))
281          {
282            currentPosition++;
283          }
284    
285          if ((start == currentPosition)
286                  && (!Character.isLetterOrDigit(str.charAt(currentPosition))))
287          {
288            currentPosition++;
289          }
290    
291          return str.substring(start, currentPosition);
292        }
293    
294        /**
295         * Tells if <code>nextToken</code> will throw an exception      * if it is called.
296         *
297         * @return true if <code>nextToken</code> can be called      * without throwing an exception.
298         */
299        boolean hasMoreTokens()
300        {
301          return (currentPosition >= maxPosition) ? false : true;
302        }
303    
304        /**
305         * Calculates the number of times that this tokenizer's
306         * <code>nextToken</code> method can be called before it generates an
307         * exception.
308         *
309         * @return  the number of tokens remaining in the string using the current
310         *          delimiter set.
311         * @see     java.util.StringTokenizer#nextToken()
312         */
313        int countTokens()
314        {
315    
316          int count = 0;
317          int currpos = currentPosition;
318    
319          while (currpos < maxPosition)
320          {
321            int start = currpos;
322    
323            while ((currpos < maxPosition)
324                   && Character.isLetterOrDigit(str.charAt(currpos)))
325            {
326              currpos++;
327            }
328    
329            if ((start == currpos)
330                    && (Character.isLetterOrDigit(str.charAt(currpos)) == false))
331            {
332              currpos++;
333            }
334    
335            count++;
336          }
337    
338          return count;
339        }
340      }  // end NumberFormatStringTokenizer
341    }