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 }