1 /* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5 /* 6 * Copyright 2001-2004 The Apache Software Foundation. 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 /* 21 * $Id: NodeCounter.java,v 1.2.4.1 2005/09/12 11:52:36 pvedula Exp $ 22 */ 23 24 package com.sun.org.apache.xalan.internal.xsltc.dom; 25 26 import java.util.Vector; 27 28 import com.sun.org.apache.xalan.internal.xsltc.DOM; 29 import com.sun.org.apache.xalan.internal.xsltc.Translet; 30 import com.sun.org.apache.xml.internal.dtm.DTM; 31 import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; 32 33 /** 34 * @author Jacek Ambroziak 35 * @author Santiago Pericas-Geertsen 36 * @author Morten Jorgensen 37 */ 38 public abstract class NodeCounter { 39 public static final int END = DTM.NULL; 40 41 protected int _node = END; 42 protected int _nodeType = DOM.FIRST_TYPE - 1; 43 protected double _value = Integer.MIN_VALUE; 44 45 public final DOM _document; 46 public final DTMAxisIterator _iterator; 47 public final Translet _translet; 48 49 protected String _format; 50 protected String _lang; 51 protected String _letterValue; 52 protected String _groupSep; 53 protected int _groupSize; 54 55 private boolean _separFirst = true; 56 private boolean _separLast = false; 57 private Vector _separToks = new Vector(); 58 private Vector _formatToks = new Vector(); 59 private int _nSepars = 0; 60 private int _nFormats = 0; 61 62 private final static String[] Thousands = 63 {"", "m", "mm", "mmm" }; 64 private final static String[] Hundreds = 65 {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"}; 66 private final static String[] Tens = 67 {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"}; 68 private final static String[] Ones = 69 {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"}; 70 71 private StringBuilder _tempBuffer = new StringBuilder(); 72 73 /** 74 * Indicates if this instance of xsl:number has a from pattern. 75 */ 76 protected boolean _hasFrom; 77 78 protected NodeCounter(Translet translet, 79 DOM document, DTMAxisIterator iterator) { 80 _translet = translet; 81 _document = document; 82 _iterator = iterator; 83 } 84 85 protected NodeCounter(Translet translet, 86 DOM document, DTMAxisIterator iterator, boolean hasFrom) { 87 _translet = translet; 88 _document = document; 89 _iterator = iterator; 90 _hasFrom = hasFrom; 91 } 92 93 /** 94 * Set the start node for this counter. The same <tt>NodeCounter</tt> 95 * object can be used multiple times by resetting the starting node. 96 */ 97 abstract public NodeCounter setStartNode(int node); 98 99 /** 100 * If the user specified a value attribute, use this instead of 101 * counting nodes. 102 */ 103 public NodeCounter setValue(double value) { 104 _value = value; 105 return this; 106 } 107 108 /** 109 * Sets formatting fields before calling formatNumbers(). 110 */ 111 protected void setFormatting(String format, String lang, String letterValue, 112 String groupSep, String groupSize) { 113 _lang = lang; 114 _groupSep = groupSep; 115 _letterValue = letterValue; 116 _groupSize = parseStringToAnInt(groupSize); 117 setTokens(format); 118 119 } 120 121 /** 122 * Effectively does the same thing as Integer.parseInt(String s) except 123 * instead of throwing a NumberFormatException, it returns 0. This method 124 * is used instead of Integer.parseInt() since it does not incur the 125 * overhead of throwing an Exception which is expensive. 126 * 127 * @param s A String to be parsed into an int. 128 * @return Either an int represented by the incoming String s, or 0 if 129 * the parsing is not successful. 130 */ 131 private int parseStringToAnInt(String s) { 132 if (s == null) 133 return 0; 134 135 int result = 0; 136 boolean negative = false; 137 int radix = 10, i = 0, max = s.length(); 138 int limit, multmin, digit; 139 140 if (max > 0) { 141 if (s.charAt(0) == '-') { 142 negative = true; 143 limit = Integer.MIN_VALUE; 144 i++; 145 } else { 146 limit = -Integer.MAX_VALUE; 147 } 148 multmin = limit / radix; 149 if (i < max) { 150 digit = Character.digit(s.charAt(i++), radix); 151 if (digit < 0) 152 return 0; 153 else 154 result = -digit; 155 } 156 while (i < max) { 157 // Accumulating negatively avoids surprises near MAX_VALUE 158 digit = Character.digit(s.charAt(i++), radix); 159 if (digit < 0) 160 return 0; 161 if (result < multmin) 162 return 0; 163 result *= radix; 164 if (result < limit + digit) 165 return 0; 166 result -= digit; 167 } 168 } else { 169 return 0; 170 } 171 if (negative) { 172 if (i > 1) 173 return result; 174 else /* Only got "-" */ 175 return 0; 176 } else { 177 return -result; 178 } 179 } 180 181 // format == null assumed here 182 private final void setTokens(final String format){ 183 if( (_format!=null) &&(format.equals(_format)) ){// has already been set 184 return; 185 } 186 _format = format; 187 // reset 188 final int length = _format.length(); 189 boolean isFirst = true; 190 _separFirst = true; 191 _separLast = false; 192 _nSepars = 0; 193 _nFormats = 0; 194 _separToks.clear() ; 195 _formatToks.clear(); 196 197 /* 198 * Tokenize the format string into alphanumeric and non-alphanumeric 199 * tokens as described in M. Kay page 241. 200 */ 201 for (int j = 0, i = 0; i < length;) { 202 char c = format.charAt(i); 203 for (j = i; Character.isLetterOrDigit(c);) { 204 if (++i == length) break; 205 c = format.charAt(i); 206 } 207 if (i > j) { 208 if (isFirst) { 209 _separToks.addElement("."); 210 isFirst = _separFirst = false; 211 } 212 _formatToks.addElement(format.substring(j, i)); 213 } 214 215 if (i == length) break; 216 217 c = format.charAt(i); 218 for (j = i; !Character.isLetterOrDigit(c);) { 219 if (++i == length) break; 220 c = format.charAt(i); 221 isFirst = false; 222 } 223 if (i > j) { 224 _separToks.addElement(format.substring(j, i)); 225 } 226 } 227 228 _nSepars = _separToks.size(); 229 _nFormats = _formatToks.size(); 230 if (_nSepars > _nFormats) _separLast = true; 231 232 if (_separFirst) _nSepars--; 233 if (_separLast) _nSepars--; 234 if (_nSepars == 0) { 235 _separToks.insertElementAt(".", 1); 236 _nSepars++; 237 } 238 if (_separFirst) _nSepars ++; 239 240 } 241 /** 242 * Sets formatting fields to their default values. 243 */ 244 public NodeCounter setDefaultFormatting() { 245 setFormatting("1", "en", "alphabetic", null, null); 246 return this; 247 } 248 249 /** 250 * Returns the position of <tt>node</tt> according to the level and 251 * the from and count patterns. 252 */ 253 abstract public String getCounter(); 254 255 /** 256 * Returns the position of <tt>node</tt> according to the level and 257 * the from and count patterns. This position is converted into a 258 * string based on the arguments passed. 259 */ 260 public String getCounter(String format, String lang, String letterValue, 261 String groupSep, String groupSize) { 262 setFormatting(format, lang, letterValue, groupSep, groupSize); 263 return getCounter(); 264 } 265 266 /** 267 * Returns true if <tt>node</tt> matches the count pattern. By 268 * default a node matches the count patterns if it is of the 269 * same type as the starting node. 270 */ 271 public boolean matchesCount(int node) { 272 return _nodeType == _document.getExpandedTypeID(node); 273 } 274 275 /** 276 * Returns true if <tt>node</tt> matches the from pattern. By default, 277 * no node matches the from pattern. 278 */ 279 public boolean matchesFrom(int node) { 280 return false; 281 } 282 283 /** 284 * Format a single value according to the format parameters. 285 */ 286 protected String formatNumbers(int value) { 287 return formatNumbers(new int[] { value }); 288 } 289 290 /** 291 * Format a sequence of values according to the format paramaters 292 * set by calling setFormatting(). 293 */ 294 protected String formatNumbers(int[] values) { 295 final int nValues = values.length; 296 297 boolean isEmpty = true; 298 for (int i = 0; i < nValues; i++) 299 if (values[i] != Integer.MIN_VALUE) 300 isEmpty = false; 301 if (isEmpty) return(""); 302 303 // Format the output string using the values array and the fmt. tokens 304 boolean isFirst = true; 305 int t = 0, n = 0, s = 1; 306 _tempBuffer.setLength(0); 307 final StringBuilder buffer = _tempBuffer; 308 309 // Append separation token before first digit/letter/numeral 310 if (_separFirst) buffer.append((String)_separToks.elementAt(0)); 311 312 // Append next digit/letter/numeral and separation token 313 while (n < nValues) { 314 final int value = values[n]; 315 if (value != Integer.MIN_VALUE) { 316 if (!isFirst) buffer.append((String) _separToks.elementAt(s++)); 317 formatValue(value, (String)_formatToks.elementAt(t++), buffer); 318 if (t == _nFormats) t--; 319 if (s >= _nSepars) s--; 320 isFirst = false; 321 } 322 n++; 323 } 324 325 // Append separation token after last digit/letter/numeral 326 if (_separLast) buffer.append((String)_separToks.lastElement()); 327 return buffer.toString(); 328 } 329 330 /** 331 * Format a single value based on the appropriate formatting token. 332 * This method is based on saxon (Michael Kay) and only implements 333 * lang="en". 334 */ 335 private void formatValue(int value, String format, StringBuilder buffer) { 336 char c = format.charAt(0); 337 338 if (Character.isDigit(c)) { 339 char zero = (char)(c - Character.getNumericValue(c)); 340 341 StringBuilder temp = buffer; 342 if (_groupSize > 0) { 343 temp = new StringBuilder(); 344 } 345 String s = ""; 346 int n = value; 347 while (n > 0) { 348 s = (char) ((int) zero + (n % 10)) + s; 349 n = n / 10; 350 } 351 352 for (int i = 0; i < format.length() - s.length(); i++) { 353 temp.append(zero); 354 } 355 temp.append(s); 356 357 if (_groupSize > 0) { 358 for (int i = 0; i < temp.length(); i++) { 359 if (i != 0 && ((temp.length() - i) % _groupSize) == 0) { 360 buffer.append(_groupSep); 361 } 362 buffer.append(temp.charAt(i)); 363 } 364 } 365 } 366 else if (c == 'i' && !_letterValue.equals("alphabetic")) { 367 buffer.append(romanValue(value)); 368 } 369 else if (c == 'I' && !_letterValue.equals("alphabetic")) { 370 buffer.append(romanValue(value).toUpperCase()); 371 } 372 else { 373 int min = (int) c; 374 int max = (int) c; 375 376 // Special case for Greek alphabet 377 if (c >= 0x3b1 && c <= 0x3c9) { 378 max = 0x3c9; // omega 379 } 380 else { 381 // General case: search for end of group 382 while (Character.isLetterOrDigit((char) (max + 1))) { 383 max++; 384 } 385 } 386 buffer.append(alphaValue(value, min, max)); 387 } 388 } 389 390 private String alphaValue(int value, int min, int max) { 391 if (value <= 0) { 392 return "" + value; 393 } 394 395 int range = max - min + 1; 396 char last = (char)(((value-1) % range) + min); 397 if (value > range) { 398 return alphaValue((value-1) / range, min, max) + last; 399 } 400 else { 401 return "" + last; 402 } 403 } 404 405 private String romanValue(int n) { 406 if (n <= 0 || n > 4000) { 407 return "" + n; 408 } 409 return 410 Thousands[n / 1000] + 411 Hundreds[(n / 100) % 10] + 412 Tens[(n/10) % 10] + 413 Ones[n % 10]; 414 } 415 416 }