Package org.exist.xquery.functions.fn

Source Code of org.exist.xquery.functions.fn.FnFormatNumbers$Formatter

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2012 The eXist Project
*  http://exist-db.org
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*
*  This program 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 Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*
*  $Id$
*/
package org.exist.xquery.functions.fn;

import org.exist.dom.QName;
import org.exist.xquery.*;
import org.exist.xquery.value.*;

import java.lang.String;
import java.math.BigDecimal;

/**
* fn:format-number($value as numeric?, $picture as xs:string) as xs:string
* fn:format-number($value as numeric?, $picture as xs:string, $decimal-format-name as xs:string) as xs:string
*
* @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
*
*/
public class FnFormatNumbers extends BasicFunction {

  private static final SequenceType NUMBER_PARAMETER = new FunctionParameterSequenceType("value", Type.NUMBER, Cardinality.ZERO_OR_ONE, "The number to format");

  private static final SequenceType PICTURE = new FunctionParameterSequenceType("picture", Type.STRING, Cardinality.EXACTLY_ONE, "The format pattern string.  Please see the JavaDoc for java.text.DecimalFormat to get the specifics of this format string.");
  private static final String PICTURE_DESCRIPTION = "The formatting of a number is controlled by a picture string. The picture string is a sequence of ·characters·, in which the characters assigned to the variables decimal-separator-sign, grouping-sign, decimal-digit-family, optional-digit-sign and pattern-separator-sign are classified as active characters, and all other characters (including the percent-sign and per-mille-sign) are classified as passive characters.";

  private static final SequenceType DECIMAL_FORMAT = new FunctionParameterSequenceType("decimal-format-name", Type.STRING, Cardinality.EXACTLY_ONE, "The decimal-format name must be a QName, which is expanded as described in [2.4 Qualified Names]. It is an error if the stylesheet does not contain a declaration of the decimal-format with the specified expanded-name.");
  private static final String DECIMAL_FORMAT_DESCRIPTION = "";

  private static final FunctionReturnSequenceType FUNCTION_RETURN_TYPE = new FunctionReturnSequenceType(Type.STRING, Cardinality.ONE, "the formatted string");

  public final static FunctionSignature signatures[] = {
    new FunctionSignature(
        new QName("format-number", Function.BUILTIN_FUNCTION_NS, FnModule.PREFIX),
        PICTURE_DESCRIPTION,
        new SequenceType[] {NUMBER_PARAMETER, PICTURE},
        FUNCTION_RETURN_TYPE
    ),
    new FunctionSignature(
        new QName("format-number", Function.BUILTIN_FUNCTION_NS, FnModule.PREFIX),
        DECIMAL_FORMAT_DESCRIPTION,
        new SequenceType[] {NUMBER_PARAMETER, PICTURE, DECIMAL_FORMAT},
        FUNCTION_RETURN_TYPE
    )
  };
 
  /**
   * @param context
   */
  public FnFormatNumbers(XQueryContext context, FunctionSignature signature) {
    super(context, signature);
  }

  @Override
  public Sequence eval(Sequence[] args, Sequence contextSequence)
      throws XPathException {

    if (args[0].isEmpty()) {
      return Sequence.EMPTY_SEQUENCE;
    }
   
    final NumericValue numericValue = (NumericValue)args[0].itemAt(0);
   
    try {
      final Formatter[] formatters = prepare(args[1].getStringValue());
      final String value = format(formatters[0], numericValue);
      return new StringValue(value);
    } catch (final java.lang.IllegalArgumentException e) {
      e.printStackTrace();
      throw new XPathException(e.getMessage());
    }
  }
 
  private String format(Formatter f, NumericValue numericValue) throws XPathException {
    if (numericValue.isNaN()) {
      return NaN;
    }

    final String minuSign = numericValue.isNegative()? String.valueOf(MINUS_SIGN) : "";
    if (numericValue.isInfinite()) {
      return minuSign + f.prefix + INFINITY + f.suffix;
    }
   
    NumericValue factor = null;
    if (f.isPercent) {
      factor = new IntegerValue(100);
    } else if (f.isPerMille) {
      factor = new IntegerValue(1000);
    }
   
    if (factor != null) {
      try {
        numericValue = (NumericValue) numericValue.mult(factor);
      } catch (final XPathException e) {
        e.printStackTrace();
        throw e;
      }
    }
   
    int pl = 0;
   
    final StringBuilder sb = new StringBuilder();
   
    if (numericValue.hasFractionalPart()) {
     
      BigDecimal val = ((DecimalValue)numericValue.convertTo(Type.DECIMAL)).getValue();

      val = val.setScale(f.flMAX, BigDecimal.ROUND_HALF_EVEN);
     
      final String number = val.toPlainString();
       
      sb.append(number);
     
      pl = number.indexOf('.');
      if (pl < 0) {
        sb.append('.');
        for (int i = 0; i < f.flMIN; i++) {
          sb.append('0');
        }
      } else {
       
      }

    } else {
      final String str = numericValue.getStringValue();
      pl = str.length();
      formatInt(str, sb, f);
    }
   
    if (f.mg != 0) {
      int pos = pl - f.mg;
      while (pos > 0) {
        sb.insert(pos, ',');
        pos -= f.mg;
      }
    }

    return sb.toString();
  }
 
  private void formatInt(String number, StringBuilder sb, Formatter f) {
    final int leadingZ = f.mlMIN - number.length();
    for (int i = 0; i < leadingZ; i++) {
      sb.append('0');
    }
    sb.append(number);
    if (f.flMIN > 0) {
      sb.append(".");
      for (int i = 0; i < f.mlMIN; i++) {
        sb.append('0');
      }
    }
  }

  private final char DECIMAL_SEPARATOR_SIGN = '.';
  private final char GROUPING_SEPARATOR_SIGN = ',';
  private final String INFINITY = "Infinity";
  private final char MINUS_SIGN = '-';
  private final String NaN = "NaN";
  private final char PERCENT_SIGN = '%';
  private final char PER_MILLE_SIGN = '\u2030';
  private final char MANDATORY_DIGIT_SIGN = '0';
  private final char OPTIONAL_DIGIT_SIGN = '#';
  private final char PATTERN_SEPARATOR_SIGN = ';';
 
  private Formatter[] prepare(String picture) throws XPathException {
    if (picture.length() == 0)
      {throw new XPathException(this, ErrorCodes.XTDE1310, "format-number() picture is zero-length");}
   
    final String[] pics = picture.split(String.valueOf(PATTERN_SEPARATOR_SIGN));
   
    final Formatter[] formatters = new Formatter[2];
    for (int i = 0; i < pics.length; i++) {
      formatters[i] = new Formatter(pics[i]);
    }
   
    return formatters;
  }
 
  class Formatter {

    String prefix = "", suffix = "";

    boolean ds = false, isPercent = false, isPerMille = false;
    int mlMAX = 0, flMAX = 0;
    int mlMIN = 0, flMIN = 0;
   
    int mg = 0, fg = 0;

    public Formatter(String picture) throws XPathException {
      if ( ! (
          picture.contains(String.valueOf(OPTIONAL_DIGIT_SIGN))
          || picture.contains(String.valueOf(MANDATORY_DIGIT_SIGN))
          )
        ) {
       
        throw new XPathException(FnFormatNumbers.this, ErrorCodes.XTDE1310,
          "A sub-picture must contain at least one character that is an optional-digit-sign or a member of the decimal-digit-family.");
      }
     
      int bmg = -1, bfg = -1;

      // 0 - beginning passive-chars
      // 1 - digit signs
      // 2 - zero signs
      // 3 - fractional zero signs
      // 4 - fractional digit signs
      // 5 - ending passive-chars
      short phase = 0;
     
      for (int i = 0; i < picture.length(); i++) {
        char ch = picture.charAt(i);
       
        switch (ch) {
        case OPTIONAL_DIGIT_SIGN:
         
          switch (phase) {
          case 0:
          case 1:
            mlMAX++;
            phase = 1;
            break;

          case 2:
            throw new XPathException(FnFormatNumbers.this, ErrorCodes.XTDE1310,
              "");

          case 3:
          case 4:
            flMAX++;
            phase = 4;
            break;

          case 5:
            throw new XPathException(FnFormatNumbers.this, ErrorCodes.XTDE1310,
              "A sub-picture must not contain a passive character that is preceded by an active character and that is followed by another active character. " +
              "Found at optional-digit-sign.");
          }
         
          break;

        case MANDATORY_DIGIT_SIGN:
         
          switch (phase) {
          case 0:
          case 1:
          case 2:
            mlMIN++;
            mlMAX++;
            phase = 2;
            break;

          case 3:
            flMIN++;
            flMAX++;
            break;

          case 4:
            throw new XPathException(FnFormatNumbers.this, ErrorCodes.XTDE1310,
              "");

          case 5:
            throw new XPathException(FnFormatNumbers.this, ErrorCodes.XTDE1310,
              "A sub-picture must not contain a passive character that is preceded by an active character and that is followed by another active character. " +
              "Found at mandatory-digit-sign.");
          }

          break;

        case GROUPING_SEPARATOR_SIGN:
         
          switch (phase) {
          case 0:
          case 1:
          case 2:
            if (bmg == -1) {
              bmg = i;
            } else {
              mg = i - bmg;
              bmg = -1;
            }
            break;

          case 3:
          case 4:
            if (bfg == -1) {
              bfg = i;
            } else {
              fg = i - bfg;
              bfg = -1;
            }
            break;
          case 5:
            throw new XPathException(FnFormatNumbers.this, ErrorCodes.XTDE1310,
              "A sub-picture must not contain a passive character that is preceded by an active character and that is followed by another active character. " +
              "Found at grouping-separator-sign.");
          }

          break;

        case DECIMAL_SEPARATOR_SIGN:
         
          switch (phase) {
          case 0:
          case 1:
          case 2:
            if (bmg != -1) {
              mg = i - bmg - 1;
              bmg = -1;
            }
            ds = true;
            phase = 3;
            break;

          case 3:
          case 4:
          case 5:
            if (ds)
              {throw new XPathException(FnFormatNumbers.this, ErrorCodes.XTDE1310,
                "A sub-picture must not contain more than one decimal-separator-sign.");}

            throw new XPathException(FnFormatNumbers.this, ErrorCodes.XTDE1310,
              "A sub-picture must not contain a passive character that is preceded by an active character and that is followed by another active character. " +
              "Found at decimal-separator-sign.");
          }

          break;

        case PERCENT_SIGN:
        case PER_MILLE_SIGN:
          if (isPercent || isPerMille)
            {throw new XPathException(FnFormatNumbers.this, ErrorCodes.XTDE1310,
              "A sub-picture must not contain more than one percent-sign or per-mille-sign, and it must not contain one of each.");}
         
          isPercent = ch == PERCENT_SIGN;
          isPerMille = ch == PER_MILLE_SIGN;
         
          switch (phase) {
          case 0:
            prefix += ch;
            break;
          case 1:
          case 2:
          case 3:
          case 4:
          case 5:
            phase = 5;
            suffix += ch;
            break;
          }

          break;

        default:
          //passive chars
          switch (phase) {
          case 0:
            prefix += ch;
            break;
          case 1:
          case 2:
          case 3:
          case 4:
          case 5:
            if (bmg != -1) {
              mg = i - bmg - 1;
              bmg = -1;
            }
            suffix += ch;
            phase = 5;
            break;
          }
          break;
        }
      }
     
      if (mlMIN == 0 && !ds) {mlMIN = 1;}
     
//      System.out.println("prefix = "+prefix);
//      System.out.println("suffix = "+suffix);
//      System.out.println("ds = "+ds);
//      System.out.println("isPercent = "+isPercent);
//      System.out.println("isPerMille = "+isPerMille);
//      System.out.println("ml = "+mlMAX);
//      System.out.println("fl = "+flMAX);
//      System.out.println("mg = "+mg);
//      System.out.println("fg = "+fg);
    }
  }
}
TOP

Related Classes of org.exist.xquery.functions.fn.FnFormatNumbers$Formatter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.