forked from sheetjs/docs.sheetjs.com
414 lines
11 KiB
Markdown
414 lines
11 KiB
Markdown
|
---
|
||
|
title: Sheets in .NET with Jint
|
||
|
sidebar_label: C# + Jint
|
||
|
description: Process structured data in C#. Seamlessly integrate spreadsheets into your program by pairing Jint and SheetJS. Handle the most complex Excel files without breaking a sweat.
|
||
|
pagination_prev: demos/bigdata/index
|
||
|
pagination_next: solutions/input
|
||
|
---
|
||
|
|
||
|
import current from '/version.js';
|
||
|
import Tabs from '@theme/Tabs';
|
||
|
import TabItem from '@theme/TabItem';
|
||
|
import CodeBlock from '@theme/CodeBlock';
|
||
|
|
||
|
Jint[^1] 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](https://sheetjs.com) 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"](#integration-example) section includes a complete
|
||
|
command-line tool for reading arbitrary workbooks and writing data to XLSB
|
||
|
(Excel 2007+ Binary Format) workbooks.
|
||
|
|
||
|
## Integration Details
|
||
|
|
||
|
:::note pass
|
||
|
|
||
|
Most of the integration functions are not documented. This explanation is based
|
||
|
on version `3.0.0-beta-2051`.
|
||
|
|
||
|
:::
|
||
|
|
||
|
The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone)
|
||
|
can be parsed and evaluated in a Jint engine instance.
|
||
|
|
||
|
### Initialize Jint
|
||
|
|
||
|
A `Jint.Engine` object can be created in one line:
|
||
|
|
||
|
```csharp
|
||
|
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
|
||
|
```js
|
||
|
global = globalThis;
|
||
|
```
|
||
|
|
||
|
```csharp
|
||
|
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:
|
||
|
|
||
|
```csharp
|
||
|
/* 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:
|
||
|
|
||
|
```csharp
|
||
|
Console.WriteLine("SheetJS version {0}", engine.Evaluate("XLSX.version"));
|
||
|
```
|
||
|
|
||
|
:::info pass
|
||
|
|
||
|
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:
|
||
|
|
||
|
```csharp
|
||
|
string filename = "pres.xlsx";
|
||
|
byte[] buf = File.ReadAllBytes(filename);
|
||
|
```
|
||
|
|
||
|
Jint natively supports `Uint8Array` construction from the byte array:
|
||
|
|
||
|
```csharp
|
||
|
Jint.Native.JsValue u8 = engine.Realm.Intrinsics.Uint8Array.Construct(buf);
|
||
|
```
|
||
|
|
||
|
`Jint.Engine#SetValue` will assign the `Uint8Array` to a scope variable in JS:
|
||
|
|
||
|
```csharp
|
||
|
engine.SetValue("buf", u8);
|
||
|
```
|
||
|
|
||
|
The `buf` variable can be parsed from JS with the SheetJS `read` method[^2]:
|
||
|
|
||
|
>The JS code is
|
||
|
```js
|
||
|
var wb = XLSX.read(buf);
|
||
|
```
|
||
|
|
||
|
```csharp
|
||
|
engine.Evaluate("var wb = XLSX.read(buf);");
|
||
|
```
|
||
|
|
||
|
`wb` is a SheetJS workbook object. The ["SheetJS Data Model"](/docs/csf) section
|
||
|
describes the object structure and the ["API Reference"](/docs/api) section
|
||
|
describes various helper functions.
|
||
|
|
||
|
### Writing Files
|
||
|
|
||
|
The SheetJS `write` method[^3] can write workbooks. The option `type: "buffer"`
|
||
|
instructs the library to generate `Uint8Array` objects.
|
||
|
|
||
|
> The JS code for exporting to the XLSB format is:
|
||
|
```js
|
||
|
var u8 = XLSX.write(wb, {bookType: 'xlsb', type: 'buffer'});
|
||
|
```
|
||
|
The file format can be changed with the `bookType` option[^4]
|
||
|
|
||
|
```csharp
|
||
|
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`:
|
||
|
|
||
|
```csharp
|
||
|
byte[] outfile = xlsb.AsUint8Array();
|
||
|
System.IO.File.WriteAllBytes("SheetJSJint.xlsb", outfile);
|
||
|
```
|
||
|
|
||
|
## Integration Example
|
||
|
|
||
|
:::note
|
||
|
|
||
|
This demo was tested in the following deployments:
|
||
|
|
||
|
| Architecture | Jint Version | Date |
|
||
|
|:-------------|:------------------|:-----------|
|
||
|
| `darwin-x64` | `3.0.0-beta-2051` | 2023-09-16 |
|
||
|
| `win10-x64` | `3.0.0-beta-2051` | 2023-09-16 |
|
||
|
|
||
|
:::
|
||
|
|
||
|
0) Install .NET[^5]
|
||
|
|
||
|
### Platform Configuration
|
||
|
|
||
|
1) Set the `DOTNET_CLI_TELEMETRY_OPTOUT` environment variable to `1`.
|
||
|
|
||
|
<details open><summary><b>How to disable telemetry</b> (click to hide)</summary>
|
||
|
|
||
|
<Tabs groupId="os">
|
||
|
<TabItem value="unix" label="Linux/MacOS">
|
||
|
|
||
|
Add the following line to `.profile`, `.bashrc` and `.zshrc`:
|
||
|
|
||
|
```bash
|
||
|
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||
|
```
|
||
|
|
||
|
Close and restart the Terminal to load the changes.
|
||
|
|
||
|
</TabItem>
|
||
|
<TabItem value="win" label="Windows">
|
||
|
|
||
|
Type `env` in the search bar and select "Edit the system environment variables".
|
||
|
|
||
|
In the new window, click the "Environment Variables..." button.
|
||
|
|
||
|
In the new window, look for the "System variables" section and click "New..."
|
||
|
|
||
|
Set the "Variable name" to `DOTNET_CLI_TELEMETRY_OPTOUT` and the value to `1`.
|
||
|
|
||
|
Click "OK" in each window (3 windows) and restart your computer.
|
||
|
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
</details>
|
||
|
|
||
|
2) Open a new Terminal window in macOS or PowerShell window in Windows.
|
||
|
|
||
|
### Base Project
|
||
|
|
||
|
3) Create a new folder `SheetJSJint` and a new project using the `dotnet` tool:
|
||
|
|
||
|
```bash
|
||
|
rm -rf SheetJSJint
|
||
|
mkdir SheetJSJint
|
||
|
cd SheetJSJint
|
||
|
dotnet new console --framework net6.0
|
||
|
dotnet run
|
||
|
```
|
||
|
|
||
|
4) Add Jint using the NuGet tool:
|
||
|
|
||
|
```bash
|
||
|
dotnet nuget add source https://www.myget.org/F/jint/api/v3/index.json
|
||
|
dotnet add package Jint --version 3.0.0-beta-2051
|
||
|
```
|
||
|
|
||
|
To verify Jint is installed, replace `Program.cs` with the following:
|
||
|
|
||
|
```csharp title="Program.cs"
|
||
|
var engine = new Jint.Engine();
|
||
|
Console.WriteLine("Hello {0}", engine.Evaluate("'Sheet' + 'JS'"));
|
||
|
```
|
||
|
|
||
|
After saving, run the program:
|
||
|
|
||
|
```bash
|
||
|
dotnet run
|
||
|
```
|
||
|
|
||
|
The terminal should display `Hello SheetJS`
|
||
|
|
||
|
### Add SheetJS
|
||
|
|
||
|
5) Download the standalone script, shim and test file:
|
||
|
|
||
|
<ul>
|
||
|
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>xlsx.full.min.js</a></li>
|
||
|
<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js`}>shim.min.js</a></li>
|
||
|
<li><a href="https://sheetjs.com/pres.xlsx">pres.xlsx</a></li>
|
||
|
</ul>
|
||
|
|
||
|
<CodeBlock language="bash">{`\
|
||
|
curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js
|
||
|
curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js
|
||
|
curl -LO https://sheetjs.com/pres.xlsx`}
|
||
|
</CodeBlock>
|
||
|
|
||
|
6) Replace `Program.cs` with the following:
|
||
|
|
||
|
```csharp title="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:
|
||
|
|
||
|
```bash
|
||
|
dotnet run
|
||
|
```
|
||
|
|
||
|
<p>The terminal should display <code>SheetJS version {current}</code></p>
|
||
|
|
||
|
### Read and Write Files
|
||
|
|
||
|
7) Replace `Program.cs` with the following:
|
||
|
|
||
|
```csharp title="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.Realm.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:
|
||
|
|
||
|
```bash
|
||
|
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.
|
||
|
|
||
|
:::caution pass
|
||
|
|
||
|
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
|
||
|
|
||
|
8) Find the runtime identifier (RID) for your platform[^6]. The RID values for
|
||
|
tested platforms are listed below:
|
||
|
|
||
|
| Platform | RID |
|
||
|
|:-----------------|:------------|
|
||
|
| Intel Mac | `osx-x64` |
|
||
|
| Windows 10 (x64) | `win10-x64` |
|
||
|
|
||
|
9) Build the standalone application. Replace `$RID` with the real value in:
|
||
|
|
||
|
```bash
|
||
|
dotnet publish -c Release -r $RID --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true
|
||
|
```
|
||
|
|
||
|
<details open><summary><b>Tested platforms</b> (click to hide)</summary>
|
||
|
|
||
|
<Tabs groupId="triple">
|
||
|
<TabItem value="darwin-x64" label="Intel Mac">
|
||
|
|
||
|
For Intel Mac, the RID is `osx-x64` and the command is
|
||
|
|
||
|
```bash
|
||
|
dotnet publish -c Release -r osx-x64 --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true
|
||
|
```
|
||
|
|
||
|
</TabItem>
|
||
|
<TabItem value="win10-x64" label="Windows x64">
|
||
|
|
||
|
For Windows 10 x64, the RID is `win10-x64` and the command is:
|
||
|
|
||
|
```powershell
|
||
|
dotnet publish -c Release -r win10-x64 --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true
|
||
|
```
|
||
|
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
</details>
|
||
|
|
||
|
10) Copy the generated executable to the project directory.
|
||
|
|
||
|
The build from step 9 is placed in `bin\Release\net6.0\$RID\publish` and the
|
||
|
binary name will be `SheetJSJint` or `SheetJSJint.exe` depending on OS.
|
||
|
|
||
|
<details open><summary><b>Tested platforms</b> (click to hide)</summary>
|
||
|
|
||
|
<Tabs groupId="triple">
|
||
|
<TabItem value="darwin-x64" label="Intel Mac">
|
||
|
|
||
|
For Intel Mac, the RID is `osx-x64` and the command is:
|
||
|
|
||
|
```bash
|
||
|
cp bin/Release/net6.0/osx-x64/publish/SheetJSJint .
|
||
|
```
|
||
|
|
||
|
</TabItem>
|
||
|
<TabItem value="win10-x64" label="Windows x64">
|
||
|
|
||
|
For Windows 10 x64, the RID is `win10-x64` and the command is:
|
||
|
|
||
|
```powershell
|
||
|
copy .\bin\Release\net6.0\win10-x64\publish\SheetJSJint.exe .
|
||
|
```
|
||
|
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
</details>
|
||
|
|
||
|
11) Run the generated command.
|
||
|
|
||
|
<Tabs groupId="os">
|
||
|
<TabItem value="unix" label="Linux/MacOS">
|
||
|
|
||
|
```bash
|
||
|
./SheetJSJint pres.xlsx
|
||
|
```
|
||
|
|
||
|
</TabItem>
|
||
|
<TabItem value="win" label="Windows">
|
||
|
|
||
|
```powershell
|
||
|
.\SheetJSJint pres.xlsx
|
||
|
```
|
||
|
|
||
|
</TabItem>
|
||
|
</Tabs>
|
||
|
|
||
|
[^1]: The Jint project recommends the ["MyGet" service](https://www.myget.org/feed/jint/package/nuget/Jint). According to the developers, the ["NuGet" package](https://www.nuget.org/packages/jint) is "occasionally published".
|
||
|
[^2]: See [`read` in "Reading Files"](/docs/api/parse-options)
|
||
|
[^3]: See [`write` in "Writing Files"](/docs/api/write-options)
|
||
|
[^4]: See ["Supported Output Formats" in "Writing Files"](/docs/api/write-options#supported-output-formats) for details on `bookType`
|
||
|
[^5]: At the time of writing, <https://dotnet.microsoft.com/en-us/download> is the official endpoint.
|
||
|
[^6]: See [".NET RID Catalog"](https://learn.microsoft.com/en-us/dotnet/core/rid-catalog) in the .NET documentation
|