| Crates.io | xlsx-handlebars |
| lib.rs | xlsx-handlebars |
| version | 0.2.2 |
| created_at | 2025-10-01 17:33:00.800271+00 |
| updated_at | 2025-11-05 10:01:36.494916+00 |
| description | A Rust library for processing XLSX files with Handlebars templates, supporting WASM, Node.js, Deno, and browsers |
| homepage | https://github.com/sail-sail/xlsx-handlebars |
| repository | https://github.com/sail-sail/xlsx-handlebars |
| max_upload_size | |
| id | 1863071 |
| size | 197,190 |
A Rust library for processing XLSX files with Handlebars templates, supporting multiple platforms:
cargo add xlsx-handlebars
npm install xlsx-handlebars
import init, { render_template } from "jsr:@sail/xlsx-handlebars";
use xlsx_handlebars::render_template;
use serde_json::json;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Read XLSX template file
let template_bytes = std::fs::read("template.xlsx")?;
// Prepare data
let data = json!({
"name": "John Doe",
"company": "ABC Tech Inc.",
"position": "Software Engineer",
"projects": [
{"name": "Project A", "status": "Completed"},
{"name": "Project B", "status": "In Progress"}
],
"has_bonus": true,
"bonus_amount": 5000
});
// Render template
let result = render_template(template_bytes, &data)?;
// Save result
std::fs::write("output.xlsx", result)?;
Ok(())
}
import init, { render_template } from 'xlsx-handlebars';
import fs from 'fs';
async function processTemplate() {
// Initialize WASM module
await init();
// Read template file
const templateBytes = fs.readFileSync('template.xlsx');
// Prepare data
const data = {
name: "Jane Smith",
company: "XYZ Technology Ltd.",
position: "Senior Developer",
projects: [
{ name: "E-commerce Platform", status: "Completed" },
{ name: "Mobile App", status: "In Development" }
],
has_bonus: true,
bonus_amount: 8000
};
// Render template
const result = render_template(templateBytes, JSON.stringify(data));
// Save result
fs.writeFileSync('output.xlsx', new Uint8Array(result));
}
processTemplate().catch(console.error);
import init, { render_template } from "https://deno.land/x/xlsx_handlebars/mod.ts";
async function processTemplate() {
// Initialize WASM module
await init();
// Read template file
const templateBytes = await Deno.readFile("template.xlsx");
// Prepare data
const data = {
name: "Alice Johnson",
department: "R&D",
projects: [
{ name: "AI Customer Service", status: "Live" },
{ name: "Data Visualization Platform", status: "In Development" }
]
};
// Render template
const result = render_template(templateBytes, JSON.stringify(data));
// Save result
await Deno.writeFile("output.xlsx", new Uint8Array(result));
}
if (import.meta.main) {
await processTemplate();
}
<!DOCTYPE html>
<html>
<head>
<title>XLSX Handlebars Example</title>
</head>
<body>
<input type="file" id="fileInput" accept=".xlsx">
<button onclick="processFile()">Process Template</button>
<script type="module">
import init, { render_template } from './pkg/xlsx_handlebars.js';
// Initialize WASM
await init();
window.processFile = async function() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) return;
const arrayBuffer = await file.arrayBuffer();
const templateBytes = new Uint8Array(arrayBuffer);
const data = {
name: "John Doe",
company: "Example Company"
};
try {
const result = render_template(templateBytes, JSON.stringify(data));
// Download result
const blob = new Blob([new Uint8Array(result)], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'processed.xlsx';
a.click();
} catch (error) {
console.error('Processing failed:', error);
}
};
</script>
</body>
</html>
Employee Name: {{name}}
Company: {{company}}
Position: {{position}}
{{#if has_bonus}}
Bonus: ${{bonus_amount}}
{{else}}
No Bonus
{{/if}}
{{#unless is_intern}}
Full-time Employee
{{/unless}}
Project Experience:
{{#each projects}}
- {{name}}: {{description}} ({{status}})
{{/each}}
Skills:
{{#each skills}}
{{@index}}. {{this}}
{{/each}}
Built-in Helper functions:
<!-- Basic helpers -->
{{upper name}} <!-- Convert to uppercase -->
{{lower company}} <!-- Convert to lowercase -->
{{len projects}} <!-- Array length -->
{{#if (eq status "completed")}}Completed{{/if}} <!-- Equality comparison -->
{{#if (gt score 90)}}Excellent{{/if}} <!-- Greater than comparison -->
{{#if (lt age 30)}}Young{{/if}} <!-- Less than comparison -->
<!-- String concatenation -->
{{concat "Hello" " " "World"}} <!-- String concatenation -->
{{concat "Total: " count}} <!-- Mix strings and variables -->
<!-- Excel-specific helpers -->
{{num employee.salary}} <!-- Mark cell as number type -->
{{formula "=SUM(A1:B1)"}} <!-- Static Excel formula -->
{{formula (concat "=SUM(" (_c) "1:" (_c) "10)")}} <!-- Dynamic formula with current column -->
{{mergeCell "C4:D5"}} <!-- Merge cells C4 to D5 -->
{{img logo.data 100 100}} <!-- Insert image with width and height -->
<!-- Column name conversion helpers -->
{{toColumnName "A" 5}} <!-- A + 5 offset = F -->
{{toColumnName (_c) 3}} <!-- Current column + 3 offset -->
{{toColumnIndex "AA"}} <!-- AA column index = 27 -->
Static Formula:
<!-- In Excel cell -->
{{formula "=SUM(A1:B1)"}}
{{formula "=AVERAGE(C2:C10)"}}
{{formula "=IF(D1>100,\"High\",\"Low\")"}}
Dynamic Formula with concat:
<!-- Dynamic row reference -->
{{formula (concat "=A" (_r) "*B" (_r))}}
<!-- Dynamic column reference -->
{{formula (concat "=SUM(" (_c) "2:" (_c) "10)")}}
<!-- Complex dynamic formula -->
{{formula (concat "=IF(" (_cr) ">100,\"High\",\"Low\")")}}
Available position helpers:
(_c) - Current column letter (A, B, C, ...)(_r) - Current row number (1, 2, 3, ...)(_cr) - Current cell reference (A1, B2, C3, ...)toColumnName - Convert column name or index to a new column name with optional offset:
<!-- Basic usage: offset from specified column -->
{{toColumnName "A" 0}} <!-- A (no offset) -->
{{toColumnName "A" 5}} <!-- F (A + 5) -->
{{toColumnName "Z" 1}} <!-- AA (Z + 1) -->
<!-- Use with current column -->
{{toColumnName (_c) 3}} <!-- Current column + 3 offset -->
<!-- Application in dynamic formulas -->
{{formula (concat "=SUM(" (_c) "1:" (toColumnName (_c) 3) "1)")}}
<!-- Example: If current column is B, generates formula =SUM(B1:E1) -->
toColumnIndex - Convert column name to column index (1-based):
{{toColumnIndex "A"}} <!-- 1 -->
{{toColumnIndex "Z"}} <!-- 26 -->
{{toColumnIndex "AA"}} <!-- 27 -->
{{toColumnIndex "AB"}} <!-- 28 -->
mergeCell - Mark cell ranges that need to be merged:
<!-- Static cell merging -->
{{mergeCell "C4:D5"}} <!-- Merge C4 to D5 region -->
{{mergeCell "F4:G4"}} <!-- Merge F4 to G4 region -->
<!-- Dynamic cell merging: from current position -->
{{mergeCell (concat (_c) (_r) ":" (toColumnName (_c) 3) (_r))}}
<!-- Example: If current is B5, merges B5:E5 (4 columns to the right) -->
<!-- Dynamic cell merging: spanning rows and columns -->
{{mergeCell (concat (_c) (_r) ":" (toColumnName (_c) 2) (add (_r) 2))}}
<!-- Example: If current is C3, merges C3:E5 (3×3 region) -->
<!-- Dynamic merging in loops -->
{{#each sections}}
{{mergeCell (concat "A" (add @index 2) ":D" (add @index 2))}}
<!-- Merge columns A-D for each section row -->
{{/each}}
Notes:
mergeCell produces no output, only collects merge informationStartCell:EndCell (e.g., "A1:B2")hyperlink - Add hyperlinks to Excel cells:
<!-- Basic usage: link to another worksheet -->
{{hyperlink (_cr) "Sheet2!A1" "View Details"}}
<!-- Link to external URL (requires pre-configuration in template) -->
{{hyperlink (_cr) "https://example.com" "Visit Website"}}
<!-- Dynamic links -->
{{#each items}}
{{hyperlink (_cr) (concat "Details!" name) name}}
{{/each}}
Parameters:
(_cr) for current cellNotes:
hyperlink produces no output, only collects hyperlink information"Sheet2!A1")Use {{num value}} to ensure a cell is treated as a number in Excel:
<!-- Without num: treated as text -->
{{employee.salary}}
<!-- With num: treated as number -->
{{num employee.salary}}
This is especially useful when:
img - Insert base64-encoded images into Excel:
<!-- Basic usage: insert image with original dimensions -->
{{img logo.data}}
<!-- Specify width and height (in pixels) -->
{{img photo.data 150 200}}
<!-- Use dimensions from data -->
{{img image.data image.width image.height}}
Features:
Complete Example:
// Prepare image data in JavaScript
import fs from 'fs';
const imageBuffer = fs.readFileSync('logo.png');
const base64Image = imageBuffer.toString('base64');
const data = {
company: {
logo: base64Image,
name: "Tech Company"
},
products: [
{
name: "Product A",
photo: base64Image,
width: 120,
height: 120
},
{
name: "Product B",
photo: base64Image,
width: 100,
height: 100
}
]
};
// Use in template
<!-- Excel template example -->
Company Logo: {{img company.logo 100 50}}
Product List:
{{#each products}}
Product Name: {{name}}
Image: {{img photo width height}}
{{/each}}
Usage Tips:
{{img}} is calleddata:image/png;base64, prefix, just the pure base64 stringdeleteCurrentSheet - Delete the current worksheet being rendered:
<!-- Basic usage -->
{{deleteCurrentSheet}}
<!-- Conditional deletion -->
{{#if shouldDelete}}
{{deleteCurrentSheet}}
{{/if}}
<!-- Delete inactive sheets -->
{{#unless isActive}}
{{deleteCurrentSheet}}
{{/unless}}
Features:
setCurrentSheetName - Rename the current worksheet:
<!-- Static name -->
{{setCurrentSheetName "Sales Report"}}
<!-- Dynamic name -->
{{setCurrentSheetName (concat department.name " - " year)}}
<!-- Loop-based naming -->
{{#each departments}}
{{setCurrentSheetName (concat "Department " @index " - " name)}}
{{/each}}
Features:
\ / ? * [ ]hideCurrentSheet - Hide the current worksheet:
<!-- Normal hide (user can unhide via right-click) -->
{{hideCurrentSheet}}
{{hideCurrentSheet "hidden"}}
<!-- Very hidden (requires VBA to unhide) -->
{{hideCurrentSheet "veryHidden"}}
<!-- Conditional hiding -->
{{#unless (eq userRole "admin")}}
{{hideCurrentSheet "veryHidden"}}
{{/unless}}
Hide Levels:
hidden - Normal hide, users can unhide via Excel's right-click menuveryHidden - Super hide, requires VBA or property editor to unhideFeatures:
Common Use Cases:
<!-- Multi-language reports: delete unused language sheets -->
{{#if (ne language "en-US")}}
{{deleteCurrentSheet}}
{{/if}}
<!-- Dynamic department reports: rename sheets by department -->
{{setCurrentSheetName (concat department.name " Report")}}
<!-- Permission control: hide admin sheets from regular users -->
{{#unless (eq userRole "admin")}}
{{hideCurrentSheet "veryHidden"}}
{{/unless}}
<!-- Conditional workflow: delete, rename, or hide based on status -->
{{#if (eq status "inactive")}}
{{deleteCurrentSheet}}
{{else}}
{{setCurrentSheetName (concat "Active - " name)}}
{{#if isInternal}}
{{hideCurrentSheet}}
{{/if}}
{{/if}}
=== Employee Report ===
Basic Information:
Name: {{employee.name}}
Department: {{employee.department}}
Position: {{employee.position}}
Hire Date: {{employee.hire_date}}
{{#if employee.has_bonus}}
💰 Bonus: ${{employee.bonus_amount}}
{{/if}}
Project Experience (Total {{len projects}}):
{{#each projects}}
{{@index}}. {{name}}
Description: {{description}}
Status: {{status}}
Team Size: {{team_size}} people
{{/each}}
Skills Assessment:
{{#each skills}}
- {{name}}: {{level}}/10 ({{years}} years of experience)
{{/each}}
To remove an entire row in a table, simply add to any cell:
{{removeRow}}
{{#if (gt performance.score 90)}}
🎉 Performance Rating: Excellent
{{else if (gt performance.score 80)}}
👍 Performance Rating: Good
{{else}}
📈 Performance Rating: Needs Improvement
{{/if}}
# Build all targets
npm run build
# Or build separately
npm run build:web # Browser version
npm run build:npm # Node.js version
npm run build:jsr # Deno version
# Rust example
cargo run --example rust_example
# Node.js example
node examples/node_example.js
# Deno example
deno run --allow-read --allow-write examples/deno_example.ts
# Browser example
cd tests/npm_test
node serve.js
# Then open http://localhost:8080 in your browser
# Select examples/template.xlsx file to test
xlsx-handlebars provides a set of utility functions to help you work more efficiently with Excel operations.
Convert between Excel column names and column indices.
use xlsx_handlebars::{to_column_name, to_column_index};
// Column name increment
assert_eq!(to_column_name("A", 0), "A");
assert_eq!(to_column_name("A", 1), "B");
assert_eq!(to_column_name("Z", 1), "AA");
assert_eq!(to_column_name("AA", 1), "AB");
// Column name to index (1-based)
assert_eq!(to_column_index("A"), 1);
assert_eq!(to_column_index("Z"), 26);
assert_eq!(to_column_index("AA"), 27);
assert_eq!(to_column_index("BA"), 53);
JavaScript/TypeScript Example:
import { wasm_to_column_name, wasm_to_column_index } from 'xlsx-handlebars';
// Column name increment
console.log(wasm_to_column_name("A", 1)); // "B"
console.log(wasm_to_column_name("Z", 1)); // "AA"
// Column name to index
console.log(wasm_to_column_index("AA")); // 27
console.log(wasm_to_column_index("BA")); // 53
Convert between Unix timestamps and Excel date serial numbers. Excel uses serial numbers starting from 1900-01-01 to represent dates.
use xlsx_handlebars::{timestamp_to_excel_date, excel_date_to_timestamp};
// Timestamp to Excel date
let timestamp = 1704067200000i64; // 2024-01-01 00:00:00 UTC
let excel_date = timestamp_to_excel_date(timestamp);
println!("Excel date serial number: {}", excel_date); // 45294.0
// Excel date to timestamp
if let Some(ts) = excel_date_to_timestamp(45294.0) {
println!("Timestamp: {}", ts); // 1704067200000
}
JavaScript/TypeScript Example:
import {
wasm_timestamp_to_excel_date,
wasm_excel_date_to_timestamp
} from 'xlsx-handlebars';
// Date to Excel serial number
const date = new Date('2024-01-01T00:00:00Z');
const excelDate = wasm_timestamp_to_excel_date(date.getTime());
console.log('Excel date:', excelDate); // 45294.0
// Excel serial number to date
const timestamp = wasm_excel_date_to_timestamp(45294.0);
if (timestamp !== null) {
const convertedDate = new Date(timestamp);
console.log('Date:', convertedDate.toISOString());
}
Common Use Cases:
// Dynamically generate cell references
let start_col = "B";
let num_cols = 5;
for i in 0..num_cols {
let col_name = to_column_name(start_col, i);
let col_index = to_column_index(&col_name);
println!("Column {}: name={}, index={}", i, col_name, col_index);
}
// Include dates in template data
use serde_json::json;
let date_timestamp = 1704067200000i64; // 2024-01-01
let excel_date = timestamp_to_excel_date(date_timestamp);
let data = json!({
"report_date": excel_date,
"employee": {
"name": "John Doe",
"hire_date": timestamp_to_excel_date(1609459200000i64) // 2021-01-01
}
});
These utility functions help you:
xlsx-handlebars delivers industry-leading performance powered by Rust:
| Data Size | Processing Time | Throughput |
|---|---|---|
| 1,000 rows | ~0.02s | Real-time generation |
| 10,000 rows | ~0.21s | Online exports |
| 100,000 rows | ~2.12s | Batch processing |
| 1,000,000 rows | ~21s | Big data reports |
Performance Comparison (100,000 rows):
| Technology | Time | Speed vs xlsx-handlebars |
|---|---|---|
| xlsx-handlebars (Rust) | 2.12s | 1x (baseline) ⭐ |
| Python (openpyxl) | 30-60s | 14-28x slower |
| JavaScript (xlsx.js) | 15-30s | 7-14x slower |
| Java (Apache POI) | 8-15s | 3-7x slower |
| C# (EPPlus) | 5-10s | 2-4x slower |
Why So Fast?
This project is licensed under the MIT License - see the LICENSE-MIT file for details.
xlsx-handlebars - Making XLSX template processing simple and efficient
