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