Package org.apache.poi.hssf.record.formula.functions

Source Code of org.apache.poi.hssf.record.formula.functions.Offset$BaseRef

/*
* 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.
*/


package org.apache.poi.hssf.record.formula.functions;

import org.apache.poi.hssf.record.formula.Area3DPtg;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.eval.Area3DEval;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.Ref3DEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* Implementation for Excel function OFFSET()<p/>
*
* OFFSET returns an area reference that is a specified number of rows and columns from a
* reference cell or area.<p/>
*
* <b>Syntax</b>:<br/>
* <b>OFFSET</b>(<b>reference</b>, <b>rows</b>, <b>cols</b>, height, width)<p/>
* <b>reference</b> is the base reference.<br/>
* <b>rows</b> is the number of rows up or down from the base reference.<br/>
* <b>cols</b> is the number of columns left or right from the base reference.<br/>
* <b>height</b> (default same height as base reference) is the row count for the returned area reference.<br/>
* <b>width</b> (default same width as base reference) is the column count for the returned area reference.<br/>
*
* @author Josh Micich
*/
public final class Offset implements FreeRefFunction {
  // These values are specific to BIFF8
  private static final int LAST_VALID_ROW_INDEX = 0xFFFF;
  private static final int LAST_VALID_COLUMN_INDEX = 0xFF;
 

  /**
   * Exceptions are used within this class to help simplify flow control when error conditions
   * are encountered
   */
  private static final class EvalEx extends Exception {
    private final ErrorEval _error;

    public EvalEx(ErrorEval error) {
      _error = error;
    }
    public ErrorEval getError() {
      return _error;
    }
  }
 
  /**
   * A one dimensional base + offset.  Represents either a row range or a column range.
   * Two instances of this class together specify an area range.
   */
  /* package */ static final class LinearOffsetRange {

    private final int _offset;
    private final int _length;

    public LinearOffsetRange(int offset, int length) {
      if(length == 0) {
        // handled that condition much earlier
        throw new RuntimeException("length may not be zero");
      }
      _offset = offset;
      _length = length;
    }
   
    public short getFirstIndex() {
      return (short) _offset;
    }
    public short getLastIndex() {
      return (short) (_offset + _length - 1);
    }
    /**
     * Moves the range by the specified translation amount.<p/>
     *
     * This method also 'normalises' the range: Excel specifies that the width and height
     * parameters (length field here) cannot be negative.  However, OFFSET() does produce
     * sensible results in these cases.  That behavior is replicated here. <p/>
     *
     * @param translationAmount may be zero negative or positive
     *
     * @return the equivalent <tt>LinearOffsetRange</tt> with a positive length, moved by the
     * specified translationAmount.
     */
    public LinearOffsetRange normaliseAndTranslate(int translationAmount) {
      if (_length > 0) {
        if(translationAmount == 0) {
          return this;
        }
        return new LinearOffsetRange(translationAmount + _offset, _length);
      }
      return new LinearOffsetRange(translationAmount + _offset + _length + 1, -_length);
    }

    public boolean isOutOfBounds(int lowValidIx, int highValidIx) {
      if(_offset < lowValidIx) {
        return true;
      }
      if(getLastIndex() > highValidIx) {
        return true;
      }
      return false;
    }
    public String toString() {
      StringBuffer sb = new StringBuffer(64);
      sb.append(getClass().getName()).append(" [");
      sb.append(_offset).append("...").append(getLastIndex());
      sb.append("]");
      return sb.toString();
    }
  }
 
 
  /**
   * Encapsulates either an area or cell reference which may be 2d or 3d.
   */
  private static final class BaseRef {
    private static final int INVALID_SHEET_INDEX = -1;
    private final int _firstRowIndex;
    private final int _firstColumnIndex;
    private final int _width;
    private final int _height;
    private final int _externalSheetIndex;
   
    public BaseRef(RefEval re) {
      _firstRowIndex = re.getRow();
      _firstColumnIndex = re.getColumn();
      _height = 1;
      _width = 1;
      if (re instanceof Ref3DEval) {
        Ref3DEval r3e = (Ref3DEval) re;
        _externalSheetIndex = r3e.getExternSheetIndex();
      } else {
        _externalSheetIndex = INVALID_SHEET_INDEX;
      }
    }

    public BaseRef(AreaEval ae) {
      _firstRowIndex = ae.getFirstRow();
      _firstColumnIndex = ae.getFirstColumn();
      _height = ae.getLastRow() - ae.getFirstRow() + 1;
      _width = ae.getLastColumn() - ae.getFirstColumn() + 1;
      if (ae instanceof Area3DEval) {
        Area3DEval a3e = (Area3DEval) ae;
        _externalSheetIndex = a3e.getExternSheetIndex();
      } else {
        _externalSheetIndex = INVALID_SHEET_INDEX;
      }
    }

    public int getWidth() {
      return _width;
    }

    public int getHeight() {
      return _height;
    }

    public int getFirstRowIndex() {
      return _firstRowIndex;
    }

    public int getFirstColumnIndex() {
      return _firstColumnIndex;
    }

    public boolean isIs3d() {
      return _externalSheetIndex > 0;
    }

    public short getExternalSheetIndex() {
      if(_externalSheetIndex < 0) {
        throw new IllegalStateException("external sheet index only available for 3d refs");
      }
      return (short) _externalSheetIndex;
    }

  }
 
  public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
   
    if(args.length < 3 || args.length > 5) {
      return ErrorEval.VALUE_INVALID;
    }
   
   
    try {
      BaseRef baseRef = evaluateBaseRef(args[0]);
      int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
      int columnOffset = evaluateIntArg(args[2], srcCellRow, srcCellCol);
      int height = baseRef.getHeight();
      int width = baseRef.getWidth();
      switch(args.length) {
        case 5:
          width = evaluateIntArg(args[4], srcCellRow, srcCellCol);
        case 4:
          height = evaluateIntArg(args[3], srcCellRow, srcCellCol);
      }
      // Zero height or width raises #REF! error
      if(height == 0 || width == 0) {
        return ErrorEval.REF_INVALID;
      }
      LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
      LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
      return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet);
    } catch (EvalEx e) {
      return e.getError();
    }
  }


  private static AreaEval createOffset(BaseRef baseRef,
      LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange,
      HSSFWorkbook workbook, HSSFSheet sheet) throws EvalEx {

    LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex());
    LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex());
   
    if(rows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) {
      throw new EvalEx(ErrorEval.REF_INVALID);
    }
    if(cols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) {
      throw new EvalEx(ErrorEval.REF_INVALID);
    }
    if(baseRef.isIs3d()) {
      Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(),
          cols.getFirstIndex(), cols.getLastIndex(),
          false, false, false, false,
          baseRef.getExternalSheetIndex());
      return HSSFFormulaEvaluator.evaluateArea3dPtg(workbook, a3dp);
    }
   
    AreaPtg ap = new AreaPtg(rows.getFirstIndex(), rows.getLastIndex(),
        cols.getFirstIndex(), cols.getLastIndex(),
        false, false, false, false);
    return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap);
  }


  private static BaseRef evaluateBaseRef(Eval eval) throws EvalEx {
   
    if(eval instanceof RefEval) {
      return new BaseRef((RefEval)eval);
    }
    if(eval instanceof AreaEval) {
      return new BaseRef((AreaEval)eval);
    }
    if (eval instanceof ErrorEval) {
      throw new EvalEx((ErrorEval) eval);
    }
    throw new EvalEx(ErrorEval.VALUE_INVALID);
  }


  /**
   * OFFSET's numeric arguments (2..5) have similar processing rules
   */
  private static int evaluateIntArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {

    double d = evaluateDoubleArg(eval, srcCellRow, srcCellCol);
    return convertDoubleToInt(d);
  }

  /**
   * Fractional values are silently truncated by Excel.
   * Truncation is toward negative infinity.
   */
  /* package */ static int convertDoubleToInt(double d) {
    // Note - the standard java type conversion from double to int truncates toward zero.
    // but Math.floor() truncates toward negative infinity
    return (int)Math.floor(d);
  }
 
 
  private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
    ValueEval ve = evaluateSingleValue(eval, srcCellRow, srcCellCol);
   
    if (ve instanceof NumericValueEval) {
      return ((NumericValueEval) ve).getNumberValue();
    }
    if (ve instanceof StringEval) {
      StringEval se = (StringEval) ve;
      Double d = parseDouble(se.getStringValue());
      if(d == null) {
        throw new EvalEx(ErrorEval.VALUE_INVALID);
      }
      return d.doubleValue();
    }
    if (ve instanceof BoolEval) {
      // in the context of OFFSET, booleans resolve to 0 and 1.
      if(((BoolEval) ve).getBooleanValue()) {
        return 1;
      }
      return 0;
    }
    throw new RuntimeException("Unexpected eval type (" + ve.getClass().getName() + ")");
  }
 
  private static Double parseDouble(String s) {
    // TODO - find a home for this method
    // TODO - support various number formats: sign char, dollars, commas
    // OFFSET and COUNTIF seem to handle these
    return Countif.parseDouble(s);
  }
 
  private static ValueEval evaluateSingleValue(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
    if(eval instanceof RefEval) {
      return ((RefEval)eval).getInnerValueEval();
    }
    if(eval instanceof AreaEval) {
      return chooseSingleElementFromArea((AreaEval)eval, srcCellRow, srcCellCol);
    }
    if (eval instanceof ValueEval) {
      return (ValueEval) eval;
    }
    throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
  }

  // TODO - this code seems to get repeated a bit
  private static ValueEval chooseSingleElementFromArea(AreaEval ae, int srcCellRow, short srcCellCol) throws EvalEx {
    if (ae.isColumn()) {
      if (ae.isRow()) {
        return ae.getValues()[0];
      }
      if (!ae.containsRow(srcCellRow)) {
        throw new EvalEx(ErrorEval.VALUE_INVALID);
      }
      return ae.getValueAt(srcCellRow, ae.getFirstColumn());
    }
    if (!ae.isRow()) {
      throw new EvalEx(ErrorEval.VALUE_INVALID);
    }
    if (!ae.containsColumn(srcCellCol)) {
      throw new EvalEx(ErrorEval.VALUE_INVALID);
    }
    return ae.getValueAt(ae.getFirstRow(), srcCellCol);
  }
}
TOP

Related Classes of org.apache.poi.hssf.record.formula.functions.Offset$BaseRef

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.