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 }