content: /*
   *  This file is part of AndroidIDE.
   *
   *  AndroidIDE is free software: you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License as published by
   *  the Free Software Foundation, either version 3 of the License, or
   *  (at your option) any later version.
   *
   *  AndroidIDE is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *   along with AndroidIDE.  If not, see <https://www.gnu.org/licenses/>.
   */
  
  /*
   * reserved comment block
   * DO NOT REMOVE OR ALTER!
   */
  /*
   * Copyright 2001-2004 The Apache Software Foundation.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /*
   * $Id: NodeCounter.java,v 1.2.4.1 2005/09/12 11:52:36 pvedula Exp $
   */
  
  package jaxp.sun.org.apache.xalan.internal.xsltc.dom;
  
  import java.util.Vector;
  
  import jaxp.sun.org.apache.xalan.internal.xsltc.DOM;
  import jaxp.sun.org.apache.xalan.internal.xsltc.Translet;
  import jaxp.sun.org.apache.xml.internal.dtm.DTM;
  import jaxp.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  
  /**
   * @author Jacek Ambroziak
   * @author Santiago Pericas-Geertsen
   * @author Morten Jorgensen
   */
  public abstract class NodeCounter {
      public static final int END = DTM.NULL;
  
      protected int _node = END;
      protected int _nodeType = DOM.FIRST_TYPE - 1;
      protected double _value = Integer.MIN_VALUE;
  
      public final DOM          _document;
      public final DTMAxisIterator _iterator;
      public final Translet _translet;
  
      protected String _format;
      protected String _lang;
      protected String _letterValue;
      protected String _groupSep;
      protected int    _groupSize;
  
      private boolean _separFirst = true;
      private boolean _separLast = false;
      private Vector _separToks = new Vector();
      private Vector _formatToks = new Vector();
      private int _nSepars  = 0;
      private int _nFormats = 0;
  
      private final static String[] Thousands =
          {"", "m", "mm", "mmm" };
      private final static String[] Hundreds =
      {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
      private final static String[] Tens =
      {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
      private final static String[] Ones =
      {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
  
      private StringBuilder _tempBuffer = new StringBuilder();
  
      /**
       * Indicates if this instance of xsl:number has a from pattern.
       */
      protected boolean _hasFrom;
  
      protected NodeCounter(Translet translet,
                DOM document, DTMAxisIterator iterator) {
      _translet = translet;
      _document = document;
      _iterator = iterator;
      }
  
      protected NodeCounter(Translet translet,
                DOM document, DTMAxisIterator iterator, boolean hasFrom) {
          _translet = translet;
          _document = document;
          _iterator = iterator;
          _hasFrom = hasFrom;
      }
  
      /**
       * Set the start node for this counter. The same <tt>NodeCounter</tt>
       * object can be used multiple times by resetting the starting node.
       */
      abstract public NodeCounter setStartNode(int node);
  
      /**
       * If the user specified a value attribute, use this instead of
       * counting nodes.
       */
      public NodeCounter setValue(double value) {
      _value = value;
      return this;
      }
  
      /**
       * Sets formatting fields before calling formatNumbers().
       */
      protected void setFormatting(String format, String lang, String letterValue,
                   String groupSep, String groupSize) {
      _lang = lang;
      _groupSep = groupSep;
      _letterValue = letterValue;
      _groupSize = parseStringToAnInt(groupSize);
      setTokens(format);
  
   }
  
      /**
       * Effectively does the same thing as Integer.parseInt(String s) except
       * instead of throwing a NumberFormatException, it returns 0.  This method
       * is used instead of Integer.parseInt() since it does not incur the
       * overhead of throwing an Exception which is expensive.
       *
       * @param s  A String to be parsed into an int.
       * @return  Either an int represented by the incoming String s, or 0 if
       *          the parsing is not successful.
       */
      private int parseStringToAnInt(String s) {
          if (s == null)
              return 0;
  
          int result = 0;
          boolean negative = false;
          int radix = 10, i = 0, max = s.length();
          int limit, multmin, digit;
  
          if (max > 0) {
              if (s.charAt(0) == '-') {
                  negative = true;
                  limit = Integer.MIN_VALUE;
                  i++;
              } else {
                  limit = -Integer.MAX_VALUE;
              }
              multmin = limit / radix;
              if (i < max) {
                  digit = Character.digit(s.charAt(i++), radix);
                  if (digit < 0)
                      return 0;
                  else
                      result = -digit;
              }
              while (i < max) {
                  // Accumulating negatively avoids surprises near MAX_VALUE
                  digit = Character.digit(s.charAt(i++), radix);
                  if (digit < 0)
                      return 0;
                  if (result < multmin)
                      return 0;
                  result *= radix;
                  if (result < limit + digit)
                      return 0;
                  result -= digit;
              }
          } else {
              return 0;
          }
          if (negative) {
              if (i > 1)
                  return result;
              else /* Only got "-" */
                  return 0;
          } else {
              return -result;
          }
      }
  
    // format == null assumed here
   private final void setTokens(final String format){
       if( (_format!=null) &&(format.equals(_format)) ){// has already been set
          return;
       }
       _format = format;
       // reset
       final int length = _format.length();
       boolean isFirst = true;
       _separFirst = true;
       _separLast = false;
       _nSepars  = 0;
       _nFormats = 0;
       _separToks.clear() ;
       _formatToks.clear();
  
           /*
            * Tokenize the format string into alphanumeric and non-alphanumeric
            * tokens as described in M. Kay page 241.
            */
           for (int j = 0, i = 0; i < length;) {
                   char c = format.charAt(i);
                   for (j = i; Character.isLetterOrDigit(c);) {
                       if (++i == length) break;
               c = format.charAt(i);
                   }
                   if (i > j) {
                       if (isFirst) {
                           _separToks.addElement(".");
                           isFirst = _separFirst = false;
                       }
                       _formatToks.addElement(format.substring(j, i));
                   }
  
                   if (i == length) break;
  
                   c = format.charAt(i);
                   for (j = i; !Character.isLetterOrDigit(c);) {
                       if (++i == length) break;
                       c = format.charAt(i);
                       isFirst = false;
                   }
                   if (i > j) {
                       _separToks.addElement(format.substring(j, i));
                   }
               }
  
           _nSepars = _separToks.size();
           _nFormats = _formatToks.size();
           if (_nSepars > _nFormats) _separLast = true;
  
           if (_separFirst) _nSepars--;
           if (_separLast) _nSepars--;
           if (_nSepars == 0) {
               _separToks.insertElementAt(".", 1);
               _nSepars++;
           }
           if (_separFirst) _nSepars ++;
  
   }
      /**
       * Sets formatting fields to their default values.
       */
      public NodeCounter setDefaultFormatting() {
      setFormatting("1", "en", "alphabetic", null, null);
      return this;
      }
  
      /**
       * Returns the position of <tt>node</tt> according to the level and
       * the from and count patterns.
       */
      abstract public String getCounter();
  
      /**
       * Returns the position of <tt>node</tt> according to the level and
       * the from and count patterns. This position is converted into a
       * string based on the arguments passed.
       */
      public String getCounter(String format, String lang, String letterValue,
                  String groupSep, String groupSize) {
      setFormatting(format, lang, letterValue, groupSep, groupSize);
      return getCounter();
      }
  
      /**
       * Returns true if <tt>node</tt> matches the count pattern. By
       * default a node matches the count patterns if it is of the
       * same type as the starting node.
       */
      public boolean matchesCount(int node) {
      return _nodeType == _document.getExpandedTypeID(node);
      }
  
      /**
       * Returns true if <tt>node</tt> matches the from pattern. By default,
       * no node matches the from pattern.
       */
      public boolean matchesFrom(int node) {
      return false;
      }
  
      /**
       * Format a single value according to the format parameters.
       */
      protected String formatNumbers(int value) {
      return formatNumbers(new int[] { value });
      }
  
      /**
       * Format a sequence of values according to the format paramaters
       * set by calling setFormatting().
       */
      protected String formatNumbers(int[] values) {
      final int nValues = values.length;
  
      boolean isEmpty = true;
      for (int i = 0; i < nValues; i++)
          if (values[i] != Integer.MIN_VALUE)
          isEmpty = false;
      if (isEmpty) return("");
  
      // Format the output string using the values array and the fmt. tokens
      boolean isFirst = true;
      int t = 0, n = 0, s = 1;
    _tempBuffer.setLength(0);
      final StringBuilder buffer = _tempBuffer;
  
      // Append separation token before first digit/letter/numeral
      if (_separFirst) buffer.append((String)_separToks.elementAt(0));
  
      // Append next digit/letter/numeral and separation token
      while (n < nValues) {
          final int value = values[n];
          if (value != Integer.MIN_VALUE) {
          if (!isFirst) buffer.append((String) _separToks.elementAt(s++));
          formatValue(value, (String)_formatToks.elementAt(t++), buffer);
          if (t == _nFormats) t--;
          if (s >= _nSepars) s--;
          isFirst = false;
          }
          n++;
      }
  
      // Append separation token after last digit/letter/numeral
      if (_separLast) buffer.append((String)_separToks.lastElement());
      return buffer.toString();
      }
  
      /**
       * Format a single value based on the appropriate formatting token.
       * This method is based on saxon (Michael Kay) and only implements
       * lang="en".
       */
      private void formatValue(int value, String format, StringBuilder buffer) {
          char c = format.charAt(0);
  
          if (Character.isDigit(c)) {
              char zero = (char)(c - Character.getNumericValue(c));
  
              StringBuilder temp = buffer;
              if (_groupSize > 0) {
                  temp = new StringBuilder();
              }
              String s = "";
              int n = value;
              while (n > 0) {
                  s = (char) ((int) zero + (n % 10)) + s;
                  n = n / 10;
              }
  
              for (int i = 0; i < format.length() - s.length(); i++) {
                  temp.append(zero);
              }
              temp.append(s);
  
              if (_groupSize > 0) {
                  for (int i = 0; i < temp.length(); i++) {
                      if (i != 0 && ((temp.length() - i) % _groupSize) == 0) {
                          buffer.append(_groupSep);
                      }
                      buffer.append(temp.charAt(i));
                  }
              }
          }
      else if (c == 'i' && !_letterValue.equals("alphabetic")) {
              buffer.append(romanValue(value));
          }
      else if (c == 'I' && !_letterValue.equals("alphabetic")) {
              buffer.append(romanValue(value).toUpperCase());
          }
      else {
          int min = (int) c;
          int max = (int) c;
  
          // Special case for Greek alphabet
          if (c >= 0x3b1 && c <= 0x3c9) {
          max = 0x3c9;    // omega
          }
          else {
          // General case: search for end of group
          while (Character.isLetterOrDigit((char) (max + 1))) {
              max++;
          }
          }
              buffer.append(alphaValue(value, min, max));
          }
      }
  
      private String alphaValue(int value, int min, int max) {
          if (value <= 0) {
          return "" + value;
      }
  
          int range = max - min + 1;
          char last = (char)(((value-1) % range) + min);
          if (value > range) {
              return alphaValue((value-1) / range, min, max) + last;
          }
      else {
              return "" + last;
          }
      }
  
      private String romanValue(int n) {
          if (n <= 0 || n > 4000) {
          return "" + n;
      }
          return
          Thousands[n / 1000] +
          Hundreds[(n / 100) % 10] +
          Tens[(n/10) % 10] +
          Ones[n % 10];
      }
  
  }
  