forked from sheetjs/docs.sheetjs.com
		
	
		
			
	
	
		
			188 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			188 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|  | --- | ||
|  | 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. |