forked from sheetjs/docs.sheetjs.com
		
	
		
			
	
	
		
			229 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
		
		
			
		
	
	
			229 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| 
								 | 
							
								/* See https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs */
							 | 
						||
| 
								 | 
							
								#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#[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 JSContext;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#[repr(C)]
							 | 
						||
| 
								 | 
							
								pub struct BytesDeallocator {
							 | 
						||
| 
								 | 
							
								  _data: [u8; 0],
							 | 
						||
| 
								 | 
							
								  _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								type JSTypedArrayBytesDeallocator = *mut BytesDeallocator;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#[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 u8) -> JSStringRef;
							 | 
						||
| 
								 | 
							
								  pub unsafe fn JSStringGetUTF8CString(string: JSStringRef, buffer: *const u8, bufferSize: usize) -> usize;
							 | 
						||
| 
								 | 
							
								  pub unsafe fn JSStringGetMaximumUTF8CStringSize(string: JSStringRef) -> usize;
							 | 
						||
| 
								 | 
							
								  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) -> usize;
							 | 
						||
| 
								 | 
							
								  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: usize, bytesDeallocator: JSTypedArrayBytesDeallocator, deallocatorContext: JSTypedArrayBytesDeallocatorContext, exception: JSValueRefRef) -> JSObjectRef;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  pub unsafe fn JSContextGetGlobalObject(ctx: JSContextRef) -> JSObjectRef;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								macro_rules! NULL { () => { std::ptr::null_mut() }; }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								pub struct JSC;
							 | 
						||
| 
								 | 
							
								impl JSC {
							 | 
						||
| 
								 | 
							
								  pub fn JSEval(ctx: JSContextRef, script: &str) -> JSValueRef { unsafe {
							 | 
						||
| 
								 | 
							
								    let script: JSStringRef = JSStringCreateWithUTF8CString(std::ffi::CString::new(script.as_bytes()).unwrap().as_ptr() as *const u8);
							 | 
						||
| 
								 | 
							
								    let result: JSValueRef = JSEvaluateScript(ctx, script, NULL!(), NULL!(), 0, NULL!()); 
							 | 
						||
| 
								 | 
							
								    JSStringRelease(script);
							 | 
						||
| 
								 | 
							
								    result
							 | 
						||
| 
								 | 
							
								  } }
							 | 
						||
| 
								 | 
							
								  pub fn JSEvaluateScript(ctx: JSContextRef, script: JSStringRef, thisObject: JSObjectRef, sourceURL: JSStringRef, startingLineNumber: i32, exception: JSValueRefRef) -> JSValueRef { unsafe {
							 | 
						||
| 
								 | 
							
								    JSEvaluateScript(ctx, script, thisObject, sourceURL, startingLineNumber, exception)
							 | 
						||
| 
								 | 
							
								  } }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  pub fn JSGlobalContextCreate(globalObjectClass: JSClassRef) -> JSGlobalContextRef { unsafe {
							 | 
						||
| 
								 | 
							
								    JSGlobalContextCreate(globalObjectClass)
							 | 
						||
| 
								 | 
							
								  } }
							 | 
						||
| 
								 | 
							
								  pub fn JSGlobalContextRelease(globalObjectClass: JSGlobalContextRef) { unsafe {
							 | 
						||
| 
								 | 
							
								    JSGlobalContextRelease(globalObjectClass)
							 | 
						||
| 
								 | 
							
								  } }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  pub fn JSStringCreateWithUTF8CString(str: &str) -> JSStringRef { unsafe {
							 | 
						||
| 
								 | 
							
								    JSStringCreateWithUTF8CString(std::ffi::CString::new(str.as_bytes()).unwrap().as_ptr() as *const u8)
							 | 
						||
| 
								 | 
							
								  } }
							 | 
						||
| 
								 | 
							
								  pub fn JSStringRelease(string: JSStringRef) { unsafe {
							 | 
						||
| 
								 | 
							
								    JSStringRelease(string)
							 | 
						||
| 
								 | 
							
								  } }
							 | 
						||
| 
								 | 
							
								  pub fn JSCastStr(ctx: JSContextRef, value: JSValueRef) -> Result<String, String> { unsafe {
							 | 
						||
| 
								 | 
							
								    match JSValueIsString(ctx, value) {
							 | 
						||
| 
								 | 
							
								      true => {
							 | 
						||
| 
								 | 
							
								        /* TODO: this leaks memory -- `buf` must be freed and `str` must be JSStringRelease-d */
							 | 
						||
| 
								 | 
							
								        let str: JSStringRef = JSValueToStringCopy(ctx, value, NULL!());
							 | 
						||
| 
								 | 
							
								        let sz: usize = JSStringGetMaximumUTF8CStringSize(str);
							 | 
						||
| 
								 | 
							
								        let layout = std::alloc::Layout::array::<u8>(sz).unwrap();
							 | 
						||
| 
								 | 
							
								        let buf = std::alloc::alloc(layout) as *mut u8;
							 | 
						||
| 
								 | 
							
								        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, size);
							 | 
						||
| 
								 | 
							
								            String::from_utf8(slice.to_vec()).map_err(|e| format!("from_utf8 error {}", e))
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      },
							 | 
						||
| 
								 | 
							
								      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 JSObjectMakeUint8Array(ctx: JSContextRef, data: Vec<u8>) -> JSObjectRef { unsafe {
							 | 
						||
| 
								 | 
							
								    JSObjectMakeTypedArrayWithBytesNoCopy(ctx, kJSTypedArrayTypeUint8Array, data.as_ptr(), data.len(), NULL!(), NULL!(), NULL!())
							 | 
						||
| 
								 | 
							
								  } }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  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<u8> { unsafe {
							 | 
						||
| 
								 | 
							
								    let result = JSC::JSEval(ctx, script);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* pull Uint8Array data back to Rust */
							 | 
						||
| 
								 | 
							
								    let u8: JSObjectRef = JSValueToObject(ctx, result, NULL!());
							 | 
						||
| 
								 | 
							
								    let sz: usize = 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<String, String> {
							 | 
						||
| 
								 | 
							
								    let result = JSC::JSEval(ctx, script);
							 | 
						||
| 
								 | 
							
								    JSC::JSCastStr(ctx, result)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								fn main() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /* 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");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let file: Vec<u8> = std::fs::read(path.clone()).unwrap();
							 | 
						||
| 
								 | 
							
								  
							 | 
						||
| 
								 | 
							
								    /* push data to JSC */
							 | 
						||
| 
								 | 
							
								    let u8: JSObjectRef = JSC::JSObjectMakeUint8Array(ctx, file);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 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);
							 | 
						||
| 
								 | 
							
								}
							 |