diff --git a/Makefile b/Makefile
index 31232488..e2a0de1c 100644
--- a/Makefile
+++ b/Makefile
@@ -129,6 +129,7 @@ tags:
doc: docs
docs:
$(Q)$(MAKE) -C docs
+ @echo "Docs built."
docs_doxygen_only:
$(Q)$(MAKE) -C docs docs_doxygen_only
diff --git a/dev/release/fix_example_docs.pl b/dev/release/fix_example_docs.pl
index 664f9736..a3e4a811 100644
--- a/dev/release/fix_example_docs.pl
+++ b/dev/release/fix_example_docs.pl
@@ -26,6 +26,7 @@ my @examples = (
[ 'hyperlinks.c', 'A example of writing urls/hyperlinks' ],
[ 'rich_strings.c', 'A example of writing "rich" multi-format strings' ],
[ 'array_formula.c', 'A example of using array formulas' ],
+ [ 'dynamic_arrays.c', 'A example of using Excel 365 dynamic array formulas' ],
[ 'utf8.c', 'A example of some UTF-8 text' ],
[ 'constant_memory.c', 'Write a large file with constant memory usage' ],
[ 'merge_range.c', 'Create a merged range of cells' ],
@@ -53,6 +54,7 @@ my @examples = (
[ 'macro.c', 'Example of adding a VBA macro to a workbook' ],
[ 'panes.c', 'Example of how to create worksheet panes' ],
[ 'ignore_errors.c', 'Example of ignoring worksheet errors/warnings' ],
+ [ 'lambda.c', 'Example of using the EXCEL 365+ LAMBDA() function' ],
[ 'chart.c', 'Example of a simple column chart' ],
[ 'chart_area.c', 'Examples of area charts' ],
[ 'chart_bar.c', 'Examples of bar charts' ],
diff --git a/docs/images/dynamic_arrays01.png b/docs/images/dynamic_arrays01.png
new file mode 100644
index 00000000..1ad33469
Binary files /dev/null and b/docs/images/dynamic_arrays01.png differ
diff --git a/docs/images/dynamic_arrays02.png b/docs/images/dynamic_arrays02.png
new file mode 100644
index 00000000..1f37efe6
Binary files /dev/null and b/docs/images/dynamic_arrays02.png differ
diff --git a/docs/images/intersection01.png b/docs/images/intersection01.png
new file mode 100644
index 00000000..8bd7570d
Binary files /dev/null and b/docs/images/intersection01.png differ
diff --git a/docs/images/intersection02.png b/docs/images/intersection02.png
new file mode 100644
index 00000000..124c81eb
Binary files /dev/null and b/docs/images/intersection02.png differ
diff --git a/docs/images/intersection03.png b/docs/images/intersection03.png
new file mode 100644
index 00000000..845cac1e
Binary files /dev/null and b/docs/images/intersection03.png differ
diff --git a/docs/images/lambda01.png b/docs/images/lambda01.png
new file mode 100644
index 00000000..f7de6176
Binary files /dev/null and b/docs/images/lambda01.png differ
diff --git a/docs/images/spill01.png b/docs/images/spill01.png
new file mode 100644
index 00000000..9287b84a
Binary files /dev/null and b/docs/images/spill01.png differ
diff --git a/docs/src/examples.dox b/docs/src/examples.dox
index 5a6eb519..99b85fd3 100644
--- a/docs/src/examples.dox
+++ b/docs/src/examples.dox
@@ -240,7 +240,7 @@ Example of writing "rich" multi-format strings to a worksheet.
| @ref rich_strings.c "<< rich_strings.c" |
- @ref utf8.c "utf8.c >>" |
+ @ref dynamic_arrays.c "dynamic_arrays.c >>" |
@@ -251,11 +251,28 @@ Example of writing array formulas to a worksheet.
-@example utf8.c
+
+@example dynamic_arrays.c
| @ref array_formula.c "<< array_formula.c" |
+ @ref utf8.c "utf8.c >>" |
+
+
+
+Example of writing Excel 365 dynamic array formulas to a worksheet.
+
+@image html dynamic_arrays01.png
+
+
+
+
+@example utf8.c
+
+
+
+ | @ref dynamic_arrays.c "<< dynamic_arrays.c" |
@ref constant_memory.c "constant_memory.c >>" |
@@ -685,7 +702,7 @@ An example of how to create panes in a worksheet, both "freeze" panes and
| @ref panes.c "<< panes.c" |
- @ref chart.c "chart.c >>" |
+ @ref lambda.c "lambda.c >>" |
@@ -696,11 +713,31 @@ Example of hiding worksheet errors and warnings.
-@example chart.c
+@example lambda.c
| @ref ignore_errors.c "<< ignore_errors.c" |
+ @ref chart.c "chart.c >>" |
+
+
+
+Example of using the new Excel `LAMBDA()` function. It demonstrates how to
+create a lambda function in Excel and also how to assign a name to it so that
+it can be called as a user defined function. This particular example converts
+from Fahrenheit to Celsius. Note, this function is only currently available if
+you are subscribed to the Microsoft Office Beta Channel program.
+
+@image html lambda01.png
+
+
+
+
+@example chart.c
+
+
+
+ | @ref lambda.c "<< lambda.c" |
@ref chart_area.c "chart_area.c >>" |
diff --git a/docs/src/examples.txt b/docs/src/examples.txt
index 657f2f2b..97fd8cdf 100644
--- a/docs/src/examples.txt
+++ b/docs/src/examples.txt
@@ -113,6 +113,14 @@ Example of writing array formulas to a worksheet.
@image html array_formula.png
+##############################################################
+
+@example dynamic_arrays.c
+
+Example of writing Excel 365 dynamic array formulas to a worksheet.
+
+@image html dynamic_arrays01.png
+
##############################################################
@example utf8.c
A simple Unicode UTF-8 example. Note, the source file is UTF-8 encoded.
@@ -315,6 +323,17 @@ Example of hiding worksheet errors and warnings.
@image html ignore_errors2.png
+##############################################################
+@example lambda.c
+
+Example of using the new Excel `LAMBDA()` function. It demonstrates how to
+create a lambda function in Excel and also how to assign a name to it so that
+it can be called as a user defined function. This particular example converts
+from Fahrenheit to Celsius. Note, this function is only currently available if
+you are subscribed to the Microsoft Office Beta Channel program.
+
+@image html lambda01.png
+
##############################################################
@example chart.c
diff --git a/docs/src/working_with_formulas.dox b/docs/src/working_with_formulas.dox
index 07012888..6b14b388 100644
--- a/docs/src/working_with_formulas.dox
+++ b/docs/src/working_with_formulas.dox
@@ -48,6 +48,250 @@ multi-lingual [Formula Translator](http://en.excel-translator.de/language/)
to help you convert the formula. It can also replace semi-colons with commas.
+
+
+@section ww_formulas_dynamic_arrays Dynamic Array support
+
+Excel introduced the concept of "Dynamic Arrays" and new functions that use
+them in Office 365. The new functions are:
+
+- `FILTER`
+- `RANDARRAY`
+- `SEQUENCE`
+- `SORTBY`
+- `SORT`
+- `UNIQUE`
+- `XLOOKUP`
+- `XMATCH`
+
+The following special case functions were also added with Dynamic Arrays:
+
+- `SINGLE`: Explained below in @ref ww_formulas_intersection.
+- `ANCHORARRAY`: Explained below in @ref ww_formulas_spill.
+- `LAMBDA` and `LET`: Explained below in @ref ww_formulas_lambda.
+
+These functions are all "future functions" and need to written in
+libxlsxwriter as follows:
+
+- `_xlfn.ANCHORARRAY`
+- `_xlfn.LAMBDA`
+- `_xlfn.RANDARRAY`
+- `_xlfn.SEQUENCE`
+- `_xlfn.SINGLE`
+- `_xlfn.SORTBY`
+- `_xlfn.UNIQUE`
+- `_xlfn.XLOOKUP`
+- `_xlfn.XMATCH`
+- `_xlfn._xlws.FILTER`
+- `_xlfn._xlws.SORT`
+
+Future functions are explained in the section below on @ref
+ww_formulas_future.
+
+@subsection ww_formulas_intro Dynamic Arrays - An introduction
+
+Dynamic arrays in Excel are ranges of return values that can change in size
+based on the results. For example, a function such as `FILTER()` returns an
+array of values that can vary in size depending on the the filter results:
+
+@code
+ worksheet_write_dynamic_array_formula(worksheet, RANGE("F2:F2"),
+ "=_xlfn._xlws.FILTER(A1:D17,C1:C17=K2)",
+ NULL);
+@endcode
+
+This formula gives the results shown in the image below. The dynamic range
+here is "F2:I5" but it can vary based on the filter criteria.
+
+@image html dynamic_arrays02.png
+
+
+It is also possible to get dynamic array behavior with older Excel
+functions. For example, the Excel function `"=LEN(A1)"` applies to a single
+cell and returns a single value but it can also apply to a range of cells and
+return a range of values using an array formula like `"{=LEN(A1:A3)}"`. This
+type of "static" array behavior is referred to as a CSE (Ctrl+Shift+Enter)
+formula and has existed in Excel since early versions. In Office 365 Excel
+updated and extended this behavior to create the concept of dynamic arrays. In
+Excel 365 you can now write the previous LEN function as `"=LEN(A1:A3)"` and
+get a dynamic range of return values. In libxlsxwriter you can use the
+`worksheet_write_array_formula()` function to get a static/CSE range and
+`worksheet_write_dynamic_array_formula()` to get a dynamic range. For example:
+
+@code
+ worksheet_write_dynamic_array_formula(worksheet, RANGE("B1:B3"),
+ "=LEN(A1:A3)",
+ NULL);
+@endcode
+
+Which gives the following result:
+
+@image html intersection03.png
+
+The difference between the two types of array functions is explained in the
+Microsoft documentation on [Dynamic array formulas vs. legacy CSE array
+formulas](https://support.microsoft.com/en-us/office/dynamic-array-formulas-vs-legacy-cse-array-formulas-ca421f1b-fbb2-4c99-9924-df571bd4f1b4). Note
+the use of the word "legacy" here. This, and the documentation itself, is a
+clear indication of the future importance of dynamic arrays in Excel.
+
+For a wider and more general introduction to dynamic arrays see the following:
+[Dynamic array formulas in Excel](https://exceljet.net/dynamic-array-formulas-in-excel).
+
+The `worksheet_write_dynamic_array_formula()` function takes a `(first_row,
+first_col, last_row, last_col)` cell range to define the area that the formula
+applies to. However, since the range is dynamic this generally won't be known
+in advance in which case you can specify the range with the same start and end
+cell. The following range is "F2:F2":
+
+@code
+ worksheet_write_dynamic_array_formula(worksheet, 1, 5, 1, 5,
+ "=_xlfn._xlws.FILTER(A1:D17,C1:C17=K2)",
+ NULL);
+@endcode
+
+As a syntactic shortcut you can use the `worksheet_write_dynamic_formula()`
+function which only requires the start cell:
+
+@code
+ worksheet_write_dynamic_formula(worksheet, 1, 5,
+ "=_xlfn._xlws.FILTER(A1:D17,C1:C17=K2)",
+ NULL);
+@endcode
+
+
+@subsection ww_formulas_intersection Dynamic Arrays - The Implicit Intersection Operator "@"
+
+The Implicit Intersection Operator, "@", is used by Excel 365 to indicate a
+position in a formula that is implicitly returning a single value when a range
+or an array could be returned.
+
+We can see how this operator works in practice by considering the formula we
+used in the last section: `=LEN(A1:A3)`. In Excel versions without support for
+dynamic arrays, i.e. prior to Excel 365, this formula would operate on a
+single value from the input range and return a single value, like the
+following in Excel 2011:
+
+@image html intersection01.png
+
+There is an implicit conversion here of the range of input values, "A1:A3", to
+a single value "A1". Since this was the default behavior of older versions of
+Excel this conversion isn't highlighted in any way. But if you open the same
+file in Excel 365 it will appear as follows:
+
+@image html intersection02.png
+
+The result of the formula is the same (this is important to note) and it still
+operates on, and returns, a single value. However the formula now contains a
+"@" operator to show that it is implicitly using a single value from the given
+range.
+
+Finally, if you entered this formula in Excel 365, or with
+`worksheet_write_dynamic_array_formula()` in libxlsxwriter, it would operate
+on the entire range and return an array of values:
+
+@image html intersection03.png
+
+If you are encountering the Implicit Intersection Operator "@" for the first
+time then it is probably from a point of view of "why is Excel/libxlsxwriter
+putting @s in my formulas". In practical terms if you encounter this operator,
+and you don't intend it to be there, then you should probably write the
+formula as a CSE or dynamic array function using
+`worksheet_write_array_formula()` or `worksheet_write_dynamic_array_formula()`.
+
+
+A full explanation of this operator is shown in the Microsoft documentation on
+the [Implicit intersection operator: \@]
+(https://support.microsoft.com/en-us/office/implicit-intersection-operator-ce3be07b-0101-4450-a24e-c1c999be2b34?ui=en-us&rs=en-us&ad=us>).
+
+One important thing to note is that the "@" operator isn't stored with the
+formula. It is just displayed by Excel 365 when reading "legacy"
+formulas. However, it is possible to write it to a formula, if necessary,
+using `_xlfn.SINGLE()`. The unusual cases where this may be necessary are
+shown in the linked document in the previous paragraph.
+
+
+@subsection ww_formulas_spill Dynamic Arrays - The Spilled Range Operator "#"
+
+In the section above on @ref ww_formulas_intro we saw that dynamic array formulas
+can return variable sized ranges of results. The Excel documentation refers to
+this as a "Spilled" range/array from the idea that the results spill into the
+required number of cells. This is explained in the Microsoft documentation on
+[Dynamic array formulas and spilled array behavior]
+(https://support.microsoft.com/en-us/office/dynamic-array-formulas-and-spilled-array-behavior-205c6b06-03ba-4151-89a1-87a7eb36e531).
+
+Since a spilled range is variable in size a new operator is required to refer
+to the range. This operator is the [Spilled range operator]
+(https://support.microsoft.com/en-us/office/spilled-range-operator-3dd5899f-bca2-4b9d-a172-3eae9ac22efd)
+and it is represented by "#". For example, the range `F2#` in the image
+below is used to refer to a dynamic array returned by `UNIQUE()` in the cell
+`F2`:
+
+@image html spill01.png
+
+Unfortunately, Excel doesn't store the formula like this and in libxlsxwriter
+you need to use the explicit function `_xlfn.ANCHORARRAY()` to refer to a
+spilled range. The example in the image above was generated using the
+following:
+
+@code
+ // Same as '=COUNTA(F2#)' in Excel.
+ worksheet_write_dynamic_formula(worksheet9, CELL("J2"),
+ "=COUNTA(_xlfn.ANCHORARRAY(F2))",
+ NULL);
+@endcode
+
+
+@subsection ww_formulas_lambda The Excel 365 LAMBDA() function
+
+@note At the time of writing the `LAMBDA()` function in Excel is only
+available to Excel 365 users subscribed to the Beta Channel updates.
+
+Beta Channel versions of Excel 365 have introduced a powerful new
+function/feature called `LAMBDA()`. This is similar to
+[lambda expressions]
+(https://docs.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=msvc-160)
+in C++ (and other languages).
+
+Consider the following Excel example which converts the variable `temp` from Fahrenheit to Celsius:
+
+ LAMBDA(temp, (5/9) * (temp-32))
+
+This could be called in Excel with an argument:
+
+ =LAMBDA(temp, (5/9) * (temp-32))(212)
+
+Or assigned to a defined name and called as a user defined function:
+
+ =ToCelsius(212)
+
+
+ An libxlsxwriter example that replicates the described Excel functionality is
+shown below:
+
+@code
+ // Write the lambda as a function.
+ worksheet_write_dynamic_formula(worksheet, CELL("A2"),
+ "=_xlfn.LAMBDA(_xlpm.temp, (5/9) * (_xlpm.temp-32))(32)",
+ NULL);
+
+ // Create the lambda function as a defined name and write it as a dynamic formula.
+ workbook_define_name(workbook,
+ "ToCelsius",
+ "=_xlfn.LAMBDA(_xlpm.temp, (5/9) * (_xlpm.temp-32))");
+
+ worksheet_write_dynamic_formula(worksheet, CELL("A3"), "=ToCelsius(212)", NULL);
+@endcode
+
+Note, that the formula name must have a "_xlfn." prefix and the parameters in
+the `LAMBDA()` function must have a "_xlpm." prefix for compatibility with
+how the formulas are stored in Excel. These prefixes won't show up in the
+formula, as shown in the image.
+
+@image html lambda01.png
+
+The `LET()` function is often used in conjunction with `LAMBDA()` to assign
+names to calculation results.
+
@section ww_formulas_future Formulas added in Excel 2010 and later
@@ -199,6 +443,25 @@ The following list is taken from
| `_xlfn.Z.TEST` |
+The dynamic array functions shown in the @ref ww_formulas_dynamic_arrays
+section above are also future functions:
+
+
+ | Dynamic Array Functions |
+ | -------------------------------- |
+ | `_xlfn.ANCHORARRAY` |
+ | `_xlfn.LAMBDA` |
+ | `_xlfn.RANDARRAY` |
+ | `_xlfn.SEQUENCE` |
+ | `_xlfn.SINGLE` |
+ | `_xlfn.SORTBY` |
+ | `_xlfn.UNIQUE` |
+ | `_xlfn.XLOOKUP` |
+ | `_xlfn.XMATCH` |
+ | `_xlfn._xlws.FILTER` |
+ | `_xlfn._xlws.SORT` |
+
+
@section ww_formulas_errors Dealing with formula errors
If there is an error in the syntax of a formula it is usually displayed in
@@ -220,6 +483,13 @@ follows:
listed above (@ref ww_formulas_future). If it does then ensure that the
correct prefix is used.
+5. If the function loads in Excel but appears with one or more ``@`` symbols
+ added then it is probably an array function and should be written using
+ `worksheet_write_array_formula()` or
+ `worksheet_write_dynamic_array_formula()` (see the sections above on @ref
+ ww_formulas_dynamic_arrays and @ref ww_formulas_intersection).
+
+
Finally if you have completed all the previous steps and still get a
@c \#NAME? error you can examine a valid Excel file to see what the correct
syntax should be. To do this you should create a valid formula in Excel and
@@ -229,7 +499,7 @@ The following shows how to do that using Linux `unzip` and `libxml's
[xmllint](http://xmlsoft.org/xmllint.html) to format the XML for clarity:
$ unzip myfile.xlsx -d myfile
- $ xmllint --format myfile/xl/worksheets/sheet1.xml | grep ''
+ $ xmllint --format myfile/xl/worksheets/sheet1.xml | grep ''
SUM(1, 2, 3)
@@ -249,7 +519,7 @@ some mobile device applications.
If required, it is also possible to specify the calculated result of the
formula using the `result` parameter for
-:func:`worksheet_write_formula_num()`:
+`worksheet_worksheet_write_formula_num()`:
@code
worksheet_write_formula_num(worksheet, 0, 0, "=2+2", NULL, 4);
diff --git a/examples/dynamic_arrays.c b/examples/dynamic_arrays.c
new file mode 100644
index 00000000..54bc4971
--- /dev/null
+++ b/examples/dynamic_arrays.c
@@ -0,0 +1,303 @@
+/*
+ * An example of how to use libxlsxwriter to write functions that create
+ * dynamic arrays. These functions are new to Excel 365. The examples mirror
+ * the examples in the Excel documentation on these functions.
+ *
+ * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org
+ *
+ */
+
+#include "xlsxwriter.h"
+
+
+void write_worksheet_data(lxw_worksheet *worksheet, lxw_format *header);
+
+
+int main() {
+
+ lxw_workbook *workbook = workbook_new("dynamic_arrays.xlsx");
+ lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, "Filter");
+ lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, "Unique");
+ lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, "Sort");
+ lxw_worksheet *worksheet4 = workbook_add_worksheet(workbook, "Sortby");
+ lxw_worksheet *worksheet5 = workbook_add_worksheet(workbook, "Xlookup");
+ lxw_worksheet *worksheet6 = workbook_add_worksheet(workbook, "Xmatch");
+ lxw_worksheet *worksheet7 = workbook_add_worksheet(workbook, "Randarray");
+ lxw_worksheet *worksheet8 = workbook_add_worksheet(workbook, "Sequence");
+ lxw_worksheet *worksheet9 = workbook_add_worksheet(workbook, "Spill ranges");
+ lxw_worksheet *worksheet10 = workbook_add_worksheet(workbook, "Older functions");
+
+ lxw_format *header1 = workbook_add_format(workbook);
+ format_set_bg_color(header1, 0x74AC4C);
+ format_set_font_color(header1, 0xFFFFFF);
+
+ lxw_format *header2 = workbook_add_format(workbook);
+ format_set_bg_color(header2, 0x528FD3);
+ format_set_font_color(header2, 0xFFFFFF);
+
+
+ /*
+ * Example of using the FILTER() function.
+ */
+ worksheet_write_dynamic_formula(worksheet1, CELL("F2"),
+ "=_xlfn._xlws.FILTER(A1:D17,C1:C17=K2)",
+ NULL);
+
+ /* Write the data the function will work on. */
+ worksheet_write_string(worksheet1, CELL("K1"), "Product", header2);
+ worksheet_write_string(worksheet1, CELL("K2"), "Apple", NULL );
+ worksheet_write_string(worksheet1, CELL("F1"), "Region", header2);
+ worksheet_write_string(worksheet1, CELL("G1"), "Sales Rep", header2);
+ worksheet_write_string(worksheet1, CELL("H1"), "Product", header2);
+ worksheet_write_string(worksheet1, CELL("I1"), "Units", header2);
+
+ write_worksheet_data(worksheet1, header1);
+ worksheet_set_column_pixels(worksheet1, COLS("E:E"), 20, NULL);
+ worksheet_set_column_pixels(worksheet1, COLS("J:J"), 20, NULL);
+
+
+ /*
+ * Example of using the UNIQUE() function.
+ */
+ worksheet_write_dynamic_formula(worksheet2, CELL("F2"),
+ "=_xlfn.UNIQUE(B2:B17)",
+ NULL);
+
+ /* A more complex example combining SORT and UNIQUE. */
+ worksheet_write_dynamic_formula(worksheet2, CELL("H2"),
+ "=_xlfn._xlws.SORT(_xlfn.UNIQUE(B2:B17))",
+ NULL);
+
+ /* Write the data the function will work on. */
+ worksheet_write_string(worksheet2, CELL("F1"), "Sales Rep", header2);
+ worksheet_write_string(worksheet2, CELL("H1"), "Sales Rep", header2);
+
+ write_worksheet_data(worksheet2, header1);
+ worksheet_set_column_pixels(worksheet2, COLS("E:E"), 20, NULL);
+ worksheet_set_column_pixels(worksheet2, COLS("G:G"), 20, NULL);
+
+
+ /*
+ * Example of using the SORT() function.
+ */
+ worksheet_write_dynamic_formula(worksheet3, CELL("F2"),
+ "=_xlfn._xlws.SORT(B2:B17)",
+ NULL);
+
+ /* A more complex example combining SORT and FILTER. */
+ worksheet_write_dynamic_formula(worksheet3, CELL("H2"),
+ "=_xlfn._xlws.SORT(_xlfn._xlws.FILTER(C2:D17,D2:D17>5000,\"\"),2,1)",
+ NULL);
+
+ /* Write the data the function will work on. */
+ worksheet_write_string(worksheet3, CELL("F1"), "Sales Rep", header2);
+ worksheet_write_string(worksheet3, CELL("H1"), "Product", header2);
+ worksheet_write_string(worksheet3, CELL("I1"), "Units", header2);
+
+ write_worksheet_data(worksheet3, header1);
+ worksheet_set_column_pixels(worksheet3, COLS("E:E"), 20, NULL);
+ worksheet_set_column_pixels(worksheet3, COLS("G:G"), 20, NULL);
+
+
+ /*
+ * Example of using the SORTBY() function.
+ */
+ worksheet_write_dynamic_formula(worksheet4, CELL("D2"),
+ "=_xlfn.SORTBY(A2:B9,B2:B9)",
+ NULL);
+
+ /* Write the data the function will work on. */
+ worksheet_write_string(worksheet4, CELL("A1"), "Name", header1);
+ worksheet_write_string(worksheet4, CELL("B1"), "Age", header1);
+
+ worksheet_write_string(worksheet4, CELL("A2"), "Tom", NULL);
+ worksheet_write_string(worksheet4, CELL("A3"), "Fred", NULL);
+ worksheet_write_string(worksheet4, CELL("A4"), "Amy", NULL);
+ worksheet_write_string(worksheet4, CELL("A5"), "Sal", NULL);
+ worksheet_write_string(worksheet4, CELL("A6"), "Fritz", NULL);
+ worksheet_write_string(worksheet4, CELL("A7"), "Srivan", NULL);
+ worksheet_write_string(worksheet4, CELL("A8"), "Xi", NULL);
+ worksheet_write_string(worksheet4, CELL("A9"), "Hector", NULL);
+
+ worksheet_write_number(worksheet4, CELL("B2"), 52, NULL);
+ worksheet_write_number(worksheet4, CELL("B3"), 65, NULL);
+ worksheet_write_number(worksheet4, CELL("B4"), 22, NULL);
+ worksheet_write_number(worksheet4, CELL("B5"), 73, NULL);
+ worksheet_write_number(worksheet4, CELL("B6"), 19, NULL);
+ worksheet_write_number(worksheet4, CELL("B7"), 39, NULL);
+ worksheet_write_number(worksheet4, CELL("B8"), 19, NULL);
+ worksheet_write_number(worksheet4, CELL("B9"), 66, NULL);
+
+ worksheet_write_string(worksheet4, CELL("D1"), "Name", header2);
+ worksheet_write_string(worksheet4, CELL("E1"), "Age", header2);
+
+ worksheet_set_column_pixels(worksheet4, COLS("C:C"), 20, NULL);
+
+
+ /*
+ * Example of using the XLOOKUP() function.
+ */
+ worksheet_write_dynamic_formula(worksheet5, CELL("F1"),
+ "=_xlfn.XLOOKUP(E1,A2:A9,C2:C9)",
+ NULL);
+
+ /* Write the data the function will work on. */
+ worksheet_write_string(worksheet5, CELL("A1"), "Country", header1);
+ worksheet_write_string(worksheet5, CELL("B1"), "Abr", header1);
+ worksheet_write_string(worksheet5, CELL("C1"), "Prefix", header1);
+
+ worksheet_write_string(worksheet5, CELL("A2"), "China", NULL);
+ worksheet_write_string(worksheet5, CELL("A3"), "India", NULL);
+ worksheet_write_string(worksheet5, CELL("A4"), "United States", NULL);
+ worksheet_write_string(worksheet5, CELL("A5"), "Indonesia", NULL);
+ worksheet_write_string(worksheet5, CELL("A6"), "Brazil", NULL);
+ worksheet_write_string(worksheet5, CELL("A7"), "Pakistan", NULL);
+ worksheet_write_string(worksheet5, CELL("A8"), "Nigeria", NULL);
+ worksheet_write_string(worksheet5, CELL("A9"), "Bangladesh", NULL);
+
+ worksheet_write_string(worksheet5, CELL("B2"), "CN", NULL);
+ worksheet_write_string(worksheet5, CELL("B3"), "IN", NULL);
+ worksheet_write_string(worksheet5, CELL("B4"), "US", NULL);
+ worksheet_write_string(worksheet5, CELL("B5"), "ID", NULL);
+ worksheet_write_string(worksheet5, CELL("B6"), "BR", NULL);
+ worksheet_write_string(worksheet5, CELL("B7"), "PK", NULL);
+ worksheet_write_string(worksheet5, CELL("B8"), "NG", NULL);
+ worksheet_write_string(worksheet5, CELL("B9"), "BD", NULL);
+
+ worksheet_write_number(worksheet5, CELL("C2"), 86, NULL);
+ worksheet_write_number(worksheet5, CELL("C3"), 91, NULL);
+ worksheet_write_number(worksheet5, CELL("C4"), 1, NULL);
+ worksheet_write_number(worksheet5, CELL("C5"), 62, NULL);
+ worksheet_write_number(worksheet5, CELL("C6"), 55, NULL);
+ worksheet_write_number(worksheet5, CELL("C7"), 92, NULL);
+ worksheet_write_number(worksheet5, CELL("C8"), 234, NULL);
+ worksheet_write_number(worksheet5, CELL("C9"), 880, NULL);
+
+ worksheet_write_string(worksheet5, CELL("E1"), "Brazil", header2);
+
+ worksheet_set_column_pixels(worksheet5, COLS("A:A"), 100, NULL);
+ worksheet_set_column_pixels(worksheet5, COLS("D:D"), 20, NULL);
+
+
+ /*
+ * Example of using the XMATCH() function.
+ */
+ worksheet_write_dynamic_formula(worksheet6, CELL("D2"),
+ "=_xlfn.XMATCH(C2,A2:A6)",
+ NULL);
+
+ /* Write the data the function will work on. */
+ worksheet_write_string(worksheet6, CELL("A1"), "Product", header1);
+
+ worksheet_write_string(worksheet6, CELL("A2"), "Apple", NULL);
+ worksheet_write_string(worksheet6, CELL("A3"), "Grape", NULL);
+ worksheet_write_string(worksheet6, CELL("A4"), "Pear", NULL);
+ worksheet_write_string(worksheet6, CELL("A5"), "Banana", NULL);
+ worksheet_write_string(worksheet6, CELL("A6"), "Cherry", NULL);
+
+ worksheet_write_string(worksheet6, CELL("C1"), "Product", header2);
+ worksheet_write_string(worksheet6, CELL("D1"), "Position", header2);
+ worksheet_write_string(worksheet6, CELL("C2"), "Grape", NULL);
+
+ worksheet_set_column_pixels(worksheet6, COLS("B:B"), 20, NULL);
+
+
+ /*
+ * Example of using the RANDARRAY() function.
+ */
+ worksheet_write_dynamic_formula(worksheet7, CELL("A1"),
+ "=_xlfn.RANDARRAY(5,3,1,100, TRUE)",
+ NULL);
+
+ /*
+ * Example of using the SEQUENCE() function.
+ */
+ worksheet_write_dynamic_formula(worksheet8, CELL("A1"),
+ "=_xlfn.SEQUENCE(4,5)",
+ NULL);
+
+
+ /*
+ * Example of using the Spill range operator.
+ */
+ worksheet_write_dynamic_formula(worksheet9, CELL("H2"),
+ "=_xlfn.ANCHORARRAY(F2)",
+ NULL);
+
+ worksheet_write_dynamic_formula(worksheet9, CELL("J2"),
+ "=COUNTA(_xlfn.ANCHORARRAY(F2))",
+ NULL);
+
+ /* Write the data the function will work on. */
+ worksheet_write_dynamic_formula(worksheet9, CELL("F2"),
+ "=_xlfn.UNIQUE(B2:B17)",
+ NULL);
+
+ worksheet_write_string(worksheet9, CELL("F1"), "Unique", header2);
+ worksheet_write_string(worksheet9, CELL("H1"), "Spill", header2);
+ worksheet_write_string(worksheet9, CELL("J1"), "Spill", header2);
+
+ write_worksheet_data(worksheet9, header1);
+ worksheet_set_column_pixels(worksheet9, COLS("E:E"), 20, NULL);
+ worksheet_set_column_pixels(worksheet9, COLS("G:G"), 20, NULL);
+ worksheet_set_column_pixels(worksheet9, COLS("I:I"), 20, NULL);
+
+
+ /*
+ * Example of using dynamic ranges with older Excel functions.
+ */
+ worksheet_write_dynamic_array_formula(worksheet10, RANGE("B1:B3"),
+ "=LEN(A1:A3)",
+ NULL);
+
+ /* Write the data the function will work on. */
+ worksheet_write_string(worksheet10, CELL("A1"), "Foo", NULL);
+ worksheet_write_string(worksheet10, CELL("A2"), "Food", NULL);
+ worksheet_write_string(worksheet10, CELL("A3"), "Frood", NULL);
+
+
+ return workbook_close(workbook);
+}
+
+
+/* A simple function and data structure to populate some of the worksheets. */
+struct worksheet_data {
+ char col1[10];
+ char col2[10];
+ char col3[10];
+ int col4;
+};
+
+void write_worksheet_data(lxw_worksheet *worksheet, lxw_format *header) {
+
+ struct worksheet_data data[160] = {
+ {"East", "Tom", "Apple", 6380},
+ {"West", "Fred", "Grape", 5619},
+ {"North", "Amy", "Pear", 4565},
+ {"South", "Sal", "Banana", 5323},
+ {"East", "Fritz", "Apple", 4394},
+ {"West", "Sravan", "Grape", 7195},
+ {"North", "Xi", "Pear", 5231},
+ {"South", "Hector", "Banana", 2427},
+ {"East", "Tom", "Banana", 4213},
+ {"West", "Fred", "Pear", 3239},
+ {"North", "Amy", "Grape", 6520},
+ {"South", "Sal", "Apple", 1310},
+ {"East", "Fritz", "Banana", 6274},
+ {"West", "Sravan", "Pear", 4894},
+ {"North", "Xi", "Grape", 7580},
+ {"South", "Hector", "Apple", 9814},
+ };
+
+ worksheet_write_string(worksheet, CELL("A1"), "Region", header);
+ worksheet_write_string(worksheet, CELL("B1"), "Sales Rep", header);
+ worksheet_write_string(worksheet, CELL("C1"), "Product", header);
+ worksheet_write_string(worksheet, CELL("D1"), "Units", header);
+
+ for (int row = 0; row < 16; row++) {
+ worksheet_write_string(worksheet, row + 1, 0, data[row].col1, NULL);
+ worksheet_write_string(worksheet, row + 1, 1, data[row].col2, NULL);
+ worksheet_write_string(worksheet, row + 1, 2, data[row].col3, NULL);
+ worksheet_write_number(worksheet, row + 1, 3, data[row].col4, NULL);
+ }
+}
diff --git a/examples/lambda.c b/examples/lambda.c
new file mode 100644
index 00000000..0c03b317
--- /dev/null
+++ b/examples/lambda.c
@@ -0,0 +1,40 @@
+/*
+ * An example of using the new Excel LAMBDA() function with the libxlsxwriter
+ * library. Note, this function is only currently available if you are
+ * subscribed to the Microsoft Office Beta Channel program.
+ *
+ * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org
+ *
+ */
+
+#include "xlsxwriter.h"
+
+int main() {
+
+ lxw_workbook *workbook = workbook_new("lambda.xlsx");
+ lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
+
+ worksheet_write_string(worksheet, 0, 0,
+ "Note: Lambda functions currently only work with "
+ "the Beta Channel versions of Excel 365", NULL);
+
+ /* Note that the formula name is prefixed with "_xlfn." and that the
+ * lambda function parameters are prefixed with "_xlpm.". These prefixes
+ * won't show up in Excel.
+ */
+ worksheet_write_dynamic_formula(worksheet, CELL("A2"),
+ "=_xlfn.LAMBDA(_xlpm.temp, (5/9) * (_xlpm.temp-32))(32)",
+ NULL);
+
+ /* Create the lambda function as a defined name and write it as a dynamic formula. */
+ workbook_define_name(workbook,
+ "ToCelsius",
+ "=_xlfn.LAMBDA(_xlpm.temp, (5/9) * (_xlpm.temp-32))");
+
+ worksheet_write_dynamic_formula(worksheet, CELL("A3"), "=ToCelsius(212)", NULL);
+
+
+ workbook_close(workbook);
+
+ return 0;
+}
diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h
index 7cce2c62..22e80ba0 100644
--- a/include/xlsxwriter/worksheet.h
+++ b/include/xlsxwriter/worksheet.h
@@ -2020,16 +2020,16 @@ lxw_error worksheet_write_formula(lxw_worksheet *worksheet,
* @brief Write an array formula to a worksheet cell.
*
* @param worksheet Pointer to a lxw_worksheet instance to be updated.
- * @param first_row The first row of the range. (All zero indexed.)
- * @param first_col The first column of the range.
- * @param last_row The last row of the range.
- * @param last_col The last col of the range.
- * @param formula Array formula to write to cell.
- * @param format A pointer to a Format instance or NULL.
+ * @param first_row The first row of the range. (All zero indexed.)
+ * @param first_col The first column of the range.
+ * @param last_row The last row of the range.
+ * @param last_col The last col of the range.
+ * @param formula Array formula to write to cell.
+ * @param format A pointer to a Format instance or NULL.
*
* @return A #lxw_error code.
*
- * The `%worksheet_write_array_formula()` function writes an array formula to
+ * The `%worksheet_write_array_formula()` function writes an array formula to
* a cell range. In Excel an array formula is a formula that performs a
* calculation on a set of values.
*
@@ -2066,6 +2066,94 @@ lxw_error worksheet_write_array_formula(lxw_worksheet *worksheet,
const char *formula,
lxw_format *format);
+/**
+ * @brief Write an Excel 365 dynamic array formula to a worksheet range.
+ *
+ * @param worksheet Pointer to a lxw_worksheet instance to be updated.
+ * @param first_row The first row of the range. (All zero indexed.)
+ * @param first_col The first column of the range.
+ * @param last_row The last row of the range.
+ * @param last_col The last col of the range.
+ * @param formula Dynamic Array formula to write to cell.
+ * @param format A pointer to a Format instance or NULL.
+ *
+ * @return A #lxw_error code.
+ *
+ *
+ * The `%worksheet_write_dynamic_array_formula()` function writes an Excel 365
+ * dynamic array formula to a cell range. Some examples of functions that
+ * return dynamic arrays are:
+ *
+ * - `FILTER`
+ * - `RANDARRAY`
+ * - `SEQUENCE`
+ * - `SORTBY`
+ * - `SORT`
+ * - `UNIQUE`
+ * - `XLOOKUP`
+ * - `XMATCH`
+ *
+ * Dynamic array formulas and their usage in libxlsxwriter is explained in
+ * detail @ref ww_formulas_dynamic_arrays. The following is a example usage:
+ *
+ * @code
+ * worksheet_write_dynamic_array_formula(worksheet, 1, 5, 1, 5,
+ * "=_xlfn._xlws.FILTER(A1:D17,C1:C17=K2)",
+ * NULL);
+ * @endcode
+ *
+ * This formula gives the results shown in the image below.
+ *
+ * @image html dynamic_arrays02.png
+ *
+ * The need for the `_xlfn._xlws.` prefix in the formula is explained in @ref
+ * ww_formulas_future.
+ */
+lxw_error worksheet_write_dynamic_array_formula(lxw_worksheet *worksheet,
+ lxw_row_t first_row,
+ lxw_col_t first_col,
+ lxw_row_t last_row,
+ lxw_col_t last_col,
+ const char *formula,
+ lxw_format *format);
+
+/**
+ * @brief Write an Excel 365 dynamic array formula to a worksheet cell.
+ *
+ * @param worksheet Pointer to a lxw_worksheet instance to be updated.
+ * @param row The zero indexed row number.
+ * @param col The zero indexed column number.
+ * @param formula Formula string to write to cell.
+ * @param format A pointer to a Format instance or NULL.
+ *
+ * @return A #lxw_error code.
+ *
+ * The `%worksheet_write_dynamic_formula()` function is similar to the
+ * `worksheet_write_dynamic_array_formula()` function, shown above, except
+ * that it writes a dynamic array formula to a single cell, rather than a
+ * range. This is a syntactic shortcut since the array range isn't generally
+ * known for a dynamic range and specifying the initial cell is sufficient for
+ * Excel, as shown in the example below:
+ *
+ * @code
+ * worksheet_write_dynamic_formula(worksheet, 7, 1,
+ * "=_xlfn._xlws.SORT(_xlfn.UNIQUE(B2:B17))",
+ * NULL);
+ * @endcode
+ *
+ * This formula gives the following result:
+ *
+ * @image html dynamic_arrays01.png
+ *
+ * The need for the `_xlfn.` and `_xlfn._xlws.` prefixes in the formula is
+ * explained in @ref ww_formulas_future.
+ */
+lxw_error worksheet_write_dynamic_formula(lxw_worksheet *worksheet,
+ lxw_row_t row,
+ lxw_col_t col,
+ const char *formula,
+ lxw_format *format);
+
lxw_error worksheet_write_array_formula_num(lxw_worksheet *worksheet,
lxw_row_t first_row,
lxw_col_t first_col,
@@ -2075,14 +2163,6 @@ lxw_error worksheet_write_array_formula_num(lxw_worksheet *worksheet,
lxw_format *format,
double result);
-lxw_error worksheet_write_dynamic_array_formula(lxw_worksheet *worksheet,
- lxw_row_t first_row,
- lxw_col_t first_col,
- lxw_row_t last_row,
- lxw_col_t last_col,
- const char *formula,
- lxw_format *format);
-
lxw_error worksheet_write_dynamic_array_formula_num(lxw_worksheet *worksheet,
lxw_row_t first_row,
lxw_col_t first_col,
@@ -2092,6 +2172,13 @@ lxw_error worksheet_write_dynamic_array_formula_num(lxw_worksheet *worksheet,
lxw_format *format,
double result);
+lxw_error worksheet_write_dynamic_formula_num(lxw_worksheet *worksheet,
+ lxw_row_t row,
+ lxw_col_t col,
+ const char *formula,
+ lxw_format *format,
+ double result);
+
/**
* @brief Write a date or time to a worksheet cell.
*
diff --git a/src/worksheet.c b/src/worksheet.c
index 2df20fa7..b660be88 100644
--- a/src/worksheet.c
+++ b/src/worksheet.c
@@ -7063,6 +7063,33 @@ worksheet_write_array_formula(lxw_worksheet *self,
LXW_FALSE);
}
+/*
+ * Write a single cell dynamic array formula with a default result to a cell.
+ */
+lxw_error
+worksheet_write_dynamic_formula(lxw_worksheet *self,
+ lxw_row_t row,
+ lxw_col_t col,
+ const char *formula, lxw_format *format)
+{
+ return _store_array_formula(self, row, col, row, col, formula, format, 0,
+ LXW_TRUE);
+}
+
+/*
+ * Write a single cell dynamic array formula with a numerical result to a cell.
+ */
+lxw_error
+worksheet_write_dynamic_formula_num(lxw_worksheet *self,
+ lxw_row_t row,
+ lxw_col_t col,
+ const char *formula,
+ lxw_format *format, double result)
+{
+ return _store_array_formula(self, row, col, row, col, formula, format,
+ result, LXW_TRUE);
+}
+
/*
* Write a dynamic array formula with a numerical result to a cell in Excel.
*/
diff --git a/test/functional/src/test_dynamic_array02.c b/test/functional/src/test_dynamic_array02.c
new file mode 100644
index 00000000..7e7427ed
--- /dev/null
+++ b/test/functional/src/test_dynamic_array02.c
@@ -0,0 +1,21 @@
+/*****************************************************************************
+ * Test cases for libxlsxwriter.
+ *
+ * Test to compare output against Excel files.
+ *
+ * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org
+ *
+ */
+
+#include "xlsxwriter.h"
+
+int main() {
+
+ lxw_workbook *workbook = workbook_new("test_dynamic_array02.xlsx");
+ lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
+
+ worksheet_write_dynamic_formula(worksheet, CELL("B1"), "=_xlfn.UNIQUE(A1)", NULL);
+ worksheet_write_number(worksheet, CELL("A1"), 0 , NULL);
+
+ return workbook_close(workbook);
+}
diff --git a/test/functional/src/test_dynamic_array03.c b/test/functional/src/test_dynamic_array03.c
new file mode 100644
index 00000000..410559b3
--- /dev/null
+++ b/test/functional/src/test_dynamic_array03.c
@@ -0,0 +1,20 @@
+/*****************************************************************************
+ * Test cases for libxlsxwriter.
+ *
+ * Test to compare output against Excel files.
+ *
+ * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org
+ *
+ */
+
+#include "xlsxwriter.h"
+
+int main() {
+
+ lxw_workbook *workbook = workbook_new("test_dynamic_array03.xlsx");
+ lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
+
+ worksheet_write_formula_num(worksheet, CELL("A1"), "=1+_xlfn.XOR(1)", NULL, 2);
+
+ return workbook_close(workbook);
+}
diff --git a/test/functional/src/test_dynamic_array51.c b/test/functional/src/test_dynamic_array51.c
new file mode 100644
index 00000000..c596efaa
--- /dev/null
+++ b/test/functional/src/test_dynamic_array51.c
@@ -0,0 +1,22 @@
+/*****************************************************************************
+ * Test cases for libxlsxwriter.
+ *
+ * Test to compare output against Excel files.
+ *
+ * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org
+ *
+ */
+
+#include "xlsxwriter.h"
+
+int main() {
+
+ lxw_workbook *workbook = workbook_new("test_dynamic_array51.xlsx");
+ lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
+
+ worksheet_write_dynamic_array_formula_num(worksheet, RANGE("A1:A1"), "=AVERAGE(TIMEVALUE(B1:B2))", NULL, 0);
+ worksheet_write_string(worksheet, CELL("B1"), "12:00" , NULL);
+ worksheet_write_string(worksheet, CELL("B2"), "12:00" , NULL);
+
+ return workbook_close(workbook);
+}
diff --git a/test/functional/src/test_dynamic_array52.c b/test/functional/src/test_dynamic_array52.c
new file mode 100644
index 00000000..9186df87
--- /dev/null
+++ b/test/functional/src/test_dynamic_array52.c
@@ -0,0 +1,22 @@
+/*****************************************************************************
+ * Test cases for libxlsxwriter.
+ *
+ * Test to compare output against Excel files.
+ *
+ * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org
+ *
+ */
+
+#include "xlsxwriter.h"
+
+int main() {
+
+ lxw_workbook *workbook = workbook_new("test_dynamic_array52.xlsx");
+ lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
+
+ worksheet_write_dynamic_formula(worksheet, CELL("A1"), "=AVERAGE(TIMEVALUE(B1:B2))", NULL);
+ worksheet_write_string(worksheet, CELL("B1"), "12:00" , NULL);
+ worksheet_write_string(worksheet, CELL("B2"), "12:00" , NULL);
+
+ return workbook_close(workbook);
+}
diff --git a/test/functional/src/test_dynamic_array53.c b/test/functional/src/test_dynamic_array53.c
new file mode 100644
index 00000000..772c277f
--- /dev/null
+++ b/test/functional/src/test_dynamic_array53.c
@@ -0,0 +1,22 @@
+/*****************************************************************************
+ * Test cases for libxlsxwriter.
+ *
+ * Test to compare output against Excel files.
+ *
+ * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org
+ *
+ */
+
+#include "xlsxwriter.h"
+
+int main() {
+
+ lxw_workbook *workbook = workbook_new("test_dynamic_array53.xlsx");
+ lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
+
+ worksheet_write_dynamic_formula_num(worksheet, CELL("A1"), "=AVERAGE(TIMEVALUE(B1:B2))", NULL, 0);
+ worksheet_write_string(worksheet, CELL("B1"), "12:00" , NULL);
+ worksheet_write_string(worksheet, CELL("B2"), "12:00" , NULL);
+
+ return workbook_close(workbook);
+}
diff --git a/test/functional/test_dynamic_array.py b/test/functional/test_dynamic_array.py
index e9abecd3..5c168849 100644
--- a/test/functional/test_dynamic_array.py
+++ b/test/functional/test_dynamic_array.py
@@ -15,3 +15,19 @@ class TestCompareXLSXFiles(base_test_class.XLSXBaseTest):
def test_dynamic_array01(self):
self.run_exe_test('test_dynamic_array01')
+
+ def test_dynamic_array02(self):
+ self.run_exe_test('test_dynamic_array02')
+
+ def test_dynamic_array03(self):
+ self.run_exe_test('test_dynamic_array03')
+
+ # Some variant of the default test case.
+ def test_dynamic_array51(self):
+ self.run_exe_test('test_dynamic_array51', 'dynamic_array01.xlsx')
+
+ def test_dynamic_array52(self):
+ self.run_exe_test('test_dynamic_array52', 'dynamic_array01.xlsx')
+
+ def test_dynamic_array53(self):
+ self.run_exe_test('test_dynamic_array53', 'dynamic_array01.xlsx')
diff --git a/test/functional/xlsx_files/dynamic_array02.xlsx b/test/functional/xlsx_files/dynamic_array02.xlsx
new file mode 100644
index 00000000..37c92438
Binary files /dev/null and b/test/functional/xlsx_files/dynamic_array02.xlsx differ
diff --git a/test/functional/xlsx_files/dynamic_array03.xlsx b/test/functional/xlsx_files/dynamic_array03.xlsx
new file mode 100644
index 00000000..068919ba
Binary files /dev/null and b/test/functional/xlsx_files/dynamic_array03.xlsx differ