forked from sheetjs/docs.sheetjs.com
		
	MuJS C demo
This commit is contained in:
		
							parent
							
								
									b699cfaf9a
								
							
						
					
					
						commit
						01481c65cf
					
				| @ -37,7 +37,7 @@ | ||||
|   </Style> | ||||
|  </Styles> | ||||
|  <Worksheet ss:Name="Engines"> | ||||
|   <Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="15" x:FullColumns="1" | ||||
|   <Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="16" x:FullColumns="1" | ||||
|    x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16"> | ||||
|    <Column ss:Index="3" ss:Width="24"/> | ||||
|    <Column ss:Width="31"/> | ||||
| @ -191,6 +191,16 @@ | ||||
|     <Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell> | ||||
|     <Cell ss:StyleID="s16"/> | ||||
|    </Row> | ||||
|    <Row> | ||||
|     <Cell><Data ss:Type="String">MuJS</Data></Cell> | ||||
|     <Cell><Data ss:Type="String">C</Data></Cell> | ||||
|     <Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell> | ||||
|     <Cell ss:StyleID="s16"/> | ||||
|     <Cell ss:StyleID="s16"/> | ||||
|     <Cell ss:StyleID="s16"/> | ||||
|     <Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell> | ||||
|     <Cell ss:StyleID="s16"/> | ||||
|    </Row> | ||||
|   </Table> | ||||
|   <WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"> | ||||
|    <PageSetup> | ||||
|  | ||||
| @ -1145,13 +1145,13 @@ The highlighted lines should be added to the iOS project `Info.plist` just | ||||
| before the last `</dict>` tag: | ||||
| 
 | ||||
| ```xml title="ios/SheetJSPres/Info.plist" | ||||
| 	<key>UIViewControllerBasedStatusBarAppearance</key> | ||||
| 	<false/> | ||||
|   <key>UIViewControllerBasedStatusBarAppearance</key> | ||||
|   <false/> | ||||
| <!-- highlight-start --> | ||||
| 	<key>UIFileSharingEnabled</key> | ||||
| 	<true/> | ||||
| 	<key>LSSupportsOpeningDocumentsInPlace</key> | ||||
| 	<true/> | ||||
|   <key>UIFileSharingEnabled</key> | ||||
|   <true/> | ||||
|   <key>LSSupportsOpeningDocumentsInPlace</key> | ||||
|   <true/> | ||||
| <!-- highlight-end --> | ||||
| </dict> | ||||
| </plist> | ||||
|  | ||||
| @ -37,19 +37,19 @@ string and return an object that represents `document`. An API method such as | ||||
| 
 | ||||
| ```mermaid | ||||
| flowchart LR | ||||
| 	subgraph Synthetic DOM Operations | ||||
| 		html(HTML\nstring) | ||||
|   subgraph Synthetic DOM Operations | ||||
|     html(HTML\nstring) | ||||
|     doc{{`document`\nDOM Object}} | ||||
| 	end | ||||
| 	subgraph SheetJS Operations | ||||
| 		table{{DOM\nTable}} | ||||
| 	  wb(((SheetJS\nWorkbook))) | ||||
|   	file(workbook\nfile) | ||||
| 	end | ||||
|   end | ||||
|   subgraph SheetJS Operations | ||||
|     table{{DOM\nTable}} | ||||
|     wb(((SheetJS\nWorkbook))) | ||||
|     file(workbook\nfile) | ||||
|   end | ||||
|   html --> |Library\n\n| doc | ||||
|   doc --> |DOM\nAPI| table | ||||
|   table --> |`table_to_book`\n\n| wb | ||||
| 	wb --> |`writeFile`\n\n| file | ||||
|   wb --> |`writeFile`\n\n| file | ||||
| ``` | ||||
| 
 | ||||
| SheetJS methods use features that may be missing from some DOM implementations. | ||||
| @ -196,9 +196,9 @@ tested version (`0.8.10`), the following patches were needed: | ||||
| 
 | ||||
| ```js | ||||
| Object.defineProperty(tbl.__proto__, "innerHTML", { get: function() { | ||||
| 	var outerHTML = new XMLSerializer().serializeToString(this); | ||||
| 	if(outerHTML.match(/</g).length == 1) return ""; | ||||
| 	return outerHTML.slice(0, outerHTML.lastIndexOf("</")).replace(/<[^"'>]*(("[^"]*"|'[^']*')[^"'>]*)*>/, ""); | ||||
|   var outerHTML = new XMLSerializer().serializeToString(this); | ||||
|   if(outerHTML.match(/</g).length == 1) return ""; | ||||
|   return outerHTML.slice(0, outerHTML.lastIndexOf("</")).replace(/<[^"'>]*(("[^"]*"|'[^']*')[^"'>]*)*>/, ""); | ||||
| }}); | ||||
| ``` | ||||
| 
 | ||||
|  | ||||
| @ -241,48 +241,48 @@ document.addEventListener('DOMContentLoaded', () => { | ||||
| 
 | ||||
| ```ts title="src/background/index.ts" | ||||
| chrome.runtime.onInstalled.addListener(function() { | ||||
| 	chrome.contextMenus.create({ | ||||
| 		type: "normal", | ||||
| 		id: "sjsexport", | ||||
| 		title: "Export Table to XLSX", | ||||
| 		contexts: ["page", "selection"] | ||||
| 	}); | ||||
| 	chrome.contextMenus.create({ | ||||
| 		type: "normal", | ||||
| 		id: "sj5export", | ||||
| 		title: "Export All Tables in Page", | ||||
| 		contexts: ["page", "selection"] | ||||
| 	}); | ||||
| 	chrome.contextMenus.onClicked.addListener(function(info/*, tab*/) { | ||||
| 		var mode = ""; | ||||
| 		switch(info.menuItemId) { | ||||
| 			case 'sjsexport': mode = "JS"; break; | ||||
| 			case 'sj5export': mode = "J5"; break; | ||||
| 			default: return; | ||||
| 		} | ||||
| 		chrome.tabs.query({active: true, currentWindow: true}, function(tabs){ | ||||
| 			chrome.tabs.sendMessage(tabs[0].id, {Sheet:mode}, sjsexport_cb); | ||||
| 		}); | ||||
| 	}); | ||||
|   chrome.contextMenus.create({ | ||||
|     type: "normal", | ||||
|     id: "sjsexport", | ||||
|     title: "Export Table to XLSX", | ||||
|     contexts: ["page", "selection"] | ||||
|   }); | ||||
|   chrome.contextMenus.create({ | ||||
|     type: "normal", | ||||
|     id: "sj5export", | ||||
|     title: "Export All Tables in Page", | ||||
|     contexts: ["page", "selection"] | ||||
|   }); | ||||
|   chrome.contextMenus.onClicked.addListener(function(info/*, tab*/) { | ||||
|     var mode = ""; | ||||
|     switch(info.menuItemId) { | ||||
|       case 'sjsexport': mode = "JS"; break; | ||||
|       case 'sj5export': mode = "J5"; break; | ||||
|       default: return; | ||||
|     } | ||||
|     chrome.tabs.query({active: true, currentWindow: true}, function(tabs){ | ||||
|       chrome.tabs.sendMessage(tabs[0].id, {Sheet:mode}, sjsexport_cb); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
| 	chrome.contextMenus.create({ | ||||
| 		id: "sjsabout", | ||||
| 		title: "About", | ||||
| 		contexts: ["browser_action"] | ||||
| 	}); | ||||
| 	chrome.contextMenus.onClicked.addListener(function(info/*, tab*/) { | ||||
| 		if(info.menuItemId !== "sjsabout") return; | ||||
| 		chrome.tabs.create({url: "https://sheetjs.com/"}); | ||||
| 	}); | ||||
|   chrome.contextMenus.create({ | ||||
|     id: "sjsabout", | ||||
|     title: "About", | ||||
|     contexts: ["browser_action"] | ||||
|   }); | ||||
|   chrome.contextMenus.onClicked.addListener(function(info/*, tab*/) { | ||||
|     if(info.menuItemId !== "sjsabout") return; | ||||
|     chrome.tabs.create({url: "https://sheetjs.com/"}); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| function sjsexport_cb(wb) { | ||||
| 	if(!wb || !wb.SheetNames || !wb.Sheets) { return alert("Error in exporting table"); } | ||||
| 	const b64 = XLSX.write(wb, {bookType: "xlsx", type: "base64"}); | ||||
| 	chrome.downloads.download({ | ||||
| 		url: `data:application/octet-stream;base64,${b64}`, | ||||
| 		filename: `SheetJSTables.xlsx` | ||||
| 	}) | ||||
|   if(!wb || !wb.SheetNames || !wb.Sheets) { return alert("Error in exporting table"); } | ||||
|   const b64 = XLSX.write(wb, {bookType: "xlsx", type: "base64"}); | ||||
|   chrome.downloads.download({ | ||||
|     url: `data:application/octet-stream;base64,${b64}`, | ||||
|     filename: `SheetJSTables.xlsx` | ||||
|   }) | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| @ -292,27 +292,27 @@ function sjsexport_cb(wb) { | ||||
| import { utils } from 'xlsx'; | ||||
| var coords = [0,0]; | ||||
| document.addEventListener('mousedown', function(mouse) { | ||||
| 	if(mouse && mouse.button == 2) coords = [mouse.clientX, mouse.clientY]; | ||||
|   if(mouse && mouse.button == 2) coords = [mouse.clientX, mouse.clientY]; | ||||
| }); | ||||
| 
 | ||||
| chrome.runtime.onMessage.addListener(function(msg, sender, cb) { | ||||
| 	if(!msg || !msg['Sheet']) return; | ||||
| 	if(msg.Sheet == "JS") { | ||||
| 		var elt = document.elementFromPoint(coords[0], coords[1]); | ||||
| 		while(elt != null) { | ||||
| 			if(elt.tagName.toLowerCase() == "table") return cb(utils.table_to_book(elt)); | ||||
| 			elt = elt.parentElement; | ||||
| 		} | ||||
| 	} else if(msg.Sheet == "J5") { | ||||
| 		var tables = document.getElementsByTagName("table"); | ||||
| 		var wb = utils.book_new(); | ||||
| 		for(var i = 0; i < tables.length; ++i) { | ||||
| 			var ws = utils.table_to_sheet(tables[i]); | ||||
| 			utils.book_append_sheet(wb, ws, "Table" + i); | ||||
| 		} | ||||
| 		return cb(wb); | ||||
| 	} | ||||
| 	cb(coords); | ||||
|   if(!msg || !msg['Sheet']) return; | ||||
|   if(msg.Sheet == "JS") { | ||||
|     var elt = document.elementFromPoint(coords[0], coords[1]); | ||||
|     while(elt != null) { | ||||
|       if(elt.tagName.toLowerCase() == "table") return cb(utils.table_to_book(elt)); | ||||
|       elt = elt.parentElement; | ||||
|     } | ||||
|   } else if(msg.Sheet == "J5") { | ||||
|     var tables = document.getElementsByTagName("table"); | ||||
|     var wb = utils.book_new(); | ||||
|     for(var i = 0; i < tables.length; ++i) { | ||||
|       var ws = utils.table_to_sheet(tables[i]); | ||||
|       utils.book_append_sheet(wb, ws, "Table" + i); | ||||
|     } | ||||
|     return cb(wb); | ||||
|   } | ||||
|   cb(coords); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
|  | ||||
| @ -359,7 +359,7 @@ For example, `duk_create_heap_default` is defined as follows: | ||||
| 
 | ||||
| ```c | ||||
| #define duk_create_heap_default() \ | ||||
| 	duk_create_heap(NULL, NULL, NULL, NULL, NULL) | ||||
|   duk_create_heap(NULL, NULL, NULL, NULL, NULL) | ||||
| ``` | ||||
| 
 | ||||
| The `duk_create_heap_default` blingo will not be defined in the shared library. | ||||
| @ -714,7 +714,7 @@ The Zig translator does not properly handle blingo `void` casts. For example, | ||||
| 
 | ||||
| ```c title="duk_eval_string_noresult blingo" | ||||
| #define duk_eval_string_noresult(ctx,src)  \ | ||||
| 	((void) duk_eval_raw((ctx), (src), 0, 0 /*args*/ | DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN | DUK_COMPILE_NORESULT | DUK_COMPILE_NOFILENAME)) | ||||
|   ((void) duk_eval_raw((ctx), (src), 0, 0 /*args*/ | DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN | DUK_COMPILE_NORESULT | DUK_COMPILE_NOFILENAME)) | ||||
| ``` | ||||
| 
 | ||||
| The compiler will throw an error involving `anyopaque` (C `void`): | ||||
|  | ||||
							
								
								
									
										391
									
								
								docz/docs/03-demos/42-engines/25-mujs.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										391
									
								
								docz/docs/03-demos/42-engines/25-mujs.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,391 @@ | ||||
| --- | ||||
| title: Munging Data in MuJS | ||||
| sidebar_label: C + MuJS | ||||
| 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'; | ||||
| 
 | ||||
| [MuJS](https://mujs.com/) is a C89-compatible embeddable JS engine. | ||||
| 
 | ||||
| [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing | ||||
| data from spreadsheets. | ||||
| 
 | ||||
| This demo uses MuJS and SheetJS to pull data from a spreadsheet and print CSV | ||||
| rows. We'll explore how to load SheetJS in a MuJS context and process | ||||
| spreadsheets from a C program. | ||||
| 
 | ||||
| The ["Integration Example"](#integration-example) section includes a complete | ||||
| command-line tool for reading data from files. | ||||
| 
 | ||||
| :::danger pass | ||||
| 
 | ||||
| The MuJS engine has a number of bugs that affect parsing in XLSX, XLML and other | ||||
| XML and plaintext file formats. If software does not need to support legacy | ||||
| systems or architecture, it is strongly recommended to use a modern engine such | ||||
| as [Duktape](/docs/demos/engines/duktape). | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Integration Details | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| Many MuJS functions are not documented. The explanation was verified against | ||||
| version `1.3.4`. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### Initialize MuJS | ||||
| 
 | ||||
| A MuJS engine instance is created with `js_newstate`: | ||||
| 
 | ||||
| ```c | ||||
| js_State *J = js_newstate(NULL, NULL, 0); | ||||
| ``` | ||||
| 
 | ||||
| #### Error Messages | ||||
| 
 | ||||
| A special `report` callback should be used to display error messages. This | ||||
| report function is used in official examples: | ||||
| 
 | ||||
| ```c | ||||
| static void report(js_State *J, const char *msg) { fprintf(stderr, "REPORT MSG: %s\n", msg); } | ||||
| ``` | ||||
| 
 | ||||
| The `js_setreport` function attaches the reporter to the engine: | ||||
| 
 | ||||
| ```c | ||||
| js_setreport(J, report); | ||||
| ``` | ||||
| 
 | ||||
| #### Global | ||||
| 
 | ||||
| MuJS does not expose a `global` variable. It can be obtained from a reference | ||||
| to `this` in an unbound function. The following snippet will be evaluated: | ||||
| 
 | ||||
| ```js | ||||
| /* create global object */ | ||||
| var global = (function(){ return this; }).call(null); | ||||
| ``` | ||||
| 
 | ||||
| In MuJS, `js_dostring` evaluates code stored in C strings: | ||||
| 
 | ||||
| ```c | ||||
| /* create `global` variable */ | ||||
| js_dostring(J, "var global = (function() { return this; })(null);"); | ||||
| ``` | ||||
| 
 | ||||
| #### Console | ||||
| 
 | ||||
| MuJS has no built-in method to print data. The official examples define the | ||||
| following `print` method: | ||||
| 
 | ||||
| ```c | ||||
| static void jsB_print(js_State *J) { | ||||
|   int i = 1, top = js_gettop(J); | ||||
|   for (; i < top; ++i) { | ||||
|     const char *s = js_tostring(J, i); | ||||
|     if (i > 1) putchar(' '); | ||||
|     /* note: the official example uses `fputs`, but `puts` makes more sense */ | ||||
|     puts(s); | ||||
|   } | ||||
|   putchar('\n'); | ||||
|   js_pushundefined(J); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| This function can be exposed in the JS engine by using `js_newcfunction` to add | ||||
| the function to the engine and `js_setglobal` to bind to a name: | ||||
| 
 | ||||
| ```c | ||||
| js_newcfunction(J, jsB_print, "print", 0); | ||||
| js_setglobal(J, "print"); | ||||
| ``` | ||||
| 
 | ||||
| After adding `print` to the engine, the following JS snippet will create a | ||||
| `console` object with a `log` method: | ||||
| 
 | ||||
| ```js | ||||
| /* create a fake `console` from the hermes `print` builtin */ | ||||
| var console = { log: function(x) { print(x); } }; | ||||
| ``` | ||||
| 
 | ||||
| In MuJS, `js_dostring` evaluates code stored in C strings: | ||||
| 
 | ||||
| ```C | ||||
| js_dostring(J, "var console = { log: print };"); | ||||
| ``` | ||||
| 
 | ||||
| ### Load SheetJS Scripts | ||||
| 
 | ||||
| [SheetJS Standalone scripts](/docs/getting-started/installation/standalone) can | ||||
| be parsed and evaluated in a C context. | ||||
| 
 | ||||
| The shim and main library can be loaded by with the MuJS `js_dofile` method. It | ||||
| reads scripts from the filesystem and evaluates in the MuJS context: | ||||
| 
 | ||||
| ```c | ||||
| /* load scripts */ | ||||
| js_dofile(J, "shim.min.js"); | ||||
| js_dofile(J, "xlsx.full.min.js"); | ||||
| ``` | ||||
| 
 | ||||
| ### Reading Files | ||||
| 
 | ||||
| MuJS does not expose a method to pass raw byte arrays into the engine. Instead, | ||||
| the raw data should be encoded in Base64. | ||||
| 
 | ||||
| #### Reading File Bytes | ||||
| 
 | ||||
| File bytes can be read using standard C library methods. The example defines a | ||||
| method `read_file` with the following signature: | ||||
| 
 | ||||
| ```c | ||||
| /* Read data from filesystem | ||||
|  * `filename` - path to filename | ||||
|  * `sz` - pointer to size_t | ||||
|  * return value is a pointer to the start of the file data | ||||
|  * the length of the data will be written to `sz` | ||||
|  */ | ||||
| char *read_file(const char *filename, size_t *sz); | ||||
| ``` | ||||
| 
 | ||||
| <details> | ||||
|   <summary><b>File Reader Implementation</b> (click to show)</summary> | ||||
| 
 | ||||
| This function uses standard C API methods. | ||||
| 
 | ||||
| ```c | ||||
| /* -------------------- */ | ||||
| /* read file from filesystem */ | ||||
| 
 | ||||
| static char *read_file(const char *filename, size_t *sz) { | ||||
|   FILE *f = fopen(filename, "rb"); | ||||
|   if(!f) return NULL; | ||||
|   long fsize; { fseek(f, 0, SEEK_END); fsize = ftell(f); fseek(f, 0, SEEK_SET); } | ||||
|   char *buf = (char *)malloc(fsize * sizeof(char)); | ||||
|   *sz = fread((void *) buf, 1, fsize, f); | ||||
|   fclose(f); | ||||
|   return buf; | ||||
| } | ||||
| 
 | ||||
| /* -------------------- */ | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| The example program will accept an argument and read the specified file: | ||||
| 
 | ||||
| ```c | ||||
| /* read file */ | ||||
| size_t dlen; char *dbuf = read_file(argv[1], &dlen); | ||||
| ``` | ||||
| 
 | ||||
| #### Base64 String | ||||
| 
 | ||||
| The example defines a method `Base64_encode` with the following signature: | ||||
| 
 | ||||
| ```c | ||||
| /* Encode data with Base64 | ||||
|  * `dst` - start of output buffer | ||||
|  * `src` - start of input data | ||||
|  * `len` - number of bytes to encode | ||||
|  * return value is the number of bytes | ||||
|  */ | ||||
| int Base64_encode(char *dst, const char *src, int len); | ||||
| ``` | ||||
| 
 | ||||
| <details> | ||||
|   <summary><b>Base64 Encoder Implementation</b> (click to show)</summary> | ||||
| 
 | ||||
| The method mirrors [the TypeScript implementation](https://git.sheetjs.com/sheetjs/sheetjs/src/branch/master/modules/04_base64.ts): | ||||
| 
 | ||||
| ```c | ||||
| /* -------------------- */ | ||||
| /* base64 encoder */ | ||||
| 
 | ||||
| const char Base64_map[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||||
| 
 | ||||
| static int Base64_encode(char *dst, const char *src, int len) { | ||||
| 	unsigned char c1 = 0, c2 = 0, c3 = 0; | ||||
| 	char *p = dst; | ||||
| 	size_t i = 0; | ||||
| 
 | ||||
| 	for(; i < len;) { | ||||
| 		c1 = src[i++]; | ||||
| 		*p++ = Base64_map[(c1 >> 2)]; | ||||
| 
 | ||||
| 		c2 = src[i++]; | ||||
| 		*p++ = Base64_map[((c1 & 3) << 4) | (c2 >> 4)]; | ||||
| 
 | ||||
| 		c3 = src[i++]; | ||||
| 		*p++ = Base64_map[((c2 & 15) << 2) | (c3 >> 6)]; | ||||
| 		*p++ = Base64_map[c3 & 0x3F]; | ||||
| 	} | ||||
| 
 | ||||
| 	if(i < len) { | ||||
| 		c1 = src[i++]; | ||||
| 		*p++ = Base64_map[(c1 >> 2)]; | ||||
| 		if(i == len) { | ||||
| 			*p++ = Base64_map[(c1 & 3) << 4]; | ||||
| 			*p++ = '='; | ||||
| 		} else { | ||||
| 			c2 = src[i++]; | ||||
| 			*p++ = Base64_map[((c1 & 3) << 4) | (c2 >> 4)]; | ||||
| 			*p++ = Base64_map[(c2 & 15) << 2]; | ||||
| 		} | ||||
| 		*p++ = '='; | ||||
| 	} | ||||
| 
 | ||||
| 	*p++ = '\0'; | ||||
| 	return p - dst; | ||||
| } | ||||
| 
 | ||||
| /* -------------------- */ | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| Typically C code will read files and encode to Base64 strings. The intermediate | ||||
| string length is approximately 33% larger than the original length (3 raw bytes | ||||
| are mapped to 4 Base64 characters). | ||||
| 
 | ||||
| ```c | ||||
| /* base64 encode the file */ | ||||
| int sz = ((dlen + 2) / 3) * 4 + 1; | ||||
| char *b64 = malloc(sz+1); | ||||
| sz = Base64_encode(b64, dbuf, dlen); | ||||
| ``` | ||||
| 
 | ||||
| #### Passing Strings | ||||
| 
 | ||||
| The Base64 string can be added to the engine using `js_pushlstring`. After | ||||
| adding to the engine, `js_setglobal` can bind the variable to the name `buf`: | ||||
| 
 | ||||
| ```c | ||||
| /* create `buf` global from the data */ | ||||
| js_pushlstring(J, b64, sz); | ||||
| js_setglobal(J, "buf"); | ||||
| ``` | ||||
| 
 | ||||
| #### SheetJS Operations | ||||
| 
 | ||||
| In this example, the goal is to pull the first worksheet and generate CSV rows. | ||||
| 
 | ||||
| `XLSX.read`[^1] parses the Base64 string and returns a SheetJS workbook object: | ||||
| 
 | ||||
| ```js | ||||
| /* parse file */ | ||||
| js_dostring(J, "var wb = XLSX.read(buf, {type: 'base64'});"); | ||||
| ``` | ||||
| 
 | ||||
| The `SheetNames` property[^2] is an array of the sheet names in the workbook. | ||||
| The first sheet name can be obtained with the following JS snippet: | ||||
| 
 | ||||
| ```js | ||||
| var first_sheet_name = wb.SheetNames[0]; | ||||
| ``` | ||||
| 
 | ||||
| The `Sheets` property[^3] is an object whose keys are sheet names and whose | ||||
| corresponding values are worksheet objects. | ||||
| 
 | ||||
| ```js | ||||
| var first_sheet = wb.Sheets[first_sheet_name]; | ||||
| ``` | ||||
| 
 | ||||
| The `sheet_to_csv` utility function[^4] generates a CSV string from the sheet: | ||||
| 
 | ||||
| ```js | ||||
| var csv = XLSX.utils.sheet_to_csv(first_sheet); | ||||
| ``` | ||||
| 
 | ||||
| _C integration code_ | ||||
| 
 | ||||
| In this example, the `console.log` method will print the generated CSV: | ||||
| 
 | ||||
| ```c | ||||
| /* print CSV from first worksheet */ | ||||
| js_dostring(J, "var ws = wb.Sheets[wb.SheetNames[0]]"); | ||||
| js_dostring(J, "console.log(XLSX.utils.sheet_to_csv(ws));"); | ||||
| ``` | ||||
| 
 | ||||
| ## Integration Example | ||||
| 
 | ||||
| :::note Tested Deployments | ||||
| 
 | ||||
| This demo was tested in the following deployments: | ||||
| 
 | ||||
| | Architecture | Version | Date       | | ||||
| |:-------------|:--------|:-----------| | ||||
| | `darwin-x64` | `1.3.4` | 2024-04-21 | | ||||
| | `linux-x64`  | `1.3.4` | 2024-04-21 | | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 1) Make a project directory: | ||||
| 
 | ||||
| ```bash | ||||
| mkdir sheetjs-mu | ||||
| cd sheetjs-mu | ||||
| ``` | ||||
| 
 | ||||
| 2) Build the MuJS shared library from source: | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://mujs.com/downloads/mujs-1.3.4.zip | ||||
| unzip mujs-1.3.4.zip | ||||
| cd mujs-1.3.4 | ||||
| make release | ||||
| cd .. | ||||
| ``` | ||||
| 
 | ||||
| 3) Copy the `mujs.h` header file and `libmujs.a` library to the project folder: | ||||
| 
 | ||||
| ```bash | ||||
| cp mujs-1.3.4/build/release/libmujs.a mujs-1.3.4/mujs.h . | ||||
| ``` | ||||
| 
 | ||||
| 4) Download [`SheetJSMu.c`](pathname:///mujs/SheetJSMu.c): | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://docs.sheetjs.com/mujs/SheetJSMu.c | ||||
| ``` | ||||
| 
 | ||||
| 5) Build the application: | ||||
| 
 | ||||
| ```bash | ||||
| gcc -o SheetJSMu SheetJSMu.c -L. -lmujs -lm -lc -std=c89 -Wall | ||||
| ``` | ||||
| 
 | ||||
| 6) Download the SheetJS Standalone script, shim script and test file. Move all | ||||
| three files to the project directory: | ||||
| 
 | ||||
| <ul> | ||||
| <li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/shim.min.js`}>shim.min.js</a></li> | ||||
| <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://sheetjs.com/pres.xlsb">pres.xlsb</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.xlsb`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 7) Run the application: | ||||
| 
 | ||||
| ```bash | ||||
| ./SheetJSMu pres.xlsb | ||||
| ``` | ||||
| 
 | ||||
| If successful, the app will print the contents of the first sheet as CSV rows. | ||||
| 
 | ||||
| [^1]: See [`read` in "Reading Files"](/docs/api/parse-options) | ||||
| [^2]: See ["Workbook Object"](/docs/csf/book) | ||||
| [^3]: See ["Workbook Object"](/docs/csf/book) | ||||
| [^4]: See [`sheet_to_csv` in "Utilities"](/docs/api/utilities/csv#csv-output) | ||||
							
								
								
									
										118
									
								
								docz/static/mujs/SheetJSMu.c
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										118
									
								
								docz/static/mujs/SheetJSMu.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include "mujs.h" | ||||
| 
 | ||||
| /* -------------------- */ | ||||
| /* these helper functions are from the official repl example */ | ||||
| 
 | ||||
| static void jsB_print(js_State *J) { | ||||
| 	int i = 1, top = js_gettop(J); | ||||
| 	for (; i < top; ++i) { | ||||
| 		const char *s = js_tostring(J, i); | ||||
| 		if (i > 1) putchar(' '); | ||||
| 		/* note: the official example uses `fputs`, but `puts` makes more sense */ | ||||
| 		puts(s); | ||||
| 	} | ||||
| 	putchar('\n'); | ||||
| 	js_pushundefined(J); | ||||
| } | ||||
| 
 | ||||
| static void report(js_State *J, const char *msg) { fprintf(stderr, "REPORT MSG: %s\n", msg); } | ||||
| 
 | ||||
| /* -------------------- */ | ||||
| /* base64 encoder */ | ||||
| 
 | ||||
| const char Base64_map[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||||
| 
 | ||||
| static int Base64_encode(char *dst, const char *src, int len) { | ||||
| 	unsigned char c1 = 0, c2 = 0, c3 = 0; | ||||
| 	char *p = dst; | ||||
| 	size_t i = 0; | ||||
| 
 | ||||
| 	for(; i < len;) { | ||||
| 		c1 = src[i++]; | ||||
| 		*p++ = Base64_map[(c1 >> 2)]; | ||||
| 
 | ||||
| 		c2 = src[i++]; | ||||
| 		*p++ = Base64_map[((c1 & 3) << 4) | (c2 >> 4)]; | ||||
| 
 | ||||
| 		c3 = src[i++]; | ||||
| 		*p++ = Base64_map[((c2 & 15) << 2) | (c3 >> 6)]; | ||||
| 		*p++ = Base64_map[c3 & 0x3F]; | ||||
| 	} | ||||
| 
 | ||||
| 	if(i < len) { | ||||
| 		c1 = src[i++]; | ||||
| 		*p++ = Base64_map[(c1 >> 2)]; | ||||
| 		if(i == len) { | ||||
| 			*p++ = Base64_map[(c1 & 3) << 4]; | ||||
| 			*p++ = '='; | ||||
| 		} else { | ||||
| 			c2 = src[i++]; | ||||
| 			*p++ = Base64_map[((c1 & 3) << 4) | (c2 >> 4)]; | ||||
| 			*p++ = Base64_map[(c2 & 15) << 2]; | ||||
| 		} | ||||
| 		*p++ = '='; | ||||
| 	} | ||||
| 
 | ||||
| 	*p++ = '\0'; | ||||
| 	return p - dst; | ||||
| } | ||||
| 
 | ||||
| /* -------------------- */ | ||||
| /* read file from filesystem */ | ||||
| 
 | ||||
| static char *read_file(const char *filename, size_t *sz) { | ||||
|   FILE *f = fopen(filename, "rb"); | ||||
|   if(!f) return NULL; | ||||
|   long fsize; { fseek(f, 0, SEEK_END); fsize = ftell(f); fseek(f, 0, SEEK_SET); } | ||||
|   char *buf = (char *)malloc(fsize * sizeof(char)); | ||||
|   *sz = fread((void *) buf, 1, fsize, f); | ||||
|   fclose(f); | ||||
|   return buf; | ||||
| } | ||||
| 
 | ||||
| /* -------------------- */ | ||||
| 
 | ||||
| int main(int argc, char **argv) { | ||||
| 	/* initialize mujs */ | ||||
| 	js_State *J = js_newstate(NULL, NULL, 0); | ||||
| 	js_setreport(J, report); | ||||
| 
 | ||||
| 	/* create `console.log` */ | ||||
| 	js_newcfunction(J, jsB_print, "print", 0); | ||||
| 	js_setglobal(J, "print"); | ||||
| 	js_dostring(J, "var console = { log: print };"); | ||||
| 
 | ||||
| 	/* create `global` variable */ | ||||
| 	js_dostring(J, "var global = (function() { return this; })(null);"); | ||||
| 
 | ||||
| 	/* load scripts */ | ||||
| 	js_dofile(J, "shim.min.js"); | ||||
| 	js_dofile(J, "xlsx.full.min.js"); | ||||
| 
 | ||||
| 	/* read file */ | ||||
| 	size_t dlen; char *dbuf = read_file(argv[1], &dlen); | ||||
| 
 | ||||
| 	/* base64 encode the file */ | ||||
| 	int sz = ((dlen + 2) / 3) * 4 + 1; | ||||
| 	char *b64 = malloc(sz+1); | ||||
| 	sz = Base64_encode(b64, dbuf, dlen); | ||||
| 
 | ||||
| 	/* create `buf` global from the data */ | ||||
| 	js_pushlstring(J, b64, sz); | ||||
| 	js_setglobal(J, "buf"); | ||||
| 
 | ||||
| 	/* parse file */ | ||||
| 	js_dostring(J, "var wb = XLSX.read(buf, {type: 'base64'});"); | ||||
| 
 | ||||
| 	/* print CSV from first worksheet */ | ||||
| 	js_dostring(J, "var ws = wb.Sheets[wb.SheetNames[0]]"); | ||||
| 	js_dostring(J, "console.log(XLSX.utils.sheet_to_csv(ws));"); | ||||
| 
 | ||||
| 	/* cleanup */ | ||||
| 	free(b64); | ||||
| 	js_freestate(J); | ||||
| 	return 0; | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user