pptx/slide-master-theme-override--override-via-themeOverride

A slide inherits its colour/font/format scheme from the theme attached to its slide-master by default. A slide may opt out by attaching its own themeOverride part via an officeDocument/relationships/themeOverride relationship from the slide's rels. This family covers two cases — inherit (no themeOverride relationship present) and override (themeOverride1.xml part attached) — each backed by a distinct fixture pptx/slide-master-theme-override--<id>.pptx generated from the same gen_slide_master_theme_override.py script.

Library verdicts: python-pptx: — pptxjs: —

Metadata

Feature idpptx/slide-master-theme-override--override-via-themeOverride
Formatpptx
Categoryslide-layout
Familypptx/slide-master-theme-override
Axis valuesoverride=override-via-themeOverride
Spececma-376-5-part-1 § 19.3.1.50 a:themeOverride

XPath assertions

ID / partPredicateXPathpython-pptxpptxjs
theme-override-rel-override-via-themeOverride
ppt/slides/_rels/slide1.xml.rels
match = ^1(\.0)?$
Slide with a themeOverride: slide1.xml.rels must carry exactly one themeOverride relationship.
count(//*[local-name()='Relationship'][@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride'])

Render assertions

No render assertions declared.

Generator source

scripts/gen_slide_master_theme_override.py

#!/usr/bin/env python3
"""Generate a single ``fixtures/pptx/slide-master-theme-override--<id>.pptx``.

Parameterised generator for the ``pptx/slide-master-theme-override``
manifest family. Two cases:

- ``inherit-from-master`` — standard single-slide presentation with no
  themeOverride part. The slide inherits its colour/font/format scheme
  from the theme attached to its slide-master. ``slide1.xml.rels``
  carries zero themeOverride relationships.
- ``override-via-themeOverride`` — single-slide presentation with a
  ``ppt/theme/themeOverride1.xml`` part attached via an
  ``officeDocument/relationships/themeOverride`` relationship from the
  slide. The override part carries a minimal but spec-valid
  <a:themeOverride> body (clrScheme + fontScheme + fmtScheme).

python-pptx has no high-level themeOverride API, so the override part
is constructed directly via the OPC package machinery (Part +
slide.part.relate_to(..., RT.THEME_OVERRIDE)).
"""

from __future__ import annotations

import argparse
import datetime as _dt
from pathlib import Path

from pptx import Presentation
from pptx.opc.constants import CONTENT_TYPE as CT
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.opc.package import Part
from pptx.opc.packuri import PackURI

_REPO_ROOT = Path(__file__).resolve().parent.parent
_REPRODUCIBLE_DT = _dt.datetime(2000, 1, 1, 0, 0, 0)

# Minimal but spec-valid <a:themeOverride> body. themeOverride is a
# restricted subset of <a:theme> containing clrScheme + fontScheme +
# fmtScheme (ECMA-376 Part 1 clause 14.2.7 / 19.3.1.50). The override
# here inverts some accent colours vs. the default Office theme so the
# part is visibly distinct from theme1.xml if inspected.
_THEME_OVERRIDE_XML = (
    b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
    b'<a:themeOverride xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">'
    b'<a:clrScheme name="Override">'
    b'<a:dk1><a:srgbClr val="000000"/></a:dk1>'
    b'<a:lt1><a:srgbClr val="FFFFFF"/></a:lt1>'
    b'<a:dk2><a:srgbClr val="1F497D"/></a:dk2>'
    b'<a:lt2><a:srgbClr val="EEECE1"/></a:lt2>'
    b'<a:accent1><a:srgbClr val="FF0000"/></a:accent1>'
    b'<a:accent2><a:srgbClr val="00FF00"/></a:accent2>'
    b'<a:accent3><a:srgbClr val="0000FF"/></a:accent3>'
    b'<a:accent4><a:srgbClr val="FFFF00"/></a:accent4>'
    b'<a:accent5><a:srgbClr val="FF00FF"/></a:accent5>'
    b'<a:accent6><a:srgbClr val="00FFFF"/></a:accent6>'
    b'<a:hlink><a:srgbClr val="0000FF"/></a:hlink>'
    b'<a:folHlink><a:srgbClr val="800080"/></a:folHlink>'
    b'</a:clrScheme>'
    b'<a:fontScheme name="Override">'
    b'<a:majorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>'
    b'<a:minorFont><a:latin typeface="Calibri"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>'
    b'</a:fontScheme>'
    b'<a:fmtScheme name="Override">'
    b'<a:fillStyleLst>'
    b'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
    b'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
    b'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
    b'</a:fillStyleLst>'
    b'<a:lnStyleLst><a:ln/><a:ln/><a:ln/></a:lnStyleLst>'
    b'<a:effectStyleLst>'
    b'<a:effectStyle><a:effectLst/></a:effectStyle>'
    b'<a:effectStyle><a:effectLst/></a:effectStyle>'
    b'<a:effectStyle><a:effectLst/></a:effectStyle>'
    b'</a:effectStyleLst>'
    b'<a:bgFillStyleLst>'
    b'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
    b'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
    b'<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>'
    b'</a:bgFillStyleLst>'
    b'</a:fmtScheme>'
    b'</a:themeOverride>'
)


def _write(kind: str, out: Path) -> None:
    prs = Presentation()
    # "Title Only" layout — a title placeholder on an otherwise plain slide.
    slide = prs.slides.add_slide(prs.slide_layouts[5])
    if slide.shapes.title is not None:
        slide.shapes.title.text = f"Theme override: {kind}"

    if kind == "override":
        partname = PackURI("/ppt/theme/themeOverride1.xml")
        package = prs.part.package
        override_part = Part(
            partname, CT.OFC_THEME_OVERRIDE, package, _THEME_OVERRIDE_XML
        )
        slide.part.relate_to(override_part, RT.THEME_OVERRIDE)
    elif kind != "inherit":
        raise ValueError(f"Unknown kind: {kind!r} (expected 'inherit' or 'override').")

    out.parent.mkdir(parents=True, exist_ok=True)
    prs.save(out, zip_date_time=_REPRODUCIBLE_DT)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--kind",
        required=True,
        choices=("inherit", "override"),
        help="'inherit' for no themeOverride; 'override' for an attached themeOverride1.xml.",
    )
    parser.add_argument(
        "--out",
        required=True,
        help="Output fixture path (absolute or repo-relative).",
    )
    args = parser.parse_args()

    out_path = Path(args.out)
    if not out_path.is_absolute():
        out_path = _REPO_ROOT / out_path
    _write(args.kind, out_path)
    print(out_path)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "pptx/slide-master-theme-override--override-via-themeOverride",
  "kind": "literal",
  "title": "Slide theme override",
  "format": "pptx",
  "category": "slide-layout",
  "summary": "A slide inherits its colour/font/format scheme from the theme attached to its slide-master by default. A slide may opt out by attaching its own themeOverride part via an officeDocument/relationships/themeOverride relationship from the slide's rels. This family covers two cases — inherit (no themeOverride relationship present) and override (themeOverride1.xml part attached) — each backed by a distinct fixture pptx/slide-master-theme-override--<id>.pptx generated from the same gen_slide_master_theme_override.py script.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "19.3.1.50",
    "element": "a:themeOverride",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/dml-main.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/dml-main.xsd",
    "notes": "ECMA-376 Part 1 clause 14.2.7 / 19.3.1.50 defines <a:themeOverride> — a subset of <a:theme> carrying clrScheme + fontScheme + fmtScheme (no objectDefaults, no extraSchemeLst) — and its relationship type http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride. A slide, slideLayout, or notesSlide may carry exactly zero or one themeOverride relationship; when present it supersedes the master-attached theme for that part only. python-pptx does not expose a high-level themeOverride API; this generator constructs the override part and relationship directly via the OPC package API (Part + slide.part.relate_to)."
  },
  "fixtures": {
    "machine": "pptx/slide-master-theme-override--override-via-themeOverride"
  },
  "generator": {
    "python": "scripts/gen_slide_master_theme_override.py",
    "arg_template": "--kind {override.kind} --out fixtures/pptx/slide-master-theme-override--{override.id}.pptx"
  },
  "_expansion": {
    "parent_id": "pptx/slide-master-theme-override",
    "bindings": {
      "override": "override-via-themeOverride"
    }
  },
  "assertions": [
    {
      "id": "theme-override-rel-override-via-themeOverride",
      "part": "ppt/slides/_rels/slide1.xml.rels",
      "namespaces": {
        "r": "http://schemas.openxmlformats.org/package/2006/relationships"
      },
      "xpath": "count(//*[local-name()='Relationship'][@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride'])",
      "must": "match",
      "value": "^1(\\.0)?$",
      "description": "Slide with a themeOverride: slide1.xml.rels must carry exactly one themeOverride relationship."
    }
  ]
}

Fixture

Download slide-master-theme-override--override-via-themeOverride.pptx (28.0 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

ECMA-376 Part 1 clause 14.2.7 / 19.3.1.50 defines <a:themeOverride> — a subset of <a:theme> carrying clrScheme + fontScheme + fmtScheme (no objectDefaults, no extraSchemeLst) — and its relationship type http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride. A slide, slideLayout, or notesSlide may carry exactly zero or one themeOverride relationship; when present it supersedes the master-attached theme for that part only. python-pptx does not expose a high-level themeOverride API; this generator constructs the override part and relationship directly via the OPC package API (Part + slide.part.relate_to).