docx/header-variant--default

A family covering the three <w:headerReference> types the OOXML schema allows on a section: default (primary), first (first-page), and even (even-numbered pages).

Library verdicts: python-docx: — docxjs: —

Metadata

Feature iddocx/header-variant--default
Formatdocx
Categorypage-layout
Familydocx/header-variant
Axis valuesvariant=default
Spececma-376-5-part-1 § 17.10.1 w:headerReference

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
header-reference-present-default
word/document.xml
exist
The section must declare a <w:headerReference> whose @w:type equals the expected variant (default / first / even).
//w:sectPr/w:headerReference[@w:type='default']
header-rel-exists-default
word/_rels/document.xml.rels
exist
word/_rels/document.xml.rels must carry at least one header-type relationship. Existence-only because the specific r:id -> Target mapping is allocation-order dependent.
//pr:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/header']
variant-header-part-text-default
word/header1.xml
exist
The variant's header part must contain the expected text literal.
//w:hdr//w:t[normalize-space()='Default header content']

Render assertions

No render assertions declared.

Generator source

scripts/gen_header_variant.py

#!/usr/bin/env python3
"""Generate ``fixtures/docx/header-variant--<variant>.docx`` fixtures.

Each run produces a single-section document with the primary (default)
header populated, and — for the ``first`` and ``even`` variants — a
second header part carrying variant-specific text:

- ``default`` — only the primary header (``word/header1.xml``) is present.
- ``first``   — primary header + first-page header
                (``word/header2.xml``). section.different_first_page_header_footer
                = True so Word honours the second part as the first-page
                override.
- ``even``    — primary header + even-page header (``word/header2.xml``).
                Section.different_odd_and_even_pages_header_footer = True
                (fork alias for Settings.even_and_odd_headers) so Word
                honours the second part as the even-page override.

python-docx allocates the variant header as ``header2.xml`` in both the
first and even cases because only one extra part is created; the
manifest's assertions pin that exact path.
"""

from __future__ import annotations

import argparse
from pathlib import Path

from docx import Document

_REPO_ROOT = Path(__file__).resolve().parent.parent

_DEFAULT_TEXT = "Default header content"
_FIRST_TEXT   = "First page header content"
_EVEN_TEXT    = "Even page header content"


def _build_default(doc: Document) -> None:
    doc.sections[0].header.paragraphs[0].text = _DEFAULT_TEXT


def _build_first(doc: Document) -> None:
    section = doc.sections[0]
    section.different_first_page_header_footer = True
    section.header.paragraphs[0].text = _DEFAULT_TEXT
    section.first_page_header.paragraphs[0].text = _FIRST_TEXT


def _build_even(doc: Document) -> None:
    section = doc.sections[0]
    section.different_odd_and_even_pages_header_footer = True
    section.header.paragraphs[0].text = _DEFAULT_TEXT
    section.even_page_header.paragraphs[0].text = _EVEN_TEXT


_VARIANTS = {
    "default": _build_default,
    "first":   _build_first,
    "even":    _build_even,
}


def _write(variant: str, out: Path) -> None:
    if variant not in _VARIANTS:
        raise SystemExit(
            "Unknown variant %r; expected one of %s"
            % (variant, ", ".join(sorted(_VARIANTS)))
        )
    doc = Document()
    _VARIANTS[variant](doc)
    # -- add a body paragraph so LibreOffice has something to render --
    doc.add_paragraph("Body paragraph.")
    out.parent.mkdir(parents=True, exist_ok=True)
    doc.save(out, reproducible=True)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--variant",
        default="default",
        choices=sorted(_VARIANTS),
        help="Which header variant to activate.",
    )
    parser.add_argument(
        "--out",
        default=str(_REPO_ROOT / "fixtures" / "docx" / "header-variant.docx"),
    )
    args = parser.parse_args()

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


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

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/header-variant--default",
  "kind": "literal",
  "title": "Header variant (default / first / even)",
  "format": "docx",
  "category": "page-layout",
  "summary": "A family covering the three <w:headerReference> types the OOXML schema allows on a section: default (primary), first (first-page), and even (even-numbered pages).",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "17.10.1",
    "element": "w:headerReference",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
    "notes": "Each expanded case produces a single-section document with the default primary header set to 'Default header content' and — for the first/even variants — a second header part carrying variant-specific text. The default case has only the primary header. The first case additionally sets section.different_first_page_header_footer = True. The even case additionally sets Section.different_odd_and_even_pages_header_footer = True (fork alias for Settings.even_and_odd_headers). python-docx allocates the extra variant header as header2.xml in both non-default cases; the default case's primary header lands in header1.xml. Assertions are pinned to those exact part paths. The rels-level check exists-only rather than pinning a Target because rels r:ids are allocation-order dependent."
  },
  "fixtures": {
    "machine": "docx/header-variant--default",
    "office": "docx/header-variant--default"
  },
  "generator": {
    "python": "scripts/gen_header_variant.py",
    "arg_template": "--variant {variant.id} --out fixtures/docx/header-variant--{variant.id}.docx"
  },
  "_expansion": {
    "parent_id": "docx/header-variant",
    "bindings": {
      "variant": "default"
    }
  },
  "assertions": [
    {
      "id": "header-reference-present-default",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
        "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
      },
      "xpath": "//w:sectPr/w:headerReference[@w:type='default']",
      "must": "exist",
      "description": "The section must declare a <w:headerReference> whose @w:type equals the expected variant (default / first / even)."
    },
    {
      "id": "header-rel-exists-default",
      "part": "word/_rels/document.xml.rels",
      "namespaces": {
        "pr": "http://schemas.openxmlformats.org/package/2006/relationships"
      },
      "xpath": "//pr:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/header']",
      "must": "exist",
      "description": "word/_rels/document.xml.rels must carry at least one header-type relationship. Existence-only because the specific r:id -> Target mapping is allocation-order dependent."
    },
    {
      "id": "variant-header-part-text-default",
      "part": "word/header1.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:hdr//w:t[normalize-space()='Default header content']",
      "must": "exist",
      "description": "The variant's header part must contain the expected text literal."
    }
  ]
}

Fixture

Download header-variant--default.docx (19.1 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

Each expanded case produces a single-section document with the default primary header set to 'Default header content' and — for the first/even variants — a second header part carrying variant-specific text. The default case has only the primary header. The first case additionally sets section.different_first_page_header_footer = True. The even case additionally sets Section.different_odd_and_even_pages_header_footer = True (fork alias for Settings.even_and_odd_headers). python-docx allocates the extra variant header as header2.xml in both non-default cases; the default case's primary header lands in header1.xml. Assertions are pinned to those exact part paths. The rels-level check exists-only rather than pinning a Target because rels r:ids are allocation-order dependent.