From e03853b9666c45a4c370896d4d1aaf15884d6e60 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Wed, 28 Jul 2021 13:51:27 +0200 Subject: [PATCH] Write XLSX files (#1906) --- RELEASES.md | 5 + .../Standard/Table/0.1.0/src/Data/Table.enso | 79 +++++++- .../0.1.0/src/Io/Spreadsheet_Write_Mode.enso | 24 +++ .../lib/Standard/Table/0.1.0/src/Main.enso | 2 + .../data/column/storage/BoolStorage.java | 7 + .../data/column/storage/DoubleStorage.java | 7 + .../data/column/storage/LongStorage.java | 7 + .../data/column/storage/ObjectStorage.java | 7 + .../table/data/column/storage/Storage.java | 12 ++ .../data/column/storage/StringStorage.java | 7 + .../org/enso/table/format/csv/Writer.java | 19 +- .../enso/table/format/util/FileSplitter.java | 33 +++ .../org/enso/table/format/xlsx/Writer.java | 189 ++++++++++++++++++ test/Table_Tests/src/Spreadsheet_Spec.enso | 57 ++++++ 14 files changed, 439 insertions(+), 16 deletions(-) create mode 100644 distribution/lib/Standard/Table/0.1.0/src/Io/Spreadsheet_Write_Mode.enso create mode 100644 std-bits/table/src/main/java/org/enso/table/format/util/FileSplitter.java create mode 100644 std-bits/table/src/main/java/org/enso/table/format/xlsx/Writer.java diff --git a/RELEASES.md b/RELEASES.md index e2f4c718e9f0..3743290ac85e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,10 @@ # Enso Next +## Libraries + +- Added support for writing tables to XLSX spreadsheets + ([#1906](https://github.com/enso-org/enso/pull/1906)). + # Enso 0.2.17 (2021-07-28) ## Interpreter/Runtime diff --git a/distribution/lib/Standard/Table/0.1.0/src/Data/Table.enso b/distribution/lib/Standard/Table/0.1.0/src/Data/Table.enso index 444dfbb73192..8e9ac1e57ed9 100644 --- a/distribution/lib/Standard/Table/0.1.0/src/Data/Table.enso +++ b/distribution/lib/Standard/Table/0.1.0/src/Data/Table.enso @@ -4,12 +4,15 @@ import Standard.Base.System.Platform import Standard.Table.Data.Column import Standard.Table.Io.Csv import Standard.Visualization +import Standard.Base.Data.Time.Date +import Standard.Table.Io.Spreadsheet_Write_Mode from Standard.Table.Data.Order_Rule as Order_Rule_Module import Order_Rule polyglot java import org.enso.table.data.table.Table as Java_Table polyglot java import org.enso.table.operations.OrderBuilder polyglot java import org.enso.table.format.csv.Writer as Csv_Writer +polyglot java import org.enso.table.format.xlsx.Writer as Spreadsheet_Writer ## Creates a new table from a vector of `[name, items]` pairs. @@ -759,6 +762,50 @@ type Table write_csv file include_header=True always_quote=False separator=',' line_ending=Line_Ending_Style.Unix max_rows_per_file=Nothing = Csv_Writer.writePath this.java_table file.absolute.path max_rows_per_file include_header always_quote line_ending.to_text separator .to_csv_field + ## ALIAS Write Excel File + UNSTABLE + + Writes this table into an XLSX spreadsheet. + + Arguments: + - file: the XLSX file to write data to. If it exists, the behavior is + specified by the `write_mode` argument. Note that other files may be + created or written to if `max_rows_per_file` is used. + - sheet: the name of the sheet to use for writing the data. + - write_mode: specifies this method's behavior if the specified file and + sheet already exist. Can be one of: + - Spreadsheet_Write_Mode.Create: this is the default value. This + setting will create a new sheet in the file, with a name chosen such + that the clash is avoided. + - Spreadsheet_Write_Mode.Overwrite: will result in removing all + contents of the existing sheet and replacing it with the new data. + - Spreadsheet_Write_Mode.Append: will append this data to the existing + sheet, such that the new data starts after the last row containing + any data. + - include_header: Specifies whether the first line of generated CSV + should contain the column names. + - max_rows_per_file: specifies the maximum number of rows that can be + written to a single file. If this option is set and its value is less + than the number of rows in this table, the behavior of the `file` + argument changes. Instead of writing the contents directly to `file`, + its name is parsed and a numbered series of files with names based + on `file` is written to instead. For example, if `file` points to + `~/my_data/output.xlsx`, `this` contains 250 rows, and + `max_rows_per_file` is set to `100`, 3 different files will be written: + - `~/my_data/output_1.xlsx`, containing rows 0 through 99; + - `~/my_data/output_2.xlsx`, containing rows 100 through 199; + - `~/my_data/output_3.xlsx`, containing rows 200 through 249. + + > Example + Write a table to an XLSX file. + + import Standard.Examples + + example_to_xlsx = Examples.inventory_table.write_xlsx (Enso_Project.data / example_xlsx_output.xlsx) + write_xlsx : File.File -> String -> Spreadsheet_Write_Mode.Speadsheet_Write_Mode -> Boolean -> Nothing | Integer -> Nothing + write_xlsx file sheet='Data' write_mode=Spreadsheet_Write_Mode.Create include_header=True max_rows_per_file=Nothing = + Spreadsheet_Writer.writeXlsx this.java_table file.absolute.path sheet write_mode.to_java include_header max_rows_per_file .write_to_spreadsheet + ## UNSTABLE Used for converting arbitrary values into fields in CSV files. @@ -770,6 +817,37 @@ Any.to_csv_field = this.to_text Used for converting text values into fields in CSV files. Text.to_csv_field = this +## UNSTABLE + ADVANCED + + Used to write a value into a spreadsheet cell. + + Arguments: + - cell: an instance of `org.apache.poi.ss.usermodel.Cell`, the value of + which should be set by this method. +Any.write_to_spreadsheet cell = cell.setCellValue this.to_text + +## UNSTABLE + ADVANCED + + Used to write a value into a spreadsheet cell. + + Arguments: + - cell: an instance of `org.apache.poi.ss.usermodel.Cell`, the value of + which should be set by this method. +Text.write_to_spreadsheet cell = cell.setCellValue this + +## UNSTABLE + ADVANCED + + Used to write a value into a spreadsheet cell. + + Arguments: + - cell: an instance of `org.apache.poi.ss.usermodel.Cell`, the value of + which should be set by this method. +Date.Date.write_to_spreadsheet cell = cell.setCellValue this.internal_local_date + + ## Represents a table with grouped rows. type Aggregate_Table @@ -950,4 +1028,3 @@ print_table header rows indices_count format_term = - y: The right operand to the comparator. comparator_to_java : (Any -> Any -> Ordering) -> Any -> Any -> Integer comparator_to_java cmp x y = cmp x y . to_sign - diff --git a/distribution/lib/Standard/Table/0.1.0/src/Io/Spreadsheet_Write_Mode.enso b/distribution/lib/Standard/Table/0.1.0/src/Io/Spreadsheet_Write_Mode.enso new file mode 100644 index 000000000000..55cfbf25a3bf --- /dev/null +++ b/distribution/lib/Standard/Table/0.1.0/src/Io/Spreadsheet_Write_Mode.enso @@ -0,0 +1,24 @@ +from Standard.Base import all + +polyglot java import org.enso.table.format.xlsx.Writer + +## Specifies the behavior of XLSX writing for pre-existing sheets. +type Spreadsheet_Write_Mode + ## Append new data to the existing sheet, such that the new data starts + after the last row containing any data. + type Append + + ## Create a new sheet, renaming it such that there is no clash with + exisitng sheets. + type Create + + ## Remove all contents from the existing sheet and write the data to it. + type Overwrite + + ## PRIVATE + + Converts this into a Java-side representation. + to_java = case this of + Append -> Writer.WriteMode.APPEND + Create -> Writer.WriteMode.CREATE_SHEET + Overwrite -> Writer.WriteMode.OVERWRITE_SHEET diff --git a/distribution/lib/Standard/Table/0.1.0/src/Main.enso b/distribution/lib/Standard/Table/0.1.0/src/Main.enso index 295cff449eb1..4c767e0ecc63 100644 --- a/distribution/lib/Standard/Table/0.1.0/src/Main.enso +++ b/distribution/lib/Standard/Table/0.1.0/src/Main.enso @@ -3,6 +3,7 @@ from Standard.Base import all import Standard.Geo.Geo_Json import Standard.Table.Io.Csv import Standard.Table.Io.Spreadsheet +import Standard.Table.Io.Spreadsheet_Write_Mode import Standard.Table.Data.Table import Standard.Table.Data.Column import Standard.Table.Data.Order_Rule @@ -10,6 +11,7 @@ import Standard.Table.Data.Order_Rule from Standard.Table.Io.Csv export all hiding Parser from Standard.Table.Io.Spreadsheet export all hiding Reader +export Standard.Table.Io.Spreadsheet_Write_Mode export Standard.Table.Data.Column from Standard.Table.Data.Table export new, from_rows, join, No_Such_Column_Error, Table diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/BoolStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/BoolStorage.java index df36b3623fcc..974156ca9068 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/BoolStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/BoolStorage.java @@ -2,8 +2,10 @@ import java.util.BitSet; import java.util.Comparator; +import java.util.function.BiConsumer; import java.util.function.Function; +import org.apache.poi.ss.usermodel.Cell; import org.enso.table.data.column.operation.map.MapOpStorage; import org.enso.table.data.column.operation.map.MapOperation; import org.enso.table.data.column.operation.map.UnaryMapOperation; @@ -323,4 +325,9 @@ public BoolStorage slice(int offset, int limit) { public String getPresentCsvString(int index, Function toCsvString) { return getItem(index) ? "True" : "False"; } + + @Override + public void writeSpreadsheetCell(int index, Cell cell, BiConsumer writeCell) { + cell.setCellValue(getItem(index)); + } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/DoubleStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/DoubleStorage.java index 25768de9325d..d23b0f4bf1df 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/DoubleStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/DoubleStorage.java @@ -2,8 +2,10 @@ import java.util.BitSet; import java.util.Comparator; +import java.util.function.BiConsumer; import java.util.function.Function; +import org.apache.poi.ss.usermodel.Cell; import org.enso.table.data.column.builder.object.NumericBuilder; import org.enso.table.data.column.operation.map.MapOpStorage; import org.enso.table.data.column.operation.map.UnaryMapOperation; @@ -271,4 +273,9 @@ public DoubleStorage slice(int offset, int limit) { public String getPresentCsvString(int index, Function toCsvString) { return String.valueOf(getItem(index)); } + + @Override + public void writeSpreadsheetCell(int index, Cell cell, BiConsumer writeCell) { + cell.setCellValue(getItem(index)); + } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/LongStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/LongStorage.java index 8d07c9ddcbd0..3355cba3847b 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/LongStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/LongStorage.java @@ -1,9 +1,11 @@ package org.enso.table.data.column.storage; import java.util.*; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.LongStream; +import org.apache.poi.ss.usermodel.Cell; import org.enso.table.data.column.builder.object.NumericBuilder; import org.enso.table.data.column.operation.aggregate.Aggregator; import org.enso.table.data.column.operation.aggregate.numeric.LongToLongAggregator; @@ -385,4 +387,9 @@ public LongStorage slice(int offset, int limit) { protected String getPresentCsvString(int index, Function toCsvString) { return String.valueOf(getItem(index)); } + + @Override + public void writeSpreadsheetCell(int index, Cell cell, BiConsumer writeCell) { + cell.setCellValue(getItem(index)); + } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/ObjectStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/ObjectStorage.java index 5267fe7fab5b..70b9b4ca4c21 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/ObjectStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/ObjectStorage.java @@ -3,8 +3,10 @@ import java.util.Arrays; import java.util.BitSet; import java.util.Comparator; +import java.util.function.BiConsumer; import java.util.function.Function; +import org.apache.poi.ss.usermodel.Cell; import org.enso.table.data.column.operation.map.MapOpStorage; import org.enso.table.data.column.operation.map.UnaryMapOperation; import org.enso.table.data.index.Index; @@ -160,4 +162,9 @@ public ObjectStorage slice(int offset, int limit) { protected String getPresentCsvString(int index, Function toCsvString) { return toCsvString.apply(getItem(index)); } + + @Override + public void writeSpreadsheetCell(int index, Cell cell, BiConsumer writeCell) { + writeCell.accept(getItem(index), cell); + } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/Storage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/Storage.java index 1604b3238794..d6d6fcbb3b78 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/Storage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/Storage.java @@ -1,5 +1,6 @@ package org.enso.table.data.column.storage; +import org.apache.poi.ss.usermodel.Cell; import org.enso.table.data.column.builder.object.Builder; import org.enso.table.data.column.builder.object.InferredBuilder; import org.enso.table.data.column.operation.aggregate.Aggregator; @@ -7,6 +8,7 @@ import org.enso.table.data.column.operation.aggregate.FunctionAggregator; import java.util.*; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; @@ -283,4 +285,14 @@ public String getCsvString(int index, Function toCsvString) { * @return a CSV representation of the value at {@code index}. */ protected abstract String getPresentCsvString(int index, Function toCsvString); + + /** + * Write the value at the specified index into an XLSX cell. + * + * @param index the index to read value at. {@link #isNa(long)} must return false for this index. + * @param cell the cell to write data to + * @param writeCell a callback to delegate writes back to Enso code + */ + public abstract void writeSpreadsheetCell( + int index, Cell cell, BiConsumer writeCell); } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/StringStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/StringStorage.java index fffabac65865..42a1ddf4706f 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/storage/StringStorage.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/StringStorage.java @@ -2,8 +2,10 @@ import java.util.BitSet; import java.util.Comparator; +import java.util.function.BiConsumer; import java.util.function.Function; +import org.apache.poi.ss.usermodel.Cell; import org.enso.table.data.column.builder.object.StringBuilder; import org.enso.table.data.column.operation.map.MapOpStorage; import org.enso.table.data.column.operation.map.MapOperation; @@ -150,4 +152,9 @@ public StringStorage slice(int offset, int limit) { protected String getPresentCsvString(int index, Function toCsvString) { return getItem(index); } + + @Override + public void writeSpreadsheetCell(int index, Cell cell, BiConsumer writeCell) { + cell.setCellValue(getItem(index)); + } } diff --git a/std-bits/table/src/main/java/org/enso/table/format/csv/Writer.java b/std-bits/table/src/main/java/org/enso/table/format/csv/Writer.java index c7ef7d9e502e..f4d1130a761a 100644 --- a/std-bits/table/src/main/java/org/enso/table/format/csv/Writer.java +++ b/std-bits/table/src/main/java/org/enso/table/format/csv/Writer.java @@ -1,6 +1,7 @@ package org.enso.table.format.csv; import org.enso.table.data.table.Table; +import org.enso.table.format.util.FileSplitter; import java.io.*; import java.util.Arrays; @@ -85,21 +86,9 @@ public static void writePath( toCsvString); } } else { - var nfiles = table.rowCount() / maxRecords + (table.rowCount() % maxRecords == 0 ? 0 : 1); - var originalName = file.getName(); - var extIndex = originalName.indexOf('.'); - var dir = file.getParentFile(); - String basename; - String extension; - if (extIndex == -1) { - basename = originalName; - extension = ""; - } else { - basename = originalName.substring(0, extIndex); - extension = originalName.substring(extIndex); - } - for (int i = 0; i < nfiles; i++) { - var currentFile = new File(dir, basename + "_" + (i + 1) + extension); + var files = new FileSplitter(table.rowCount(), maxRecords, file); + for (int i = 0; i < files.getNumberOfFiles(); i++) { + var currentFile = files.getFile(i); try (var writer = new PrintWriter(new FileWriter(currentFile))) { writeAppendable( table, diff --git a/std-bits/table/src/main/java/org/enso/table/format/util/FileSplitter.java b/std-bits/table/src/main/java/org/enso/table/format/util/FileSplitter.java new file mode 100644 index 000000000000..d8354f085eec --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/format/util/FileSplitter.java @@ -0,0 +1,33 @@ +package org.enso.table.format.util; + +import java.io.File; + +public class FileSplitter { + private final int numberOfFiles; + private final String basename; + private final String extension; + private final File dir; + + public FileSplitter(int numberOfRecords, int maxRecordsPerFile, File prototype) { + numberOfFiles = + numberOfRecords / maxRecordsPerFile + (numberOfRecords % maxRecordsPerFile == 0 ? 0 : 1); + var originalName = prototype.getName(); + var extIndex = originalName.indexOf('.'); + dir = prototype.getParentFile(); + if (extIndex == -1) { + basename = originalName; + extension = ""; + } else { + basename = originalName.substring(0, extIndex); + extension = originalName.substring(extIndex); + } + } + + public int getNumberOfFiles() { + return numberOfFiles; + } + + public File getFile(int index) { + return new File(dir, basename + "_" + (index + 1) + extension); + } +} diff --git a/std-bits/table/src/main/java/org/enso/table/format/xlsx/Writer.java b/std-bits/table/src/main/java/org/enso/table/format/xlsx/Writer.java new file mode 100644 index 000000000000..75de61684d88 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/format/xlsx/Writer.java @@ -0,0 +1,189 @@ +package org.enso.table.format.xlsx; + +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.enso.table.data.table.Table; +import org.enso.table.format.util.FileSplitter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.function.BiConsumer; + +/** Writer for XLSX files. */ +public class Writer { + /** Specifies write behavior for files that already exist. */ + public enum WriteMode { + /** Append new contents to the existing sheet. */ + APPEND, + /** Remove old contents and replace with new. */ + OVERWRITE_SHEET, + /** Create a new sheet, avoiding a name clash. */ + CREATE_SHEET + } + + /** + * Write a table to XLSX. + * + * @param table the table + * @param path the path to the xlsx file + * @param sheetName the name of the sheet + * @param writeMode specification of this function's behavior when the specified sheet already + * exists + * @param writeHeader whether the first row should contain column names + * @param maxRecords the max number of records that can be written to a single file + * @param writeCell a helper for writing arbitrary objects into XLSX cells. + * @throws IOException when any of the files cannot be read. + * @throws InvalidFormatException when the specified file exists, but is not an XLSX file. + */ + public static void writeXlsx( + Table table, + String path, + String sheetName, + WriteMode writeMode, + boolean writeHeader, + Integer maxRecords, + BiConsumer writeCell) + throws IOException, InvalidFormatException { + if (maxRecords == null || maxRecords >= table.rowCount()) { + var file = new File(path); + writeXlsx(table, file, sheetName, writeMode, writeHeader, 0, table.rowCount(), writeCell); + } else { + var splitter = new FileSplitter(table.rowCount(), maxRecords, new File(path)); + for (int i = 0; i < splitter.getNumberOfFiles(); i++) { + writeXlsx( + table, + splitter.getFile(i), + sheetName, + writeMode, + writeHeader, + i * maxRecords, + maxRecords, + writeCell); + } + } + } + + private static Workbook workbookForFile(File file) throws IOException, InvalidFormatException { + if (file.exists()) { + try (var stream = new FileInputStream(file)) { + return new XSSFWorkbook(stream); + } + } else { + return new XSSFWorkbook(); + } + } + + private static void writeXlsx( + Table table, + File file, + String sheetName, + WriteMode writeMode, + boolean writeHeader, + int startRecord, + int numRecords, + BiConsumer writeCell) + throws IOException, InvalidFormatException { + try (var workbook = workbookForFile(file)) { + writeWorkbook( + table, workbook, sheetName, writeMode, writeHeader, startRecord, numRecords, writeCell); + try (var outputStream = new FileOutputStream(file)) { + workbook.write(outputStream); + } + } + } + + private static void writeWorkbook( + Table table, + Workbook workbook, + String sheetName, + WriteMode writeMode, + boolean writeHeader, + int startRecord, + int numRecords, + BiConsumer writeCell) { + var sheet = workbook.getSheet(sheetName); + if (sheet == null) { + var newSheet = workbook.createSheet(sheetName); + writeSheet(table, newSheet, writeHeader, startRecord, numRecords, 0, 0, writeCell); + return; + } + switch (writeMode) { + case APPEND: + writeSheet( + table, + sheet, + writeHeader, + startRecord, + numRecords, + sheet.getLastRowNum() + 1, + 0, + writeCell); + workbook.setForceFormulaRecalculation(true); + return; + case OVERWRITE_SHEET: + int row; + while ((row = sheet.getLastRowNum()) != -1) { + sheet.removeRow(sheet.getRow(row)); + } + writeSheet(table, sheet, writeHeader, startRecord, numRecords, 0, 0, writeCell); + workbook.setForceFormulaRecalculation(true); + return; + case CREATE_SHEET: + int currentSheet = 1; + var newSheetName = ""; + do { + newSheetName = sheetName + " " + currentSheet; + sheet = workbook.getSheet(newSheetName); + currentSheet++; + } while (sheet != null); + sheet = workbook.createSheet(newSheetName); + writeSheet(table, sheet, writeHeader, startRecord, numRecords, 0, 0, writeCell); + workbook.setForceFormulaRecalculation(true); + return; + } + } + + private static void writeSheet( + Table table, + Sheet sheet, + boolean writeHeader, + int startRecord, + int numRecords, + int startRow, + int startCol, + BiConsumer writeCell) { + var columns = Arrays.asList(table.getColumns()); + var index = table.getIndex().toColumn(); + if (index != null) { + columns.add(0, index); + } + if (writeHeader) { + var row = sheet.createRow(startRow); + startRow++; + for (int j = 0; j < columns.size(); j++) { + var cell = row.createCell(startCol + j, CellType.STRING); + cell.setCellValue(columns.get(j).getName()); + } + } + var rowLimit = Math.min(numRecords, table.rowCount() - startRecord); + for (int i = 0; i < rowLimit; i++) { + var row = sheet.createRow(startRow + i); + for (int j = 0; j < columns.size(); j++) { + var cell = row.createCell(startCol + j); + var storage = columns.get(j).getStorage(); + if (storage.isNa(startRecord + i)) { + cell.setBlank(); + } else { + storage.writeSpreadsheetCell(startRecord + i, cell, writeCell); + } + } + } + } +} diff --git a/test/Table_Tests/src/Spreadsheet_Spec.enso b/test/Table_Tests/src/Spreadsheet_Spec.enso index 708185953d5e..996f0e7c5d81 100644 --- a/test/Table_Tests/src/Spreadsheet_Spec.enso +++ b/test/Table_Tests/src/Spreadsheet_Spec.enso @@ -34,6 +34,63 @@ spec_fmt header file read_method = t.columns.map .name . should_equal ['Item', 'Price', 'Quantity', 'Price 1'] t.at 'Price 1' . to_vector . should_equal [20, 40, 0, 60, 0, 10] +Table.Table.should_equal expected = + this_cols = this.columns + that_cols = expected.columns + this_cols.map .name . should_equal (that_cols.map .name) + this_cols.map .to_vector . should_equal (that_cols.map .to_vector) + spec = here.spec_fmt 'XLSX reading' Examples.xlsx .read_xlsx + here.spec_fmt 'XLS reading' Examples.xls .read_xls + + Test.group 'XLSX writing' <| + out = Enso_Project.data / 'out.xlsx' + table = Enso_Project.data/'varied_column.csv' . read_csv has_header=False + clothes = Enso_Project.data/'clothes.csv' . read_csv + + Test.specify 'should write tables to non-existent XLSX files' <| + out.delete_if_exists + table.write_xlsx out + written = out.read_xlsx + written.should_equal table + out.delete_if_exists + + Test.specify 'should create a new sheet if it already exists and write mode is Create' <| + out.delete_if_exists + table.write_xlsx out sheet='Foo' + clothes.write_xlsx out sheet='Foo' + read_1 = out.read_xlsx sheet='Foo' + read_1 . should_equal table + read_2 = out.read_xlsx sheet='Foo 1' + read_2 . should_equal clothes + out.delete_if_exists + + Test.specify 'should overwrite a sheet if it already exists and write mode is Overwrite' <| + out.delete_if_exists + table.write_xlsx out sheet='Foo' + clothes.write_xlsx out sheet='Foo' write_mode=Table.Spreadsheet_Write_Mode.Overwrite + read = out.read_xlsx sheet='Foo' + read . should_equal clothes + out.delete_if_exists + + Test.specify 'should append to a sheet if it already exists and write mode is Append' <| + out.delete_if_exists + clothes.write_xlsx out sheet='Foo' + clothes.write_xlsx out sheet='Foo' write_mode=Table.Spreadsheet_Write_Mode.Append include_header=False + read = out.read_xlsx sheet='Foo' + read . should_equal (clothes.concat clothes) + out.delete_if_exists + + Test.specify 'should write multiple files if row limit is specified' <| + out_1 = Enso_Project.data / 'out_1.xlsx' + out_2 = Enso_Project.data / 'out_2.xlsx' + out_1.delete_if_exists + out_2.delete_if_exists + clothes.write_xlsx out max_rows_per_file=4 + out_1.read_xlsx . should_equal (clothes.take_start 4) + out_2.read_xlsx . should_equal (clothes.take_end 2) + out_1.delete_if_exists + out_2.delete_if_exists +