forked from sheetjs/docs.sheetjs.com
		
	JSC Swift Linux demo
This commit is contained in:
		
							parent
							
								
									7d749963f4
								
							
						
					
					
						commit
						066741f185
					
				| @ -334,8 +334,8 @@ | ||||
|     <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"/> | ||||
|     <Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell> | ||||
|     <Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell> | ||||
|    </Row> | ||||
|    <Row> | ||||
|     <Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/rb#complete-example"><Data ss:Type="String">ExecJS</Data></Cell> | ||||
|  | ||||
| @ -24,7 +24,7 @@ generate CSV files for the LangChain CSV loader. These conversions can be run in | ||||
| a preprocessing step without disrupting existing CSV workflows. | ||||
| 
 | ||||
| In ["SheetJS Loader"](#sheetjs-loader), we will use SheetJS libraries in a | ||||
| custom loader to directly generate documents and metadata. | ||||
| custom `LoadOfSheet` data loader to directly generate documents and metadata. | ||||
| 
 | ||||
| ["SheetJS Loader Demo"](#sheetjs-loader-demo) is a complete demo that uses the | ||||
| SheetJS Loader to answer questions based on data from a XLS workbook. | ||||
|  | ||||
| @ -312,11 +312,25 @@ FILE *f = fopen("sheetjsw.xlsb", "wb"); fwrite(buf, 1, sz, f); fclose(f); | ||||
| 
 | ||||
| This demo was tested in the following environments: | ||||
| 
 | ||||
| **Built-in** | ||||
| 
 | ||||
| Swift on MacOS supports JavaScriptCore without additional dependencies. | ||||
| 
 | ||||
| | Architecture | Swift   | Date       | | ||||
| |:-------------|:--------|:-----------| | ||||
| | `darwin-x64` | `5.10`  | 2024-04-04 | | ||||
| | `darwin-arm` | `5.9.2` | 2024-02-21 | | ||||
| 
 | ||||
| **Compiled** | ||||
| 
 | ||||
| The ["Swift C"](#swift-c) section starts from the static libraries built in the | ||||
| ["C++"](#c) section and build Swift bindings. | ||||
| 
 | ||||
| | Architecture | Version          | Date       | | ||||
| |:-------------|:-----------------|:-----------| | ||||
| | `linux-x64`  | `7618.2.12.11.7` | 2024-06-22 | | ||||
| | `linux-arm`  | `7618.2.12.11.7` | 2024-06-22 | | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| The demo includes a sample `SheetJSCore` Wrapper class to simplify operations. | ||||
| @ -325,6 +339,8 @@ The demo includes a sample `SheetJSCore` Wrapper class to simplify operations. | ||||
| 
 | ||||
| This example requires MacOS + Swift and will not work on Windows or Linux! | ||||
| 
 | ||||
| The ["Swift C"](#swift-c) section covers integration in other platforms. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 0) Ensure Swift is installed by running the following command in the terminal: | ||||
| @ -391,8 +407,8 @@ This demo was tested in the following environments: | ||||
| |:-------------|:-----------------|:-----------| | ||||
| | `darwin-x64` | `7618.1.15.14.7` | 2024-04-24 | | ||||
| | `darwin-arm` | `7618.2.12.11.7` | 2024-05-24 | | ||||
| | `linux-x64`  | `7618.1.15.14.7` | 2024-04-24 | | ||||
| | `linux-arm`  | `7618.2.12.11.7` | 2024-05-25 | | ||||
| | `linux-x64`  | `7618.2.12.11.7` | 2024-06-22 | | ||||
| | `linux-arm`  | `7618.2.12.11.7` | 2024-06-22 | | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -401,12 +417,20 @@ This demo was tested in the following environments: | ||||
| <details> | ||||
|   <summary><b>Installation Notes</b> (click to show)</summary> | ||||
| 
 | ||||
| On the Steam Deck, a few dependencies must be installed before building JSC: | ||||
| The build requires CMake and Ruby. | ||||
| 
 | ||||
| On the Steam Deck, dependencies should be installed with `pacman`: | ||||
| 
 | ||||
| ```bash | ||||
| sudo pacman -Syu base-devel cmake ruby icu glibc linux-api-headers | ||||
| ``` | ||||
| 
 | ||||
| On Debian and Ubuntu, dependencies should be installed with `apt`: | ||||
| 
 | ||||
| ```bash | ||||
| sudo apt-get install build-essential cmake ruby | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| 1) Create a project folder: | ||||
| @ -523,7 +547,7 @@ When this demo was last tested on ARM64, there was a dangling pointer error: | ||||
| {"      |"}                                                <span {...r}>~~~~~~~^~~~~~</span> | ||||
| </pre> | ||||
| 
 | ||||
| The error can be suppressed with a preprocessor pragma: | ||||
| The error can be suppressed with preprocessor directives around the definition: | ||||
| 
 | ||||
| ```cpp title="WebKitBuild/JSCOnly/Release/WTF/Headers/wtf/SentinelLinkedList.h (add highlighted lines)" | ||||
|     BasicRawSentinelNode() = default; | ||||
| @ -609,6 +633,118 @@ curl -LO https://docs.sheetjs.com/pres.numbers`} | ||||
| If successful, a CSV will be printed to console. The script also tries to write | ||||
| to `sheetjsw.xlsb`, which can be opened in a spreadsheet editor. | ||||
| 
 | ||||
| ### Swift C | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| For macOS and iOS deployments, it is strongly encouraged to use the official | ||||
| `JavaScriptCore` bindings. This demo is suited for Linux Swift applications. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| 0) Install the Swift toolchain.[^8] | ||||
| 
 | ||||
| <details> | ||||
|   <summary><b>Installation Notes</b> (click to show)</summary> | ||||
| 
 | ||||
| The `linux-x64` test was run on [Ubuntu 22.04 using Swift 5.10.1](https://download.swift.org/swift-5.10.1-release/ubuntu2204/swift-5.10.1-RELEASE/swift-5.10.1-RELEASE-ubuntu22.04.tar.gz) | ||||
| 
 | ||||
| The `linux-arm` test was run on [Debian 12 "bookworm" using Swift 5.10.1](https://download.swift.org/swift-5.10.1-release/debian12-aarch64/swift-5.10.1-RELEASE/swift-5.10.1-RELEASE-debian12-aarch64.tar.gz) | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| 1) Follow the entire ["C" demo](#c). The shared library will be used in Swift. | ||||
| 
 | ||||
| 2) Enter the `sheetjs-jsc` folder from the previous step. | ||||
| 
 | ||||
| 3) Create a folder `sheetjswift`. It should be in the `sheetjs-jsc` folder: | ||||
| 
 | ||||
| ```bash | ||||
| mkdir sheetjswift | ||||
| cd sheetjswift | ||||
| ``` | ||||
| 
 | ||||
| 4) Download the SheetJS Standalone script and the test file. Save both files in | ||||
| the project directory: | ||||
| 
 | ||||
| <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://docs.sheetjs.com/pres.numbers">pres.numbers</a></li> | ||||
| </ul> | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| curl -LO https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js | ||||
| curl -LO https://docs.sheetjs.com/pres.numbers`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 5) Copy all generated headers to the current directory: | ||||
| 
 | ||||
| ```bash | ||||
| find ../WebKit-WebKit*/WebKitBuild/JSCOnly/Release/JavaScriptCore/Headers/  -name \*.h | xargs -I '%' cp '%' . | ||||
| ``` | ||||
| 
 | ||||
| 6) Edit each header file and replace all instances of `<JavaScriptCore/` with | ||||
| `<`. For example, `JavaScript.h` includes `<JavaScriptCore/JSBase.h>`: | ||||
| 
 | ||||
| ```c title="JavaScript.h (original include)" | ||||
| #include <JavaScriptCore/JSBase.h> | ||||
| ``` | ||||
| 
 | ||||
| This must be changed to `<JSBase.h>`: | ||||
| 
 | ||||
| ```c title="JavaScript.h (modified include)" | ||||
| #include <JSBase.h> | ||||
| ``` | ||||
| 
 | ||||
| 7) Print the current working directory. It will be the path to `sheetjswift`: | ||||
| 
 | ||||
| ```bash | ||||
| pwd | ||||
| ``` | ||||
| 
 | ||||
| 8) Create a new header named `JavaScriptCore-Bridging-Header.h` : | ||||
| 
 | ||||
| ```c title="JavaScriptCore-Bridging-Header.h" | ||||
| #import "/tmp/sheetjs-jsc/sheetjswift/JavaScript.h" | ||||
| ``` | ||||
| 
 | ||||
| Replace the import path to the working directory from step 7. For example, if | ||||
| the path was `/home/sheetjs/sheetjs-jsc/sheetjswift/`, the import should be | ||||
| 
 | ||||
| ```c title="JavaScriptCore-Bridging-Header.h" | ||||
| #import "/home/sheetjs/sheetjs-jsc/JavaScript.h" | ||||
| ``` | ||||
| 
 | ||||
| 9) Create the default module map `module.modulemap`: | ||||
| 
 | ||||
| ```text title="module.modulemap" | ||||
| module JavaScriptCore { | ||||
|   header "./JavaScript.h" | ||||
|   link "JavaScriptCore" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 10) Download [`SheetJSCRaw.swift`](pathname:///swift/SheetJSCRaw.swift): | ||||
| 
 | ||||
| ```bash | ||||
| curl -LO https://docs.sheetjs.com/swift/SheetJSCRaw.swift | ||||
| ``` | ||||
| 
 | ||||
| 11) Build `SheetJSwift`: | ||||
| 
 | ||||
| ```bash | ||||
| swiftc -Xcc -I$(pwd) -Xlinker -L../WebKit-WebKit-7618.2.12.11.7/WebKitBuild/JSCOnly/Release/lib/ -Xlinker -lJavaScriptCore -Xlinker -lWTF -Xlinker -lbmalloc -Xlinker -lstdc++ -Xlinker -latomic -Xlinker -licuuc -Xlinker -licui18n -import-objc-header JavaScriptCore-Bridging-Header.h SheetJSCRaw.swift -o SheetJSwift | ||||
| ``` | ||||
| 
 | ||||
| 12) Run the command: | ||||
| 
 | ||||
| ```bash | ||||
| ./SheetJSwift pres.numbers | ||||
| ``` | ||||
| 
 | ||||
| If successful, a CSV will be printed to console. The program also tries to write | ||||
| to `SheetJSwift.xlsx`, which can be opened in a spreadsheet editor. | ||||
| 
 | ||||
| [^1]: See [`read` in "Reading Files"](/docs/api/parse-options) | ||||
| [^2]: See [`writeFile` in "Writing Files"](/docs/api/write-options) | ||||
| [^3]: See [`JSObjectMakeTypedArrayWithBytesNoCopy`](https://developer.apple.com/documentation/javascriptcore/jsobjectmaketypedarraywithbytesnocopy(_:_:_:_:_:_:_:)/) in the JavaScriptCore documentation. | ||||
| @ -616,4 +752,5 @@ to `sheetjsw.xlsb`, which can be opened in a spreadsheet editor. | ||||
| [^5]: See [`JSObjectGetTypedArrayBytesPtr`]( | ||||
| https://developer.apple.com/documentation/javascriptcore/jsobjectgettypedarraybytesptr(_:_:_:)/) in the JavaScriptCore documentation. | ||||
| [^6]: See [`read` in "Reading Files"](/docs/api/parse-options) | ||||
| [^7]: See [`writeFile` in "Writing Files"](/docs/api/write-options) | ||||
| [^7]: See [`writeFile` in "Writing Files"](/docs/api/write-options) | ||||
| [^8]: See ["Install Swift"](https://www.swift.org/install) in the Swift website. | ||||
|  | ||||
							
								
								
									
										189
									
								
								docz/static/swift/SheetJSCRaw.swift
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										189
									
								
								docz/static/swift/SheetJSCRaw.swift
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,189 @@ | ||||
| /* SheetJSCore (C) 2024-present SheetJS LLC -- https://sheetjs.com */ | ||||
| import Foundation; | ||||
| import JavaScriptCore; | ||||
| 
 | ||||
| enum JSCError: Error { | ||||
|   case badJSContext; | ||||
|   case badJSWorkbook; | ||||
|   case badJSWorksheet; | ||||
|   case badJSValue; | ||||
| }; | ||||
| 
 | ||||
| func DOIT(code: String, ctx: JSContextRef) -> JSValueRef { | ||||
|   let script: JSStringRef = JSStringCreateWithUTF8CString(code); | ||||
|   let result: JSValueRef = JSEvaluateScript(ctx, script, nil, nil, 0, nil); | ||||
|   JSStringRelease(script); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| func JS_STR_TO_C(val: JSValueRef, ctx: JSContextRef) -> String { | ||||
|   let str: JSStringRef = JSValueToStringCopy(ctx, val, nil); | ||||
|   let sz = JSStringGetMaximumUTF8CStringSize(str); | ||||
|   let buf = malloc(sz); | ||||
|   JSStringGetUTF8CString(str, buf, sz); | ||||
|   let ptr = buf!.bindMemory(to: CChar.self, capacity: 1); | ||||
|   let result = String.init(cString: ptr); | ||||
|   free(buf); | ||||
|   JSStringRelease(str); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| func GET_NAMED_PROP(val: JSValueRef, key: String, ctx: JSContextRef) throws -> JSValueRef { | ||||
|   if(!JSValueIsObject(ctx, val)) { throw JSCError.badJSValue; } | ||||
|   let o: JSObjectRef = JSValueToObject(ctx, val, nil); | ||||
|   let k: JSStringRef = JSStringCreateWithUTF8CString(key); | ||||
|   let result: JSValueRef = JSObjectGetProperty(ctx, o, k, nil); | ||||
|   JSStringRelease(k); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| func SET_NAMED_PROP(obj: JSValueRef, key: String, val: JSValueRef, ctx: JSContextRef) throws { | ||||
|   if(!JSValueIsObject(ctx, obj)) { throw JSCError.badJSValue; } | ||||
|   let k: JSStringRef = JSStringCreateWithUTF8CString(key); | ||||
|   JSObjectSetProperty(ctx, obj, k, val, 0, nil); | ||||
|   JSStringRelease(k); | ||||
| } | ||||
| 
 | ||||
| class SJSWorksheet { | ||||
|   var context: JSContextRef!; | ||||
|   var wb: JSValueRef; | ||||
|   var ws: JSValueRef; | ||||
|   var idx: UInt32; | ||||
| 
 | ||||
|   func toCSV() throws -> String { | ||||
|     let global = JSContextGetGlobalObject(self.context); | ||||
|     let XLSX = try GET_NAMED_PROP(val: global!, key: "XLSX", ctx: self.context); | ||||
|     let utils: JSValueRef = try GET_NAMED_PROP(val: XLSX, key: "utils", ctx: self.context); | ||||
|     let sheet_to_csv: JSValueRef = try GET_NAMED_PROP(val: utils, key: "sheet_to_csv", ctx: self.context); | ||||
|     var exc: JSValueRef?; | ||||
|     let result = JSObjectCallAsFunction(self.context, JSValueToObject(self.context, sheet_to_csv, nil), JSValueToObject(self.context, utils, nil), 1, [ws], &exc); | ||||
|     if(exc != nil && JSValueIsObject(exc, self.context)) { | ||||
|       let e = JS_STR_TO_C(val: exc!, ctx: self.context); | ||||
|       print(e) | ||||
|       throw JSCError.badJSValue; | ||||
|     } | ||||
|     return JS_STR_TO_C(val: result!, ctx: self.context); | ||||
|   } | ||||
| 
 | ||||
|   init(ctx: JSContextRef, wb: JSValueRef, ws: JSValueRef, idx: UInt32) throws { | ||||
|     self.context = ctx; | ||||
|     self.wb = wb; | ||||
|     self.ws = ws; | ||||
|     self.idx = idx; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class SJSWorkbook { | ||||
|   var context: JSContextRef!; | ||||
|   var wb: JSValueRef; | ||||
|   var SheetNames: JSValueRef; | ||||
|   var Sheets: JSValueRef; | ||||
| 
 | ||||
|   func getSheetAtIndex(idx: UInt32) throws -> SJSWorksheet { | ||||
|     let SheetNameRef = try GET_NAMED_PROP(val: self.SheetNames, key: String(idx), ctx: self.context); | ||||
|     let SheetName: String = JS_STR_TO_C(val: SheetNameRef, ctx: self.context) | ||||
|     let ws: JSValueRef! = try GET_NAMED_PROP(val: self.Sheets, key: SheetName, ctx: self.context); | ||||
|     return try SJSWorksheet(ctx: self.context, wb: self.wb, ws: ws, idx: idx); | ||||
|   } | ||||
| 
 | ||||
|   func writeData(bookType: String = "xlsx") throws -> Data { | ||||
|     let global = JSContextGetGlobalObject(self.context)!; | ||||
|     let XLSX = try GET_NAMED_PROP(val: global, key: "XLSX", ctx: self.context); | ||||
|     let write: JSValueRef = try GET_NAMED_PROP(val: XLSX, key: "write", ctx: self.context); | ||||
|     let opts = DOIT(code: String(format: "({type:'buffer', bookType:'%@', WTF:1})", bookType), ctx: self.context); | ||||
| 
 | ||||
|     var exc: JSValueRef?; | ||||
|     let result = JSObjectCallAsFunction(self.context, JSValueToObject(self.context, write, nil), JSValueToObject(self.context, XLSX, nil), 2, [self.wb, opts], &exc); | ||||
|     if(exc != nil && JSValueIsObject(exc, self.context)) { | ||||
|       let e = JS_STR_TO_C(val: exc!, ctx: self.context); | ||||
|       print(e) | ||||
|       throw JSCError.badJSValue; | ||||
|     } | ||||
|     let u8: JSObjectRef = JSValueToObject(self.context, result, nil); | ||||
|     let sz = JSObjectGetTypedArrayLength(self.context, result, nil); | ||||
|     let buf = JSObjectGetTypedArrayBytesPtr(self.context, u8, nil); | ||||
|     let data = Data(bytes: buf!, count: sz); | ||||
|     return data; | ||||
|   } | ||||
| 
 | ||||
|   init(ctx: JSContextRef, wb: JSValueRef) throws { | ||||
|     self.context = ctx; | ||||
|     self.wb = wb; | ||||
|     self.SheetNames = try GET_NAMED_PROP(val: self.wb, key: "SheetNames", ctx: self.context); | ||||
|     self.Sheets = try GET_NAMED_PROP(val: self.wb, key: "Sheets", ctx: self.context); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class SheetJSCore { | ||||
|   var context: JSContextRef!; | ||||
|   var XLSX: JSValueRef!; | ||||
| 
 | ||||
|   func init_context() throws -> JSContextRef { | ||||
|     let context = JSGlobalContextCreate(nil); | ||||
|     if (context == nil) { throw JSCError.badJSContext; } | ||||
|     do { | ||||
|       _ = DOIT(code: "var global = (function(){ return this; }).call(null);", ctx: context!); | ||||
|       _ = DOIT(code: "if(typeof wbs == 'undefined') wbs = [];", ctx: context!); | ||||
|       let src = try String(contentsOfFile: "xlsx.full.min.js"); | ||||
|       _ = DOIT(code: src, ctx: context!); | ||||
|       return context!; | ||||
|     } catch { print(error.localizedDescription); } | ||||
|     throw JSCError.badJSContext; | ||||
|   } | ||||
| 
 | ||||
|   func version() throws -> String { | ||||
|     if(self.context == nil) { throw JSCError.badJSContext; } | ||||
|     let res: JSValueRef = try GET_NAMED_PROP(val: self.XLSX, key: "version", ctx: self.context); | ||||
|     if(!JSValueIsString(self.context!, res)) { | ||||
|       print("Could not get SheetJS version."); | ||||
|       throw JSCError.badJSValue; | ||||
|     } | ||||
|     return JS_STR_TO_C(val: res, ctx: self.context); | ||||
|   } | ||||
| 
 | ||||
|   func readData(data: inout Data) throws -> SJSWorkbook { | ||||
|     let wb: JSValueRef = try data.withUnsafeMutableBytes{ (ptr: UnsafeMutableRawBufferPointer) throws in | ||||
|       let u8: JSValueRef = JSObjectMakeTypedArrayWithBytesNoCopy(self.context, kJSTypedArrayTypeUint8Array, ptr.baseAddress, ptr.count, nil, nil, nil); | ||||
|       try SET_NAMED_PROP(obj: JSContextGetGlobalObject(self.context), key: "payload", val: u8, ctx: self.context); | ||||
|       let wb: JSValueRef = DOIT(code: "XLSX.read(payload);", ctx: self.context); | ||||
|       if !JSValueIsObject(wb, self.context) { throw JSCError.badJSWorkbook; } | ||||
|       return wb; | ||||
|     } | ||||
|     return try SJSWorkbook(ctx: context, wb: wb); | ||||
|   } | ||||
|   func readFile(file: String) throws -> SJSWorkbook { | ||||
|     var data: Data! = try NSData(contentsOfFile: file) as Data; | ||||
|     return try readData(data: &data); | ||||
|   } | ||||
| 
 | ||||
|   init() throws { | ||||
|     self.context = try init_context(); | ||||
|     do { | ||||
|       let global = JSContextGetGlobalObject(self.context); | ||||
|       self.XLSX = try GET_NAMED_PROP(val: global!, key: "XLSX", ctx: self.context); | ||||
|       if self.XLSX == nil { throw JSCError.badJSContext; } | ||||
|     } catch { print(error.localizedDescription); } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // --- | ||||
| 
 | ||||
| let sheetjs = try SheetJSCore(); | ||||
| 
 | ||||
| 
 | ||||
| /* Print the SheetJS library version */ | ||||
| try print(sheetjs.version()); | ||||
| 
 | ||||
| /* Read file */ | ||||
| let wb: SJSWorkbook = try sheetjs.readFile(file: CommandLine.arguments[1]); | ||||
| 
 | ||||
| /* Convert the first worksheet to CSV and print */ | ||||
| let ws: SJSWorksheet = try wb.getSheetAtIndex(idx: 0); | ||||
| let csv: String = try ws.toCSV(); | ||||
| print(csv); | ||||
| 
 | ||||
| /* write an XLSX file to SheetJSwift.xlsx */ | ||||
| var wbout: Data = try wb.writeData(bookType: "xlsx"); | ||||
| let cwd = FileManager.default.currentDirectoryPath; | ||||
| let uri = URL(fileURLWithPath: cwd).appendingPathComponent("SheetJSwift.xlsx"); | ||||
| try wbout.write(to: uri); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user