moonleaf

A pure MoonBit, cross-platform OOXML viewer and lightweight editor core.

office
ooxml
docx
xlsx
pptx
moon add vectie/moonleaf@0.1.14
Download zip
Author
Version
0.1.14
License
Apache-2.0
Last updated
3 days ago
Downloads
45

Dependencies

README

#MoonLeaf

Library ยท conservative OOXML alpha. Read the product contract for the format matrix, bounded archive boundary, host boundary and release gates.

MoonLeaf is a pure MoonBit, cross-platform core for previewing and making deliberately small edits to Office Open XML documents.

FormatViewer subsetLightweight edit
.docxParagraphs, headings, lists, tables, basic run stylesExact text replacement inside w:t
.xlsxSheets, common cell values, formulas, styles, merged rangesSet a cell to inline text
.pptxSlide order and size, text, shapes, pictures, transformsExact text replacement inside a:t

MoonLeaf is not an Office clone. It preserves unrecognized package parts and defaults to save-as-copy workflows so unsupported features are not silently discarded.

The same MoonBit source is intended to compile for native, JavaScript, Wasm, and Wasm-GC targets. Renderers consume the neutral structures in vectie/moonleaf/scene; UI frameworks such as Rabbita remain outside this library.

#Architecture

  • xml: namespace-tolerant XML DOM, parser, and serializer
  • opc: ZIP/OPC access, relationships, path safety, and part preservation
  • scene: neutral document structures for UI renderers
  • docx, xlsx, pptx: format readers and conservative editors
  • root package: filename-based viewer dispatch

#Public entry points

  • @moonleaf.open detects the viewer from a filename
  • @moonleaf.open_with_policy(filename, bytes, policy) applies explicit per-open ZIP/OPC limits; open uses conservative defaults
  • @moonleaf.open_session(filename, bytes) opens a DOCX, XLSX, or PPTX editing session and renders its initial scene
  • @moonleaf.open_session_with_policy(filename, bytes, policy) carries the selected policy through verified save/reopen
  • DocumentSession::capabilities() reports whether the detected format supports scene rendering, save-as-copy, text replacement, or cell editing
  • DocumentSession::scene() and diagnostics() return defensive copies: callers may mutate returned nested arrays without changing the session
  • VerifiedDocumentCopy::bytes() likewise returns a defensive byte copy, and session() returns the verified reopened session
  • @opc.open and @opc.open_with_policy preflight ZIP metadata, require per-entry CRC integrity, and decode the OOXML container; @opc.save encodes it
  • @docx.read, @xlsx.read, and @pptx.read return neutral scene values
  • @docx.preview_document returns ordered DOCX paragraph/table/section flow with resolved alignment, spacing, indentation, CJK fonts, run emphasis, table widths, and page geometry for higher-fidelity host previews
  • @docx.replace_text, @xlsx.set_cell_text, and @pptx.replace_text return new packages and leave the input package unchanged

Successful changes increment revision and set is_dirty(); a semantic no-op returns the same session with the existing revision and dirty state. In particular, text replacement returns self when find == replacement after capability/format validation and before low-level DOCX/PPTX editor dispatch, so entity-coded source XML is not needlessly canonicalized. XLSX replace_text remains unsupported even when find == replacement. Failed edits leave the original session and its saved bytes unchanged. Dirty tracking is deliberately conservative: it records successful session edits relative to the opened baseline, not a byte-for-byte comparison with that baseline or an undo model. An edit-away/edit-back sequence therefore remains dirty until save.

Replacement is format-specific. DOCX replaces substrings independently within each w:t node, so a match never spans runs. PPTX replaces only an a:t whose entire text payload exactly equals the find string. XLSX editing targets a validated cell and writes inline text; formula cells and non-anchor cells in a merged range are rejected.

save_as_copy() serializes, reopens, and renders the result before returning it. Unknown OPC parts are intended to be preserved, but byte-identical ZIP layout, signatures, macros, encryption, unsupported relationship semantics, and application-specific metadata are not preservation guarantees. OPC Package and Part values are public mutable data; no defensive-copy claim is made for package parts, and sessions expose no package-part accessor.

#Security and release limits

Every public open now uses a MoonLeaf-owned ArchivePolicy. The default bounds the compressed input, entry count, per-entry compressed and uncompressed size, total uncompressed size, compression ratio, path depth, path length, and duplicate/aliased paths. MoonLeaf inspects central-directory metadata before extraction, extracts with CRC verification enabled, and compares actual entry names and sizes with the inspected metadata before parsing OPC parts.

This closes the ZIP resource/integrity release blocker; it is not a blanket claim that every malicious document is safe. Macros, digital signatures, encryption, external relationships, embedded active content, and unsupported semantic features remain outside the trust guarantee. See archive security and the preservation matrix.

#Status

This repository is an early implementation. The initial compatibility target is unencrypted OOXML without macros or digital signatures. Advanced layout, formula recalculation, charts, transitions, and animation are intentionally out of scope.

#Development

moon check --target all moon test --target all moon info moon fmt

#License

Apache-2.0. See package-level provenance notes for behavioral references.

#Current verification checkpoint

The prior broad checkpoint passed 93/93 tests on each target. For the current archive-policy slice, moon check --target all --warn-list +73 reports zero errors; 12 focused OPC tests plus one root-policy test pass on wasm, wasm-gc, JavaScript, and native (52 focused executions); and moon info, formatting, and diff checks pass. The broad format suite was intentionally not repeated for this bounded change.

#
ArchivePolicy

Resource and path limits applied to one ZIP/OPC open.

These limits are enforced before extraction from central-directory metadata and checked again against the extracted entries. The dependency's own hard limits remain an additional ceiling.

#
DocumentSessionError

pub(all) suberror DocumentSessionError {
UnsupportedFormat(String)
Package(String)
Render(String)
OperationNotSupported(operation~ : String, format~ : DocumentFormat)
InvalidFind(String)
InvalidCoordinates(row~ : Int, column~ : Int)
SheetNotFound(String)
FormulaCell(sheet~ : String, row~ : Int, column~ : Int)
MergedCellNotAnchor(sheet~ : String, row~ : Int, column~ : Int)
SaveVerification(String)
} derive(Eq,
Debug
)

#
MoonLeafError

pub(all) suberror MoonLeafError {
UnsupportedExtension(String)
Package(String)
Document(String)
} derive(Eq,
Debug
)

#
DocumentCapabilities

pub(all) struct DocumentCapabilities {
render_scene : Bool
save_as_copy : Bool
replace_text : Bool
set_cell_text : Bool
} derive(Eq,
Debug
)

#
DocumentDiagnostic

pub(all) struct DocumentDiagnostic {
code : String
message : String
part : String?
} derive(Eq,
Debug
)

#
DocumentFormat

pub(all) enum DocumentFormat {
Docx
Xlsx
Pptx
} derive(Eq,
Debug
)

#
DocumentFormat::capabilities

#
DocumentSession

pub struct DocumentSession {
// private fields
}

#
DocumentSession::capabilities

#
DocumentSession::diagnostics

#
DocumentSession::filename

fn DocumentSession::filename(self : DocumentSession) -> String

#
DocumentSession::format

#
DocumentSession::is_dirty

fn DocumentSession::is_dirty(self : DocumentSession) -> Bool

#
DocumentSession::replace_text

fn DocumentSession::replace_text(self : DocumentSession, find : String, replacement : String) -> DocumentSession raise DocumentSessionError

#
DocumentSession::revision

fn DocumentSession::revision(self : DocumentSession) -> Int

#
DocumentSession::save_as_copy

#
DocumentSession::set_cell_text

fn DocumentSession::set_cell_text(self : DocumentSession, sheet : String, row : Int, column : Int, text : String) -> DocumentSession raise DocumentSessionError

#
VerifiedDocumentCopy

pub struct VerifiedDocumentCopy {
// private fields
}

#
VerifiedDocumentCopy::bytes

fn VerifiedDocumentCopy::bytes(self : VerifiedDocumentCopy) -> FixedArray[Byte]

#
VerifiedDocumentCopy::session

#
open

fn open(filename : String, content : FixedArray[Byte]) ->
Document
raise MoonLeafError

#
open_session

fn open_session(filename : String, content : FixedArray[Byte]) -> DocumentSession raise DocumentSessionError

limits, mandatory CRC verification, revision zero, and a clean baseline.

#
open_session_with_policy

fn open_session_with_policy(filename : String, content : FixedArray[Byte], policy :
ArchivePolicy
) -> DocumentSession raise DocumentSessionError

Open a document session with explicit, validated ZIP/OPC limits.

#
open_with_policy

fn open_with_policy(filename : String, content : FixedArray[Byte], policy :
ArchivePolicy
) ->
Document
raise MoonLeafError

Open and render an OOXML file with explicit ZIP/OPC resource limits.

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

ยฉ 2026 mooncakes.io