xlsx/relationship-orphan-detection

Negative invariant: every r:id referenced on a cell-level <hyperlink> must resolve to a matching <Relationship Id='...'/> in the worksheet's rels file, and no duplicate Ids may appear.

Library verdicts: python-xlsx: — xlsxjs: —

Metadata

Feature idxlsx/relationship-orphan-detection
Formatxlsx
Categoryreferences
Spececma-376-5-part-2 § 9.3 pr:Relationship

XPath assertions

ID / partPredicateXPathpython-xlsxxlsxjs
sheet-hyperlink-has-rid
xl/worksheets/sheet1.xml
match = ^1(\.0+)?$
Exactly one cell-level <hyperlink> with an r:id must be present in Sheet1.
count(//x:hyperlinks/x:hyperlink[@r:id])
sheet-rel-count-is-1-hyperlink
xl/worksheets/_rels/sheet1.xml.rels
match = ^1(\.0+)?$
The worksheet's rels file must carry exactly one hyperlink Relationship.
count(//pr:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink'])
sheet-rel-ids-unique
xl/worksheets/_rels/sheet1.xml.rels
absent
Relationship Ids must be unique within the rels file.
//pr:Relationship[@Id = preceding-sibling::pr:Relationship/@Id]
sheet-hyperlink-rel-is-external
xl/worksheets/_rels/sheet1.xml.rels
equal = External
The hyperlink relationship in this fixture is external; it must carry TargetMode='External'.
string(//pr:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink']/@TargetMode)

Render assertions

No render assertions declared.

Generator source

scripts/gen_relationship_orphan_detection_xlsx.py

#!/usr/bin/env python3
"""Generate ``fixtures/xlsx/relationship-orphan-detection.xlsx``.

A minimal workbook with exactly one external-URL cell hyperlink on
Sheet1!A1. Designed to satisfy ``xlsx/relationship-orphan-detection``.

.. versionadded:: 2026.05.0
"""

from __future__ import annotations

import datetime as _dt
from pathlib import Path

from xlsx import Workbook

_REPO_ROOT = Path(__file__).resolve().parent.parent
_OUT = _REPO_ROOT / "fixtures" / "xlsx" / "relationship-orphan-detection.xlsx"
_REPRODUCIBLE_DT = _dt.datetime(2000, 1, 1, 0, 0, 0)


def main() -> int:
    wb = Workbook()
    ws = wb.active
    ws.title = "Sheet1"
    ws["A1"] = "Resolved external hyperlink"
    ws["A1"].hyperlink = "https://example.com/no-orphan"

    _OUT.parent.mkdir(parents=True, exist_ok=True)
    wb.save(_OUT, zip_date_time=_REPRODUCIBLE_DT)
    print(_OUT)
    return 0


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

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "xlsx/relationship-orphan-detection",
  "title": "Relationship orphan detection (xlsx)",
  "format": "xlsx",
  "category": "references",
  "summary": "Negative invariant: every r:id referenced on a cell-level <hyperlink> must resolve to a matching <Relationship Id='...'/> in the worksheet's rels file, and no duplicate Ids may appear.",
  "spec": {
    "source": "ecma-376-5-part-2",
    "clause": "9.3",
    "element": "pr:Relationship",
    "rnc_reference": "spec/ecma-376-5/part-2/rnc/opc-relationships.rnc",
    "xsd_reference": "spec/ecma-376-5/part-2/xsd/opc-relationships.xsd",
    "notes": "Part-2 OPC invariants applied at worksheet scope. The fixture carries exactly one external-URL cell hyperlink on Sheet1!A1. The assertions pin both sides: the worksheet xml references exactly one <hyperlink> with r:id, and xl/worksheets/_rels/sheet1.xml.rels contains exactly one hyperlink Relationship."
  },
  "fixtures": {
    "machine": "xlsx/relationship-orphan-detection"
  },
  "generator": {
    "python": "scripts/gen_relationship_orphan_detection_xlsx.py"
  },
  "assertions": [
    {
      "id": "sheet-hyperlink-has-rid",
      "part": "xl/worksheets/sheet1.xml",
      "namespaces": {
        "x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
        "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
      },
      "xpath": "count(//x:hyperlinks/x:hyperlink[@r:id])",
      "must": "match",
      "value": "^1(\\.0+)?$",
      "description": "Exactly one cell-level <hyperlink> with an r:id must be present in Sheet1."
    },
    {
      "id": "sheet-rel-count-is-1-hyperlink",
      "part": "xl/worksheets/_rels/sheet1.xml.rels",
      "namespaces": {
        "pr": "http://schemas.openxmlformats.org/package/2006/relationships"
      },
      "xpath": "count(//pr:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink'])",
      "must": "match",
      "value": "^1(\\.0+)?$",
      "description": "The worksheet's rels file must carry exactly one hyperlink Relationship."
    },
    {
      "id": "sheet-rel-ids-unique",
      "part": "xl/worksheets/_rels/sheet1.xml.rels",
      "namespaces": {
        "pr": "http://schemas.openxmlformats.org/package/2006/relationships"
      },
      "xpath": "//pr:Relationship[@Id = preceding-sibling::pr:Relationship/@Id]",
      "must": "absent",
      "description": "Relationship Ids must be unique within the rels file."
    },
    {
      "id": "sheet-hyperlink-rel-is-external",
      "part": "xl/worksheets/_rels/sheet1.xml.rels",
      "namespaces": {
        "pr": "http://schemas.openxmlformats.org/package/2006/relationships"
      },
      "xpath": "string(//pr:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink']/@TargetMode)",
      "must": "equal",
      "value": "External",
      "description": "The hyperlink relationship in this fixture is external; it must carry TargetMode='External'."
    }
  ]
}

Fixture

Download relationship-orphan-detection.xlsx (5.1 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

Part-2 OPC invariants applied at worksheet scope. The fixture carries exactly one external-URL cell hyperlink on Sheet1!A1. The assertions pin both sides: the worksheet xml references exactly one <hyperlink> with r:id, and xl/worksheets/_rels/sheet1.xml.rels contains exactly one hyperlink Relationship.