diff --git a/docz/docs/03-demos/31-engines/01_duktape.md b/docz/docs/03-demos/31-engines/01_duktape.md index 7d13dca..54132a5 100644 --- a/docz/docs/03-demos/31-engines/01_duktape.md +++ b/docz/docs/03-demos/31-engines/01_duktape.md @@ -164,7 +164,7 @@ gcc -std=c99 -Wall -osheetjs.duk sheetjs.duk.c duktape.c -lm If the program succeeded, the CSV contents will be printed to console and the file `sheetjsw.xlsb` will be created. That file can be opened with Excel. -#### Flow Diagram +### Flow Diagram ```mermaid sequenceDiagram @@ -229,4 +229,55 @@ sequenceDiagram C->>F: Write File end deactivate C +``` + +## Bindings + +Duktape is easily embeddable. Bindings exist for many languages. + +### Perl + +The Perl binding for Duktape is available on CPAN: + +```bash +cpan install JavaScript::Duktape +``` + +The Perl binding does not have raw `Buffer` ops, so Base64 strings are used. +With the [Extendscript](/docs/getting-started/installation/extendscript) build: + +```perl SheetJSDuk.pl +# usage: perl SheetJSDuk.pl path/to/file +use JavaScript::Duktape; +use File::Slurp; +use MIME::Base64 qw( encode_base64 decode_base64 ); + +# Initialize +my $js = JavaScript::Duktape->new( max_memory => 1024 * 1024 * 1024 ); +$js->eval("var global = (function(){ return this; }).call(null);"); + +# Load the ExtendScript build +my $src = read_file('xlsx.extendscript.js', { binmode => ':raw' }); +$src =~ s/^\xEF\xBB\xBF//; +my $XLSX = $js->eval($src); + +# Print version number +$js->set('log' => sub { print $_[0], "\n"; }); +$js->eval("log('SheetJS library version ' + XLSX.version);"); + +# Parse File +my $raw_data = encode_base64(read_file($ARGV[0], { binmode => ':raw' }), ""); +$js->set("b64", $raw_data); +$js->eval(qq{ + global.wb = XLSX.read(b64, {type: "base64"}); + global.ws = wb.Sheets[wb.SheetNames[0]]; +}); + +# Print first worksheet CSV +my $csv = $js->eval('XLSX.utils.sheet_to_csv(global.ws)'); +print $csv + +# Write XLSB file +my $xlsb = $js->eval("XLSX.write(global.wb, {type:'base64', bookType:'xlsb'})"); +write_file("SheetJSDuk.xlsb", decode_base64($xlsb)); ``` \ No newline at end of file diff --git a/docz/docs/03-demos/31-engines/11_perl.md b/docz/docs/03-demos/31-engines/11_perl.md new file mode 100644 index 0000000..88815ec --- /dev/null +++ b/docz/docs/03-demos/31-engines/11_perl.md @@ -0,0 +1,188 @@ +--- +title: Perl + JE +pagination_prev: demos/cli +pagination_next: demos/clipboard +--- + +:::warning + +In a production application, it is strongly recommended to use a binding for a +C engine like [`JavaScript::Duktape`](/docs/demos/engines/duktape) + +::: + +JE is a pure-Perl JavaScript engine. + +The [Extendscript build](/docs/getting-started/installation/extendscript) can be +parsed and evaluated in a JE context. + + +## Integration Details + +The engine deviates from ES3. Modifying prototypes can fix some behavior: + +```js +/* String#charCodeAt is missing */ +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; +}; + +/* workaround for String split bug */ +Number.prototype.charCodeAt = function(n) { return this + 48; }; + +/* String#match bug with empty results */ +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; +}; +``` + +When loading the ExtendScript build, the BOM must be removed: + +```perl +## Load SheetJS source +my $src = read_file('xlsx.extendscript.js', { binmode => ':raw' }); +$src =~ s/^\xEF\xBB\xBF//; ## remove UTF8 BOM +my $XLSX = $je->eval($src); +``` + +### Reading Files + +Data should be passed as Base64 strings: + +```perl +use File::Slurp; +use MIME::Base64 qw( encode_base64 ); + +## 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); } } +EOF + +## Read file +my $raw_data = encode_base64(read_file($ARGV[0], { binmode => ':raw' }), ""); + +## Call method with data +$return_val = $je->method(sheetjsparse => $raw_data); +``` + +### Writing Files + +Due to bugs in data interchange, it is strongly recommended to use a simple +format like `.fods`: + +```perl +use File::Slurp; + +## Set up conversion method +$je->eval(<<'EOF'); +function sheetjswrite(wb) { try { + return XLSX.write(wb, { WTF:1, bookType: "fods", type: "string" }); +} catch(e) { return String(e); } } +EOF + +## Generate file +my $fods = $je->method(sheetjswrite => $workbook); + +## Write to filesystem +write_file("SheetJE.fods", $fods); +``` + +## Complete Example + +:::note + +This demo was tested on 2023 February 12 against JE 0.066 + +::: + +1) Install `JE` through CPAN: + +```bash +cpan install JE +``` + +2) Download the [ExtendScript build](/docs/getting-started/installation/extendscript): + +```bash +curl -LO https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.extendscript.js +``` + +3) Save the following to `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); +``` + +4) Download a test file and run: + +```bash +curl -LO https://sheetjs.com/data/cd.xls +perl SheetJE.pl cd.xls +``` + +After a short wait, the contents will be displayed in CSV form. It will also +write a file `SheetJE.fods` that can be opened in LibreOffice. \ No newline at end of file diff --git a/docz/docs/03-demos/31-engines/index.md b/docz/docs/03-demos/31-engines/index.md index 34a8b8e..a0a1268 100644 --- a/docz/docs/03-demos/31-engines/index.md +++ b/docz/docs/03-demos/31-engines/index.md @@ -63,7 +63,7 @@ Duktape is an embeddable JS engine written in C. It has been ported to a number of exotic architectures and operating systems. This demo has been moved [to a dedicated page](/docs/demos/engines/duktape). - +The demo includes examples in C and Perl. ### Goja diff --git a/docz/docusaurus.config.js b/docz/docusaurus.config.js index c8749aa..805868a 100644 --- a/docz/docusaurus.config.js +++ b/docz/docusaurus.config.js @@ -142,7 +142,7 @@ const config = { prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, - additionalLanguages: [ "swift", "java", "csharp" ], + additionalLanguages: [ "swift", "java", "csharp", "perl" ], }, liveCodeBlock: { playgroundPosition: 'top'