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: NodeCounter.java 468651 2006-10-28 07:04:25Z minchau $ 020 */ 021 022 package org.apache.xalan.xsltc.dom; 023 024 import java.util.Vector; 025 026 import org.apache.xalan.xsltc.DOM; 027 import org.apache.xalan.xsltc.Translet; 028 import org.apache.xml.dtm.DTM; 029 import org.apache.xml.dtm.DTMAxisIterator; 030 import org.apache.xml.dtm.Axis; 031 032 /** 033 * @author Jacek Ambroziak 034 * @author Santiago Pericas-Geertsen 035 * @author Morten Jorgensen 036 */ 037 public abstract class NodeCounter { 038 public static final int END = DTM.NULL; 039 040 protected int _node = END; 041 protected int _nodeType = DOM.FIRST_TYPE - 1; 042 protected double _value = Integer.MIN_VALUE; 043 044 public final DOM _document; 045 public final DTMAxisIterator _iterator; 046 public final Translet _translet; 047 048 protected String _format; 049 protected String _lang; 050 protected String _letterValue; 051 protected String _groupSep; 052 protected int _groupSize; 053 054 private boolean _separFirst = true; 055 private boolean _separLast = false; 056 private Vector _separToks = new Vector(); 057 private Vector _formatToks = new Vector(); 058 private int _nSepars = 0; 059 private int _nFormats = 0; 060 061 private final static String[] Thousands = 062 {"", "m", "mm", "mmm" }; 063 private final static String[] Hundreds = 064 {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"}; 065 private final static String[] Tens = 066 {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"}; 067 private final static String[] Ones = 068 {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"}; 069 070 private StringBuffer _tempBuffer = new StringBuffer(); 071 072 protected NodeCounter(Translet translet, 073 DOM document, DTMAxisIterator iterator) { 074 _translet = translet; 075 _document = document; 076 _iterator = iterator; 077 } 078 079 /** 080 * Set the start node for this counter. The same <tt>NodeCounter</tt> 081 * object can be used multiple times by resetting the starting node. 082 */ 083 abstract public NodeCounter setStartNode(int node); 084 085 /** 086 * If the user specified a value attribute, use this instead of 087 * counting nodes. 088 */ 089 public NodeCounter setValue(double value) { 090 _value = value; 091 return this; 092 } 093 094 /** 095 * Sets formatting fields before calling formatNumbers(). 096 */ 097 protected void setFormatting(String format, String lang, String letterValue, 098 String groupSep, String groupSize) { 099 _lang = lang; 100 _groupSep = groupSep; 101 _letterValue = letterValue; 102 103 try { 104 _groupSize = Integer.parseInt(groupSize); 105 } 106 catch (NumberFormatException e) { 107 _groupSize = 0; 108 } 109 setTokens(format); 110 111 } 112 113 // format == null assumed here 114 private final void setTokens(final String format){ 115 if( (_format!=null) &&(format.equals(_format)) ){// has already been set 116 return; 117 } 118 _format = format; 119 // reset 120 final int length = _format.length(); 121 boolean isFirst = true; 122 _separFirst = true; 123 _separLast = false; 124 _nSepars = 0; 125 _nFormats = 0; 126 _separToks.clear() ; 127 _formatToks.clear(); 128 129 /* 130 * Tokenize the format string into alphanumeric and non-alphanumeric 131 * tokens as described in M. Kay page 241. 132 */ 133 for (int j = 0, i = 0; i < length;) { 134 char c = format.charAt(i); 135 for (j = i; Character.isLetterOrDigit(c);) { 136 if (++i == length) break; 137 c = format.charAt(i); 138 } 139 if (i > j) { 140 if (isFirst) { 141 _separToks.addElement("."); 142 isFirst = _separFirst = false; 143 } 144 _formatToks.addElement(format.substring(j, i)); 145 } 146 147 if (i == length) break; 148 149 c = format.charAt(i); 150 for (j = i; !Character.isLetterOrDigit(c);) { 151 if (++i == length) break; 152 c = format.charAt(i); 153 isFirst = false; 154 } 155 if (i > j) { 156 _separToks.addElement(format.substring(j, i)); 157 } 158 } 159 160 _nSepars = _separToks.size(); 161 _nFormats = _formatToks.size(); 162 if (_nSepars > _nFormats) _separLast = true; 163 164 if (_separFirst) _nSepars--; 165 if (_separLast) _nSepars--; 166 if (_nSepars == 0) { 167 _separToks.insertElementAt(".", 1); 168 _nSepars++; 169 } 170 if (_separFirst) _nSepars ++; 171 172 } 173 /** 174 * Sets formatting fields to their default values. 175 */ 176 public NodeCounter setDefaultFormatting() { 177 setFormatting("1", "en", "alphabetic", null, null); 178 return this; 179 } 180 181 /** 182 * Returns the position of <tt>node</tt> according to the level and 183 * the from and count patterns. 184 */ 185 abstract public String getCounter(); 186 187 /** 188 * Returns the position of <tt>node</tt> according to the level and 189 * the from and count patterns. This position is converted into a 190 * string based on the arguments passed. 191 */ 192 public String getCounter(String format, String lang, String letterValue, 193 String groupSep, String groupSize) { 194 setFormatting(format, lang, letterValue, groupSep, groupSize); 195 return getCounter(); 196 } 197 198 /** 199 * Returns true if <tt>node</tt> matches the count pattern. By 200 * default a node matches the count patterns if it is of the 201 * same type as the starting node. 202 */ 203 public boolean matchesCount(int node) { 204 return _nodeType == _document.getExpandedTypeID(node); 205 } 206 207 /** 208 * Returns true if <tt>node</tt> matches the from pattern. By default, 209 * no node matches the from pattern. 210 */ 211 public boolean matchesFrom(int node) { 212 return false; 213 } 214 215 /** 216 * Format a single value according to the format parameters. 217 */ 218 protected String formatNumbers(int value) { 219 return formatNumbers(new int[] { value }); 220 } 221 222 /** 223 * Format a sequence of values according to the format paramaters 224 * set by calling setFormatting(). 225 */ 226 protected String formatNumbers(int[] values) { 227 final int nValues = values.length; 228 final int length = _format.length(); 229 230 boolean isEmpty = true; 231 for (int i = 0; i < nValues; i++) 232 if (values[i] != Integer.MIN_VALUE) 233 isEmpty = false; 234 if (isEmpty) return(""); 235 236 // Format the output string using the values array and the fmt. tokens 237 boolean isFirst = true; 238 int t = 0, n = 0, s = 1; 239 _tempBuffer.setLength(0); 240 final StringBuffer buffer = _tempBuffer; 241 242 // Append separation token before first digit/letter/numeral 243 if (_separFirst) buffer.append((String)_separToks.elementAt(0)); 244 245 // Append next digit/letter/numeral and separation token 246 while (n < nValues) { 247 final int value = values[n]; 248 if (value != Integer.MIN_VALUE) { 249 if (!isFirst) buffer.append((String) _separToks.elementAt(s++)); 250 formatValue(value, (String)_formatToks.elementAt(t++), buffer); 251 if (t == _nFormats) t--; 252 if (s >= _nSepars) s--; 253 isFirst = false; 254 } 255 n++; 256 } 257 258 // Append separation token after last digit/letter/numeral 259 if (_separLast) buffer.append((String)_separToks.lastElement()); 260 return buffer.toString(); 261 } 262 263 /** 264 * Format a single value based on the appropriate formatting token. 265 * This method is based on saxon (Michael Kay) and only implements 266 * lang="en". 267 */ 268 private void formatValue(int value, String format, StringBuffer buffer) { 269 char c = format.charAt(0); 270 271 if (Character.isDigit(c)) { 272 char zero = (char)(c - Character.getNumericValue(c)); 273 274 StringBuffer temp = buffer; 275 if (_groupSize > 0) { 276 temp = new StringBuffer(); 277 } 278 String s = ""; 279 int n = value; 280 while (n > 0) { 281 s = (char) ((int) zero + (n % 10)) + s; 282 n = n / 10; 283 } 284 285 for (int i = 0; i < format.length() - s.length(); i++) { 286 temp.append(zero); 287 } 288 temp.append(s); 289 290 if (_groupSize > 0) { 291 for (int i = 0; i < temp.length(); i++) { 292 if (i != 0 && ((temp.length() - i) % _groupSize) == 0) { 293 buffer.append(_groupSep); 294 } 295 buffer.append(temp.charAt(i)); 296 } 297 } 298 } 299 else if (c == 'i' && !_letterValue.equals("alphabetic")) { 300 buffer.append(romanValue(value)); 301 } 302 else if (c == 'I' && !_letterValue.equals("alphabetic")) { 303 buffer.append(romanValue(value).toUpperCase()); 304 } 305 else { 306 int min = (int) c; 307 int max = (int) c; 308 309 // Special case for Greek alphabet 310 if (c >= 0x3b1 && c <= 0x3c9) { 311 max = 0x3c9; // omega 312 } 313 else { 314 // General case: search for end of group 315 while (Character.isLetterOrDigit((char) (max + 1))) { 316 max++; 317 } 318 } 319 buffer.append(alphaValue(value, min, max)); 320 } 321 } 322 323 private String alphaValue(int value, int min, int max) { 324 if (value <= 0) { 325 return "" + value; 326 } 327 328 int range = max - min + 1; 329 char last = (char)(((value-1) % range) + min); 330 if (value > range) { 331 return alphaValue((value-1) / range, min, max) + last; 332 } 333 else { 334 return "" + last; 335 } 336 } 337 338 private String romanValue(int n) { 339 if (n <= 0 || n > 4000) { 340 return "" + n; 341 } 342 return 343 Thousands[n / 1000] + 344 Hundreds[(n / 100) % 10] + 345 Tens[(n/10) % 10] + 346 Ones[n % 10]; 347 } 348 349 } 350