docs.sheetjs.com/docz/static/swift/SheetJSCRaw.swift
2024-06-23 04:56:00 -04:00

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