123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- /* ====================================================================
- 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.ss.formula.functions;
-
- import org.apache.poi.ss.formula.FormulaParseException;
- import org.apache.poi.ss.formula.FormulaParser;
- import org.apache.poi.ss.formula.FormulaParsingWorkbook;
- import org.apache.poi.ss.formula.OperationEvaluationContext;
- import org.apache.poi.ss.formula.eval.BlankEval;
- import org.apache.poi.ss.formula.eval.ErrorEval;
- import org.apache.poi.ss.formula.eval.EvaluationException;
- import org.apache.poi.ss.formula.eval.MissingArgEval;
- import org.apache.poi.ss.formula.eval.OperandResolver;
- import org.apache.poi.ss.formula.eval.ValueEval;
- import org.apache.poi.ss.formula.ptg.Area3DPxg;
- import org.apache.poi.ss.usermodel.Table;
-
- /**
- * Implementation for Excel function INDIRECT<p>
- *
- * INDIRECT() returns the cell or area reference denoted by the text argument.<p>
- *
- * <b>Syntax</b>:<br>
- * <b>INDIRECT</b>(<b>ref_text</b>,isA1Style)<p>
- *
- * <b>ref_text</b> a string representation of the desired reference as it would
- * normally be written in a cell formula.<br>
- * <b>isA1Style</b> (default TRUE) specifies whether the ref_text should be
- * interpreted as A1-style or R1C1-style.
- */
- public final class Indirect implements FreeRefFunction {
-
- public static final FreeRefFunction instance = new Indirect();
-
- private Indirect() {
- // enforce singleton
- }
-
- @Override
- public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
- if (args.length < 1) {
- return ErrorEval.VALUE_INVALID;
- }
-
- boolean isA1style;
- String text;
- try {
- ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec
- .getColumnIndex());
- text = OperandResolver.coerceValueToString(ve);
- switch (args.length) {
- case 1:
- isA1style = true;
- break;
- case 2:
- isA1style = evaluateBooleanArg(args[1], ec);
- break;
- default:
- return ErrorEval.VALUE_INVALID;
- }
- } catch (EvaluationException e) {
- return e.getErrorEval();
- }
-
- return evaluateIndirect(ec, text, isA1style);
- }
-
- private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec)
- throws EvaluationException {
- ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex());
-
- if (ve == BlankEval.instance || ve == MissingArgEval.instance) {
- return false;
- }
- // numeric quantities follow standard boolean conversion rules
- // for strings, only "TRUE" and "FALSE" (case insensitive) are valid
- return OperandResolver.coerceValueToBoolean(ve, false);
- }
-
- private static ValueEval evaluateIndirect(final OperationEvaluationContext ec, String text,
- boolean isA1style) {
-
- // Search backwards for '!' because sheet names can contain '!'
- int plingPos = text.lastIndexOf('!');
-
- String workbookName;
- String sheetName;
- String refText; // whitespace around this gets trimmed OK
- if (plingPos < 0) {
- workbookName = null;
- sheetName = null;
- refText = text;
- } else {
- String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos));
- if (parts == null) {
- return ErrorEval.REF_INVALID;
- }
- workbookName = parts[0];
- sheetName = parts[1];
- refText = text.substring(plingPos + 1);
- }
-
- if (Table.isStructuredReference.matcher(refText).matches()) {
- // The argument is structured reference
- Area3DPxg areaPtg;
- try {
- areaPtg = FormulaParser.parseStructuredReference(refText, (FormulaParsingWorkbook) ec.getWorkbook(), ec.getRowIndex());
- } catch (FormulaParseException e) {
- return ErrorEval.REF_INVALID;
- }
- return ec.getArea3DEval(areaPtg);
- } else {
- // The argument is regular reference
- String refStrPart1;
- String refStrPart2;
- int colonPos = refText.indexOf(':');
- if (colonPos < 0) {
- refStrPart1 = refText.trim();
- refStrPart2 = null;
- } else {
- refStrPart1 = refText.substring(0, colonPos).trim();
- refStrPart2 = refText.substring(colonPos + 1).trim();
- }
- return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
- }
- }
-
- /**
- * @return array of length 2: {workbookName, sheetName,}. Second element will always be
- * present. First element may be null if sheetName is unqualified.
- * Returns {@code null} if text cannot be parsed.
- */
- private static String[] parseWorkbookAndSheetName(CharSequence text) {
- int lastIx = text.length() - 1;
- if (lastIx < 0) {
- return null;
- }
- if (canTrim(text)) {
- return null;
- }
- char firstChar = text.charAt(0);
- if (Character.isWhitespace(firstChar)) {
- return null;
- }
- if (firstChar == '\'') {
- // workbookName or sheetName needs quoting
- // quotes go around both
- if (text.charAt(lastIx) != '\'') {
- return null;
- }
- firstChar = text.charAt(1);
- if (Character.isWhitespace(firstChar)) {
- return null;
- }
- String wbName;
- int sheetStartPos;
- if (firstChar == '[') {
- int rbPos = text.toString().lastIndexOf(']');
- if (rbPos < 0) {
- return null;
- }
- wbName = unescapeString(text.subSequence(2, rbPos));
- if (wbName == null || canTrim(wbName)) {
- return null;
- }
- sheetStartPos = rbPos + 1;
- } else {
- wbName = null;
- sheetStartPos = 1;
- }
-
- // else - just sheet name
- String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx));
- if (sheetName == null) { // note - when quoted, sheetName can
- // start/end with whitespace
- return null;
- }
- return new String[] { wbName, sheetName, };
- }
-
- if (firstChar == '[') {
- int rbPos = text.toString().lastIndexOf(']');
- if (rbPos < 0) {
- return null;
- }
- CharSequence wbName = text.subSequence(1, rbPos);
- if (canTrim(wbName)) {
- return null;
- }
- CharSequence sheetName = text.subSequence(rbPos + 1, text.length());
- if (canTrim(sheetName)) {
- return null;
- }
- return new String[] { wbName.toString(), sheetName.toString(), };
- }
- // else - just sheet name
- return new String[] { null, text.toString(), };
- }
-
- /**
- * @return {@code null} if there is a syntax error in any escape sequence
- * (the typical syntax error is a single quote character not followed by another).
- */
- private static String unescapeString(CharSequence text) {
- int len = text.length();
- StringBuilder sb = new StringBuilder(len);
- int i = 0;
- while (i < len) {
- char ch = text.charAt(i);
- if (ch == '\'') {
- // every quote must be followed by another
- i++;
- if (i >= len) {
- return null;
- }
- ch = text.charAt(i);
- if (ch != '\'') {
- return null;
- }
- }
- sb.append(ch);
- i++;
- }
- return sb.toString();
- }
-
- private static boolean canTrim(CharSequence text) {
- int lastIx = text.length() - 1;
- if (lastIx < 0) {
- return false;
- }
- if (Character.isWhitespace(text.charAt(0))) {
- return true;
- }
- return Character.isWhitespace(text.charAt(lastIx));
- }
- }
|