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