Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 4 additions & 239 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ crate-type = ["cdylib"]
# Uses `../ironcalc/base` when used locally, and uses
# the inicated version from crates.io when published.
# https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#multiple-locations
ironcalc = { path = "../../xlsx", version = "0.5.0" }
ironcalc_base = { path = "../../base", version = "0.5", features = ["use_regex_lite"] }
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = "0.2.92"
Expand Down
3 changes: 1 addition & 2 deletions bindings/wasm/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# IronCalc Web bindings

This crate is used to build the web bindings for IronCalc.
Note that it does not contain the xlsx writer and reader, only the engine.
This package contains web bindings for IronCalc. It exposes the engine and helper functions to import or export workbooks as XLSX or IronCalc (icalc) byte arrays. The built-in XLSX support focuses on core spreadsheet features like cell values, formulas, and styling.

https://www.npmjs.com/package/@ironcalc/wasm?activeTab=readme

Expand Down
21 changes: 20 additions & 1 deletion bindings/wasm/README.pkg.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# IronCalc Web bindings

This package contains web bindings for IronCalc. Note that it does not contain the xlsx writer and reader, only the engine.
This package contains web bindings for IronCalc. It exposes the engine and helper functions to import or export workbooks as XLSX or IronCalc (icalc) byte arrays. The built-in XLSX support focuses on core spreadsheet features like cell values, formulas, and styling.


## Usage
Expand Down Expand Up @@ -31,3 +31,22 @@ function compute() {

compute();
```

### Importing and exporting bytes

The `Model` class provides helpers to load or save workbooks as raw byte arrays.

```ts
// create a new workbook and export as XLSX bytes
const model = new Model('Workbook1', 'en', 'UTC');
model.setUserInput(0, 1, 1, '42');
const xlsxBytes = model.saveToXlsx();

// load from those bytes
const roundTripped = Model.fromXlsxBytes(xlsxBytes, 'Workbook1', 'en', 'UTC');

// same helpers exist for IronCalc's internal format
const icalcBytes = model.saveToIcalc();
const restored = Model.fromIcalcBytes(icalcBytes);
```

40 changes: 40 additions & 0 deletions bindings/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ use wasm_bindgen::{
JsValue,
};

use ironcalc::{
base::Model as BaseWorkbookModel, export::save_xlsx_to_writer, import::load_from_xlsx_bytes,
};
use ironcalc_base::{
expressions::{lexer::util::get_tokens as tokenizer, types::Area, utils::number_to_column},
types::{CellType, Style},
BorderArea, ClipboardData, UserModel as BaseModel,
};
use std::io::{BufWriter, Cursor};

fn to_js_error(error: String) -> JsError {
JsError::new(&error.to_string())
Expand Down Expand Up @@ -55,6 +59,27 @@ impl Model {
Ok(Model { model })
}

#[wasm_bindgen(js_name = "fromICalcBytes")]
pub fn from_icalc_bytes(bytes: &[u8]) -> Result<Model, JsError> {
let model = BaseModel::from_bytes(bytes).map_err(to_js_error)?;
Ok(Model { model })
}

#[wasm_bindgen(js_name = "fromXLSXBytes")]
pub fn from_xlsx_bytes(
bytes: &[u8],
name: &str,
locale: &str,
timezone: &str,
) -> Result<Model, JsError> {
let workbook = load_from_xlsx_bytes(bytes, name, locale, timezone)
.map_err(|e| to_js_error(e.to_string()))?;
let base_model =
BaseWorkbookModel::from_workbook(workbook).map_err(|e| to_js_error(e.to_string()))?;
let user_model = BaseModel::from_model(base_model);
Ok(Model { model: user_model })
}

pub fn undo(&mut self) -> Result<(), JsError> {
self.model.undo().map_err(to_js_error)
}
Expand Down Expand Up @@ -579,6 +604,21 @@ impl Model {
self.model.to_bytes()
}

#[wasm_bindgen(js_name = "saveToICalc")]
pub fn save_to_icalc(&self) -> Vec<u8> {
self.model.to_bytes()
}

#[wasm_bindgen(js_name = "saveToXLSX")]
pub fn save_to_xlsx(&self) -> Result<Vec<u8>, JsError> {
let mut buffer: Vec<u8> = Vec::new();
let cursor = Cursor::new(&mut buffer);
let writer = BufWriter::new(cursor);
save_xlsx_to_writer(self.model.get_model(), writer)
.map_err(|e| JsError::new(&e.to_string()))?;
Ok(buffer)
}

#[wasm_bindgen(js_name = "getName")]
pub fn get_name(&self) -> String {
self.model.get_name()
Expand Down
47 changes: 47 additions & 0 deletions bindings/wasm/tests/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,52 @@ test("autofill", () => {
assert.strictEqual(result, "23");
});

test('saveToXLSX returns data', () => {
const model = new Model('Workbook1', 'en', 'UTC');
const bytes = model.saveToXLSX();
assert.ok(bytes instanceof Uint8Array);
assert.ok(bytes.length > 0);
});

test('saveToICalc returns data', () => {
const model = new Model('Workbook1', 'en', 'UTC');
const bytes = model.saveToICalc();
assert.ok(bytes instanceof Uint8Array);
assert.ok(bytes.length > 0);
});

test('fromICalcBytes loads model', () => {
const model = new Model('Workbook1', 'en', 'UTC');
model.setUserInput(0, 1, 1, '42');
const bytes = model.saveToICalc();
const m2 = Model.fromICalcBytes(bytes);
assert.strictEqual(m2.getCellContent(0, 1, 1), '42');
});

test('fromXLSXBytes loads model', () => {
const model = new Model('Workbook1', 'en', 'UTC');
model.setUserInput(0, 1, 1, '5');
const bytes = model.saveToXLSX();
const m2 = Model.fromXLSXBytes(bytes, 'Workbook1', 'en', 'UTC');
assert.strictEqual(m2.getCellContent(0, 1, 1), '5');
});

test('roundtrip via xlsx bytes', () => {
const m1 = new Model('Workbook1', 'en', 'UTC');
m1.setUserInput(0, 1, 1, '7');
m1.setUserInput(0, 1, 2, '=A1*3');
const bytes = m1.saveToXLSX();
const m2 = Model.fromXLSXBytes(bytes, 'Workbook1', 'en', 'UTC');
m2.evaluate();
assert.strictEqual(m2.getFormattedCellValue(0, 1, 2), '21');
});

test('roundtrip via icalc bytes', () => {
const m1 = new Model('Workbook1', 'en', 'UTC');
m1.setUserInput(0, 1, 1, '9');
m1.setUserInput(0, 1, 2, '=A1*4');
const bytes = m1.saveToICalc();
const m2 = Model.fromICalcBytes(bytes);
m2.evaluate();
assert.strictEqual(m2.getFormattedCellValue(0, 1, 2), '36');
});
4 changes: 3 additions & 1 deletion xlsx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ readme = "README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
zip = "0.6"
zip = { version = "0.6.6", default-features = false, features = ["deflate", "time"] }
flate2 = { version = "1.0", default-features = false, features = ["rust_backend"] }
time = { version = "0.3", features = ["wasm-bindgen"] }
roxmltree = "0.19"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
7 changes: 7 additions & 0 deletions xlsx/src/import/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,10 @@ pub fn load_from_icalc(file_name: &str) -> Result<Model, XlsxError> {
.map_err(|e| XlsxError::IO(format!("Failed to decode file: {}", e)))?;
Model::from_workbook(workbook).map_err(XlsxError::Workbook)
}

/// Loads a [`Model`] from the bytes of an `ic` file.
pub fn load_from_icalc_bytes(bytes: &[u8]) -> Result<Model, XlsxError> {
let workbook: Workbook = bitcode::decode(bytes)
.map_err(|e| XlsxError::IO(format!("Failed to decode file: {}", e)))?;
Model::from_workbook(workbook).map_err(XlsxError::Workbook)
}
14 changes: 13 additions & 1 deletion xlsx/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use uuid::Uuid;

use ironcalc::compare::{test_file, test_load_and_saving};
use ironcalc::export::save_to_xlsx;
use ironcalc::import::{load_from_icalc, load_from_xlsx, load_from_xlsx_bytes};
use ironcalc::import::{
load_from_icalc, load_from_icalc_bytes, load_from_xlsx, load_from_xlsx_bytes,
};
use ironcalc_base::types::{HorizontalAlignment, VerticalAlignment};
use ironcalc_base::{Model, UserModel};

Expand Down Expand Up @@ -62,6 +64,16 @@ fn test_load_from_xlsx_bytes() {
assert_eq!(workbook.views[&0].sheet, 7);
}

#[test]
fn test_load_from_icalc_bytes() {
let mut file = fs::File::open("tests/example.ic").unwrap();
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).unwrap();
let model = load_from_icalc_bytes(&bytes).unwrap();
let model_from_file = load_from_icalc("tests/example.ic").unwrap();
assert_eq!(model.workbook, model_from_file.workbook);
}

#[test]
fn no_grid() {
let model = load_from_xlsx("tests/NoGrid.xlsx", "en", "UTC").unwrap();
Expand Down