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