Criar uma Planilha XLSX do Zero
O XlsxWriter constrói um arquivo Excel .xlsx do zero na memória e o grava em disco ou em um buffer de bytes — sem template, sem instalação do Excel, sem automação COM. É a ferramenta certa quando você quer controle total sobre abas, células, estilos, mesclagens e larguras de coluna, em vez de mapear através do IR agnóstico de formato.
O escritor faz parte do mesmo núcleo em Rust que extrai XLSX a 5,0ms de média com 100% de taxa de aprovação em arquivos Office válidos, e está disponível em Python, Rust, Go, C# e JavaScript (Node nativo).
Como criar um arquivo XLSX do zero?
Instancie um XlsxWriter, adicione uma aba, escreva células por (row, col) (ambos começando em 0) e chame save. Os valores das células podem ser str, int, float, bool ou None. O mesmo padrão funciona em todos os bindings.
Rust
use office_oxide::xlsx::write::{XlsxWriter, CellData, CellStyle, NumberFormat};
fn main() -> office_oxide::Result<()> {
let mut wb = XlsxWriter::new();
let mut sheet = wb.add_sheet("Sales");
// Bold header row
let header = CellStyle::new().bold().background("D3D3D3");
sheet.set_cell_styled(0, 0, CellData::String("Item".into()), header.clone());
sheet.set_cell_styled(0, 1, CellData::String("Amount".into()), header);
// Data rows
sheet.set_cell(1, 0, CellData::String("Widget".into()));
sheet.set_cell(1, 1, CellData::Number(1500.0));
sheet.set_cell(2, 0, CellData::String("Gadget".into()));
sheet.set_cell(2, 1, CellData::Number(2400.0));
// SUM formula with currency formatting (omit the leading '=')
let currency = CellStyle::new().number_format(NumberFormat::Currency);
sheet.set_cell_styled(3, 1, CellData::Formula("SUM(B2:B3)".into()), currency);
// Merge a title banner across two columns
sheet.merge_cells(4, 0, 1, 2);
sheet.set_column_width(0, 20.0);
sheet.set_column_width(1, 15.0);
wb.save("sales.xlsx")?;
Ok(())
}
Python
from office_oxide import XlsxWriter
wb = XlsxWriter()
sheet = wb.add_sheet("Sales") # -> 0 (the sheet index)
# Header row — bold, light-grey fill
wb.set_cell_styled(sheet, 0, 0, "Item", bold=True, bg_color="D3D3D3")
wb.set_cell_styled(sheet, 0, 1, "Amount", bold=True, bg_color="D3D3D3")
# Data rows: row, col are 0-based
wb.set_cell(sheet, 1, 0, "Widget")
wb.set_cell(sheet, 1, 1, 1500.0)
wb.set_cell(sheet, 2, 0, "Gadget")
wb.set_cell(sheet, 2, 1, 2400.0)
# Widen the columns (Excel character units)
wb.set_column_width(sheet, 0, 20.0)
wb.set_column_width(sheet, 1, 15.0)
wb.save("sales.xlsx")
JavaScript
import { writeFileSync } from 'node:fs';
import { XlsxWriter } from 'office-oxide';
const wb = new XlsxWriter();
const sheet = wb.addSheet('Sales'); // 0-based index
// Header row — bold, grey fill (6-char hex string or null)
wb.setCellStyled(sheet, 0, 0, 'Item', true, 'D3D3D3');
wb.setCellStyled(sheet, 0, 1, 'Amount', true, 'D3D3D3');
// Data rows: value may be null, string, number, or boolean
wb.setCell(sheet, 1, 0, 'Widget');
wb.setCell(sheet, 1, 1, 1500.0);
wb.setCell(sheet, 2, 0, 'Gadget');
wb.setCell(sheet, 2, 1, 2400.0);
wb.mergeCells(sheet, 3, 0, 1, 2); // title banner: 1 row x 2 cols
wb.setColumnWidth(sheet, 0, 20.0);
wb.setColumnWidth(sheet, 1, 15.0);
wb.save('sales.xlsx');
// Or export to a Buffer:
const data = wb.toBytes();
writeFileSync('sales-copy.xlsx', data);
wb.close(); // release the native handle
Go
package main
import (
"os"
officeoxide "github.com/yfedoseev/office_oxide/go"
)
func main() {
wb := officeoxide.NewXlsxWriter()
defer wb.Close()
sheet := wb.AddSheet("Sales") // uint32, 0-based index
// Header row — bold with a grey fill
wb.SetCellStyled(sheet, 0, 0, "Item", true, "D3D3D3")
wb.SetCellStyled(sheet, 0, 1, "Amount", true, "D3D3D3")
// Data rows: value may be nil, string, float64, int, or bool
wb.SetCell(sheet, 1, 0, "Widget")
wb.SetCell(sheet, 1, 1, 1500.0)
wb.SetCell(sheet, 2, 0, "Gadget")
wb.SetCell(sheet, 2, 1, 2400.0)
wb.MergeCells(sheet, 3, 0, 1, 2) // title banner: 1 row x 2 cols
wb.SetColumnWidth(sheet, 0, 20.0)
wb.SetColumnWidth(sheet, 1, 15.0)
if err := wb.Save("sales.xlsx"); err != nil {
panic(err)
}
// Or export to bytes:
data, err := wb.ToBytes()
if err != nil {
panic(err)
}
_ = os.WriteFile("sales-copy.xlsx", data, 0o644)
}
C#
using OfficeOxide;
using var wb = new XlsxWriter();
uint sheet = wb.AddSheet("Sales"); // 0-based index
// Header row — bold, grey fill (6-char hex, no '#')
wb.SetCellStyled(sheet, 0, 0, "Item", bold: true, bgColor: "D3D3D3");
wb.SetCellStyled(sheet, 0, 1, "Amount", bold: true, bgColor: "D3D3D3");
// Data rows: value may be null, string, double, int, long, or bool
wb.SetCell(sheet, 1, 0, "Widget");
wb.SetCell(sheet, 1, 1, 1500.0);
wb.SetCell(sheet, 2, 0, "Gadget");
wb.SetCell(sheet, 2, 1, 2400.0);
wb.MergeCells(sheet, 3, 0, 1, 2); // title banner: 1 row x 2 cols
wb.SetColumnWidth(sheet, 0, 20.0);
wb.SetColumnWidth(sheet, 1, 15.0);
wb.Save("sales.xlsx");
// Or export to a byte[]:
byte[] data = wb.ToBytes();
File.WriteAllBytes("sales-copy.xlsx", data);
C
OfficeXlsxWriterHandle *w = office_xlsx_writer_new();
uint32_t s = office_xlsx_writer_add_sheet(w, "Sales"); /* 0-based index */
/* value_type in the writer: EMPTY=0, STRING=1, NUMBER=2 (no BOOLEAN) */
/* header row — bold + 6-char hex bg ("D3D3D3") or NULL */
office_xlsx_sheet_set_cell_styled(w, s, 0, 0, OFFICE_CELL_STRING, "Item", 0.0, true, "D3D3D3");
office_xlsx_sheet_set_cell_styled(w, s, 0, 1, OFFICE_CELL_STRING, "Amount", 0.0, true, "D3D3D3");
/* data rows */
office_xlsx_sheet_set_cell(w, s, 1, 0, OFFICE_CELL_STRING, "Widget", 0.0);
office_xlsx_sheet_set_cell(w, s, 1, 1, OFFICE_CELL_NUMBER, NULL, 1500.0);
office_xlsx_sheet_set_cell(w, s, 2, 0, OFFICE_CELL_STRING, "Gadget", 0.0);
office_xlsx_sheet_set_cell(w, s, 2, 1, OFFICE_CELL_NUMBER, NULL, 2400.0);
office_xlsx_sheet_merge_cells(w, s, 3, 0, 1, 2); /* title banner: row_span/col_span >= 1 */
office_xlsx_sheet_set_column_width(w, s, 0, 20.0); /* Excel char units */
office_xlsx_sheet_set_column_width(w, s, 1, 15.0);
int err = 0;
office_xlsx_writer_save(w, "sales.xlsx", &err);
/* or: uint8_t *b = office_xlsx_writer_to_bytes(w, &out_len, &err); ... office_oxide_free_bytes */
office_xlsx_writer_free(w);
Para passar os bytes para uma resposta HTTP ou um armazenamento de objetos em vez de gravar em disco, use to_bytes() (exemplo em Python abaixo):
Python
data = wb.to_bytes() # -> bytes (a complete .xlsx ZIP)
with open("sales.xlsx", "wb") as f:
f.write(data)
Assinaturas dos métodos (Python)
| Método | Assinatura |
|---|---|
| Construtor | XlsxWriter() |
| Adicionar aba | add_sheet(name: str) -> int |
| Definir célula | set_cell(sheet: int, row: int, col: int, value: None|str|bool|int|float) -> None |
| Definir célula estilizada | set_cell_styled(sheet: int, row: int, col: int, value, bold: bool, bg_color: str|None = None) -> None |
| Mesclar | merge_cells(sheet: int, row: int, col: int, row_span: int, col_span: int) -> None |
| Largura da coluna | set_column_width(sheet: int, col: int, width: float) -> None |
| Salvar | save(path) -> None |
| Exportar | to_bytes() -> bytes |
add_sheet retorna o índice baseado em 0 da nova aba — passe-o como o argumento sheet em todas as outras chamadas. Linhas e colunas começam em 0, portanto a célula A1 é (row=0, col=0) e B1 é (row=0, col=1).
Como mesclar células e criar um banner de título?
merge_cells(sheet, row, col, row_span, col_span) mescla um intervalo retangular ancorado em (row, col). Ambos os spans devem ser >= 1; o valor mesclado vem da célula âncora.
Python
from office_oxide import XlsxWriter
wb = XlsxWriter()
s = wb.add_sheet("Report")
# A title that spans columns A through C of the first row
wb.set_cell_styled(s, 0, 0, "Q3 Revenue Report", bold=True, bg_color="FFE699")
wb.merge_cells(s, 0, 0, 1, 3) # 1 row tall, 3 columns wide
# Sub-header
wb.set_cell_styled(s, 1, 0, "Region", bold=True)
wb.set_cell_styled(s, 1, 1, "Q3", bold=True)
wb.set_cell_styled(s, 1, 2, "QoQ %", bold=True)
wb.set_cell(s, 2, 0, "NA")
wb.set_cell(s, 2, 1, 1_200_000)
wb.set_cell(s, 2, 2, 0.18)
wb.set_column_width(s, 0, 18.0)
wb.save("report.xlsx")
Assinaturas por linguagem
Rust — o crate Rust expõe um builder mais rico. add_sheet retorna um handle emprestado SheetData, e as células recebem um valor CellData (String, Number, Boolean, Formula ou Empty) mais um CellStyle opcional. CellStyle é um builder que suporta bold(), italic(), background(), number_format(), align() e mais. Principais assinaturas (em SheetData):
pub fn set_cell(&mut self, row: usize, col: usize, value: CellData) -> &mut Self
pub fn set_cell_styled(&mut self, row: usize, col: usize, value: CellData, style: CellStyle) -> &mut Self
pub fn merge_cells(&mut self, row: usize, col: usize, row_span: usize, col_span: usize) -> &mut Self
pub fn set_column_width(&mut self, col: usize, width: f64) -> &mut Self
e em XlsxWriter: add_sheet(name: &str) -> SheetData<'_>, save(path) -> Result<()> e write_to<W: Write + Seek>(writer) -> Result<()> para saída em memória. Um espelho baseado em índice (sheet_set_cell, sheet_set_cell_styled, sheet_merge_cells, sheet_set_column_width) também está disponível para quem prefere a mesma forma plana dos outros bindings.
Python — XlsxWriter(), add_sheet(name: str) -> int, set_cell(sheet: int, row: int, col: int, value: None|str|bool|int|float) -> None, set_cell_styled(sheet: int, row: int, col: int, value, bold: bool, bg_color: str|None = None) -> None, merge_cells(sheet: int, row: int, col: int, row_span: int, col_span: int) -> None, set_column_width(sheet: int, col: int, width: float) -> None, save(path) -> None, to_bytes() -> bytes.
JavaScript — addSheet(name), setCell(sheet, row, col, value), setCellStyled(sheet, row, col, value, bold, bgColor = null), mergeCells(sheet, row, col, rowSpan, colSpan), setColumnWidth(sheet, col, width), save(path), toBytes(), close().
Go — NewXlsxWriter() *XlsxWriter, AddSheet(name string) uint32, SetCell(sheet, row, col uint32, value any), SetCellStyled(sheet, row, col uint32, value any, bold bool, bgColor string), MergeCells(sheet, row, col, rowSpan, colSpan uint32), SetColumnWidth(sheet, col uint32, width float64), Save(path string) error, ToBytes() ([]byte, error). Passe "" como bgColor para sem preenchimento.
C# — AddSheet(string name) -> uint, SetCell(uint sheet, uint row, uint col, object? value), SetCellStyled(uint sheet, uint row, uint col, object? value, bool bold, string? bgColor = null), MergeCells(uint sheet, uint row, uint col, uint rowSpan, uint colSpan), SetColumnWidth(uint sheet, uint col, double width), Save(string path), ToBytes() -> byte[]. XlsxWriter implementa IDisposable.
C — office_xlsx_writer_new() -> OfficeXlsxWriterHandle*, office_xlsx_writer_add_sheet(w, name) -> uint32_t, office_xlsx_sheet_set_cell(w, sheet, row, col, value_type, value_str, value_num), office_xlsx_sheet_set_cell_styled(w, sheet, row, col, value_type, value_str, value_num, bold, bg_color), office_xlsx_sheet_merge_cells(w, sheet, row, col, row_span, col_span), office_xlsx_sheet_set_column_width(w, sheet, col, width), office_xlsx_writer_save(w, path, &err), office_xlsx_writer_to_bytes(w, &out_len, &err), office_xlsx_writer_free(w). value_type é OFFICE_CELL_EMPTY (0), OFFICE_CELL_STRING (1) ou OFFICE_CELL_NUMBER (2) — o writer não tem tipo de célula booleana.
Atenção: o XLSX-writer-from-scratch está disponível nos bindings nativos (Rust, Python, JavaScript/Node, Go, C#) e no C ABI. A compilação WASM para navegador é somente leitura — não há classes de writer — portanto construa planilhas com um dos itens acima e faça stream dos bytes.
Referência de estilos
Nos bindings Python, Go, C#, JavaScript e C, o setter estilizado expõe dois controles:
bold— um booleano que aplica negrito à célula.bg_color/bgColor— uma string hexadecimal RGB de 6 caracteres sem#inicial (por exemplo,"D3D3D3"para cinza claro,"FFE699"para âmbar). PasseNone/""/nullpara sem preenchimento.
O builder CellStyle do Rust é a superfície completa — itálico, sublinhado, cor da fonte, tamanho da fonte, nome da fonte, formatos de número (General, Integer, Decimal2, Currency, Percent, Percent2, Date, DateTime), alinhamento horizontal e quebra de texto. Os outros bindings expõem as duas opções mais comuns diretamente; para estilos mais ricos nesses idiomas, construa via IR ou faça pós-processamento.
Perguntas frequentes
Linhas e colunas começam em 0 ou em 1?
Tanto row quanto col são inteiros baseados em 0. A célula A1 é (row=0, col=0), B1 é (row=0, col=1) e A2 é (row=1, col=0). O índice de aba retornado por add_sheet também começa em 0.
Preciso instalar o Excel ou algum runtime Microsoft?
Não. O XlsxWriter gera um .xlsx ZIP OOXML válido inteiramente em Rust. Não há automação COM, JVM nem dependência de sistema — o mesmo motor lê XLSX a 5,0ms de média com 100% de aprovação em arquivos Office válidos.
Como obter o arquivo como bytes em vez de gravar em disco?
Chame to_bytes() (Python/C#/JS) ou ToBytes() (Go), que retorna um buffer completo de .xlsx. Em Rust use write_to(writer) com qualquer alvo Write + Seek, como um Cursor<Vec<u8>>. Ideal para respostas HTTP e uploads para armazenamento de objetos.
Posso escrever fórmulas?
Sim, em Rust via CellData::Formula("SUM(B2:B3)") (omita o = inicial). Os bindings planos (Python/Go/C#/JS) atualmente suportam valores de células string, número, booleano e vazio via set_cell.
Como adicionar uma segunda planilha?
Chame add_sheet novamente — cada chamada acrescenta uma aba e retorna o próximo índice baseado em 0. Passe esse índice como o argumento sheet nas chamadas subsequentes de set_cell / merge_cells / set_column_width.
Veja também
- Criar documentos a partir do IR — um schema, três formatos de destino (DOCX/XLSX/PPTX)
- Editar células XLSX no lugar — modificar células em uma planilha existente com
EditableDocument - Extrair dados de XLSX — ler uma planilha no IR estruturado