#![allow(missing_docs)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
#![allow(clippy::too_many_arguments)]
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::path::PathBuf;
use std::ptr;
use std::slice;
use crate::Document;
use crate::edit::EditableDocument;
use crate::format::DocumentFormat;
pub const OFFICE_OK: i32 = 0;
pub const OFFICE_ERR_INVALID_ARG: i32 = 1;
pub const OFFICE_ERR_IO: i32 = 2;
pub const OFFICE_ERR_PARSE: i32 = 3;
pub const OFFICE_ERR_EXTRACTION: i32 = 4;
pub const OFFICE_ERR_INTERNAL: i32 = 5;
pub const OFFICE_ERR_UNSUPPORTED: i32 = 6;
fn set_err(ptr: *mut i32, code: i32) {
if !ptr.is_null() {
unsafe { *ptr = code };
}
}
fn classify_error(e: &crate::OfficeError) -> i32 {
match e {
crate::OfficeError::UnsupportedFormat(_) => OFFICE_ERR_UNSUPPORTED,
_ => {
let msg = format!("{e}").to_lowercase();
if msg.contains("not found") || msg.contains("no such file") || msg.contains("io") {
OFFICE_ERR_IO
} else if msg.contains("parse") || msg.contains("invalid") || msg.contains("xml") {
OFFICE_ERR_PARSE
} else {
OFFICE_ERR_INTERNAL
}
},
}
}
fn to_c_string(s: &str) -> *mut c_char {
let cleaned: String = s.replace('\0', "\u{FFFD}");
match CString::new(cleaned) {
Ok(cs) => cs.into_raw(),
Err(_) => ptr::null_mut(),
}
}
fn cstr_to_str<'a>(ptr: *const c_char) -> Option<&'a str> {
if ptr.is_null() {
return None;
}
unsafe { CStr::from_ptr(ptr).to_str().ok() }
}
fn cstr_to_pathbuf(ptr: *const c_char) -> Option<PathBuf> {
cstr_to_str(ptr).map(PathBuf::from)
}
#[unsafe(no_mangle)]
pub extern "C" fn office_oxide_version() -> *const c_char {
static VERSION: &[u8] = concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes();
VERSION.as_ptr() as *const c_char
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn office_oxide_free_string(ptr: *mut c_char) {
if !ptr.is_null() {
drop(unsafe { CString::from_raw(ptr) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn office_oxide_free_bytes(ptr: *mut u8, len: usize) {
if !ptr.is_null() && len > 0 {
drop(unsafe { Vec::from_raw_parts(ptr, len, len) });
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_oxide_detect_format(path: *const c_char) -> *const c_char {
let Some(path) = cstr_to_pathbuf(path) else {
return ptr::null();
};
match DocumentFormat::from_path(&path) {
Some(f) => format_to_cstr(f),
None => ptr::null(),
}
}
fn format_to_cstr(f: DocumentFormat) -> *const c_char {
static DOCX: &[u8] = b"docx\0";
static XLSX: &[u8] = b"xlsx\0";
static PPTX: &[u8] = b"pptx\0";
static DOC: &[u8] = b"doc\0";
static XLS: &[u8] = b"xls\0";
static PPT: &[u8] = b"ppt\0";
let s: &[u8] = match f {
DocumentFormat::Docx => DOCX,
DocumentFormat::Xlsx => XLSX,
DocumentFormat::Pptx => PPTX,
DocumentFormat::Doc => DOC,
DocumentFormat::Xls => XLS,
DocumentFormat::Ppt => PPT,
};
s.as_ptr() as *const c_char
}
fn parse_format(s: &str) -> Option<DocumentFormat> {
DocumentFormat::from_extension(s)
}
pub struct OfficeDocumentHandle {
_doc: Document,
}
#[unsafe(no_mangle)]
pub extern "C" fn office_document_open(
path: *const c_char,
error_code: *mut i32,
) -> *mut OfficeDocumentHandle {
let Some(path) = cstr_to_pathbuf(path) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
};
match Document::open(&path) {
Ok(doc) => {
set_err(error_code, OFFICE_OK);
Box::into_raw(Box::new(OfficeDocumentHandle { _doc: doc })) as *mut _
},
Err(e) => {
set_err(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_document_open_from_bytes(
data: *const u8,
len: usize,
format: *const c_char,
error_code: *mut i32,
) -> *mut OfficeDocumentHandle {
if data.is_null() || len == 0 {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
}
let Some(fmt_str) = cstr_to_str(format) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
};
let Some(fmt) = parse_format(fmt_str) else {
set_err(error_code, OFFICE_ERR_UNSUPPORTED);
return ptr::null_mut();
};
let bytes = unsafe { slice::from_raw_parts(data, len) }.to_vec();
let cursor = std::io::Cursor::new(bytes);
match Document::from_reader(cursor, fmt) {
Ok(doc) => {
set_err(error_code, OFFICE_OK);
Box::into_raw(Box::new(OfficeDocumentHandle { _doc: doc })) as *mut _
},
Err(e) => {
set_err(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn office_document_free(handle: *mut OfficeDocumentHandle) {
if !handle.is_null() {
drop(unsafe { Box::from_raw(handle) });
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_document_format(handle: *const OfficeDocumentHandle) -> *const c_char {
if handle.is_null() {
return ptr::null();
}
let h = unsafe { &*handle };
format_to_cstr(h._doc.format())
}
#[unsafe(no_mangle)]
pub extern "C" fn office_document_plain_text(
handle: *const OfficeDocumentHandle,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
}
let h = unsafe { &*handle };
let s = h._doc.plain_text();
set_err(error_code, OFFICE_OK);
to_c_string(&s)
}
#[unsafe(no_mangle)]
pub extern "C" fn office_document_to_markdown(
handle: *const OfficeDocumentHandle,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
}
let h = unsafe { &*handle };
let s = h._doc.to_markdown();
set_err(error_code, OFFICE_OK);
to_c_string(&s)
}
#[unsafe(no_mangle)]
pub extern "C" fn office_document_to_html(
handle: *const OfficeDocumentHandle,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
}
let h = unsafe { &*handle };
let s = h._doc.to_html();
set_err(error_code, OFFICE_OK);
to_c_string(&s)
}
#[unsafe(no_mangle)]
pub extern "C" fn office_document_to_ir_json(
handle: *const OfficeDocumentHandle,
error_code: *mut i32,
) -> *mut c_char {
if handle.is_null() {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
}
let h = unsafe { &*handle };
let ir = h._doc.to_ir();
match serde_json::to_string(&ir) {
Ok(s) => {
set_err(error_code, OFFICE_OK);
to_c_string(&s)
},
Err(_) => {
set_err(error_code, OFFICE_ERR_INTERNAL);
ptr::null_mut()
},
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_document_save_as(
handle: *const OfficeDocumentHandle,
path: *const c_char,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
}
let Some(path) = cstr_to_pathbuf(path) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
};
let h = unsafe { &*handle };
match h._doc.save_as(&path) {
Ok(()) => {
set_err(error_code, OFFICE_OK);
OFFICE_OK
},
Err(e) => {
let c = classify_error(&e);
set_err(error_code, c);
c
},
}
}
pub struct OfficeEditableHandle {
doc: EditableDocument,
}
#[unsafe(no_mangle)]
pub extern "C" fn office_editable_open(
path: *const c_char,
error_code: *mut i32,
) -> *mut OfficeEditableHandle {
let Some(path) = cstr_to_pathbuf(path) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
};
match EditableDocument::open(&path) {
Ok(doc) => {
set_err(error_code, OFFICE_OK);
Box::into_raw(Box::new(OfficeEditableHandle { doc })) as *mut _
},
Err(e) => {
set_err(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_editable_open_from_bytes(
data: *const u8,
len: usize,
format: *const c_char,
error_code: *mut i32,
) -> *mut OfficeEditableHandle {
if data.is_null() || len == 0 {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
}
let Some(fmt_str) = cstr_to_str(format) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
};
let Some(fmt) = parse_format(fmt_str) else {
set_err(error_code, OFFICE_ERR_UNSUPPORTED);
return ptr::null_mut();
};
let bytes = unsafe { slice::from_raw_parts(data, len) }.to_vec();
let cursor = std::io::Cursor::new(bytes);
match EditableDocument::from_reader(cursor, fmt) {
Ok(doc) => {
set_err(error_code, OFFICE_OK);
Box::into_raw(Box::new(OfficeEditableHandle { doc })) as *mut _
},
Err(e) => {
set_err(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn office_editable_free(handle: *mut OfficeEditableHandle) {
if !handle.is_null() {
drop(unsafe { Box::from_raw(handle) });
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_editable_replace_text(
handle: *mut OfficeEditableHandle,
find: *const c_char,
replace: *const c_char,
error_code: *mut i32,
) -> i64 {
if handle.is_null() {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return -1;
}
let Some(find_s) = cstr_to_str(find) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return -1;
};
let Some(replace_s) = cstr_to_str(replace) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return -1;
};
let h = unsafe { &mut *handle };
let n = h.doc.replace_text(find_s, replace_s);
set_err(error_code, OFFICE_OK);
n as i64
}
#[unsafe(no_mangle)]
pub extern "C" fn office_editable_set_cell(
handle: *mut OfficeEditableHandle,
sheet_index: u32,
cell_ref: *const c_char,
value_type: i32,
value_str: *const c_char,
value_num: f64,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
}
let Some(cell) = cstr_to_str(cell_ref) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
};
let value = match value_type {
0 => crate::xlsx::edit::CellValue::Empty,
1 => {
let Some(s) = cstr_to_str(value_str) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
};
crate::xlsx::edit::CellValue::String(s.to_string())
},
2 => crate::xlsx::edit::CellValue::Number(value_num),
3 => crate::xlsx::edit::CellValue::Boolean(value_num != 0.0),
_ => {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
},
};
let h = unsafe { &mut *handle };
match h.doc.set_cell(sheet_index as usize, cell, value) {
Ok(()) => {
set_err(error_code, OFFICE_OK);
OFFICE_OK
},
Err(e) => {
let c = classify_error(&e);
set_err(error_code, c);
c
},
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_editable_save(
handle: *const OfficeEditableHandle,
path: *const c_char,
error_code: *mut i32,
) -> i32 {
if handle.is_null() {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
}
let Some(path) = cstr_to_pathbuf(path) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
};
let h = unsafe { &*handle };
match h.doc.save(&path) {
Ok(()) => {
set_err(error_code, OFFICE_OK);
OFFICE_OK
},
Err(e) => {
let c = classify_error(&e);
set_err(error_code, c);
c
},
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_editable_save_to_bytes(
handle: *const OfficeEditableHandle,
out_len: *mut usize,
error_code: *mut i32,
) -> *mut u8 {
if handle.is_null() || out_len.is_null() {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
}
let h = unsafe { &*handle };
let buf: Vec<u8> = Vec::new();
let mut cursor = std::io::Cursor::new(buf);
match h.doc.write_to(&mut cursor) {
Ok(()) => {
let mut bytes = cursor.into_inner();
bytes.shrink_to_fit();
let len = bytes.len();
let ptr = bytes.as_mut_ptr();
std::mem::forget(bytes);
unsafe { *out_len = len };
set_err(error_code, OFFICE_OK);
ptr
},
Err(e) => {
set_err(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_extract_text(path: *const c_char, error_code: *mut i32) -> *mut c_char {
let Some(path) = cstr_to_pathbuf(path) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
};
match crate::extract_text(&path) {
Ok(s) => {
set_err(error_code, OFFICE_OK);
to_c_string(&s)
},
Err(e) => {
set_err(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_to_markdown(path: *const c_char, error_code: *mut i32) -> *mut c_char {
let Some(path) = cstr_to_pathbuf(path) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
};
match crate::to_markdown(&path) {
Ok(s) => {
set_err(error_code, OFFICE_OK);
to_c_string(&s)
},
Err(e) => {
set_err(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_to_html(path: *const c_char, error_code: *mut i32) -> *mut c_char {
let Some(path) = cstr_to_pathbuf(path) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return ptr::null_mut();
};
match crate::to_html(&path) {
Ok(s) => {
set_err(error_code, OFFICE_OK);
to_c_string(&s)
},
Err(e) => {
set_err(error_code, classify_error(&e));
ptr::null_mut()
},
}
}
#[unsafe(no_mangle)]
pub extern "C" fn office_create_from_markdown(
markdown: *const c_char,
format: *const c_char,
path: *const c_char,
error_code: *mut i32,
) -> i32 {
let Some(md) = cstr_to_str(markdown) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
};
let Some(fmt_str) = cstr_to_str(format) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
};
let Some(out_path) = cstr_to_pathbuf(path) else {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
};
let doc_format = match fmt_str.to_ascii_lowercase().as_str() {
"docx" => crate::DocumentFormat::Docx,
"xlsx" => crate::DocumentFormat::Xlsx,
"pptx" => crate::DocumentFormat::Pptx,
_ => {
set_err(error_code, OFFICE_ERR_INVALID_ARG);
return OFFICE_ERR_INVALID_ARG;
},
};
match crate::create::create_from_markdown(md, doc_format, &out_path) {
Ok(()) => {
set_err(error_code, OFFICE_OK);
OFFICE_OK
},
Err(e) => {
let code = classify_error(&e);
set_err(error_code, code);
code
},
}
}