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 }