Skip to content

Создание рабочей книги XLSX с нуля

XlsxWriter создаёт новый Excel-файл .xlsx в памяти и записывает его на диск или в байтовый буфер — без шаблонов, без установки Excel, без COM-автоматизации. Это правильный инструмент, когда нужен полный контроль над листами, ячейками, стилями, объединениями и шириной столбцов, в отличие от маппинга через формато-независимый IR.

Писатель является частью того же ядра на Rust, которое читает XLSX со средним временем 5,0 мс и 100% успешным прохождением на корректных файлах Office, и доступен из Python, Rust, Go, C# и JavaScript (нативный Node).

Как создать XLSX-файл с нуля?

Создайте XlsxWriter, добавьте лист, запишите ячейки по координатам (row, col) (оба индекса с нуля), затем вызовите save. Значения ячеек могут быть str, int, float, bool или None. Одна и та же схема работает во всех привязках.

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);

Чтобы передать байты в HTTP-ответ или объектное хранилище вместо записи на диск, используйте to_bytes() (пример на Python ниже):

Python

data = wb.to_bytes()          # -> bytes (a complete .xlsx ZIP)
with open("sales.xlsx", "wb") as f:
    f.write(data)

Сигнатуры методов (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

add_sheet возвращает индекс нового листа (с нуля) — передавайте его в качестве аргумента sheet во все остальные вызовы. Строки и столбцы нумеруются с нуля: ячейка A1 соответствует (row=0, col=0), B1(row=0, col=1).

Как объединить ячейки и создать заголовочный баннер?

merge_cells(sheet, row, col, row_span, col_span) объединяет прямоугольный диапазон с якорем в (row, col). Оба параметра span должны быть >= 1; значение объединённой области берётся из якорной ячейки.

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")

Сигнатуры по языкам

Rust — крейт Rust предоставляет более богатый строитель. add_sheet возвращает заимствованный дескриптор SheetData, а ячейки принимают значение CellData (String, Number, Boolean, Formula или Empty) и опциональный CellStyle. CellStyle — это строитель, поддерживающий bold(), italic(), background(), number_format(), align() и другие. Ключевые сигнатуры (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

На XlsxWriter: add_sheet(name: &str) -> SheetData<'_>, save(path) -> Result<()> и write_to<W: Write + Seek>(writer) -> Result<()> для записи в память. Также доступно индексное зеркало (sheet_set_cell, sheet_set_cell_styled, sheet_merge_cells, sheet_set_column_width) — для тех, кто предпочитает плоскую форму, как в других привязках.

PythonXlsxWriter(), 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.

JavaScriptaddSheet(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().

GoNewXlsxWriter() *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). Передайте "" в bgColor для отключения заливки.

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 реализует IDisposable.

Coffice_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) или OFFICE_CELL_NUMBER (2) — булевого типа ячейки у писателя нет.

Примечание: запись XLSX с нуля доступна в нативных привязках (Rust, Python, JavaScript/Node, Go, C#) и C ABI. Браузерная сборка WASM работает только на чтение — классов писателя в ней нет — поэтому создавайте рабочие книги через одну из перечисленных привязок и передавайте байты потоком.

Справочник по стилям

В привязках Python, Go, C#, JavaScript и C стилизованный сеттер предоставляет два параметра:

  • bold — булево значение, применяющее жирное начертание к ячейке.
  • bg_color / bgColor — 6-символьная RGB hex-строка без ведущего # (например, "D3D3D3" для светло-серого, "FFE699" для янтарного). Передайте None / "" / null для отключения заливки.

Строитель CellStyle в Rust — наиболее полная поверхность: курсив, подчёркивание, цвет шрифта, размер шрифта, имя шрифта, числовые форматы (General, Integer, Decimal2, Currency, Percent, Percent2, Date, DateTime), горизонтальное выравнивание и перенос текста. Другие привязки напрямую открывают лишь два наиболее часто используемых параметра; для более сложной стилизации из этих языков используйте IR или постобработку.

Часто задаваемые вопросы

Строки и столбцы нумеруются с нуля или с единицы? И row, и col — целые числа с нулевой нумерацией. Ячейка A1 — это (row=0, col=0), B1(row=0, col=1), A2(row=1, col=0). Индекс листа, возвращаемый add_sheet, тоже начинается с нуля.

Нужно ли устанавливать Excel или Microsoft Runtime? Нет. XlsxWriter генерирует корректный OOXML .xlsx ZIP полностью на Rust. Никакой COM-автоматизации, JVM и системных зависимостей — тот же движок читает XLSX со средним временем 5,0 мс и 100% успехом на корректных файлах Office.

Как получить файл в виде байтов, не записывая на диск? Вызовите to_bytes() (Python/C#/JS) или ToBytes() (Go) — будет возвращён полный байтовый буфер .xlsx. В Rust используйте write_to(writer) с любым Write + Seek-таргетом, например Cursor<Vec<u8>>. Это идеально для HTTP-ответов и загрузки в объектные хранилища.

Можно ли записывать формулы? Да, в Rust через CellData::Formula("SUM(B2:B3)") (ведущий = опускается). Плоские привязки (Python/Go/C#/JS) пока поддерживают строковые, числовые, булевы и пустые значения ячеек через set_cell.

Как добавить второй рабочий лист? Снова вызовите add_sheet — каждый вызов добавляет лист и возвращает следующий индекс с нуля. Передавайте этот индекс как аргумент sheet в последующие вызовы set_cell / merge_cells / set_column_width.

Смотрите также