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