You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Offset.java 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.formula.functions;
  16. import org.apache.poi.ss.formula.eval.AreaEval;
  17. import org.apache.poi.ss.formula.eval.ErrorEval;
  18. import org.apache.poi.ss.formula.eval.EvaluationException;
  19. import org.apache.poi.ss.formula.eval.MissingArgEval;
  20. import org.apache.poi.ss.formula.eval.OperandResolver;
  21. import org.apache.poi.ss.formula.eval.RefEval;
  22. import org.apache.poi.ss.formula.eval.ValueEval;
  23. /**
  24. * Implementation for Excel function OFFSET()<p/>
  25. *
  26. * OFFSET returns an area reference that is a specified number of rows and columns from a
  27. * reference cell or area.<p/>
  28. *
  29. * <b>Syntax</b>:<br/>
  30. * <b>OFFSET</b>(<b>reference</b>, <b>rows</b>, <b>cols</b>, height, width)<p/>
  31. * <b>reference</b> is the base reference.<br/>
  32. * <b>rows</b> is the number of rows up or down from the base reference.<br/>
  33. * <b>cols</b> is the number of columns left or right from the base reference.<br/>
  34. * <b>height</b> (default same height as base reference) is the row count for the returned area reference.<br/>
  35. * <b>width</b> (default same width as base reference) is the column count for the returned area reference.<br/>
  36. *
  37. * @author Josh Micich
  38. */
  39. public final class Offset implements Function {
  40. // These values are specific to BIFF8
  41. private static final int LAST_VALID_ROW_INDEX = 0xFFFF;
  42. private static final int LAST_VALID_COLUMN_INDEX = 0xFF;
  43. /**
  44. * A one dimensional base + offset. Represents either a row range or a column range.
  45. * Two instances of this class together specify an area range.
  46. */
  47. /* package */ static final class LinearOffsetRange {
  48. private final int _offset;
  49. private final int _length;
  50. public LinearOffsetRange(int offset, int length) {
  51. if(length == 0) {
  52. // handled that condition much earlier
  53. throw new RuntimeException("length may not be zero");
  54. }
  55. _offset = offset;
  56. _length = length;
  57. }
  58. public short getFirstIndex() {
  59. return (short) _offset;
  60. }
  61. public short getLastIndex() {
  62. return (short) (_offset + _length - 1);
  63. }
  64. /**
  65. * Moves the range by the specified translation amount.<p/>
  66. *
  67. * This method also 'normalises' the range: Excel specifies that the width and height
  68. * parameters (length field here) cannot be negative. However, OFFSET() does produce
  69. * sensible results in these cases. That behavior is replicated here. <p/>
  70. *
  71. * @param translationAmount may be zero negative or positive
  72. *
  73. * @return the equivalent <tt>LinearOffsetRange</tt> with a positive length, moved by the
  74. * specified translationAmount.
  75. */
  76. public LinearOffsetRange normaliseAndTranslate(int translationAmount) {
  77. if (_length > 0) {
  78. if(translationAmount == 0) {
  79. return this;
  80. }
  81. return new LinearOffsetRange(translationAmount + _offset, _length);
  82. }
  83. return new LinearOffsetRange(translationAmount + _offset + _length + 1, -_length);
  84. }
  85. public boolean isOutOfBounds(int lowValidIx, int highValidIx) {
  86. if(_offset < lowValidIx) {
  87. return true;
  88. }
  89. if(getLastIndex() > highValidIx) {
  90. return true;
  91. }
  92. return false;
  93. }
  94. public String toString() {
  95. StringBuffer sb = new StringBuffer(64);
  96. sb.append(getClass().getName()).append(" [");
  97. sb.append(_offset).append("...").append(getLastIndex());
  98. sb.append("]");
  99. return sb.toString();
  100. }
  101. }
  102. /**
  103. * Encapsulates either an area or cell reference which may be 2d or 3d.
  104. */
  105. private static final class BaseRef {
  106. private final int _firstRowIndex;
  107. private final int _firstColumnIndex;
  108. private final int _width;
  109. private final int _height;
  110. private final RefEval _refEval;
  111. private final AreaEval _areaEval;
  112. public BaseRef(RefEval re) {
  113. _refEval = re;
  114. _areaEval = null;
  115. _firstRowIndex = re.getRow();
  116. _firstColumnIndex = re.getColumn();
  117. _height = 1;
  118. _width = 1;
  119. }
  120. public BaseRef(AreaEval ae) {
  121. _refEval = null;
  122. _areaEval = ae;
  123. _firstRowIndex = ae.getFirstRow();
  124. _firstColumnIndex = ae.getFirstColumn();
  125. _height = ae.getLastRow() - ae.getFirstRow() + 1;
  126. _width = ae.getLastColumn() - ae.getFirstColumn() + 1;
  127. }
  128. public int getWidth() {
  129. return _width;
  130. }
  131. public int getHeight() {
  132. return _height;
  133. }
  134. public int getFirstRowIndex() {
  135. return _firstRowIndex;
  136. }
  137. public int getFirstColumnIndex() {
  138. return _firstColumnIndex;
  139. }
  140. public AreaEval offset(int relFirstRowIx, int relLastRowIx,
  141. int relFirstColIx, int relLastColIx) {
  142. if (_refEval == null) {
  143. return _areaEval.offset(relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
  144. }
  145. return _refEval.offset(relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
  146. }
  147. }
  148. public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) {
  149. if(args.length < 3 || args.length > 5) {
  150. return ErrorEval.VALUE_INVALID;
  151. }
  152. try {
  153. BaseRef baseRef = evaluateBaseRef(args[0]);
  154. int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
  155. int columnOffset = evaluateIntArg(args[2], srcCellRow, srcCellCol);
  156. int height = baseRef.getHeight();
  157. int width = baseRef.getWidth();
  158. // optional arguments
  159. // If height or width is omitted, it is assumed to be the same height or width as reference.
  160. switch(args.length) {
  161. case 5:
  162. if(!(args[4] instanceof MissingArgEval)) {
  163. width = evaluateIntArg(args[4], srcCellRow, srcCellCol);
  164. }
  165. case 4:
  166. if(!(args[3] instanceof MissingArgEval)) {
  167. height = evaluateIntArg(args[3], srcCellRow, srcCellCol);
  168. }
  169. }
  170. // Zero height or width raises #REF! error
  171. if(height == 0 || width == 0) {
  172. return ErrorEval.REF_INVALID;
  173. }
  174. LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
  175. LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
  176. return createOffset(baseRef, rowOffsetRange, colOffsetRange);
  177. } catch (EvaluationException e) {
  178. return e.getErrorEval();
  179. }
  180. }
  181. private static AreaEval createOffset(BaseRef baseRef,
  182. LinearOffsetRange orRow, LinearOffsetRange orCol) throws EvaluationException {
  183. LinearOffsetRange absRows = orRow.normaliseAndTranslate(baseRef.getFirstRowIndex());
  184. LinearOffsetRange absCols = orCol.normaliseAndTranslate(baseRef.getFirstColumnIndex());
  185. if(absRows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) {
  186. throw new EvaluationException(ErrorEval.REF_INVALID);
  187. }
  188. if(absCols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) {
  189. throw new EvaluationException(ErrorEval.REF_INVALID);
  190. }
  191. return baseRef.offset(orRow.getFirstIndex(), orRow.getLastIndex(), orCol.getFirstIndex(), orCol.getLastIndex());
  192. }
  193. private static BaseRef evaluateBaseRef(ValueEval eval) throws EvaluationException {
  194. if(eval instanceof RefEval) {
  195. return new BaseRef((RefEval)eval);
  196. }
  197. if(eval instanceof AreaEval) {
  198. return new BaseRef((AreaEval)eval);
  199. }
  200. if (eval instanceof ErrorEval) {
  201. throw new EvaluationException((ErrorEval) eval);
  202. }
  203. throw new EvaluationException(ErrorEval.VALUE_INVALID);
  204. }
  205. /**
  206. * OFFSET's numeric arguments (2..5) have similar processing rules
  207. */
  208. static int evaluateIntArg(ValueEval eval, int srcCellRow, int srcCellCol) throws EvaluationException {
  209. ValueEval ve = OperandResolver.getSingleValue(eval, srcCellRow, srcCellCol);
  210. return OperandResolver.coerceValueToInt(ve);
  211. }
  212. }