XLSX 워크북을 처음부터 만들기
XlsxWriter는 새 Excel .xlsx 파일을 메모리에서 구축하고 디스크나 바이트 버퍼에 씁니다 — 템플릿 불필요, Excel 설치 불필요, COM 자동화 불필요. 형식에 독립적인 IR을 통해 매핑하는 대신 시트, 셀, 스타일, 병합, 열 너비를 완전히 제어하고 싶을 때 적합한 도구입니다.
이 라이터는 평균 5.0ms, 유효한 Office 파일에 대해 100% 통과율로 XLSX를 읽는 동일한 Rust 코어의 일부이며, Python, Rust, Go, C#, JavaScript(네이티브 Node)에서 사용할 수 있습니다.
XLSX 파일을 처음부터 만들려면?
XlsxWriter를 생성하고, 시트를 추가하고, (row, col) (둘 다 0부터 시작)로 셀을 작성한 다음 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);
바이트를 디스크에 쓰는 대신 웹 응답이나 오브젝트 스토어에 전달하려면 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는 새 시트의 0부터 시작하는 인덱스를 반환합니다 — 이것을 다른 모든 호출의 sheet 인자로 전달하세요. 행과 열은 0부터 시작하므로 셀 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)도 사용할 수 있습니다.
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). 채우기 없음의 경우 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을 구현합니다.
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), 또는 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 16진수 문자열 (예: 연회색은"D3D3D3", 황갈색은"FFE699"). 채우기 없음에는None/""/null을 전달하세요.
Rust의 CellStyle 빌더가 가장 풍부한 기능을 제공합니다 — 이탤릭, 밑줄, 글꼴 색상, 글꼴 크기, 글꼴 이름, 숫자 형식(General, Integer, Decimal2, Currency, Percent, Percent2, Date, DateTime), 수평 정렬, 텍스트 줄바꿈. 다른 바인딩은 가장 일반적인 두 가지 옵션만 직접 노출합니다. 해당 언어에서 더 풍부한 스타일링이 필요하다면 IR을 통해 구축하거나 후처리하세요.
자주 묻는 질문
행과 열은 0부터 시작하나요, 1부터 시작하나요?
row와 col 모두 0부터 시작하는 정수입니다. 셀 A1은 (row=0, col=0), B1은 (row=0, col=1), A2는 (row=1, col=0)입니다. add_sheet가 반환하는 시트 인덱스도 0부터 시작합니다.
Excel이나 Microsoft 런타임을 설치해야 하나요?
아니요. XlsxWriter는 순수 Rust로 유효한 OOXML .xlsx ZIP을 생성합니다. COM 자동화, JVM, 시스템 의존성이 전혀 없습니다 — 동일한 엔진이 유효한 Office 파일에 대해 평균 5.0ms, 100% 통과율로 XLSX를 읽습니다.
디스크에 쓰는 대신 바이트로 파일을 받으려면?
to_bytes()(Python/C#/JS) 또는 ToBytes()(Go)를 호출하면 완전한 .xlsx 바이트 버퍼가 반환됩니다. Rust에서는 Cursor<Vec<u8>> 같은 Write + Seek 대상에 write_to(writer)를 사용하세요. HTTP 응답 및 오브젝트 스토어 업로드에 이상적입니다.
수식을 작성할 수 있나요?
네, Rust에서는 CellData::Formula("SUM(B2:B3)")(앞의 = 생략)로 가능합니다. 평면 바인딩(Python/Go/C#/JS)은 현재 set_cell을 통해 문자열, 숫자, 불리언, 빈 셀 값을 지원합니다.
두 번째 워크시트를 추가하려면?
add_sheet를 다시 호출하세요 — 각 호출이 시트를 추가하고 다음 0부터 시작하는 인덱스를 반환합니다. 이 인덱스를 후속 set_cell / merge_cells / set_column_width 호출의 sheet 인자로 전달하세요.
참고 항목
- IR에서 문서 만들기 — 하나의 스키마, 세 가지 대상 형식 (DOCX/XLSX/PPTX)
- XLSX 셀 인플레이스 편집 —
EditableDocument로 기존 워크북의 셀 수정 - XLSX에서 데이터 추출 — 워크북을 구조화된 IR로 읽기