]> source.dussan.org Git - poi.git/commitdiff
support basic R1C1 refs in INDIRECT function
authorPJ Fanning <fanningpj@apache.org>
Fri, 4 Feb 2022 18:46:25 +0000 (18:46 +0000)
committerPJ Fanning <fanningpj@apache.org>
Fri, 4 Feb 2022 18:46:25 +0000 (18:46 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1897777 13f79535-47bb-0310-9956-ffa450edef68

poi/src/main/java/org/apache/poi/ss/formula/OperationEvaluationContext.java
poi/src/main/java/org/apache/poi/ss/formula/functions/Indirect.java
poi/src/test/java/org/apache/poi/ss/formula/functions/TestIndirect.java

index 60fc0145e7b842d4e0a652c11184faec7e50d38e..4050d7b11fe6f1e191368b100c74d193c3644d2f 100644 (file)
@@ -33,14 +33,20 @@ import org.apache.poi.ss.formula.eval.RefEval;
 import org.apache.poi.ss.formula.eval.StringEval;
 import org.apache.poi.ss.formula.eval.ValueEval;
 import org.apache.poi.ss.formula.functions.FreeRefFunction;
-import org.apache.poi.ss.formula.ptg.*;
+import org.apache.poi.ss.formula.ptg.Area3DPtg;
+import org.apache.poi.ss.formula.ptg.Area3DPxg;
+import org.apache.poi.ss.formula.ptg.NameXPtg;
+import org.apache.poi.ss.formula.ptg.NameXPxg;
+import org.apache.poi.ss.formula.ptg.Ptg;
+import org.apache.poi.ss.formula.ptg.Ref3DPtg;
+import org.apache.poi.ss.formula.ptg.Ref3DPxg;
 import org.apache.poi.ss.util.CellReference;
 import org.apache.poi.ss.util.CellReference.NameType;
 
 /**
  * Contains all the contextual information required to evaluate an operation
  * within a formula
- *
+ * <p>
  * For POI internal use only
  */
 public final class OperationEvaluationContext {
@@ -55,12 +61,12 @@ public final class OperationEvaluationContext {
     private boolean _isInArrayContext;
 
     public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
-            int srcColNum, EvaluationTracker tracker) {
+                                      int srcColNum, EvaluationTracker tracker) {
         this(bookEvaluator, workbook, sheetIndex, srcRowNum, srcColNum, tracker, true);
     }
 
     public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
-            int srcColNum, EvaluationTracker tracker, boolean isSingleValue) {
+                                      int srcColNum, EvaluationTracker tracker, boolean isSingleValue) {
         _bookEvaluator = bookEvaluator;
         _workbook = workbook;
         _sheetIndex = sheetIndex;
@@ -70,10 +76,11 @@ public final class OperationEvaluationContext {
         _isSingleValue = isSingleValue;
     }
 
-    public boolean isArraymode(){
+    public boolean isArraymode() {
         return _isInArrayContext;
     }
-    public void setArrayMode(boolean value){
+
+    public void setArrayMode(boolean value) {
         _isInArrayContext = value;
     }
 
@@ -92,14 +99,17 @@ public final class OperationEvaluationContext {
     SheetRangeEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) {
         return createExternSheetRefEvaluator(ptg.getExternSheetIndex());
     }
+
     SheetRangeEvaluator createExternSheetRefEvaluator(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
         ExternalSheet externalSheet = _workbook.getExternalSheet(firstSheetName, lastSheetName, externalWorkbookNumber);
         return createExternSheetRefEvaluator(externalSheet);
     }
+
     SheetRangeEvaluator createExternSheetRefEvaluator(int externSheetIndex) {
         ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
         return createExternSheetRefEvaluator(externalSheet);
     }
+
     SheetRangeEvaluator createExternSheetRefEvaluator(ExternalSheet externalSheet) {
         WorkbookEvaluator targetEvaluator;
         int otherFirstSheetIndex;
@@ -107,14 +117,14 @@ public final class OperationEvaluationContext {
         if (externalSheet == null || externalSheet.getWorkbookName() == null) {
             // sheet is in same workbook
             targetEvaluator = _bookEvaluator;
-            if(externalSheet == null) {
+            if (externalSheet == null) {
                 otherFirstSheetIndex = 0;
             } else {
                 otherFirstSheetIndex = _workbook.getSheetIndex(externalSheet.getSheetName());
             }
 
             if (externalSheet instanceof ExternalSheetRange) {
-                String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName();
+                String lastSheetName = ((ExternalSheetRange) externalSheet).getLastSheetName();
                 otherLastSheetIndex = _workbook.getSheetIndex(lastSheetName);
             }
         } else {
@@ -128,7 +138,7 @@ public final class OperationEvaluationContext {
 
             otherFirstSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName());
             if (externalSheet instanceof ExternalSheetRange) {
-                String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName();
+                String lastSheetName = ((ExternalSheetRange) externalSheet).getLastSheetName();
                 otherLastSheetIndex = targetEvaluator.getSheetIndex(lastSheetName);
             }
 
@@ -143,9 +153,9 @@ public final class OperationEvaluationContext {
             otherLastSheetIndex = otherFirstSheetIndex;
         }
 
-        SheetRefEvaluator[] evals = new SheetRefEvaluator[otherLastSheetIndex-otherFirstSheetIndex+1];
-        for (int i=0; i<evals.length; i++) {
-            int otherSheetIndex = i+otherFirstSheetIndex;
+        SheetRefEvaluator[] evals = new SheetRefEvaluator[otherLastSheetIndex - otherFirstSheetIndex + 1];
+        for (int i = 0; i < evals.length; i++) {
+            int otherSheetIndex = i + otherFirstSheetIndex;
             evals[i] = new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
         }
         return new SheetRangeEvaluator(otherFirstSheetIndex, otherLastSheetIndex, evals);
@@ -181,29 +191,25 @@ public final class OperationEvaluationContext {
     }
 
 
-
     /**
      * Resolves a cell or area reference dynamically.
+     *
      * @param workbookName the name of the workbook containing the reference.  If {@code null}
-     * the current workbook is assumed.  Note - to evaluate formulas which use multiple workbooks,
-     * a {@link CollaboratingWorkbooksEnvironment} must be set up.
-     * @param sheetName the name of the sheet containing the reference.  May be {@code null}
-     * (when {@code workbookName} is also null) in which case the current workbook and sheet is
-     * assumed.
-     * @param refStrPart1 the single cell reference or first part of the area reference.  Must not
-     * be {@code null}.
-     * @param refStrPart2 the second part of the area reference. For single cell references this
-     * parameter must be {@code null}
-     * @param isA1Style specifies the format for {@code refStrPart1} and {@code refStrPart2}.
-     * Pass {@code true} for 'A1' style and {@code false} for 'R1C1' style.
-     * TODO - currently POI only supports 'A1' reference style
+     *                     the current workbook is assumed.  Note - to evaluate formulas which use multiple workbooks,
+     *                     a {@link CollaboratingWorkbooksEnvironment} must be set up.
+     * @param sheetName    the name of the sheet containing the reference.  May be {@code null}
+     *                     (when {@code workbookName} is also null) in which case the current workbook and sheet is
+     *                     assumed.
+     * @param refStrPart1  the single cell reference or first part of the area reference.  Must not
+     *                     be {@code null}.
+     * @param refStrPart2  the second part of the area reference. For single cell references this
+     *                     parameter must be {@code null}
+     * @param isA1Style    specifies the format for {@code refStrPart1} and {@code refStrPart2}.
+     *                     Pass {@code true} for 'A1' style and {@code false} for 'R1C1' style.
      * @return a {@link RefEval} or {@link AreaEval}
      */
     public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1,
-            String refStrPart2, boolean isA1Style) {
-        if (!isA1Style) {
-            throw new RuntimeException("R1C1 style not supported yet");
-        }
+                                         String refStrPart2, boolean isA1Style) {
         SheetRefEvaluator se = createExternSheetRefEvaluator(workbookName, sheetName);
         if (se == null) {
             return ErrorEval.REF_INVALID;
@@ -212,17 +218,17 @@ public final class OperationEvaluationContext {
 
         SpreadsheetVersion ssVersion = _workbook.getSpreadsheetVersion();
 
-        NameType part1refType = classifyCellReference(refStrPart1, ssVersion);
+        NameType part1refType = isA1Style ? classifyCellReference(refStrPart1, ssVersion) : NameType.CELL;
         switch (part1refType) {
             case BAD_CELL_OR_NAMED_RANGE:
                 return ErrorEval.REF_INVALID;
             case NAMED_RANGE:
                 EvaluationName nm = _workbook.getName(refStrPart1, _sheetIndex);
-                if(nm == null) {
+                if (nm == null) {
                     throw new RuntimeException("Specified name '" + refStrPart1 +
-                            "' is not found in the workbook (sheetIndex=" + _sheetIndex +").");
+                            "' is not found in the workbook (sheetIndex=" + _sheetIndex + ").");
                 }
-                if(!nm.isRange()) {
+                if (!nm.isRange()) {
                     throw new RuntimeException("Specified name '" + refStrPart1 + "' is not a range as expected.");
                 }
                 return _bookEvaluator.evaluateNameFormula(nm.getNameDefinition(), this);
@@ -234,12 +240,17 @@ public final class OperationEvaluationContext {
                 case ROW:
                     return ErrorEval.REF_INVALID;
                 case CELL:
-                    CellReference cr = new CellReference(refStrPart1);
+                    CellReference cr;
+                    if (isA1Style) {
+                        cr = new CellReference(refStrPart1);
+                    } else {
+                        cr = applyR1C1Reference(new CellReference(getRowIndex(), getColumnIndex()), refStrPart1);
+                    }
                     return new LazyRefEval(cr.getRow(), cr.getCol(), sre);
             }
             throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
         }
-        NameType part2refType = classifyCellReference(refStrPart1, ssVersion);
+        NameType part2refType = isA1Style ? classifyCellReference(refStrPart1, ssVersion) : NameType.CELL;
         switch (part2refType) {
             case BAD_CELL_OR_NAMED_RANGE:
                 return ErrorEval.REF_INVALID;
@@ -255,14 +266,12 @@ public final class OperationEvaluationContext {
         int firstRow, firstCol, lastRow, lastCol;
         switch (part1refType) {
             case COLUMN:
-                firstRow =0;
-                if (part2refType.equals(NameType.COLUMN))
-                {
+                firstRow = 0;
+                if (part2refType.equals(NameType.COLUMN)) {
                     lastRow = ssVersion.getLastRowIndex();
                     firstCol = parseRowRef(refStrPart1);
                     lastCol = parseRowRef(refStrPart2);
-                }
-                else {
+                } else {
                     lastRow = ssVersion.getLastRowIndex();
                     firstCol = parseColRef(refStrPart1);
                     lastCol = parseColRef(refStrPart2);
@@ -271,8 +280,7 @@ public final class OperationEvaluationContext {
             case ROW:
                 // support of cell range in the form of integer:integer
                 firstCol = 0;
-                if (part2refType.equals(NameType.ROW))
-                {
+                if (part2refType.equals(NameType.ROW)) {
                     firstRow = parseColRef(refStrPart1);
                     lastRow = parseColRef(refStrPart2);
                     lastCol = ssVersion.getLastColumnIndex();
@@ -284,10 +292,18 @@ public final class OperationEvaluationContext {
                 break;
             case CELL:
                 CellReference cr;
-                cr = new CellReference(refStrPart1);
+                if (isA1Style) {
+                    cr = new CellReference(refStrPart1);
+                } else {
+                    cr = applyR1C1Reference(new CellReference(getRowIndex(), getColumnIndex()), refStrPart1);
+                }
                 firstRow = cr.getRow();
                 firstCol = cr.getCol();
-                cr = new CellReference(refStrPart2);
+                if (isA1Style) {
+                    cr = new CellReference(refStrPart2);
+                } else {
+                    cr = applyR1C1Reference(new CellReference(getRowIndex(), getColumnIndex()), refStrPart2);
+                }
                 lastRow = cr.getRow();
                 lastCol = cr.getCol();
                 break;
@@ -321,10 +337,12 @@ public final class OperationEvaluationContext {
         SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
         return new LazyRefEval(rowIndex, columnIndex, sre);
     }
+
     public ValueEval getRef3DEval(Ref3DPtg rptg) {
         SheetRangeEvaluator sre = createExternSheetRefEvaluator(rptg.getExternSheetIndex());
         return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre);
     }
+
     public ValueEval getRef3DEval(Ref3DPxg rptg) {
         SheetRangeEvaluator sre = createExternSheetRefEvaluator(
                 rptg.getSheetName(), rptg.getLastSheetName(), rptg.getExternalWorkbookNumber());
@@ -332,15 +350,17 @@ public final class OperationEvaluationContext {
     }
 
     public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex,
-            int lastRowIndex, int lastColumnIndex) {
+                                 int lastRowIndex, int lastColumnIndex) {
         SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
         return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre);
     }
+
     public ValueEval getArea3DEval(Area3DPtg aptg) {
         SheetRangeEvaluator sre = createExternSheetRefEvaluator(aptg.getExternSheetIndex());
         return new LazyAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(),
                 aptg.getLastRow(), aptg.getLastColumn(), sre);
     }
+
     public ValueEval getArea3DEval(Area3DPxg aptg) {
         SheetRangeEvaluator sre = createExternSheetRefEvaluator(
                 aptg.getSheetName(), aptg.getLastSheetName(), aptg.getExternalWorkbookNumber());
@@ -349,7 +369,7 @@ public final class OperationEvaluationContext {
     }
 
     public ValueEval getAreaValueEval(int firstRowIndex, int firstColumnIndex,
-            int lastRowIndex, int lastColumnIndex, Object[][] tokens) {
+                                      int lastRowIndex, int lastColumnIndex, Object[][] tokens) {
 
         ValueEval[] values = new ValueEval[tokens.length * tokens[0].length];
 
@@ -361,7 +381,7 @@ public final class OperationEvaluationContext {
         }
 
         return new CacheAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex,
-                                 lastColumnIndex, values);
+                lastColumnIndex, values);
     }
 
     private ValueEval convertObjectEval(Object token) {
@@ -369,7 +389,7 @@ public final class OperationEvaluationContext {
             throw new RuntimeException("Array item cannot be null");
         }
         if (token instanceof String) {
-            return new StringEval((String)token);
+            return new StringEval((String) token);
         }
         if (token instanceof Double) {
             return new NumberEval((Double) token);
@@ -378,7 +398,7 @@ public final class OperationEvaluationContext {
             return BoolEval.valueOf((Boolean) token);
         }
         if (token instanceof ErrorConstant) {
-            return ErrorEval.valueOf(((ErrorConstant)token).getErrorCode());
+            return ErrorEval.valueOf(((ErrorConstant) token).getErrorCode());
         }
         throw new IllegalArgumentException("Unexpected constant class (" + token.getClass().getName() + ")");
     }
@@ -387,7 +407,7 @@ public final class OperationEvaluationContext {
     public ValueEval getNameXEval(NameXPtg nameXPtg) {
         // Is the name actually on our workbook?
         ExternalSheet externSheet = _workbook.getExternalSheet(nameXPtg.getSheetRefIndex());
-        if(externSheet == null || externSheet.getWorkbookName() == null) {
+        if (externSheet == null || externSheet.getWorkbookName() == null) {
             // External reference to our own workbook's name
             return getLocalNameXEval(nameXPtg);
         }
@@ -395,14 +415,15 @@ public final class OperationEvaluationContext {
         // Look it up for the external workbook
         String workbookName = externSheet.getWorkbookName();
         ExternalName externName = _workbook.getExternalName(
-              nameXPtg.getSheetRefIndex(),
-              nameXPtg.getNameIndex()
+                nameXPtg.getSheetRefIndex(),
+                nameXPtg.getNameIndex()
         );
         return getExternalNameXEval(externName, workbookName);
     }
+
     public ValueEval getNameXEval(NameXPxg nameXPxg) {
         ExternalSheet externSheet = _workbook.getExternalSheet(nameXPxg.getSheetName(), null, nameXPxg.getExternalWorkbookNumber());
-        if(externSheet == null || externSheet.getWorkbookName() == null) {
+        if (externSheet == null || externSheet.getWorkbookName() == null) {
             // External reference to our own workbook's name
             return getLocalNameXEval(nameXPxg);
         }
@@ -410,9 +431,9 @@ public final class OperationEvaluationContext {
         // Look it up for the external workbook
         String workbookName = externSheet.getWorkbookName();
         ExternalName externName = _workbook.getExternalName(
-              nameXPxg.getNameName(),
-              nameXPxg.getSheetName(),
-              nameXPxg.getExternalWorkbookNumber()
+                nameXPxg.getNameName(),
+                nameXPxg.getSheetName(),
+                nameXPxg.getExternalWorkbookNumber()
         );
         return getExternalNameXEval(externName, workbookName);
     }
@@ -435,6 +456,7 @@ public final class OperationEvaluationContext {
             return new FunctionNameEval(name);
         }
     }
+
     private ValueEval getLocalNameXEval(NameXPtg nameXPtg) {
         String name = _workbook.resolveNameXText(nameXPtg);
 
@@ -444,7 +466,7 @@ public final class OperationEvaluationContext {
         if (sheetNameAt > -1) {
             // Sheet based name
             String sheetName = name.substring(0, sheetNameAt);
-            String nameName = name.substring(sheetNameAt+1);
+            String nameName = name.substring(sheetNameAt + 1);
             evalName = _workbook.getName(nameName, _workbook.getSheetIndex(sheetName));
         } else {
             // Workbook based name
@@ -459,12 +481,14 @@ public final class OperationEvaluationContext {
             return new FunctionNameEval(name);
         }
     }
+
     public int getSheetIndex() {
         return _sheetIndex;
     }
 
     /**
      * default true
+     *
      * @return flag indicating whether evaluation should "unwrap" the result to a single value based on the context row/column
      */
     public boolean isSingleValue() {
@@ -475,8 +499,8 @@ public final class OperationEvaluationContext {
         try {
             // Fetch the workbook this refers to, and the name as defined with that
             WorkbookEvaluator refWorkbookEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
-            EvaluationName evaluationName = refWorkbookEvaluator.getName(externName.getName(),externName.getIx()-1);
-            if (evaluationName != null && evaluationName.hasFormula()){
+            EvaluationName evaluationName = refWorkbookEvaluator.getName(externName.getName(), externName.getIx() - 1);
+            if (evaluationName != null && evaluationName.hasFormula()) {
                 if (evaluationName.getNameDefinition().length > 1) {
                     throw new RuntimeException("Complex name formulas not supported yet");
                 }
@@ -486,23 +510,61 @@ public final class OperationEvaluationContext {
                         refWorkbookEvaluator, refWorkbookEvaluator.getWorkbook(), -1, -1, -1, _tracker);
 
                 Ptg ptg = evaluationName.getNameDefinition()[0];
-                if (ptg instanceof Ref3DPtg){
-                    Ref3DPtg ref3D = (Ref3DPtg)ptg;
+                if (ptg instanceof Ref3DPtg) {
+                    Ref3DPtg ref3D = (Ref3DPtg) ptg;
                     return refWorkbookContext.getRef3DEval(ref3D);
-                } else if (ptg instanceof Ref3DPxg){
-                    Ref3DPxg ref3D = (Ref3DPxg)ptg;
+                } else if (ptg instanceof Ref3DPxg) {
+                    Ref3DPxg ref3D = (Ref3DPxg) ptg;
                     return refWorkbookContext.getRef3DEval(ref3D);
-                } else if(ptg instanceof Area3DPtg){
-                    Area3DPtg area3D = (Area3DPtg)ptg;
+                } else if (ptg instanceof Area3DPtg) {
+                    Area3DPtg area3D = (Area3DPtg) ptg;
                     return refWorkbookContext.getArea3DEval(area3D);
-                } else if(ptg instanceof Area3DPxg){
-                    Area3DPxg area3D = (Area3DPxg)ptg;
+                } else if (ptg instanceof Area3DPxg) {
+                    Area3DPxg area3D = (Area3DPxg) ptg;
                     return refWorkbookContext.getArea3DEval(area3D);
                 }
             }
             return ErrorEval.REF_INVALID;
-        } catch(WorkbookNotFoundException wnfe){
+        } catch (WorkbookNotFoundException wnfe) {
             return ErrorEval.REF_INVALID;
         }
-   }
+    }
+
+    public static CellReference applyR1C1Reference(CellReference anchorReference, String relativeReference) {
+        int rpos = relativeReference.indexOf('R');
+        int cpos = relativeReference.indexOf('C');
+        if (rpos >= 0 && cpos > rpos) {
+            String rval = relativeReference.substring(rpos + 1, cpos).trim();
+            String cval = relativeReference.substring(cpos + 1).trim();
+            int absoluteR = -1;
+            int relativeR = 0;
+            if (rval.startsWith("[") && rval.endsWith("]")) {
+                relativeR = Integer.parseInt(rval.substring(1, rval.length() - 1).trim());
+            } else if (!rval.isEmpty()) {
+                absoluteR = Integer.parseInt(rval);
+            }
+            int absoluteC = -1;
+            int relativeC = 0;
+            if (cval.startsWith("[") && cval.endsWith("]")) {
+                relativeC = Integer.parseInt(cval.substring(1, cval.length() - 1).trim());
+            } else if (!cval.isEmpty()) {
+                absoluteC = Integer.parseInt(cval);
+            }
+            int newR;
+            if (absoluteR >= 0) {
+                newR = absoluteR - 1;
+            } else {
+                newR = anchorReference.getRow() + relativeR;
+            }
+            int newC;
+            if (absoluteC >= 0) {
+                newC = absoluteC - 1;
+            } else {
+                newC = anchorReference.getCol() + relativeC;
+            }
+            return new CellReference(newR, newC);
+        } else {
+            throw new IllegalArgumentException(relativeReference + " is not a valid R1C1 reference");
+        }
+    }
 }
index 2728e0ea2551ad80df5b57453a739f38aac639f4..90b581d765ad12251c56d19c8c2817847be31e9e 100644 (file)
@@ -93,7 +93,7 @@ public final class Indirect implements FreeRefFunction {
     }
 
     private static ValueEval evaluateIndirect(final OperationEvaluationContext ec, String text,
-            boolean isA1style) {
+                                              boolean isA1style) {
 
         // Search backwards for '!' because sheet names can contain '!'
         int plingPos = text.lastIndexOf('!');
@@ -115,7 +115,7 @@ public final class Indirect implements FreeRefFunction {
             refText = text.substring(plingPos + 1);
         }
 
-        if (Table.isStructuredReference.matcher(refText).matches()) {
+        if (isA1style && Table.isStructuredReference.matcher(refText).matches()) {
             // The argument is structured reference
             Area3DPxg areaPtg;
             try {
@@ -130,8 +130,8 @@ public final class Indirect implements FreeRefFunction {
             String refStrPart2;
             int colonPos = refText.indexOf(':');
             if (colonPos < 0) {
-                 refStrPart1 = refText.trim();
-                 refStrPart2 = null;
+                refStrPart1 = refText.trim();
+                refStrPart2 = null;
             } else {
                 refStrPart1 = refText.substring(0, colonPos).trim();
                 refStrPart2 = refText.substring(colonPos + 1).trim();
@@ -187,7 +187,7 @@ public final class Indirect implements FreeRefFunction {
             // else - just sheet name
             String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx));
             if (sheetName == null) { // note - when quoted, sheetName can
-                                     // start/end with whitespace
+                // start/end with whitespace
                 return null;
             }
             return new String[] { wbName, sheetName, };
index 45422e19e767b4cf3f75b9a2a3a574097bfaff97..7a4ec7f270403c484900a06a7ff72c4ad906b91b 100644 (file)
@@ -25,12 +25,14 @@ import org.apache.poi.hssf.usermodel.HSSFName;
 import org.apache.poi.hssf.usermodel.HSSFRow;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
 import org.apache.poi.ss.formula.eval.ErrorEval;
 import org.apache.poi.ss.formula.eval.ValueEval;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellType;
 import org.apache.poi.ss.usermodel.CellValue;
 import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.util.CellReference;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -154,6 +156,30 @@ final class TestIndirect {
         wbA.close();
     }
 
+    @Test
+    void testBasicR1C1() throws Exception {
+        try (HSSFWorkbook wbA = createWBA()) {
+            HSSFCell c = wbA.getSheetAt(0).createRow(5).createCell(2);
+            HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
+
+            // non-error cases
+            confirm(feA, c, "INDIRECT(\"R2C3\", FALSE)", 23);
+            confirm(feA, c, "INDIRECT(\"R[-4]C[0]\", FALSE)", 23);
+            confirm(feA, c, "INDIRECT(\"R[-4]C\", FALSE)", 23);
+            confirm(feA, c, "INDIRECT(\"R1C1:R1C7\", FALSE)", 13); // de-reference area ref (note formula is in C4)
+            confirm(feA, c, "SUM(INDIRECT(\"Sheet2!R1C2:R3C3\", FALSE))", 351); // area ref
+            confirm(feA, c, "SUM(INDIRECT(\"Sheet2! R1C2 : R3C3 \", FALSE))", 351); // spaces in area ref
+
+            //scenarios yet to support
+            //R[-4] -- supports getting full row
+            //C[-4] -- supports getting full column
+
+            // simple error propagation:
+
+            confirm(feA, c, "INDIRECT(\"'Sheet1 '!R3C4\", FALSE)", ErrorEval.REF_INVALID);
+        }
+    }
+
     @Test
     void testMultipleWorkbooks() throws Exception {
         HSSFWorkbook wbA = createWBA();
@@ -179,6 +205,22 @@ final class TestIndirect {
         wbA.close();
     }
 
+    @Test
+    void testInvalidInput() {
+        assertEquals(ErrorEval.VALUE_INVALID, Indirect.instance.evaluate(new ValueEval[] {}, null));
+    }
+
+    @Test
+    void testRelativeR1C1() {
+        CellReference cr = new CellReference("C3");
+        assertEquals(new CellReference("A3"), OperationEvaluationContext.applyR1C1Reference(cr, "RC[-2]"));
+        assertEquals(new CellReference("E3"), OperationEvaluationContext.applyR1C1Reference(cr, "RC[2]"));
+        assertEquals(new CellReference("C2"), OperationEvaluationContext.applyR1C1Reference(cr, "R[-1]C"));
+        assertEquals(new CellReference("C4"), OperationEvaluationContext.applyR1C1Reference(cr, "R[1]C"));
+        assertEquals(new CellReference("D4"), OperationEvaluationContext.applyR1C1Reference(cr, "R[1]C[1]"));
+        assertEquals(new CellReference("A1"), OperationEvaluationContext.applyR1C1Reference(cr, "R1C1"));
+    }
+
     private static void confirm(FormulaEvaluator fe, Cell cell, String formula, double expectedResult) {
         fe.clearAllCachedResultValues();
         cell.setCellFormula(formula);
@@ -195,9 +237,4 @@ final class TestIndirect {
         int expCode = expectedResult.getErrorCode();
         assertEquals(expCode, cv.getErrorValue(), "Expected error '" + ErrorEval.getText(expCode) + "' but got '" + cv.formatAsString() + "'.");
     }
-
-    @Test
-    void testInvalidInput() {
-        assertEquals(ErrorEval.VALUE_INVALID, Indirect.instance.evaluate(new ValueEval[] {}, null));
-    }
 }