Skip to content

Repository files navigation

Spine

A Go library for reading and writing Microsoft Office documents (PPTX, DOCX, XLSX) using the Open Packaging Conventions (OPC) standard.

Features

Each bullet links to the guide that carries the detail.

  • OPC package support — a low-level API for working with Open Packaging Convention packages.
  • Round-trip preservation — byte-identical round-trip fidelity for unmodified parts across all formats.
  • In-memory I/OSaveBytes and OpenReader on all three formats; see Working with Documents in Memory.
  • Merge, append & split — combine or divide packages with automatic id, part-name, and relationship remapping (no dangling references or duplicate parts): pptx, docx, xlsx.
  • Password encryption & digital signatures — write real AES-encrypted documents (agile and standard schemes) across all three formats, read them back into a document model in every format (pass opc.WithPassword to the ordinary Open), and sign/verify OPC package signatures; see Encryption and signing.
  • VBA macros — extract, inject/replace, and remove vbaProject.bin; see the trust caveat.
  • Charts — a format-agnostic builder for column, bar, line, pie, scatter, combo, bubble, and 3D charts wired into all three formats; see Charts.
  • Text extraction — a symmetric, read-only "give me all the text" API across all three formats for search, indexing, and LLM ingestion: docx, xlsx, pptx.
  • Document & custom properties — core, extended, and custom document properties on all three formats; see Document properties.
  • Embedded OLE objects — read with OLEObjects() and embed new objects in docx, pptx, and xlsx; unmodified objects round-trip verbatim.
  • Form controls, ActiveX, ink & 3D models — read Word/Excel form controls and ActiveX across formats (details), and extract ink annotations and 3D models.
  • PowerPoint (PPTX) — create and modify presentations: shapes, tables, images, charts, animations, transitions, SmartArt, media, sections, and comments; see docs/pptx.md.
  • Word (DOCX) — create and modify documents: styles, tables, tracked changes, comments, footnotes, mail merge, content controls, and fields; see docs/docx.md.
  • Excel (XLSX) — create and modify workbooks: add and delete sheets (deletion cascades to the parts only that sheet owned), formulas, styles, pivot tables, conditional formatting, sparklines, tables, and page/print setup; see docs/xlsx.md.

Installation

go get github.com/mgilbir/spine

Quick Start

Creating a PowerPoint Presentation

package main

import (
    "github.com/mgilbir/spine/common/dml"
    "github.com/mgilbir/spine/pptx"
)

func main() {
    // Create a new presentation
    p := pptx.Create()
    p.Properties.Title = "My Presentation"
    p.Properties.Creator = "Author Name"

    // Add a slide
    slide := p.AddSlide()
    slide.SetName("Introduction")

    // Add a text box
    textBox := slide.AddTextBox()
    textBox.SetPosition(dml.Inches(1), dml.Inches(1))
    textBox.SetSize(dml.Inches(8), dml.Inches(1))
    textBox.SetText("Hello, World!")

    // Save the presentation
    if err := p.Save("presentation.pptx"); err != nil {
        panic(err)
    }
}

Creating a Word Document

package main

import (
    "github.com/mgilbir/spine/docx"
)

func main() {
    // Create a new document
    doc := docx.Create()
    doc.Properties.Title = "My Document"

    // Add a heading
    doc.AddHeading("Welcome", 1)

    // Add a paragraph
    doc.AddParagraphWithText("This is a simple document created with Spine.")

    // Add formatted text
    p := doc.AddParagraph()
    bold := p.AddRun()
    bold.SetText("Bold text")
    bold.SetBold(true)

    // Save the document
    if err := doc.Save("document.docx"); err != nil {
        panic(err)
    }
}

Creating an Excel Spreadsheet

package main

import (
    "fmt"
    "github.com/mgilbir/spine/xlsx"
)

func main() {
    // Create a new workbook
    wb := xlsx.Create()

    // Add a sheet. AddSheet reports an illegal or already-taken name rather
    // than quietly renaming the sheet.
    sheet, err := wb.AddSheet("Sales")
    if err != nil {
        panic(err)
    }

    // Set cell values
    sheet.SetCellValue("A1", "Product")
    sheet.SetCellValue("B1", "Revenue")
    sheet.SetCellValue("A2", "Widgets")
    sheet.SetCellValue("B2", 1500.0)
    sheet.SetCellValue("A3", "Gadgets")
    sheet.SetCellValue("B3", 3200.0)

    // Add a formula
    cell, _ := sheet.Cell("B4")
    cell.SetFormula("SUM(B2:B3)")

    // Save the workbook
    if err := wb.Save("spreadsheet.xlsx"); err != nil {
        panic(err)
    }

    fmt.Printf("Sheets: %d\n", wb.SheetCount())
}

For the full per-format feature set and code walkthroughs (opening, templating, comments, hyperlinks, images, protection), see the guides: pptx, docx, xlsx, and charts.

Opening vs. Creating Documents

Create builds a new document from scratch; Open/OpenReader parse an existing file. Both return the same types with the same mutation API, and edits made after Open persist on save: document properties, cell values, text edits, and added slides, sheets, or paragraphs are all written back, while parts you did not touch are preserved byte-for-byte. Known asymmetries that remain:

  • pptx: Create() produces a 4:3 deck (use CreateWithOptions with SlideSizeWidescreen, or CreateWidescreen(), for 16:9). The baked master and layouts size their placeholders to the slide, so both aspect ratios are internally consistent.
  • docx: markup the library does not model is captured raw when a document is opened and preserved verbatim on save, but it is opaque to the API — Text() does not see text inside it and SetText/ReplaceText cannot edit it.
  • pptx: master and layout Placeholders() and Theme() are read-only views; mutating the returned values does not change the saved parts. To edit placeholder geometry use EditablePlaceholders()/EditablePlaceholder(), which write back to the part.

Working with Documents in Memory

All three formats can be saved to and opened from memory. SaveBytes exists on pptx.Presentation, docx.Document, and xlsx.Workbook; each package also provides OpenReader:

package main

import (
    "bytes"
    "github.com/mgilbir/spine/xlsx"
)

func main() {
    wb := xlsx.Create()
    sheet, err := wb.AddSheet("Export")
    if err != nil {
        panic(err)
    }
    if err := sheet.SetCellValue("A1", "hello"); err != nil {
        panic(err)
    }

    data, err := wb.SaveBytes()
    if err != nil {
        panic(err)
    }

    wb2, err := xlsx.OpenReader(bytes.NewReader(data), int64(len(data)))
    if err != nil {
        panic(err)
    }
    defer wb2.Close()
}

For xlsx, WriteToBuffer still works but is deprecated: it is SaveBytes wrapped in a *bytes.Buffer, and neither docx nor pptx has a counterpart.

Validation

Every top-level type — pptx.Presentation, docx.Document, xlsx.Workbook — has a Validate() method that inspects the current in-memory model (without saving) and returns a validate.Report (from github.com/mgilbir/spine/common/validate): a slice of structured findings. Each finding carries a stable Code, a Severity (error or warning), the Part it concerns, and a human-readable Detail, so callers can triage programmatically rather than parse a string.

Save, SaveBytes, and SaveTo run Validate() first and refuse to write when any error-severity finding is present, so a structurally corrupt package is never produced. Warnings never block a save. The findings are sound — no error-severity finding fires on a file the corresponding Office app accepts.

p, _ := pptx.Open("deck.pptx")
// ... mutate ...

// Inspect findings without saving.
for _, f := range p.Validate() {
    fmt.Printf("%s [%s] %s: %s\n", f.Code, f.Severity, f.Part, f.Detail)
}

// SaveBytes validates first and returns the report as an error if any
// finding is error-severity; nothing is written in that case.
if _, err := p.SaveBytes(); err != nil {
    log.Fatal(err)
}

This is the complete catalog. Error severity means the save is refused; warning severity means the finding is reported and the save proceeds, because the corresponding Office app accepts the structure. The table is the single source of truth for these severities and is verified against the validators themselves by internal/docsguard — a severity that changes in code without changing here fails the build.

Code Severity Fires when
dangling-rel error and warning A reference names a relationship id that does not exist. Error where the structure cannot survive it — a section's headerReference/footerReference (docx), a workbook sheet (xlsx), a master's sldLayoutId (pptx). Warning for the references Word opens anyway: a drawing's image or chart embed, and a hyperlink (docx).
defined-name-scope error A definedName is scoped to a localSheetId that is out of range for the workbook.
duplicate-part-name error Two part names collide case-insensitively, which OPC forbids.
rel-id-dup error Two relationships in one part's .rels share an Id, so a reference resolves to whichever the reader sees first (pptx).
merge-overlap error Two merged cell ranges on a sheet overlap.
shape-id-dup error Two shapes on one slide share a cNvPr id.
shared-formula-orphan error A shared-formula follower cell (t="shared") has no master defining that si.
sheet-id-dup error Two sheets share a sheetId.
bookmark-missing warning A bookmarkEnd has no matching bookmarkStart (or the reverse).
chart-no-rel warning A slide's chart reference has no matching relationship (pptx).
chart-target-missing warning A drawing's chart relationship points at a part that is not in the package (xlsx).
comment-missing warning A commentReference names a comment id that comments.xml does not define (docx).
comment-no-author warning A comment references an author id with no entry in the author list; PowerPoint shows it as "Unknown".
comment-person-orphan warning A threaded comment references a person id that persons.xml does not define (xlsx).
comment-ref-invalid warning A comment's cell reference is not a valid cell address (xlsx).
content-type-missing warning A part has no content type — no Default extension mapping and no Override.
data-validation-range warning A data-validation sqref is malformed or out of range.
hyperlink-no-rel warning A slide hyperlink's r:id has no matching relationship (pptx).
hyperlink-rel-missing warning A cell hyperlink's r:id has no matching relationship (xlsx).
merge-malformed warning A merged-range reference does not parse as a range.
note-missing warning A footnote/endnote reference names an id the notes part does not define (docx).
numbering-missing warning A paragraph's numPr references a numId with no matching w:num, or the document has no numbering part at all. Warning, not error: real documents in the wild reference a numId while shipping an empty or partial numbering part, and Word opens them — blocking the save would reject a file Word accepts.
sldid-rel-type error A p:sldId or p:custShow entry resolves to a relationship whose type is not a slide (pptx).
rel-target-missing warning A relationship's target part is not present in the package.
style-cycle warning A style's basedOn chain forms a cycle or points at itself (docx).
style-missing warning A paragraph or run references a style id that styles.xml does not define (docx).
zoom-no-target warning A slide or section zoom names a target id that the deck does not contain (pptx).
styles-empty warning The workbook has a styles part with no formatting records (xlsx).

If a finding is advisory for your use case, SaveToUnvalidated writes without the pre-save check. When a save is refused or a file misbehaves, see Troubleshooting.

Supported Flavors

Each format family comes in several ECMA-376 flavors, distinguished only by the main part's content type. Open accepts all of them and a save re-emits the flavor the file was opened with — a slideshow stays a slideshow, and a macro-enabled workbook keeps both its vbaProject.bin (preserved verbatim) and its macro-enabled content type. The Flavor() accessor on Presentation, Document, and Workbook reports the main part's content type (opc.ContentType* constants).

Package Flavors opened and round-tripped
pptx presentation (.pptx), slideshow (.ppsx), template (.potx), macro-enabled presentation (.pptm), slideshow (.ppsm), and template (.potm)
docx document (.docx), template (.dotx), macro-enabled document (.docm), and template (.dotm)
xlsx workbook (.xlsx), template (.xltx), macro-enabled workbook (.xlsm), template (.xltm), and add-in (.xlam)

Documents built with Create always save as the regular flavor. Converting a file from one flavor to another (e.g. saving an .xlsm as a plain .xlsx, which would also need its macro parts stripped) is not supported.

Opening an ISO-Strict (ISO/IEC 29500 Strict) package — a valid but as-yet-unread OOXML dialect that uses the purl.oclc.org/ooxml namespaces instead of the transitional ones — returns opc.ErrStrictOOXML, a distinct signal that the file is a genuine Office document in an unsupported dialect rather than a corrupt or non-Office file.

Thread safety

A pptx.Presentation, docx.Document, or xlsx.Workbook — and everything reached through it (slides, sheets, paragraphs, shapes) — is not safe for concurrent use and must be confined to one goroutine, or all access guarded by external synchronization. In particular Save/SaveBytes/SaveTo mutate shared state while serializing, so they must not run concurrently with each other or with any mutation of the same value. Distinct values may be used from different goroutines.

Resource limits

To bound memory against decompression ("zip bomb") attacks and other resource exhaustion, an opened package is capped by five limits, each with a default and an option that overrides it for one reader:

Option Default Bounds
opc.WithMaxDecompressedPartSize 1 GiB bytes any single part may decompress to
opc.WithMaxDecompressedPackageSize 4 GiB bytes decompressed across the whole package
opc.WithMaxPackageEntries 65536 zip entries in the package
opc.WithMaxNestingDepth 1000 element nesting depth in any XML part
opc.WithMaxEncryptedInputSize 2 GiB bytes read from an encrypted input before parsing its container
// One reader, raised bounds; nothing global changes.
doc, err := docx.Open(path, opc.WithMaxDecompressedPartSize(4<<30))

A bound of zero or less disables it. The defaults are the opc.Default* constants, and opc.DefaultReaderOptions() returns them as a value you can adjust wholesale and pass with opc.WithReaderOptions. Configuration is resolved per open, so concurrent opens with different limits need no coordination — there is no global state to mutate, and no window in which one open sees another's settings.

Units

Positions and sizes take EMUs (English Metric Units), the standard unit in Office Open XML. Helper functions are provided for common conversions:

import "github.com/mgilbir/spine/common/dml"

// Convert from inches to EMUs
width := dml.Inches(10.5)

// Convert from centimeters to EMUs
height := dml.Centimeters(5.0)

// Convert from points to EMUs (e.g. for line widths)
lineWidth := dml.Points(2)

Font sizes are the exception: SetFontSize takes plain points, not EMUs — use run.SetFontSize(12) for 12pt text.

Package Structure

  • opc/ - Open Packaging Conventions implementation
  • common/ - Shared types and utilities
    • crypto/ - Office password encryption (agile/standard AES, RC4 decrypt) and OPC XML-DSig signing (crypto.ErrWrongPassword and friends)
    • dml/ - DrawingML types (colors, geometry, fills, lines)
      • chart/ - Chart types
      • diagram/ - Diagram types
    • enum/ - Common enumerations
    • omml/ - Office Math Markup Language types
    • oxml/ - Shared Office XML types
    • validate/ - Structured validation vocabulary (Report, Error) shared by the format packages
    • vml/ - Vector Markup Language types
    • xml/ - XML namespace handling and Builder-based serialization
  • chart/ - Public, format-agnostic chart builder, serialization, and reader
  • pptx/ - PowerPoint document support
  • docx/ - Word document support
  • xlsx/ - Excel document support

Documentation

Runnable programs for all three formats live in examples/:

  • create_presentation — build a PowerPoint deck.
  • pptx_diagram — build a diagram deck: a connector bound to two shapes, slide-master text-style and slide-layout background editing, speaker notes, and the SmartArt read path, then reopen to verify the round-trip.
  • pptx_deck — build a rich PowerPoint deck: a native chart with an auto-embedded data workbook, a table with an in-text hyperlink, an auto shape with layered effects (shadow, glow, reflection) and an entrance animation, Zoom/Wheel transitions, sections, and a threaded comment — saved in two phases (so shape ids materialize) and reopened to read the sections, animations, charts, and comments back.
  • create_spreadsheet — build an Excel workbook.
  • create_document — build a Word document (page setup, lists, table, image).
  • docx_report — author a rich Word report: custom paragraph/character styles, a custom numbered list, a table of contents, a table with a vertical cell merge, an inline image, an embedded chart, threaded comments, a content control, a two-column page-numbered section, and document protection.
  • docx_review — review a Word document: list tracked changes and comment threads, then accept all revisions and save a clean copy.
  • xlsx_report — a guided tour of the newer XLSX authoring features: a table with a totals row, conditional formatting, an embedded chart, page/print setup, freeze panes, named styles and sheet/workbook protection.
  • xlsx_dashboard — build a sales dashboard: a pivot table cross-tabulating regions against months, per-row and per-column sparklines, and a line chart, then reopen the file to read the pivot layout and sparkline groups back.
  • docx_mailmerge — author a mail-merge form letter: mail-merge configuration with a data source, MERGEFIELD placeholders, a floating text-box callout, a "DRAFT" text watermark, and author-side tracked changes — reopened to read the merge fields, text boxes, watermark, and revisions back.

Testing

Unit tests run against small synthetic fixtures (committed) and larger real-world Office files that are fetched on demand (make fetch, make fetch-cc) and skip silently when absent. See CONTRIBUTING.md for the full build/test/lint/fuzz flow, testdata/README.md for the external and python-pptx fixtures, and testdata/cc/README.md for the Common Crawl corpus. To run the full suite: make test.

Requirements

  • Go 1.25 or later

Spine is pre-1.0 (module v0.x): the API may change between minor versions, per the Go module versioning conventions. All non-internal packages are part of the public API surface. Some are imported directly by the user-facing examples (chart, common/dml, common/enum, opc); the rest are reachable through the format packages' own signatures — Validate() returns a common/validate.Report, and the encrypted open surfaces common/crypto's errors and options — so they are equally part of the contract. Anything under an internal/ path is not.

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! See CONTRIBUTING.md for build, test, and fixture instructions, and please feel free to submit issues and pull requests.

About

Zero-dependency Go library for reading and writing Microsoft Office documents (.docx, .xlsx, .pptx) with byte-identical round-trip fidelity

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages