This commit is contained in:
SheetJS 2023-02-14 20:00:49 -05:00
parent 39b171c011
commit 8517e019e0
6 changed files with 280 additions and 125 deletions

@ -0,0 +1,196 @@
---
title: Java + Rhino
pagination_prev: demos/cli
pagination_next: demos/clipboard
---
Rhino is an ES3+ engine in Java.
The [Standalone scripts](/docs/getting-started/installation/standalone) can be
parsed and evaluated in a Rhino context.
This demo wraps workbooks and sheets into separate Java classes. The final
result is a JAR.
:::caution
Rhino does not support Uint8Array, so certain formats like NUMBERS cannot be
parsed or written from Rhino JS code!
:::
## Integration Details
:::note
Due to code generation errors, optimization must be turned off:
```java
Context context = Context.enter();
context.setOptimizationLevel(-1);
```
:::
Binary strings can be passed back and forth.
_Initialize Rhino_
Rhino does not provide a `global` variable. It can be created:
```java
Context cx = Context.enter();
Scriptable scope = cx.initStandardObjects();
/* Disable optimization */
cx.setOptimizationLevel(-1);
/* Initialize `global` variable */
// highlight-start
String s = "var global = (function(){ return this; }).call(null);";
cx.evaluateString(scope, s, "<cmd>", 1, null);
// highlight-end
```
_Load SheetJS Scripts_
The main library can be loaded by reading the scripts from the Java Archive and
evaluating in the Rhino context:
```java
/* pull source from JAR */
String s = new Scanner(
SheetJS.class.getResourceAsStream("/xlsx.full.min.js")
).useDelimiter("\\Z").next();
/* evaluate */
cx.evaluateString(scope, s, "<cmd>", 1, null);
```
To confirm the library is loaded, `XLSX.version` can be inspected:
```swift
/* get handle to XLSX */
Object XLSX = scope.get("XLSX", scope);
if(XLSX == Scriptable.NOT_FOUND) throw new Exception("XLSX not found");
/* get the version string */
String version = ((NativeObject)XLSX).get("version", scope).toString();
System.out.println("SheetJS version " + version);
```
### Reading Files
A binary string can be generated from a `byte` array:
```java
static String read_file(String file) throws IOException {
/* read file -> array of bytes */
byte[] b = Files.readAllBytes(Paths.get(file));
/* construct binary string */
StringBuilder sb = new StringBuilder();
for(int i = 0; i < b.length; ++i) sb.append(Character.toString((char)(b[i] < 0 ? b[i] + 256 : b[i])));
return sb.toString();
}
```
This string can be loaded into the JS engine and processed:
```java
/* options argument */
String os = "q = {'type':'binary', 'WTF':1};";
NativeObject o = (NativeObject)cx.evaluateString(scope, os, "<cmd>", 2, null);
/* set up function arguments */
String data = read_file(path_to_file);
Object args[] = {data, o};
/* call read -> wb workbook */
NativeObject nXLSX = (NativeObject)XLSX;
Function readfunc = (Function)XLSX.get("read", scope);
NativeObject wb = (NativeObject)readfunc.call(cx, scope, nXLSX, args);
```
`wb` will be a reference to the JS workbook object.
## Complete Example
:::note
This demo was tested on 2023 February 14 using Rhino 1.7.14.
:::
0) Ensure Java is installed. Create a folder for the project, download the
[JAR](https://github.com/mozilla/rhino/releases/download/Rhino1_7_14_Release/rhino-1.7.14.jar)
and rename to `rhino.jar`:
```bash
mkdir sheetjs-java
cd sheetjs-java
curl -L -o rhino.jar https://github.com/mozilla/rhino/releases/download/Rhino1_7_14_Release/rhino-1.7.14.jar
```
1) Download the standalone script and the test file:
<ul>
<li><a href={`https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js`}>xlsx.full.min.js</a></li>
<li><a href="https://sheetjs.com/pres.xlsx">pres.xlsx</a></li>
</ul>
```bash
curl -LO https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js
curl -LO https://sheetjs.com/pres.xlsx
```
2) Download [`SheetJSRhino.zip`](pathname:///rhino/SheetJSRhino.zip) and unzip
```bash
curl -LO https://docs.sheetjs.com/rhino/SheetJSRhino.zip
unzip SheetJSRhino.zip
```
3) Save the following code to `SheetJSRhino.java`:
```java title="SheetJSRhino.java"
/* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */
/* vim: set ts=2: */
import com.sheetjs.SheetJS;
import com.sheetjs.SheetJSFile;
import com.sheetjs.SheetJSSheet;
public class SheetJSRhino {
public static void main(String args[]) throws Exception {
try {
SheetJS sjs = new SheetJS();
/* open file */
SheetJSFile xl = sjs.read_file(args[0]);
/* get sheetnames */
String[] sheetnames = xl.get_sheet_names();
System.err.println(sheetnames[0]);
/* convert to CSV */
SheetJSSheet sheet = xl.get_sheet(0);
String csv = sheet.get_csv();
System.out.println(csv);
} catch(Exception e) { throw e; } finally { SheetJS.close(); }
}
}
```
4) Assemble `SheetJS.jar` from the demo code:
```bash
javac -cp .:rhino.jar SheetJSRhino.java
jar -cf SheetJS.jar SheetJSRhino.class com/sheetjs/*.class xlsx.full.min.js
```
5) Test the program:
```bash
java -cp .:SheetJS.jar:rhino.jar SheetJSRhino pres.xlsx
```
If successful, a CSV will be printed to console.

@ -1,5 +1,5 @@
---
title: Ruby Bindings
title: Ruby + Bindings
pagination_prev: demos/cli
pagination_next: demos/clipboard
---

@ -115,66 +115,10 @@ cpan install JE
curl -LO https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.extendscript.js
```
3) Save the following to `SheetJE.pl`:
3) Download the demo [`SheetJE.pl`](pathname:///perl/SheetJE.pl):
```perl title="SheetJE.pl"
use JE;
use File::Slurp;
use MIME::Base64 qw( encode_base64 );
## Initialize
say STDERR "Initializing Engine";
my $je = new JE;
$je->eval("var global = (function(){ return this; }).call(null);");
$je->eval(<<'EOF');
Number.prototype.charCodeAt = function(n) { return this + 48; };
var string = ""; for(var i = 0; i < 256; ++i) string += String.fromCharCode(i);
String.prototype.charCodeAt = function(n) {
var result = string.indexOf(this.charAt(n));
if(result == -1) throw this.charAt(n);
return result;
};
String.prototype.old_match = String.prototype.match;
String.prototype.match = function(p) {
var result = this.old_match(p);
return (Array.isArray(result) && result.length == 0) ? null : result;
};
EOF
## Load SheetJS source
say STDERR "Loading SheetJS Library";
my $src = read_file('xlsx.extendscript.js', { binmode => ':raw' });
$src =~ s/^\xEF\xBB\xBF//;
my $XLSX = $je->eval($src);
## Set up conversion method
$je->eval(<<'EOF');
function sheetjsparse(data) { try {
return XLSX.read(String(data), {type: "base64", WTF:1});
} catch(e) { return String(e); } }
function sheetjscsv(wb) { try {
var ws = wb.Sheets[wb.SheetNames[0]];
return XLSX.utils.sheet_to_csv(ws);
} catch(e) { return String(e); } }
function sheetjswrite(wb) { try {
return XLSX.write(wb, { WTF:1, bookType: "fods", type: "string" });
} catch(e) { return String(e); } }
EOF
## Read file
say STDERR "Parsing " . $ARGV[0];
my $raw_data = encode_base64(read_file($ARGV[0], { binmode => ':raw' }), "");
$workbook = $je->method(sheetjsparse => $raw_data);
## Convert to CSV
say STDERR "Converting to CSV";
my $csv_data = $je->method(sheetjscsv => $workbook);
print $csv_data;
## Write to SheetJE.fods
say STDERR "Writing to SheetJE.fods";
my $fods = $je->method(sheetjswrite => $workbook);
write_file("SheetJE.fods", $fods);
```bash
curl -LO https://docs.sheetjs.com/perl/SheetJE.pl
```
4) Download a test file and run:

@ -301,72 +301,9 @@ If successful, the script will generate `SheetJSQuick.xlsx`.
### Rhino
Rhino is an ES3+ engine in Java. The `SheetJSRhino` class and `com.sheetjs`
package show a complete JAR deployment, including the full XLSX source.
Rhino is an ES3+ engine in Java.
Due to code generation errors, optimization must be turned off:
```java
Context context = Context.enter();
context.setOptimizationLevel(-1);
```
<details><summary><b>Complete Example</b> (click to show)</summary>
0) Download the appropriate Rhino build and rename to `rhino.jar`
1) Download [`SheetJSRhino.zip`](pathname:///rhino/SheetJSRhino.zip) and unzip
2) Save the following code to `SheetJSRhino.java`:
```java title="SheetJSRhino.java"
/* sheetjs (C) 2013-present SheetJS -- https://sheetjs.com */
/* vim: set ts=2: */
import com.sheetjs.SheetJS;
import com.sheetjs.SheetJSFile;
import com.sheetjs.SheetJSSheet;
public class SheetJSRhino {
public static void main(String args[]) throws Exception {
try {
SheetJS sjs = new SheetJS();
/* open file */
SheetJSFile xl = sjs.read_file(args[0]);
/* get sheetnames */
String[] sheetnames = xl.get_sheet_names();
System.err.println(sheetnames[0]);
/* convert to CSV */
SheetJSSheet sheet = xl.get_sheet(0);
String csv = sheet.get_csv();
System.out.println(csv);
} catch(Exception e) {
throw e;
} finally {
SheetJS.close();
}
}
}
```
3) Assemble `SheetJS.jar` from the demo code:
```bash
javac -cp .:rhino.jar SheetJSRhino.java
jar -cf SheetJS.jar SheetJSRhino.class com/sheetjs/*.class
```
4) Download <https://sheetjs.com/pres.xlsx> and test:
```bash
java -cp .:SheetJS.jar:rhino.jar SheetJSRhino pres.xlsx
```
</details>
This demo has been moved [to a dedicated page](/docs/demos/engines/rhino).
## Legacy Engines

@ -129,6 +129,25 @@ async function fetch_workbook_and_error_on_404(url) {
}
```
#### Cloudflare Worker "Error: Script startup exceeded CPU time limit."
This may show up in projects with many dependencies. The official workaround is
dynamic `import`. For example:
```ts
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// highlight-next-line
const XLSX = await import("xlsx"); // dynamic import
const wb = XLSX.read("abc\n123", {type: "string"});
const buf = XLSX.write(wb, {type: "buffer", bookType: "xlsb"});
const response = new Response(buf);
response.headers.set("Content-Disposition", 'attachment; filename="cf.xlsb"');
return response;
},
};
```
#### SCRIPT5022: DataCloneError
IE10 does not properly support `Transferable`.

@ -0,0 +1,59 @@
#!/usr/bin/env perl
# SheetJE.pl (c) SheetJS LLC -- https://sheetjs.com
use JE;
use File::Slurp;
use MIME::Base64 qw( encode_base64 );
## Initialize
say STDERR "Initializing Engine";
my $je = new JE;
$je->eval("var global = (function(){ return this; }).call(null);");
$je->eval(<<'EOF');
Number.prototype.charCodeAt = function(n) { return this + 48; };
var string = ""; for(var i = 0; i < 256; ++i) string += String.fromCharCode(i);
String.prototype.charCodeAt = function(n) {
var result = string.indexOf(this.charAt(n));
if(result == -1) throw this.charAt(n);
return result;
};
String.prototype.old_match = String.prototype.match;
String.prototype.match = function(p) {
var result = this.old_match(p);
return (Array.isArray(result) && result.length == 0) ? null : result;
};
EOF
## Load SheetJS source
say STDERR "Loading SheetJS Library";
my $src = read_file('xlsx.extendscript.js', { binmode => ':raw' });
$src =~ s/^\xEF\xBB\xBF//;
my $XLSX = $je->eval($src);
## Set up conversion method
$je->eval(<<'EOF');
function sheetjsparse(data) { try {
return XLSX.read(String(data), {type: "base64", WTF:1});
} catch(e) { return String(e); } }
function sheetjscsv(wb) { try {
var ws = wb.Sheets[wb.SheetNames[0]];
return XLSX.utils.sheet_to_csv(ws);
} catch(e) { return String(e); } }
function sheetjswrite(wb) { try {
return XLSX.write(wb, { WTF:1, bookType: "fods", type: "string" });
} catch(e) { return String(e); } }
EOF
## Read file
say STDERR "Parsing " . $ARGV[0];
my $raw_data = encode_base64(read_file($ARGV[0], { binmode => ':raw' }), "");
$workbook = $je->method(sheetjsparse => $raw_data);
## Convert to CSV
say STDERR "Converting to CSV";
my $csv_data = $je->method(sheetjscsv => $workbook);
print $csv_data;
## Write to SheetJE.fods
say STDERR "Writing to SheetJE.fods";
my $fods = $je->method(sheetjswrite => $workbook);
write_file("SheetJE.fods", $fods);