Rust Office 라이브러리 — 빠른 시작
office_oxide는 Office 문서(DOCX, XLSX, PPTX 및 레거시 바이너리 형식 DOC, XLS, PPT)를 파싱·변환·편집하는 순수 Rust 크레이트입니다. 단일 크레이트, 통합된 Document 핸들, 네이티브 의존성 0.
설치
[dependencies]
office_oxide = "0.1.0"
선택적 피처:
office_oxide = { version = "0.1.0", features = ["mmap"] } # 큰 OOXML을 위한 mmap 열기
office_oxide = { version = "0.1.0", features = ["parallel"] } # rayon 기반 병렬 파싱 헬퍼
문서 읽기
use office_oxide::Document;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let doc = Document::open("report.docx")?;
println!("{}", doc.plain_text());
Ok(())
}
원샷 헬퍼:
let text = office_oxide::extract_text("report.docx")?;
핵심 API
Document 핸들은 모든 형식에서 동일하게 동작합니다 — 확장자 감지에 더해 매직 바이트로 검증합니다.
use office_oxide::{Document, DocumentFormat};
let doc = Document::open("file.xlsx")?;
assert_eq!(doc.format(), DocumentFormat::Xlsx);
let plain = doc.plain_text();
let md = doc.to_markdown();
let html = doc.to_html();
let ir = doc.to_ir(); // 형식 무관 IR
doc.save_as("file.docx")?; // 레거시 → OOXML도 가능
Document::open은 AsRef<Path>를, Document::from_reader는 명시적인 DocumentFormat과 함께 Read + Seek + Send + 'static을 받습니다.
모듈 레벨 단축:
let text = office_oxide::extract_text("file.docx")?;
let md = office_oxide::to_markdown("file.pptx")?;
let html = office_oxide::to_html("file.xlsx")?;
형식별 접근
시트, 슬라이드, 테이블 셀 같은 형식별 데이터가 필요하면 내부 문서를 풀어내세요:
if let Some(xlsx) = doc.as_xlsx() {
for sheet in xlsx.sheets() {
println!("sheet: {}", sheet.name());
}
}
as_docx, as_pptx, as_doc, as_xls, as_ppt도 같은 패턴입니다.
편집
EditableDocument는 읽기·수정·저장 흐름을 지원하며, 변경되지 않은 OPC 파트(이미지, 차트, 스타일, 관계)를 그대로 보존합니다. DOCX, XLSX, PPTX만 지원합니다.
use office_oxide::edit::EditableDocument;
let mut doc = EditableDocument::open("template.docx")?;
let n = doc.replace_text("{{name}}", "Alice");
println!("{n}건 치환");
doc.save("out.docx")?;
replace_text는 DOCX의 <w:t> 요소와 PPTX의 <a:t> 요소를 순회하며 치환 횟수를 반환합니다(XLSX는 0 반환 — 대신 set_cell 사용).
XLSX 셀 설정
use office_oxide::edit::EditableDocument;
use office_oxide::xlsx::edit::CellValue;
let mut wb = EditableDocument::open("budget.xlsx")?;
wb.set_cell(0, "B2", CellValue::Number(42.0))?;
wb.set_cell(0, "A1", CellValue::String("Total".into()))?;
wb.set_cell(0, "C1", CellValue::Boolean(true))?;
wb.set_cell(0, "D1", CellValue::Empty)?;
wb.save("budget.xlsx")?;
시트 인덱스는 0부터; 셀 참조는 표준 스프레드시트 표기법(A1, AA12).
형식 무관 IR
DocumentIR은 형식 간 구조적 다리입니다 — to_html, save_as, 레거시 변환을 떠받칩니다. Serialize / Deserialize를 구현하므로 JSON으로 출력해 다운스트림 도구에 넘길 수 있습니다.
let legacy = Document::open("old.doc")?;
legacy.save_as("migrated.docx")?; // CFB → OOXML 한 줄로
바이트에서 열기
use std::io::Cursor;
use office_oxide::{Document, DocumentFormat};
let bytes = std::fs::read("file.pptx")?;
let doc = Document::from_reader(Cursor::new(bytes), DocumentFormat::Pptx)?;
메모리 매핑 열기
mmap 피처를 켜면 Document::open_mmap이 큰 OOXML 파일을 힙으로 복사하지 않습니다:
let doc = Document::open_mmap("huge.xlsx")?;
mmap 가능한 것은 DOCX/XLSX/PPTX뿐 — 레거시 CFB 파서는 소유 버퍼가 필요합니다.
오류
모든 실패 가능 진입점은 office_oxide::Result<T>(즉 Result<T, OfficeError>)를 반환합니다. enum은 IO, 파싱, 미지원 형식, 추출 실패를 다룹니다.
use office_oxide::{Document, OfficeError};
match Document::open("weird.file") {
Ok(doc) => println!("{}", doc.plain_text()),
Err(OfficeError::UnsupportedFormat(ext)) => eprintln!(".{ext}는 열 수 없습니다"),
Err(e) => eprintln!("실패: {e}"),
}
문제 해결
| 증상 | 원인 |
|---|---|
UnsupportedFormat("(none)") |
경로에 확장자가 없습니다 — from_reader에 명시적인 DocumentFormat을 전달하세요. |
| DOC 텍스트 깨짐 | 파일이 암호화되었거나 드문 piece-table 인코딩일 수 있습니다. CFB 매직 D0 CF 11 E0을 확인하세요. |
| DOCX 하이퍼링크 누락 | 하이퍼링크는 w:rels로 해석됩니다. ZIP 내 .rels 사이드카가 존재하는지 확인하세요. |
| 작은 스택 스레드에서 stack overflow | office_oxide는 RLIMIT_STACK < 12 MB일 때 16 MB 파싱 스레드를 만듭니다. 자체 스레드 풀에서는 Builder::stack_size(16 * 1024 * 1024)를 지정하세요. |
더 보기
- Python 빠른 시작 — 같은 API
- 성능 벤치마크 — 6,062개 파일의 전체 수치
- 아키텍처: ARCHITECTURE.md
- 크레이트: crates.io, 문서: docs.rs