Skip to content

Commit

Permalink
feat: implement readExcel function with sheetName param
Browse files Browse the repository at this point in the history
  • Loading branch information
jackfiszr committed Dec 28, 2024
1 parent d2ac8c1 commit d113138
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 43 deletions.
2 changes: 2 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"dev": "deno run --watch main.ts"
},
"imports": {
"@std/assert": "jsr:@std/assert@^1.0.10",
"@std/fs": "jsr:@std/fs@^1.0.8",
"@tinkie101/exceljs-wrapper": "jsr:@tinkie101/exceljs-wrapper@^1.0.2",
"polars": "npm:nodejs-polars@^0.17.0"
}
Expand Down
25 changes: 25 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 3 additions & 43 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,7 @@
import pl from "polars";
import type pl from "polars";
import ExcelJS from "@tinkie101/exceljs-wrapper";

type RowData = Record<string, string | number | boolean | null | undefined>;

/**
* Reads an Excel file and returns its content as a Polars DataFrame.
* This function loads the first sheet of the workbook and converts it into a DataFrame.
*
* @param filePath - The path to the Excel file to be read.
* @returns A Promise resolving to a Polars DataFrame containing the data from the first sheet.
*/
async function readExcel(filePath: string): Promise<pl.DataFrame> {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(filePath);

const worksheet = workbook.worksheets[0];
if (!worksheet) {
throw new Error("No sheets found in the Excel file.");
}

const jsonData: RowData[] = [];
const headers: string[] = [];

worksheet.eachRow((row, rowNumber) => {
const rowData: RowData = {};

row.eachCell((cell, colNumber) => {
const cellValue = cell.value as string | number | boolean | null;

if (rowNumber === 1) {
headers[colNumber - 1] = cellValue?.toString() || `Column${colNumber}`;
} else {
rowData[headers[colNumber - 1]] = cellValue;
}
});

if (rowNumber > 1) {
jsonData.push(rowData);
}
});

return pl.DataFrame(jsonData);
}
import { readExcel } from "./read_excel.ts";
import type { RowData } from "./types.ts";

/**
* Writes a Polars DataFrame to an Excel file.
Expand Down
44 changes: 44 additions & 0 deletions read_excel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import pl from "polars";
import ExcelJS from "@tinkie101/exceljs-wrapper";
import type { RowData } from "./types.ts";

export async function readExcel(
filePath: string,
sheetName?: string,
): Promise<pl.DataFrame> {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(filePath);

const worksheet = sheetName
? workbook.getWorksheet(sheetName)
: workbook.worksheets[0];

if (!worksheet) {
throw new Error(
`Worksheet ${sheetName || "Sheet1"} not found in the Excel file.`,
);
}

const jsonData: RowData[] = [];
const headers: string[] = [];

worksheet.eachRow((row, rowNumber) => {
const rowData: RowData = {};

row.eachCell((cell, colNumber) => {
const cellValue = cell.value as string | number | boolean | null;

if (rowNumber === 1) {
headers[colNumber - 1] = cellValue?.toString() || `Column${colNumber}`;
} else {
rowData[headers[colNumber - 1]] = cellValue;
}
});

if (rowNumber > 1) {
jsonData.push(rowData);
}
});

return pl.DataFrame(jsonData);
}
66 changes: 66 additions & 0 deletions read_excel_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { assertEquals, assertRejects } from "@std/assert";
import { readExcel } from "./read_excel.ts";
import { createTestExcelFile, removeTestFile } from "./test_utils.ts";
import ExcelJS from "@tinkie101/exceljs-wrapper";

Deno.test({
name: "readExcel - Reads data from a valid Excel file",
async fn() {
const filePath = "./test-read-valid.xlsx";
const testData = {
headers: ["Name", "Age", "Country"],
rows: [
["Alice", 30, "USA"],
["Bob", 25, "Canada"],
],
};
await createTestExcelFile(filePath, testData);

const df = await readExcel(filePath);

const expected = [
{ Name: "Alice", Age: 30, Country: "USA" },
{ Name: "Bob", Age: 25, Country: "Canada" },
];
assertEquals(df.toRecords(), expected);

await removeTestFile(filePath);
},
});

Deno.test({
name: "readExcel - Throws error for missing sheet",
async fn() {
const filePath = "./test-read-missing-sheet.xlsx";
const testData = {
headers: ["Name", "Age"],
rows: [["Alice", 30]],
};
await createTestExcelFile(filePath, testData);

await assertRejects(
async () => {
await readExcel(filePath, "NonExistentSheet");
},
Error,
"Worksheet NonExistentSheet not found in the Excel file.",
);
await removeTestFile(filePath);
},
});

Deno.test({
name: "readExcel - Handles empty Excel file",
async fn() {
const filePath = "./test-empty.xlsx";
const workbook = new ExcelJS.Workbook();
workbook.addWorksheet("Sheet1");
await workbook.xlsx.writeFile(filePath);

const df = await readExcel(filePath);

assertEquals(df.shape, { height: 0, width: 0 });

await removeTestFile(filePath);
},
});
21 changes: 21 additions & 0 deletions test_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ExcelJS from "jsr:@tinkie101/exceljs-wrapper@^1.0.2";
import { exists } from "@std/fs";

export async function createTestExcelFile(
filePath: string,
data: { headers: string[]; rows: (string | number | boolean)[][] },
): Promise<void> {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Sheet1");

worksheet.addRow(data.headers);
data.rows.forEach((row) => worksheet.addRow(row));

await workbook.xlsx.writeFile(filePath);
}

export async function removeTestFile(filePath: string): Promise<void> {
if (await exists(filePath)) {
await Deno.remove(filePath);
}
}
4 changes: 4 additions & 0 deletions types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type RowData = Record<
string,
string | number | boolean | null | undefined
>;

0 comments on commit d113138

Please sign in to comment.