/* See https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs */ #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] use libc::{c_char, size_t}; #[repr(C)] pub struct JSContext { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } type JSContextRef = *mut JSContext; type JSGlobalContextRef = JSContextRef; #[repr(C)] pub struct JSClass { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } type JSClassRef = *mut JSClass; #[repr(C)] pub struct JSValue { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } type JSValueRef = *mut JSValue; /* NOTE: JSEvaluateScript takes a pointer-to-a-pointer, hence RefRef */ type JSValueRefRef = *mut JSValueRef; #[repr(C)] pub struct JSObject { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } type JSObjectRef = *mut JSObject; #[repr(C)] pub struct JSString { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } type JSStringRef = *mut JSString; #[repr(C)] pub struct BytesDeallocator { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } type JSTypedArrayBytesDeallocator = Option; #[repr(C)] pub struct BytesDeallocatorContext { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } type JSTypedArrayBytesDeallocatorContext = *mut BytesDeallocatorContext; /* NOTE: this is technically an enum */ type JSTypedArrayType = u32; const kJSTypedArrayTypeUint8Array: u32 = 3; /* NOTE: this is technically "unsigned int" */ type JSPropertyAttributes = u32; unsafe extern "C" { pub unsafe fn JSEvaluateScript(ctx: JSContextRef, script: JSStringRef, thisObject: JSObjectRef, sourceURL: JSStringRef, startingLineNumber: i32, exception: JSValueRefRef) -> JSValueRef; pub unsafe fn JSGlobalContextCreate(string: JSClassRef) -> JSGlobalContextRef; pub unsafe fn JSGlobalContextRelease(ctx: JSGlobalContextRef); pub unsafe fn JSStringCreateWithUTF8CString(string: *const c_char) -> JSStringRef; pub unsafe fn JSStringGetUTF8CString(string: JSStringRef, buffer: *mut c_char, bufferSize: size_t) -> size_t; pub unsafe fn JSStringGetMaximumUTF8CStringSize(string: JSStringRef) -> size_t; pub unsafe fn JSStringRelease(string: JSStringRef); pub unsafe fn JSValueIsString(ctx: JSContextRef, value: JSValueRef) -> bool; pub unsafe fn JSValueToStringCopy(ctx: JSContextRef, value: JSValueRef, exception: JSValueRefRef) -> JSStringRef; pub unsafe fn JSValueToObject(ctx: JSContextRef, value: JSValueRef, exception: JSValueRefRef) -> JSObjectRef; pub unsafe fn JSObjectGetTypedArrayLength(ctx: JSContextRef, object: JSObjectRef, exception: JSValueRefRef) -> size_t; pub unsafe fn JSObjectGetTypedArrayBytesPtr(ctx: JSContextRef, object: JSObjectRef, exception: JSValueRefRef) -> *const u8; pub unsafe fn JSObjectSetProperty(ctx: JSContextRef, object: JSObjectRef, propertyName: JSStringRef, value: JSValueRef, attributes: JSPropertyAttributes, exception: JSValueRefRef); pub unsafe fn JSObjectMakeTypedArrayWithBytesNoCopy(ctx: JSContextRef, arrayType: JSTypedArrayType, bytes: *const u8, byteLength: size_t, bytesDeallocator: JSTypedArrayBytesDeallocator, deallocatorContext: JSTypedArrayBytesDeallocatorContext, exception: JSValueRefRef) -> JSObjectRef; pub unsafe fn JSContextGetGlobalObject(ctx: JSContextRef) -> JSObjectRef; } macro_rules! NULL { () => { std::ptr::null_mut() }; } unsafe fn read_file(filename: &str) -> (*mut u8, size_t) { use std::fs::File; use std::io::Read; let mut file = File::open(filename).expect("Failed to open file"); let mut buffer = Vec::new(); file.read_to_end(&mut buffer).expect("Failed to read file"); let len = buffer.len(); unsafe { let ptr = libc::malloc(len) as *mut u8; if ptr.is_null() { panic!("malloc failed"); } std::ptr::copy(buffer.as_ptr(), ptr, len); std::mem::forget(buffer); (ptr, len) } } unsafe fn free_file(ptr: *mut u8) { if !ptr.is_null() { unsafe { libc::free(ptr as *mut libc::c_void); } } } pub struct JSC; impl JSC { pub fn JSEval(ctx: JSContextRef, script: &str) -> JSValueRef { unsafe { let cstr = std::ffi::CString::new(script).unwrap(); let script: JSStringRef = JSStringCreateWithUTF8CString(cstr.as_ptr()); let result: JSValueRef = JSEvaluateScript(ctx, script, NULL!(), NULL!(), 0, NULL!()); JSStringRelease(script); result } } pub fn JSGlobalContextCreate(globalObjectClass: JSClassRef) -> JSGlobalContextRef { unsafe { JSGlobalContextCreate(globalObjectClass) } } pub fn JSGlobalContextRelease(globalObjectClass: JSGlobalContextRef) { unsafe { JSGlobalContextRelease(globalObjectClass) } } pub fn JSStringCreateWithUTF8CString(str: &str) -> JSStringRef { unsafe { let cstr = std::ffi::CString::new(str).unwrap(); JSStringCreateWithUTF8CString(cstr.as_ptr()) } } pub fn JSStringRelease(string: JSStringRef) { unsafe { JSStringRelease(string) } } pub fn JSCastStr(ctx: JSContextRef, value: JSValueRef) -> Result { unsafe { match JSValueIsString(ctx, value) { true => { let str: JSStringRef = JSValueToStringCopy(ctx, value, NULL!()); let sz: size_t = JSStringGetMaximumUTF8CStringSize(str); let buf = libc::malloc(sz) as *mut c_char; match buf.is_null() { true => Err(format!("Unable to allocate {} bytes", sz)), false => { let size = JSStringGetUTF8CString(str, buf, sz); let slice = std::slice::from_raw_parts(buf as *const u8, size); let result = String::from_utf8(slice.to_vec()).map_err(|e| format!("from_utf8 error {}", e)); libc::free(buf as *mut libc::c_void); JSStringRelease(str); result } } }, false => Err("Value is not a string".to_string()) } } } pub fn JSObjectSetProperty(ctx: JSContextRef, object: JSObjectRef, propertyName: JSStringRef, value: JSValueRef, attributes: JSPropertyAttributes, exception: JSValueRefRef) { unsafe { JSObjectSetProperty(ctx, object, propertyName, value, attributes, exception) } } pub fn JSContextGetGlobalObject(ctx: JSContextRef) -> JSObjectRef { unsafe { JSContextGetGlobalObject(ctx) } } pub fn JSContextSetGlobalProperty(ctx: JSContextRef, key: &str, val: JSValueRef) { let global: JSObjectRef = JSC::JSContextGetGlobalObject(ctx); let k: JSStringRef = JSC::JSStringCreateWithUTF8CString(key); JSC::JSObjectSetProperty(ctx, global, k, val, 0, NULL!()); JSC::JSStringRelease(k); } pub fn JSCastObjectToValue(obj: JSObjectRef) -> JSValueRef { obj as JSValueRef } pub fn JSEvalAB(ctx: JSContextRef, script: &str) -> Vec { unsafe { let result = JSC::JSEval(ctx, script); /* pull Uint8Array data back to Rust */ let u8: JSObjectRef = JSValueToObject(ctx, result, NULL!()); let sz: size_t = JSObjectGetTypedArrayLength(ctx, u8, NULL!()); let buf: *const u8 = JSObjectGetTypedArrayBytesPtr(ctx, u8, NULL!()); std::slice::from_raw_parts(buf, sz).to_vec() } } pub fn JSEvalStr(ctx: JSContextRef, script: &str) -> Result { let result = JSC::JSEval(ctx, script); JSC::JSCastStr(ctx, result) } } fn main() { let mut file_buf: *mut u8 = std::ptr::null_mut(); let mut file_len: size_t = 0; /* initialize */ let ctx: JSGlobalContextRef = JSC::JSGlobalContextCreate(NULL!()); /* JSC does not expose a standard "global" by default */ JSC::JSEval(ctx, "var global = (function(){ return this; }).call(null);"); /* load library */ JSC::JSEval(ctx, include_str!("xlsx.full.min.js")); /* get version string */ match JSC::JSEvalStr(ctx, "XLSX.version") { Ok(s) => { println!("SheetJS library version {}", s); }, Err(e) => { eprintln!("Could not get SheetJS version: {}", e); JSC::JSGlobalContextRelease(ctx); std::process::exit(1) } } /* read file */ { let mut iter = std::env::args(); let path: String = iter.nth(1).expect("must specify a file name"); unsafe { (file_buf, file_len) = read_file(&path); } /* push data to JSC */ unsafe { let u8: JSObjectRef = JSObjectMakeTypedArrayWithBytesNoCopy(ctx, kJSTypedArrayTypeUint8Array, file_buf, file_len, None, std::ptr::null_mut(), NULL!()); /* assign to `global.buf` */ JSC::JSContextSetGlobalProperty(ctx, "buf", JSC::JSCastObjectToValue(u8)); } } /* parse workbook and print CSV */ match JSC::JSEvalStr(ctx, " var wb = XLSX.read(global.buf); var ws = wb.Sheets[wb.SheetNames[0]]; XLSX.utils.sheet_to_csv(ws) ") { Ok(s) => { println!("{}", s); }, Err(e) => { eprintln!("Could not generate CSV: {}", e); JSC::JSGlobalContextRelease(ctx); std::process::exit(2) } } /* write file */ { let result = JSC::JSEvalAB(ctx, "XLSX.write(wb, {type:'buffer', bookType:'xlsb'});"); /* save file */ std::fs::write("sheetjsw.xlsb", result).unwrap(); } JSC::JSGlobalContextRelease(ctx); unsafe { free_file(file_buf); } }