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.

Indirect.java 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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.FormulaParseException;
  17. import org.apache.poi.ss.formula.FormulaParser;
  18. import org.apache.poi.ss.formula.FormulaParsingWorkbook;
  19. import org.apache.poi.ss.formula.OperationEvaluationContext;
  20. import org.apache.poi.ss.formula.eval.BlankEval;
  21. import org.apache.poi.ss.formula.eval.ErrorEval;
  22. import org.apache.poi.ss.formula.eval.EvaluationException;
  23. import org.apache.poi.ss.formula.eval.MissingArgEval;
  24. import org.apache.poi.ss.formula.eval.OperandResolver;
  25. import org.apache.poi.ss.formula.eval.ValueEval;
  26. import org.apache.poi.ss.formula.ptg.Area3DPxg;
  27. import org.apache.poi.ss.usermodel.Table;
  28. /**
  29. * Implementation for Excel function INDIRECT<p>
  30. *
  31. * INDIRECT() returns the cell or area reference denoted by the text argument.<p>
  32. *
  33. * <b>Syntax</b>:<br>
  34. * <b>INDIRECT</b>(<b>ref_text</b>,isA1Style)<p>
  35. *
  36. * <b>ref_text</b> a string representation of the desired reference as it would
  37. * normally be written in a cell formula.<br>
  38. * <b>isA1Style</b> (default TRUE) specifies whether the ref_text should be
  39. * interpreted as A1-style or R1C1-style.
  40. */
  41. public final class Indirect implements FreeRefFunction {
  42. public static final FreeRefFunction instance = new Indirect();
  43. private Indirect() {
  44. // enforce singleton
  45. }
  46. @Override
  47. public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
  48. if (args.length < 1) {
  49. return ErrorEval.VALUE_INVALID;
  50. }
  51. boolean isA1style;
  52. String text;
  53. try {
  54. ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec
  55. .getColumnIndex());
  56. text = OperandResolver.coerceValueToString(ve);
  57. switch (args.length) {
  58. case 1:
  59. isA1style = true;
  60. break;
  61. case 2:
  62. isA1style = evaluateBooleanArg(args[1], ec);
  63. break;
  64. default:
  65. return ErrorEval.VALUE_INVALID;
  66. }
  67. } catch (EvaluationException e) {
  68. return e.getErrorEval();
  69. }
  70. return evaluateIndirect(ec, text, isA1style);
  71. }
  72. private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec)
  73. throws EvaluationException {
  74. ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex());
  75. if (ve == BlankEval.instance || ve == MissingArgEval.instance) {
  76. return false;
  77. }
  78. // numeric quantities follow standard boolean conversion rules
  79. // for strings, only "TRUE" and "FALSE" (case insensitive) are valid
  80. return OperandResolver.coerceValueToBoolean(ve, false);
  81. }
  82. private static ValueEval evaluateIndirect(final OperationEvaluationContext ec, String text,
  83. boolean isA1style) {
  84. // Search backwards for '!' because sheet names can contain '!'
  85. int plingPos = text.lastIndexOf('!');
  86. String workbookName;
  87. String sheetName;
  88. String refText; // whitespace around this gets trimmed OK
  89. if (plingPos < 0) {
  90. workbookName = null;
  91. sheetName = null;
  92. refText = text;
  93. } else {
  94. String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos));
  95. if (parts == null) {
  96. return ErrorEval.REF_INVALID;
  97. }
  98. workbookName = parts[0];
  99. sheetName = parts[1];
  100. refText = text.substring(plingPos + 1);
  101. }
  102. if (Table.isStructuredReference.matcher(refText).matches()) {
  103. // The argument is structured reference
  104. Area3DPxg areaPtg;
  105. try {
  106. areaPtg = FormulaParser.parseStructuredReference(refText, (FormulaParsingWorkbook) ec.getWorkbook(), ec.getRowIndex());
  107. } catch (FormulaParseException e) {
  108. return ErrorEval.REF_INVALID;
  109. }
  110. return ec.getArea3DEval(areaPtg);
  111. } else {
  112. // The argument is regular reference
  113. String refStrPart1;
  114. String refStrPart2;
  115. int colonPos = refText.indexOf(':');
  116. if (colonPos < 0) {
  117. refStrPart1 = refText.trim();
  118. refStrPart2 = null;
  119. } else {
  120. refStrPart1 = refText.substring(0, colonPos).trim();
  121. refStrPart2 = refText.substring(colonPos + 1).trim();
  122. }
  123. return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
  124. }
  125. }
  126. /**
  127. * @return array of length 2: {workbookName, sheetName,}. Second element will always be
  128. * present. First element may be null if sheetName is unqualified.
  129. * Returns {@code null} if text cannot be parsed.
  130. */
  131. private static String[] parseWorkbookAndSheetName(CharSequence text) {
  132. int lastIx = text.length() - 1;
  133. if (lastIx < 0) {
  134. return null;
  135. }
  136. if (canTrim(text)) {
  137. return null;
  138. }
  139. char firstChar = text.charAt(0);
  140. if (Character.isWhitespace(firstChar)) {
  141. return null;
  142. }
  143. if (firstChar == '\'') {
  144. // workbookName or sheetName needs quoting
  145. // quotes go around both
  146. if (text.charAt(lastIx) != '\'') {
  147. return null;
  148. }
  149. firstChar = text.charAt(1);
  150. if (Character.isWhitespace(firstChar)) {
  151. return null;
  152. }
  153. String wbName;
  154. int sheetStartPos;
  155. if (firstChar == '[') {
  156. int rbPos = text.toString().lastIndexOf(']');
  157. if (rbPos < 0) {
  158. return null;
  159. }
  160. wbName = unescapeString(text.subSequence(2, rbPos));
  161. if (wbName == null || canTrim(wbName)) {
  162. return null;
  163. }
  164. sheetStartPos = rbPos + 1;
  165. } else {
  166. wbName = null;
  167. sheetStartPos = 1;
  168. }
  169. // else - just sheet name
  170. String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx));
  171. if (sheetName == null) { // note - when quoted, sheetName can
  172. // start/end with whitespace
  173. return null;
  174. }
  175. return new String[] { wbName, sheetName, };
  176. }
  177. if (firstChar == '[') {
  178. int rbPos = text.toString().lastIndexOf(']');
  179. if (rbPos < 0) {
  180. return null;
  181. }
  182. CharSequence wbName = text.subSequence(1, rbPos);
  183. if (canTrim(wbName)) {
  184. return null;
  185. }
  186. CharSequence sheetName = text.subSequence(rbPos + 1, text.length());
  187. if (canTrim(sheetName)) {
  188. return null;
  189. }
  190. return new String[] { wbName.toString(), sheetName.toString(), };
  191. }
  192. // else - just sheet name
  193. return new String[] { null, text.toString(), };
  194. }
  195. /**
  196. * @return {@code null} if there is a syntax error in any escape sequence
  197. * (the typical syntax error is a single quote character not followed by another).
  198. */
  199. private static String unescapeString(CharSequence text) {
  200. int len = text.length();
  201. StringBuilder sb = new StringBuilder(len);
  202. int i = 0;
  203. while (i < len) {
  204. char ch = text.charAt(i);
  205. if (ch == '\'') {
  206. // every quote must be followed by another
  207. i++;
  208. if (i >= len) {
  209. return null;
  210. }
  211. ch = text.charAt(i);
  212. if (ch != '\'') {
  213. return null;
  214. }
  215. }
  216. sb.append(ch);
  217. i++;
  218. }
  219. return sb.toString();
  220. }
  221. private static boolean canTrim(CharSequence text) {
  222. int lastIx = text.length() - 1;
  223. if (lastIx < 0) {
  224. return false;
  225. }
  226. if (Character.isWhitespace(text.charAt(0))) {
  227. return true;
  228. }
  229. return Character.isWhitespace(text.charAt(lastIx));
  230. }
  231. }