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