Skip to main content

Sheets in .NET with Jint

Jint1 is a JavaScript interpreter for .NET Standard and .NET Core. It has built-in support for binary data with .NET byte[] and ES6 Uint8Array.

SheetJS is a JavaScript library for reading and writing data from spreadsheets.

This demo uses Jint and SheetJS to read and write spreadsheets. We'll explore how to load SheetJS in the Jint engine, exchange binary data with a C# program, and process spreadsheets and structured data.

The "Integration Example" section includes a complete command-line tool for reading arbitrary workbooks and writing data to XLSB (Excel 2007+ Binary Format) workbooks.

Telemetry

The dotnet command embeds telemetry.

The DOTNET_CLI_TELEMETRY_OPTOUT environment variable should be set to 1.

"Platform Configuration" includes instructions for setting the environment variable on supported platforms.

Integration Details

Most of the integration functions are not documented. This explanation is based on version 3.1.0.

The SheetJS Standalone scripts can be parsed and evaluated in a Jint engine instance.

Initialize Jint

A Jint.Engine object can be created in one line:

var engine = new Jint.Engine();

Jint does not expose the NodeJS global but does provide globalThis. Using Jint.Engine#Evaluate, a global global variable can be created:

The JS code is

global = globalThis;
engine.Evaluate("global = globalThis");

Load SheetJS Scripts

The main library can be loaded by reading the scripts from the file system with System.IO.File.ReadAllText and evaluating in the Jint engine instance:

/* read and evaluate the shim script */
string src = System.IO.File.ReadAllText("shim.min.js");
engine.Evaluate(src);
/* read and evaluate the main library */
engine.Evaluate(System.IO.File.ReadAllText("xlsx.full.min.js"));

To confirm the library is loaded, XLSX.version can be inspected:

Console.WriteLine("SheetJS version {0}", engine.Evaluate("XLSX.version"));

The Jint Evaluate method returns a generic Jint.Native.JsValue object.

When the JS expression returns a string, the JsValue object is an instance of Jint.Native.JsString. C# ToString will return the underlying string value.

Reading Files

In C#, System.IO.File.ReadAllBytes reads file data into a byte[] byte array:

string filename = "pres.xlsx";
byte[] buf = File.ReadAllBytes(filename);

Jint natively supports Uint8Array construction from the byte array:

Jint.Native.JsValue u8 = engine.Intrinsics.Uint8Array.Construct(buf);

Jint.Engine#SetValue will assign the Uint8Array to a scope variable in JS:

engine.SetValue("buf", u8);

The buf variable can be parsed from JS with the SheetJS read method2:

The JS code is

var wb = XLSX.read(buf);
engine.Evaluate("var wb = XLSX.read(buf);");

wb is a SheetJS workbook object. The "SheetJS Data Model" section describes the object structure and the "API Reference" section describes various helper functions.

Writing Files

The SheetJS write method3 can write workbooks. The option type: "buffer" instructs the library to generate Uint8Array objects.

The JS code for exporting to the XLSB format is:

var u8 = XLSX.write(wb, {bookType: 'xlsb', type: 'buffer'});

The file format can be changed with the bookType option4

Jint.Native.JsValue xlsb = engine.Evaluate("XLSX.write(wb, {bookType: 'xlsb', type: 'buffer'})");

xlsb represents a Uint8Array. xlsb.AsUint8Array() returns the bytes as a byte[] array which can be exported with System.IO.File.WriteAllBytes:

byte[] outfile = xlsb.AsUint8Array();
System.IO.File.WriteAllBytes("SheetJSJint.xlsb", outfile);

Integration Example

Tested Deployments

This demo was tested in the following deployments:

ArchitectureJint VersionDate
darwin-x643.0.12024-03-15
darwin-arm3.0.0-beta-20562023-12-01
win10-x643.1.02024-04-17
win11-arm3.0.0-beta-20562023-12-01
linux-x643.1.02024-04-25
linux-arm3.0.0-beta-20562023-12-01

Platform Configuration

  1. Set the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to 1.
How to disable telemetry (click to hide)

Add the following line to .profile, .bashrc and .zshrc:

(add to .profile , .bashrc , and .zshrc)
export DOTNET_CLI_TELEMETRY_OPTOUT=1

Close and restart the Terminal to load the changes.

  1. Install .NET
Installation Notes (click to show)

For macOS x64 and ARM64, install the dotnet-sdk Cask with Homebrew:

brew install --cask dotnet-sdk

For Steam Deck Holo and other Arch Linux x64 distributions, the dotnet-sdk and dotnet-runtime packages should be installed using pacman:

sudo pacman -Syu dotnet-sdk dotnet-runtime

https://dotnet.microsoft.com/en-us/download/dotnet/6.0 is the official source for Windows and ARM64 Linux versions.

  1. Open a new Terminal window in macOS or PowerShell window in Windows.

Base Project

  1. Create a new folder SheetJSJint and a new project using the dotnet tool:
mkdir SheetJSJint
cd SheetJSJint
dotnet new console
dotnet run
  1. Add Jint using the NuGet tool:
dotnet nuget add source https://www.myget.org/F/jint/api/v3/index.json
dotnet add package Jint --version 3.1.0

To verify Jint is installed, replace Program.cs with the following:

Program.cs
var engine = new Jint.Engine();
Console.WriteLine("Hello {0}", engine.Evaluate("'Sheet' + 'JS'"));

After saving, run the program:

dotnet run

The terminal should display Hello SheetJS

Add SheetJS

  1. Download the SheetJS Standalone script, shim script and test file. Move all three files to the project directory:
curl -LO https://cdn.sheetjs.com/xlsx-0.20.2/package/dist/shim.min.js
curl -LO https://cdn.sheetjs.com/xlsx-0.20.2/package/dist/xlsx.full.min.js
curl -LO https://docs.sheetjs.com/pres.xlsx
  1. Replace Program.cs with the following:
Program.cs
var engine = new Jint.Engine();
engine.Evaluate("global = globalThis;");
engine.Evaluate(File.ReadAllText("shim.min.js"));
engine.Evaluate(File.ReadAllText("xlsx.full.min.js"));
Console.WriteLine("SheetJS version {0}", engine.Evaluate("XLSX.version"));

After saving, run the program:

dotnet run

The terminal should display SheetJS version 0.20.2

Read and Write Files

  1. Replace Program.cs with the following:
Program.cs
using Jint;

/* Initialize Jint */
var engine = new Jint.Engine();
engine.Evaluate("global = globalThis;");

/* Load SheetJS Scripts */
engine.Evaluate(File.ReadAllText("shim.min.js"));
engine.Evaluate(File.ReadAllText("xlsx.full.min.js"));
Console.WriteLine("SheetJS version {0}", engine.Evaluate("XLSX.version"));

/* Read and Parse File */
byte[] filedata = File.ReadAllBytes(args[0]);
Jint.Native.JsValue u8 = engine.Intrinsics.Uint8Array.Construct(filedata);
engine.SetValue("buf", u8);
engine.Evaluate("var wb = XLSX.read(buf);");

/* Print CSV of first worksheet*/
engine.Evaluate("var ws = wb.Sheets[wb.SheetNames[0]];");
Jint.Native.JsValue csv = engine.Evaluate("XLSX.utils.sheet_to_csv(ws)");
Console.Write(csv);

/* Generate XLSB file and save to SheetJSJint.xlsb */
Jint.Native.JsValue xlsb = engine.Evaluate("XLSX.write(wb, {bookType: 'xlsb', type: 'buffer'})");
File.WriteAllBytes("SheetJSJint.xlsb", xlsb.AsUint8Array());

After saving, run the program and pass the test file name as an argument:

dotnet run pres.xlsx

If successful, the program will print the contents of the first sheet as CSV rows. It will also create SheetJSJint.xlsb which can be opened in Excel or another spreadsheet editor.

Running dotnet run without the filename argument will show an error:

Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Program.<Main>$(String[] args) in C:\Users\Me\SheetJSJint\Program.cs:line 13

The command must be run with an argument specifying the name of the workbook:

dotnet run pres.xlsx

If the using Jint; directive is omitted, the build will fail:

'JsValue' does not contain a definition for 'AsUint8Array' and no accessible extension method 'AsUint8Array' accepting a first argument of type 'JsValue' could be found

Standalone Application

  1. Find the runtime identifier (RID) for your platform5. The RID values for tested platforms are listed below:
PlatformRID
Intel Macosx-x64
ARM64 Macosx-arm64
Windows 10 (x64)win10-x64
Windows 11 (ARM)win-arm64
Linux (x64)linux-x64
Linux (ARM)linux-arm64
  1. Build the standalone application.
Tested platforms (click to hide)

For Intel Mac, the RID is osx-x64 and the command is

dotnet publish -c Release -r osx-x64 --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true
  1. Copy the generated executable to the project directory.

The binary name will be SheetJSJint or SheetJSJint.exe depending on OS.

The last line of the output from Step 9 will print the output folder.

Tested platforms (click to hide)

For Intel Mac, the RID is osx-x64 and the command is:

cp bin/Release/net*/osx-x64/publish/SheetJSJint .
  1. Run the generated command.
./SheetJSJint pres.xlsx

Footnotes

  1. The Jint project recommends the "MyGet" service. According to the developers, the "NuGet" package is "occasionally published".

  2. See read in "Reading Files"

  3. See write in "Writing Files"

  4. See "Supported Output Formats" in "Writing Files" for details on bookType

  5. See ".NET RID Catalog" in the .NET documentation