Implement CONCAT function #65185

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1887656 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java b/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
index 076eafd..096f6b2 100644
--- a/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
+++ b/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
@@ -37,7 +37,9 @@
 import org.apache.poi.ss.formula.functions.Imaginary;
 import org.apache.poi.ss.formula.functions.Oct2Dec;
 import org.apache.poi.ss.formula.functions.Quotient;
+import org.apache.poi.ss.formula.functions.Single;
 import org.apache.poi.ss.formula.functions.Sumifs;
+import org.apache.poi.ss.formula.functions.TextFunction;
 import org.apache.poi.ss.formula.functions.WeekNum;
 import org.apache.poi.ss.formula.udf.UDFFinder;
 
@@ -95,6 +97,7 @@
         r(m, "BIN2HEX", null);
         r(m, "BIN2OCT", null);
         r(m, "COMPLEX", Complex.instance);
+        r(m, "CONCAT", TextFunction.CONCAT);
         r(m, "CONVERT", null);
         r(m, "COUNTIFS", Countifs.instance);
         r(m, "COUPDAYBS", null);
@@ -175,6 +178,7 @@
         r(m, "RECEIVED", null);
         r(m, "RTD", null);
         r(m, "SERIESSUM", null);
+        r(m, "SINGLE", Single.instance);
         r(m, "SQRTPI", null);
         r(m, "SUMIFS", Sumifs.instance);
         r(m, "TBILLEQ", null);
diff --git a/src/java/org/apache/poi/ss/formula/functions/Single.java b/src/java/org/apache/poi/ss/formula/functions/Single.java
new file mode 100644
index 0000000..38e8a20
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Single.java
@@ -0,0 +1,67 @@
+/* ====================================================================
+   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.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.AreaEvalBase;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Implementation of the SINGLE function, used by Dynamic Arrays, which is 
+ *  now largely replaced by the @ character.
+ */
+public final class Single implements FreeRefFunction {
+    public static final FreeRefFunction instance = new Single();
+    private Single() {
+        // Enforce singleton
+    }
+
+    public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+        // Look for a single Eval which matches either our Row or
+        //  our Column
+        ValueEval intersect = null;
+        int col = ec.getColumnIndex();
+        int row = ec.getRowIndex();
+
+        for (ValueEval val : args) {
+            if (val instanceof AreaEvalBase) {
+                AreaEvalBase area = (AreaEvalBase)val;
+
+                if (area.contains(row, col)) {
+                    if (intersect != null) return ErrorEval.VALUE_INVALID; 
+                    intersect = area.getAbsoluteValue(row, col);
+                } else if (area.containsRow(row)) {
+                    if (intersect != null) return ErrorEval.VALUE_INVALID;
+                    intersect = area.getAbsoluteValue(row, area.getFirstColumn());
+                } else if (area.containsColumn(col)) {
+                    if (intersect != null) return ErrorEval.VALUE_INVALID;
+                    intersect = area.getAbsoluteValue(area.getFirstRow(), col);
+                }
+            }
+        }
+        
+        // If we found only one, it's that
+        if (intersect != null) {
+            return intersect;
+        }
+        
+        // If in doubt, it's probably invalid
+        return ErrorEval.VALUE_INVALID;
+    }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/TextFunction.java b/src/java/org/apache/poi/ss/formula/functions/TextFunction.java
index 74573f7..f344d57 100644
--- a/src/java/org/apache/poi/ss/formula/functions/TextFunction.java
+++ b/src/java/org/apache/poi/ss/formula/functions/TextFunction.java
@@ -19,6 +19,8 @@
 
 import java.util.Locale;
 
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.AreaEval;
 import org.apache.poi.ss.formula.eval.BoolEval;
 import org.apache.poi.ss.formula.eval.ErrorEval;
 import org.apache.poi.ss.formula.eval.EvaluationException;
@@ -268,19 +270,41 @@
 	public static final Function LEFT = new LeftRight(true);
 	public static final Function RIGHT = new LeftRight(false);
 
+	public static final FreeRefFunction CONCAT = new FreeRefFunction() {
+	    public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+	        StringBuilder sb = new StringBuilder();
+	        for (ValueEval arg : args) {
+	            try {
+	                if (arg instanceof AreaEval) {
+	                    AreaEval area = (AreaEval)arg;
+	                    for (int rn=0; rn<area.getHeight(); rn++) {
+	                        for (int cn=0; cn<area.getWidth(); cn++) {
+	                            ValueEval ve = area.getRelativeValue(rn, cn);
+	                            sb.append(evaluateStringArg(ve, ec.getRowIndex(), ec.getColumnIndex()));
+	                        }
+	                    }
+	                } else {
+	                    sb.append(evaluateStringArg(arg, ec.getRowIndex(), ec.getColumnIndex()));
+	                }
+	            } catch (EvaluationException e) {
+	                return e.getErrorEval();
+	            }
+	        }
+	        return new StringEval(sb.toString());
+	    }
+	};
 	public static final Function CONCATENATE = new Function() {
-
-		public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
-			StringBuilder sb = new StringBuilder();
-			for (ValueEval arg : args) {
-				try {
-					sb.append(evaluateStringArg(arg, srcRowIndex, srcColumnIndex));
-				} catch (EvaluationException e) {
-					return e.getErrorEval();
-				}
-			}
-			return new StringEval(sb.toString());
-		}
+	    public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
+	        StringBuilder sb = new StringBuilder();
+	        for (ValueEval arg : args) {
+	            try {
+	                sb.append(evaluateStringArg(arg, srcRowIndex, srcColumnIndex));
+	            } catch (EvaluationException e) {
+	                return e.getErrorEval();
+	            }
+	        }
+	        return new StringEval(sb.toString());
+	    }
 	};
 
 	public static final Function EXACT = new Fixed2ArgFunction() {
diff --git a/test-data/spreadsheet/FormulaEvalTestData.xls b/test-data/spreadsheet/FormulaEvalTestData.xls
index b916564..74823e2 100644
--- a/test-data/spreadsheet/FormulaEvalTestData.xls
+++ b/test-data/spreadsheet/FormulaEvalTestData.xls
Binary files differ