Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/EPPlus/FormulaParsing/Excel/Functions/BuiltInFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public BuiltInFunctions()
Functions["unichar"] = new Unichar();
Functions["numbervalue"] = new NumberValue();
Functions["dollar"] = new Dollar();
Functions["usdollar"] = new UsDollar();
Functions["encodeurl"] = new EncodeUrl();
Functions["code"] = new CodeFunction();
Functions["textsplit"] = new TextSplit();
Functions["textbefore"] = new TextBefore(DelimiterFunction.TextBefore);
Functions["textafter"] = new TextAfter(DelimiterFunction.TextAfter);
Expand Down Expand Up @@ -374,6 +377,8 @@ public BuiltInFunctions()
Functions["hstack"] = new Hstack();
Functions["getpivotdata"] = new GetPivotData();
Functions["image"] = new ImageFunction();
Functions["wraprows"] = new WrapRows();
Functions["wrapcols"] = new WrapCols();
// Date
Functions["date"] = new Date();
Functions["datedif"] = new DateDif();
Expand Down
60 changes: 60 additions & 0 deletions src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/WrapCols.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*************************************************************************************************
Required Notice: Copyright (C) EPPlus Software AB.
This software is licensed under PolyForm Noncommercial License 1.0.0
and may only be used for noncommercial purposes
https://polyformproject.org/licenses/noncommercial/1.0.0/

A commercial license to use this software can be purchased at https://epplussoftware.com
*************************************************************************************************
Date Author Change
*************************************************************************************************
XX/XX/XXXX EPPlus Software AB EPPlus vX
*************************************************************************************************/
using OfficeOpenXml.FormulaParsing.Excel.Functions.Metadata;
using OfficeOpenXml.FormulaParsing.FormulaExpressions;
using OfficeOpenXml.FormulaParsing.Ranges;
using System.Collections.Generic;

namespace OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup
{
[FunctionMetadata(
Category = ExcelFunctionCategory.LookupAndReference,
EPPlusVersion = "8.6",
Description = "Wraps a row or column vector into a 2D array of the specified number of rows per column.",
SupportsArrays = true)]
internal class WrapCols : WrapFunctionBase
{
public override CompileResult Execute(IList<FunctionArgument> arguments, ParsingContext context)
{
int wrapCount;
object padValue;
List<object> items;
ParseArguments(arguments, out wrapCount, out padValue, out items, out CompileResult error);
if (error != null) return error;

// Output shape: wrap_count rows, ceil(n / wrap_count) columns.
var itemCount = items.Count;
var resultCols = (itemCount + wrapCount - 1) / wrapCount;
var resultRange = new InMemoryRange(new RangeDefinition(wrapCount, (short)resultCols));

// Fill column-major: column 0 top-to-bottom, then column 1, etc.
for (var i = 0; i < wrapCount * resultCols; i++)
{
var col = i / wrapCount;
var row = i % wrapCount;
object value;
if (i < itemCount)
{
value = items[i];
}
else
{
value = padValue;
}
resultRange.SetValue(row, col, value);
}

return CreateDynamicArrayResult(resultRange, DataType.ExcelRange);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*************************************************************************************************
Required Notice: Copyright (C) EPPlus Software AB.
This software is licensed under PolyForm Noncommercial License 1.0.0
and may only be used for noncommercial purposes
https://polyformproject.org/licenses/noncommercial/1.0.0/

A commercial license to use this software can be purchased at https://epplussoftware.com
*************************************************************************************************
Date Author Change
*************************************************************************************************
XX/XX/XXXX EPPlus Software AB EPPlus vX
*************************************************************************************************/
using OfficeOpenXml.FormulaParsing.FormulaExpressions;
using System.Collections.Generic;

namespace OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup
{
/// <summary>
/// Base class for the WRAPROWS and WRAPCOLS functions. Provides shared
/// argument parsing, vector flattening and validation.
/// </summary>
internal abstract class WrapFunctionBase : ExcelFunction
{
public override string NamespacePrefix => "_xlfn.";
public override int ArgumentMinLength => 2;

/// <summary>
/// If the function is allowed in a pivot table calculated field.
/// </summary>
public override bool IsAllowedInCalculatedPivotTableField => false;

/// <summary>
/// Parses and validates the common WRAPROWS/WRAPCOLS arguments and flattens
/// the source vector. Returns null on success; otherwise a CompileResult
/// holding the error to return.
/// </summary>
/// <param name="arguments">The function arguments.</param>
/// <param name="wrapCount">Out: the validated wrap count.</param>
/// <param name="padValue">Out: the pad value (defaults to #N/A).</param>
/// <param name="items">Out: the flattened source items.</param>
/// <param name="errResult">Out: if an error occurs during parsing</param>
protected void ParseArguments(
IList<FunctionArgument> arguments,
out int wrapCount,
out object padValue,
out List<object> items,
out CompileResult errResult)
{
wrapCount = 0;
padValue = ExcelErrorValue.Create(eErrorType.NA);
items = null;
errResult = default;

// wrap_count must be present and a positive integer
wrapCount = ArgToInt(arguments, 1, out ExcelErrorValue wrapErr);
if (wrapErr != null)
{
errResult = CompileResult.GetDynamicArrayResultError(wrapErr.Type);
return;
}
if (wrapCount < 1)
{
errResult = CompileResult.GetDynamicArrayResultError(eErrorType.Num);
return;
}

// Optional pad value
if (arguments.Count > 2 && arguments[2].Value != null)
{
padValue = arguments[2].Value;
}

// Collect the source values as a flat list. Input must be a 1D vector
// (single row or single column). A 2D range yields #VALUE!.
var firstArg = arguments[0];
if (firstArg.IsExcelRange)
{
var range = firstArg.ValueAsRangeInfo;
var rows = range.Size.NumberOfRows;
var cols = range.Size.NumberOfCols;
if (rows > 1 && cols > 1)
{
errResult = CompileResult.GetDynamicArrayResultError(eErrorType.Value);
return;
}
items = FlattenVector(range, rows, cols);
}
else
{
// Scalar input behaves as a single-element vector.
items = new List<object>();
items.Add(firstArg.Value);
}
}

/// <summary>
/// Flattens a 1D range (single row or single column) into a list of values.
/// </summary>
private static List<object> FlattenVector(IRangeInfo range, int rows, int cols)
{
var result = new List<object>(rows * cols);
if (cols == 1)
{
for (var r = 0; r < rows; r++)
{
result.Add(range.GetOffset(r, 0));
}
}
else
{
for (var c = 0; c < cols; c++)
{
result.Add(range.GetOffset(0, c));
}
}
return result;
}
}
}
60 changes: 60 additions & 0 deletions src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/WrapRows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*************************************************************************************************
Required Notice: Copyright (C) EPPlus Software AB.
This software is licensed under PolyForm Noncommercial License 1.0.0
and may only be used for noncommercial purposes
https://polyformproject.org/licenses/noncommercial/1.0.0/

A commercial license to use this software can be purchased at https://epplussoftware.com
*************************************************************************************************
Date Author Change
*************************************************************************************************
XX/XX/XXXX EPPlus Software AB EPPlus vX
*************************************************************************************************/
using OfficeOpenXml.FormulaParsing.Excel.Functions.Metadata;
using OfficeOpenXml.FormulaParsing.FormulaExpressions;
using OfficeOpenXml.FormulaParsing.Ranges;
using System.Collections.Generic;

namespace OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup
{
[FunctionMetadata(
Category = ExcelFunctionCategory.LookupAndReference,
EPPlusVersion = "8.6",
Description = "Wraps a row or column vector into a 2D array of the specified number of columns per row.",
SupportsArrays = true)]
internal class WrapRows : WrapFunctionBase
{
public override CompileResult Execute(IList<FunctionArgument> arguments, ParsingContext context)
{
int wrapCount;
object padValue;
List<object> items;
ParseArguments(arguments, out wrapCount, out padValue, out items, out CompileResult error);
if (error != null) return error;

// Output shape: ceil(n / wrap_count) rows, wrap_count columns.
var itemCount = items.Count;
var resultRows = (itemCount + wrapCount - 1) / wrapCount;
var resultRange = new InMemoryRange(new RangeDefinition(resultRows, (short)wrapCount));

// Fill row-major: row 0 left-to-right, then row 1, etc.
for (var i = 0; i < resultRows * wrapCount; i++)
{
var row = i / wrapCount;
var col = i % wrapCount;
object value;
if (i < itemCount)
{
value = items[i];
}
else
{
value = padValue;
}
resultRange.SetValue(row, col, value);
}

return CreateDynamicArrayResult(resultRange, DataType.ExcelRange);
}
}
}
42 changes: 42 additions & 0 deletions src/EPPlus/FormulaParsing/Excel/Functions/Text/CodeFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*************************************************************************************************
Required Notice: Copyright (C) EPPlus Software AB.
This software is licensed under PolyForm Noncommercial License 1.0.0
and may only be used for noncommercial purposes
https://polyformproject.org/licenses/noncommercial/1.0.0/

A commercial license to use this software can be purchased at https://epplussoftware.com
*************************************************************************************************
Date Author Change
*************************************************************************************************
XX/XX/XXXX EPPlus Software AB EPPlus vX
*************************************************************************************************/
using OfficeOpenXml.FormulaParsing.Excel.Functions.Metadata;
using OfficeOpenXml.FormulaParsing.FormulaExpressions;
using System.Collections.Generic;

namespace OfficeOpenXml.FormulaParsing.Excel.Functions.Text
{
[FunctionMetadata(
Category = ExcelFunctionCategory.Text,
EPPlusVersion = "8.6",
Description = "Returns the numeric code for the first character of a text string.", SupportsArrays = true)]
internal class CodeFunction : ExcelFunction
{
public override ExcelFunctionArrayBehaviour ArrayBehaviour => ExcelFunctionArrayBehaviour.FirstArgCouldBeARange;

public override int ArgumentMinLength => 1;

public override CompileResult Execute(IList<FunctionArgument> arguments, ParsingContext context)
{
var text = ArgToString(arguments, 0);
if (string.IsNullOrEmpty(text))
{
return CompileResult.GetErrorResult(eErrorType.Value);
}
// Return the numeric code (Unicode code unit) of the first character.
// For surrogate pairs we return the leading high surrogate, matching Excel's behavior.
int code = text[0];
return CreateResult((double)code, DataType.Decimal);
}
}
}
18 changes: 14 additions & 4 deletions src/EPPlus/FormulaParsing/Excel/Functions/Text/Dollar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ namespace OfficeOpenXml.FormulaParsing.Excel.Functions.Text
Description = "Converts a supplied number into text, using a currency format")]
internal class Dollar : ExcelFunction
{
public override ExcelFunctionArrayBehaviour ArrayBehaviour => ExcelFunctionArrayBehaviour.Custom;

public override void ConfigureArrayBehaviour(ArrayBehaviourConfig config)
{
config.SetArrayParameterIndexes(0, 1);
}

public override int ArgumentMinLength => 1;
public override CompileResult Execute(IList<FunctionArgument> arguments, ParsingContext context)
{
Expand All @@ -38,15 +45,18 @@ public override CompileResult Execute(IList<FunctionArgument> arguments, Parsing
if (e2 != null) return CompileResult.GetErrorResult(e2.Type);
}
double result;
if(decimals >= 0)
if (decimals >= 0)
{
result = Math.Round(number, decimals);
result = Math.Round(number, decimals, MidpointRounding.AwayFromZero);
}
else
{
result = Math.Round(number * System.Math.Pow(10, decimals)) / System.Math.Pow(10, decimals);
var factor = Math.Pow(10, decimals);
result = Math.Round(number * factor, MidpointRounding.AwayFromZero) / factor;
}
return CreateResult(result.ToString(GetFormatString(decimals), CultureInfo.CurrentCulture), DataType.String);
var formatCulture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
formatCulture.NumberFormat.CurrencyNegativePattern = 0;
return CreateResult(result.ToString(GetFormatString(decimals), formatCulture), DataType.String);
}

private string GetFormatString(int decimals)
Expand Down
41 changes: 41 additions & 0 deletions src/EPPlus/FormulaParsing/Excel/Functions/Text/EncodeUrl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*************************************************************************************************
Required Notice: Copyright (C) EPPlus Software AB.
This software is licensed under PolyForm Noncommercial License 1.0.0
and may only be used for noncommercial purposes
https://polyformproject.org/licenses/noncommercial/1.0.0/

A commercial license to use this software can be purchased at https://epplussoftware.com
*************************************************************************************************
Date Author Change
*************************************************************************************************
XX/XX/XXXX EPPlus Software AB EPPlus vX
*************************************************************************************************/
using OfficeOpenXml.FormulaParsing.Excel.Functions.Metadata;
using OfficeOpenXml.FormulaParsing.FormulaExpressions;
using System;
using System.Collections.Generic;

namespace OfficeOpenXml.FormulaParsing.Excel.Functions.Text
{
[FunctionMetadata(
Category = ExcelFunctionCategory.Text,
EPPlusVersion = "8.6",
Description = "Returns a URL-encoded string, replacing characters that are not allowed in URLs with their percent-encoded equivalents.",
SupportsArrays = true)]
internal class EncodeUrl : ExcelFunction
{
public override ExcelFunctionArrayBehaviour ArrayBehaviour => ExcelFunctionArrayBehaviour.FirstArgCouldBeARange;

public override string NamespacePrefix => "_xlfn.";
public override int ArgumentMinLength => 1;

public override CompileResult Execute(IList<FunctionArgument> arguments, ParsingContext context)
{
var text = ArgToString(arguments, 0);
if (text == null) text = string.Empty;
// Uri.EscapeDataString uses UTF-8 percent-encoding, matching Excel's behavior.
var encoded = Uri.EscapeDataString(text);
return CreateResult(encoded, DataType.String);
}
}
}
Loading
Loading