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 |