Appendix C - 2. Office Models ({Slides,Docs,Sheets}Model)

The Structure of Office Models ({Slides,Docs,Sheets}Model) — its shape at a glance:

Three formats, one pattern. Each format's call sites route through its typed model; the models share a common layer. Two ban-lints guard the raw edges — one on the raw SDK, one on regexing the serialized XML.

flowchart LR
  S[Slides sites] --> SM[Slides model]
  D[Docs sites] --> DM[Docs model]
  X[Sheets sites] --> XM[Sheets model]
  SM --> Common["Shared common layer"]
  DM --> Common
  XM --> Common
  Common --> Raw[(Raw office SDK)]
  L{{Two ban-lints}} -. raw SDK + raw-XML regex .-> Raw

Accessible description: three format-specific call sites route through three typed models into a shared common layer, which is the only path to the raw office SDK. Two ban-lints guard the raw edges — one bans the raw SDK, one bans regexing the serialized XML — so no site skips the models.

Projected from the catalogue entry canonical-models-and-seams / Office Models ({Slides,Docs,Sheets}Model).

On this page: Intent · Motivation · Applicability · Structure · Sample Code · Consequences · Example use within DocAble · Related Patterns

Intent

Intent — Route all remediation of a format family through one typed model, with raw library access (and raw string-matching into the serialized form) banned by lint. The same construction+ban-lint pattern as pdf-model, on a second object model (our instance: {Slides,Docs,Sheets}Model over DocumentFormat.OpenXml).

Motivation

Raw OpenXML SDK access, and the sneakier path of regexing into the XML, are the Office equivalent of the raw-PDF-library minefield: brittle, corruption-prone, and with no single point to enforce structural invariants. Left ad hoc, the same raw-library corruption class recurs across three separate document formats.

Applicability

Structure

The Structure diagram appears at the top of this page.

Sample Code

The pattern is the same sole-seam-plus-ban-lint as any typed model, applied once per format over a shared base. What is worth showing is the second lint: the raw-SDK ban alone leaves a hole, because the serialized document is text and a caller can regex into it behind the model's back. The string-match ban closes that path.

import ast, sys

RAW_SDK_ROOT = "office_sdk"          # the raw library the models wrap
SEAM_MODULES = {"slides_model", "docs_model", "sheets_model", "office_common"}

def lint(path: str, mod: str, source: str) -> list[str]:
    if mod in SEAM_MODULES:
        return []                    # the models + common layer may touch the raw SDK
    findings = []
    for node in ast.walk(ast.parse(source)):
        # ban 1: importing the raw SDK anywhere but the seam
        if isinstance(node, ast.ImportFrom) and (node.module or "").startswith(RAW_SDK_ROOT):
            findings.append(f"{path}:{node.lineno}: raw office SDK import — route through a typed model")
        # ban 2: the sneaky path — a regex reaching into the serialized XML
        if isinstance(node, ast.Attribute) and node.attr in {"search", "match", "findall"}:
            findings.append(f"{path}:{node.lineno}: regex over serialized XML — walk the typed model instead")
    return findings

if __name__ == "__main__":
    hits = [f for p in sys.argv[1:] for f in lint(p, p.rsplit("/", 1)[-1][:-3], open(p).read())]
    print("\n".join(hits)); sys.exit(1 if hits else 0)

Consequences

Example use within DocAble

Contents
© James C. Davis, 2026–present