CORE

Descriptor System

Declarative XML mapping for OOXML elements — stringify and parse

All OOXML XML parts in @office-open/docx, @office-open/pptx, and @office-open/xlsx are defined as descriptors — plain objects that declare how to map between TypeScript options objects and XML. The same descriptor drives both directions: stringify (JSON → XML) and parse (XML → JSON).

Core Concepts

A descriptor describes the bidirectional mapping between a TypeScript options interface and its XML representation. Every descriptor is a CustomDescriptor<T> whose stringify() and parse() methods are hand-written for the part — there is no declarative attr/child builder. The same descriptor drives both directions: stringify (JSON → XML) and parse (XML → JSON).

Descriptors are consumed by two runtime functions: stringify(desc, value, ctx) and parse(desc, element, ctx).

CustomDescriptor

Each part exports its descriptor object with kind: "custom" and hand-written stringify/parse:

import type { CustomDescriptor } from "@office-open/core";

interface MyOptions {
  items: string[];
  separator?: string;
}

const myDesc: CustomDescriptor<MyOptions> = {
  kind: "custom",
  stringify(value, ctx) {
    const parts = value.items.map((item) => `<w:item val="${item}"/>`).join("");
    return `<w:container>${parts}</w:container>`;
  },
  parse(el, ctx) {
    const items = (el.elements ?? [])
      .filter((c) => c.type === "element" && c.name === "w:item")
      .map((c) => c.attributes?.["w:val"] ?? "");
    return { items } as MyOptions;
  },
};

Advanced: When a descriptor's stringify input differs from its parse output (for example, an accumulator-style input versus a structured parse result), declare both explicitly: CustomDescriptor<TInput, Ctx, TOutput>. TOutput defaults to TInput, so the common single-parameter form CustomDescriptor<T> is shorthand for CustomDescriptor<T, WriteContext, T>.

Runtime Functions

The runtime is a single step in each direction — no intermediate representation:

import { stringify, parse } from "@office-open/core";

stringify(desc, value, ctx)

Serialize an options object to an XML string. Returns undefined when an optional element should be omitted.

const xml = stringify(myDesc, { items: ["a"] }, writeCtx);

parse(desc, element, ctx)

Parse an XML element into an options object.

const result = parse(myDesc, element, readCtx);

XSD Value Mappings

When an XSD type uses abbreviations (e.g. ST_TextAlignType "ctr" for center), the bidirectional mapping lives in @office-open/core (util/mappings.ts). Each mapping is built once with bidi() and exposes .to() (user value → XSD value) and .from() (XSD value → user value), so the same map serves both stringify and parse:

import { xsdTextAlign } from "@office-open/core";

// stringify (Options → XML)
attrs.push(`algn="${xsdTextAlign.to(options.alignment)}"`); // "center" → "ctr"

// parse (XML → Options)
result.alignment = xsdTextAlign.from(String(el.attributes["algn"])); // "ctr" → "center"

When the XSD already uses full English words (e.g. "start", "center"), no mapping is needed — the value is written and read verbatim.

Context Objects

WriteContext

Passed during stringify (write path):

interface WriteContext {
  addRelationship(type: string, target: string, mode?: string): string;
  addMedia(data: Uint8Array, type: string): string;
}

ReadContext

Passed during parse (read path):

interface ReadContext {
  resolveRelationship(rId: string): string | undefined;
  getPart(path: string): XmlElement | undefined;
  getRaw(path: string): Uint8Array | undefined;
}

When to Use

You typically don't need to call stringify() and parse() directly — the format packages' top-level functions handle everything internally:

  • generateDocument() / generatePresentation() / generateWorkbook() — call stringify internally
  • parseDocument() / parsePresentation() / parseWorkbook() — call parse internally

Use the descriptor system directly when you need to:

  • Define custom OOXML elements not covered by the format packages
  • Build format-specific extensions or plugins
  • Implement bidirectional (stringify + parse) support for new XML parts

Naming Conventions

ConventionExample
Descriptor names: <part>DescspacingDesc, settingsDesc, slideDesc
Options interfaces: <Part>OptionsWorkbookOptions, DocumentOptions
Helper functions: stringify*() / parse*()stringifyWorksheet(), parseWorkbook()
Copyright © 2026