context: /* ====================================================================
     Licensed to the Apache Software Foundation (ASF) under one or more
     contributor license agreements.  See the NOTICE file distributed with
     this work for Additional information regarding copyright ownership.
     The ASF licenses this file to You 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.
  ==================================================================== */
  namespace AL.Utils.NPOI.SS.Format
  {
      using System;
      using System.Text.RegularExpressions;
      using System.Text;
      using System.Collections.Generic;
      using System.Collections.ObjectModel;
      using AL.Utils.NPOI.SS.Util;
      using System.Collections;
  
  
      /**
       * This class : printing out a value using a number format.
       *
       * @author Ken Arnold, Industrious Media LLC
       */
      public class CellNumberFormatter : CellFormatter
      {
          private String desc;
          private String printfFmt;
          private double scale;
          private Special decimalPoint;
          private Special slash;
          private Special exponent;
          private Special numerator;
          private Special Afterint;
          private Special AfterFractional;
          private bool integerCommas;
          private List<Special> specials;
          private List<Special> integerSpecials;
          private List<Special> fractionalSpecials;
          private List<Special> numeratorSpecials;
          private List<Special> denominatorSpecials;
          private List<Special> exponentSpecials;
          private List<Special> exponentDigitSpecials;
          private int maxDenominator;
          private String numeratorFmt;
          private String denominatorFmt;
          private bool improperFraction;
          private DecimalFormat decimalFmt;
          private static List<Special> EmptySpecialList = new List<Special>();
          private class SimpleNumberCellFormatter : CellFormatter
          {
              public SimpleNumberCellFormatter(string format)
                  : base(format)
              {
  
              }
              public override void FormatValue(StringBuilder toAppendTo, Object value)
              {
                  if (value == null)
                      return;
                  if (value.GetType().IsPrimitive/* is Number*/)
                  {
                      double num = (double)value;
                      if (num % 1.0 == 0)
                          SIMPLE_INT.FormatValue(toAppendTo, value);
                      else
                          SIMPLE_FLOAT.FormatValue(toAppendTo, value);
                  }
                  else
                  {
                      CellTextFormatter.SIMPLE_TEXT.FormatValue(toAppendTo, value);
                  }
              }
              public override void SimpleValue(StringBuilder toAppendTo, Object value)
              {
                  FormatValue(toAppendTo, value);
              }
          }
          static CellFormatter SIMPLE_NUMBER = new SimpleNumberCellFormatter("General");
  
          private static CellFormatter SIMPLE_INT = new CellNumberFormatter("#");
          private static CellFormatter SIMPLE_FLOAT = new CellNumberFormatter("#.#");
  
          /**
           * This class is used to mark where the special characters in the format
           * are, as opposed to the other characters that are simply printed.
           */
          internal class Special
          {
              internal char ch;
              internal int pos;
  
              public Special(char ch, int pos)
              {
                  this.ch = ch;
                  this.pos = pos;
              }
  
  
              public override String ToString()
              {
                  return "'" + ch + "' @ " + pos;
              }
          }
  
          /**
           * This class represents a single modification to a result string.  The way
           * this works is complicated, but so is numeric formatting.  In general, for
           * most formats, we use a DecimalFormat object that will Put the string out
           * in a known format, usually with all possible leading and trailing zeros.
           * We then walk through the result and the orginal format, and note any
           * modifications that need to be made.  Finally, we go through and apply
           * them all, dealing with overlapping modifications.
           */
          internal class StringMod : IComparable<StringMod>
          {
              internal Special special;
              internal int op;
              internal string toAdd;
              internal Special end;
              internal bool startInclusive;
              internal bool endInclusive;
  
              public const int BEFORE = 1;
              public const int AFTER = 2;
              public const int REPLACE = 3;
  
              public StringMod(Special special, string toAdd, int op)
              {
                  this.special = special;
                  this.toAdd = toAdd;
                  this.op = op;
              }
  
              public StringMod(Special start, bool startInclusive, Special end,
                      bool endInclusive, char toAdd)
                  : this(start, startInclusive, end, endInclusive)
              {
                  this.toAdd = toAdd + "";
              }
  
              public StringMod(Special start, bool startInclusive, Special end,
                      bool endInclusive)
              {
                  special = start;
                  this.startInclusive = startInclusive;
                  this.end = end;
                  this.endInclusive = endInclusive;
                  op = REPLACE;
                  toAdd = "";
              }
  
              public int CompareTo(StringMod that)
              {
                  int diff = special.pos - that.special.pos;
                  if (diff != 0)
                      return diff;
                  else
                      return op - that.op;
              }
  
  
              public override bool Equals(Object that)
              {
                  try
                  {
                      return CompareTo((StringMod)that) == 0;
                  }
                  catch (Exception)
                  {
                      // NullPointerException or CastException
                      return false;
                  }
              }
  
  
              public override int GetHashCode()
              {
                  return special.GetHashCode() + op;
              }
          }
  
          private class NumPartHandler : CellFormatPart.IPartHandler
          {
              private char insertSignForExponent;
              CellNumberFormatter formatter;
              public NumPartHandler(CellNumberFormatter formatter)
              {
                  this.formatter = formatter;
              }
              public String HandlePart(Match m, String part, CellFormatType type,
                      StringBuilder desc)
              {
                  int pos = desc.Length;
                  char firstCh = part[0];
                  switch (firstCh)
                  {
                      case 'e':
                      case 'E':
                          // See comment in WriteScientific -- exponent handling is complex.
                          // (1) When parsing the format, remove the sign from After the 'e' and
                          // Put it before the first digit of the exponent.
                          if (formatter.exponent == null && formatter.specials.Count > 0)
                          {
                              formatter.specials.Add(formatter.exponent = new Special('.', pos));
                              insertSignForExponent = part[1];
                              return part.Substring(0, 1);
                          }
                          break;
  
                      case '0':
                      case '?':
                      case '#':
                          if (insertSignForExponent != '\0')
                          {
                              formatter.specials.Add(new Special(insertSignForExponent, pos));
                              desc.Append(insertSignForExponent);
                              insertSignForExponent = '\0';
                              pos++;
                          }
                          for (int i = 0; i < part.Length; i++)
                          {
                              char ch = part[i];
                              formatter.specials.Add(new Special(ch, pos + i));
                          }
                          break;
  
                      case '.':
                          if (formatter.decimalPoint == null && formatter.specials.Count > 0)
                              formatter.specials.Add(formatter.decimalPoint = new Special('.', pos));
                          break;
  
                      case '/':
                          //!! This assumes there is a numerator and a denominator, but these are actually optional
                          if (formatter.slash == null && formatter.specials.Count > 0)
                          {
                              formatter.numerator = formatter.previousNumber();
                              // If the first number in the whole format is the numerator, the
                              // entire number should be printed as an improper fraction
                              if (formatter.numerator == firstDigit(formatter.specials))
                                  formatter.improperFraction = true;
                              formatter.specials.Add(formatter.slash = new Special('.', pos));
                          }
                          break;
  
                      case '%':
                          // don't need to remember because we don't need to do anything with these
                          formatter.scale *= 100;
                          break;
  
                      default:
                          return null;
                  }
                  return part;
              }
          }
          /**
           * Creates a new cell number formatter.
           *
           * @param format The format to Parse.
           */
          public CellNumberFormatter(String format)
              : base(format)
          {
              ;
  
              scale = 1;
  
              specials = new List<Special>();
  
              NumPartHandler partHandler = new NumPartHandler(this);
              StringBuilder descBuf = CellFormatPart.ParseFormat(format,
                      CellFormatType.NUMBER, partHandler);
              string fmt = descBuf.ToString();
              // These are inconsistent Settings, so ditch 'em
              if ((decimalPoint != null || exponent != null) && slash != null)
              {
                  slash = null;
                  numerator = null;
              }
  
              interpretCommas(descBuf);
  
              int precision;
              int fractionPartWidth = 0;
              if (decimalPoint == null)
              {
                  precision = 0;
              }
              else
              {
                  precision = interpretPrecision();
                  fractionPartWidth = 1 + precision;
                  if (precision == 0)
                  {
                      // This means the format has a ".", but that output should have no decimals After it.
                      // We just stop treating it specially
                      specials.Remove(decimalPoint);
                      decimalPoint = null;
                  }
              }
  
              if (precision == 0)
                  fractionalSpecials = EmptySpecialList;
              else
              {
                  int pos = specials.IndexOf(decimalPoint) + 1;
                  fractionalSpecials = specials.GetRange(pos, fractionalEnd() - pos);
              }
              if (exponent == null)
                  exponentSpecials = EmptySpecialList;
              else
              {
                  int exponentPos = specials.IndexOf(exponent);
                  exponentSpecials = specialsFor(exponentPos, 2);
                  exponentDigitSpecials = specialsFor(exponentPos + 2);
              }
  
              if (slash == null)
              {
                  numeratorSpecials = EmptySpecialList;
                  denominatorSpecials = EmptySpecialList;
              }
              else
              {
                  if (numerator == null)
                      numeratorSpecials = EmptySpecialList;
                  else
                      numeratorSpecials = specialsFor(specials.IndexOf(numerator));
  
                  denominatorSpecials = specialsFor(specials.IndexOf(slash) + 1);
                  if (denominatorSpecials.Count == 0)
                  {
                      // no denominator follows the slash, drop the fraction idea
                      numeratorSpecials = EmptySpecialList;
                  }
                  else
                  {
                      maxDenominator = maxValue(denominatorSpecials);
                      numeratorFmt = SingleNumberFormat(numeratorSpecials);
                      denominatorFmt = SingleNumberFormat(denominatorSpecials);
                  }
              }
  
              integerSpecials = specials.GetRange(0, integerEnd());
  
              if (exponent == null)
              {
                  StringBuilder fmtBuf = new StringBuilder();
  
                  int integerPartWidth = calculateintPartWidth();
                  //int totalWidth = integerPartWidth + fractionPartWidth;
  
                  fmtBuf.Append('0', integerPartWidth).Append('.').Append('0', precision);
  
                  //fmtBuf.Append("f");
                  printfFmt = fmtBuf.ToString();
  
                  //this number format is legal in C#;
                  //printfFmt = fmt;
              }
              else
              {
                  StringBuilder fmtBuf = new StringBuilder();
                  bool first = true;
                  List<Special> specialList = integerSpecials;
                  if (integerSpecials.Count == 1)
                  {
                      // If we don't do this, we Get ".6e5" instead of "6e4"
                      fmtBuf.Append("0");
                      first = false;
                  }
                  else
                      foreach (Special s in specialList)
                      {
                          if (IsDigitFmt(s))
                          {
                              fmtBuf.Append(first ? '#' : '0');
                              first = false;
                          }
                      }
                  if (fractionalSpecials.Count > 0)
                  {
                      fmtBuf.Append('.');
                      foreach (Special s in fractionalSpecials)
                      {
                          if (IsDigitFmt(s))
                          {
                              if (!first)
                                  fmtBuf.Append('0');
                              first = false;
                          }
                      }
                  }
                  fmtBuf.Append('E');
                  placeZeros(fmtBuf, exponentSpecials.GetRange(2,
                          exponentSpecials.Count - 2));
                  decimalFmt = new DecimalFormat(fmtBuf.ToString());
              }
  
              if (exponent != null)
                  scale =
                          1;  // in "e" formats,% and trailing commas have no scaling effect
  
              desc = descBuf.ToString();
          }
  
          private static void placeZeros(StringBuilder sb, List<Special> specials)
          {
              foreach (Special s in specials)
              {
                  if (IsDigitFmt(s))
                      sb.Append('0');
              }
          }
  
          private static Special firstDigit(List<Special> specials)
          {
              foreach (Special s in specials)
              {
                  if (IsDigitFmt(s))
                      return s;
              }
              return null;
          }
  
          static StringMod insertMod(Special special, string toAdd, int where)
          {
              return new StringMod(special, toAdd, where);
          }
  
          static StringMod deleteMod(Special start, bool startInclusive,
                  Special end, bool endInclusive)
          {
  
              return new StringMod(start, startInclusive, end, endInclusive);
          }
  
          static StringMod ReplaceMod(Special start, bool startInclusive,
                  Special end, bool endInclusive, char withChar)
          {
  
              return new StringMod(start, startInclusive, end, endInclusive,
                      withChar);
          }
  
          private static String SingleNumberFormat(List<Special> numSpecials)
          {
              //return "%0" + numSpecials.Count + "d";
              return "D" + numSpecials.Count;
          }
  
          private static int maxValue(List<Special> s)
          {
              return (int)Math.Round(Math.Pow(10, s.Count) - 1);
          }
  
          private List<Special> specialsFor(int pos, int takeFirst)
          {
              if (pos >= specials.Count)
                  return EmptySpecialList;
              IEnumerator<Special> it = specials.GetRange(pos + takeFirst, specials.Count - pos - takeFirst).GetEnumerator();
              //.ListIterator(pos + takeFirst);
              it.MoveNext();
              Special last = it.Current;
              int end = pos + takeFirst;
              while (it.MoveNext())
              {
                  Special s = it.Current;
                  if (!IsDigitFmt(s) || s.pos - last.pos > 1)
                      break;
                  end++;
                  last = s;
              }
              return specials.GetRange(pos, end + 1 - pos);
          }
  
          private List<Special> specialsFor(int pos)
          {
              return specialsFor(pos, 0);
          }
  
          private static bool IsDigitFmt(Special s)
          {
              return s.ch == '0' || s.ch == '?' || s.ch == '#';
          }
  
          private Special previousNumber()
          {
              //ListIterator<Special> it = specials.ListIterator(specials.Count);
              //while (it.HasPrevious())
              for (int i = specials.Count - 1; i >= 0; i--)
              {
                  Special s = specials[i];
                  if (IsDigitFmt(s))
                  {
                      Special numStart = s;
                      Special last = s;
  
gt:                     while (i > 0)