Skip to content

Introduce a shared typed InlineSpan representation for rich text #958

Description

@stevenobiajulu

Problem

safe-docx currently represents inline formatting in several overlapping forms:

  • formatting_tags.ts emits an HTML-shaped tagged_text string.
  • serialize_markdown.ts and serialize_html.ts independently translate that representation.
  • tag_parser.ts parses an allowlisted tag syntax into ReplacementPart[] for edits.
  • AddRunProps and ReplacementPart are the typed structures that ultimately drive OOXML changes.

The string representations duplicate tokenization and formatting rules. They also make it easy for read, edit, Markdown export, and HTML export behavior to drift.

Proposed direction

Introduce a shared typed inline representation, for example:

type InlineSpan = {
  text: string;
  bold?: boolean;
  italic?: boolean;
  underline?: boolean | string;
  highlight?: string;
  color?: string;
  fontName?: string;
  fontSize?: number;
  link?: { href: string };
};

The exact schema should follow the formatting semantics already supported by AddRunProps and the serializers.

Markdown and sanitized HTML should be input/output formats around this typed model, not the canonical model itself:

DOCX/OOXML -> InlineSpan[] -> tagged text / Markdown / HTML
Markdown or allowlisted HTML -> InlineSpan[] -> ReplacementPart[] -> OOXML

Markdown can represent common emphasis. A small allowlisted HTML subset can represent semantics Markdown lacks, such as <u>, <mark>, and foreground color.

Scope boundary

Do not use Markdown or HTML as the canonical representation for tracked changes or document structure. Revision IDs, authors, dates, prior formatting snapshots, comments, fields, relationships, paragraph changes, style inheritance, and other OOXML semantics must remain typed.

The first implementation should normalize ordinary inline run formatting only. It should not encode tracked changes as <ins> or <del> except in a one-way display serializer.

Relevant code

  • packages/docx-core/src/primitives/formatting_tags.ts
  • packages/docx-core/src/primitives/serialize_markdown.ts
  • packages/docx-core/src/primitives/serialize_html.ts
  • packages/docx-core/src/primitives/text.ts
  • packages/docx-mcp/src/tools/tag_parser.ts
  • packages/docx-mcp/src/tools/replace_text.ts
  • packages/docx-mcp/src/tools/insert_paragraph.ts

Security requirements

  • Parse a fixed allowlist rather than retaining arbitrary HTML.
  • Reject event attributes and unsupported elements or attributes.
  • Restrict link URL schemes.
  • Restrict CSS to explicitly supported properties and validated values.
  • Escape unknown markup as text.
  • Keep the existing lossy-export warning where Markdown cannot preserve Word semantics.

Acceptance criteria

  • One typed inline-span model is shared by formatting extraction, edit parsing, and Markdown/HTML serialization.
  • Markdown and allowlisted HTML normalize into that model before OOXML editing.
  • Existing bold, italic, underline, highlight, color, font, size, and link behavior has fixture coverage.
  • Unsupported or unsafe HTML is rejected or escaped.
  • Markdown export remains explicitly lossy and is not used to reconstruct a DOCX.
  • Tracked changes and structural OOXML remain outside the inline-span model unless represented by separate typed nodes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions