/* 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);