forked from sheetjs/docs.sheetjs.com
		
	
		
			
	
	
		
			190 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
		
		
			
		
	
	
			190 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
|  | /* 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); |