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 }