A w:hyperlink's inner run can either inherit its visible formatting from the built-in 'Hyperlink' character style via w:rStyle, or override it directly with an inline w:rPr (color, underline, etc.). This family exercises three variants: style-only (default), explicit-color override, and explicit underline=none override.
Library verdicts: python-docx: — docxjs: —
| Feature id | docx/hyperlink-run-properties--explicit-color |
|---|---|
| Format | docx |
| Category | references |
| Family | docx/hyperlink-run-properties |
| Axis values | variant=explicit-color |
| Spec | ecma-376-5-part-1 § 17.16.22 w:hyperlink |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
has-hyperlink-explicit-colorword/document.xml | existThe generated fixture must contain one external-URL w:hyperlink. | //w:hyperlink[@r:id] | — | — |
rstyle-explicit-colorword/document.xml | absentIn the style-only variant the run carries <w:rStyle w:val='Hyperlink'/>. Override variants suppress that so the override can take effect cleanly. | //w:hyperlink/w:r/w:rPr/w:rStyle[@w:val='Hyperlink'] | — | — |
color-explicit-colorword/document.xml | existThe explicit-color variant overrides the theme hyperlink color with w:color w:val='FF0000' (pure red). | //w:hyperlink/w:r/w:rPr/w:color[@w:val='FF0000'] | — | — |
underline-off-explicit-colorword/document.xml | absentThe underline-off variant suppresses the default hyperlink underline with w:u w:val='none'. | //w:hyperlink/w:r/w:rPr/w:u[@w:val='none'] | — | — |
No render assertions declared.
scripts/gen_hyperlink_run_properties.py
#!/usr/bin/env python3
"""Generate ``fixtures/docx/hyperlink-run-properties--<variant>.docx``.
Parameterised across the 3 run-property variants described by
``features/docx/hyperlink-run-properties.json``:
* ``style-only`` - w:rStyle=Hyperlink (no explicit overrides)
* ``explicit-color`` - w:color w:val='FF0000' (no rStyle, the override
supersedes the character-style default)
* ``underline-off`` - w:u w:val='none'
The ``style-only`` variant deliberately relies on the built-in
``Hyperlink`` character style. A fresh ``Document()`` does not carry
that style by default; we add it explicitly here so the fixture is
self-contained and matches Word's on-disk shape.
.. versionadded:: 2026.05.0
"""
from __future__ import annotations
import argparse
from pathlib import Path
from docx import Document
from docx.enum.style import WD_STYLE_TYPE
from docx.oxml.ns import qn
from docx.oxml.parser import OxmlElement
from docx.shared import RGBColor
_REPO_ROOT = Path(__file__).resolve().parent.parent
def _ensure_hyperlink_style(doc) -> None:
"""Ensure the 'Hyperlink' character style exists."""
styles = doc.styles
try:
styles["Hyperlink"]
return
except KeyError:
pass
styles.add_style("Hyperlink", WD_STYLE_TYPE.CHARACTER)
def _write(variant: str, out: Path) -> None:
doc = Document()
p = doc.add_paragraph()
if variant == "style-only":
_ensure_hyperlink_style(doc)
p.add_hyperlink(
url="https://example.com/",
text="Click me",
style="Hyperlink",
)
elif variant == "explicit-color":
# style=None suppresses the w:rStyle; then apply explicit color.
hl = p.add_hyperlink(
url="https://example.com/",
text="Click me",
style=None,
)
run = hl.runs[0]
run.font.color.rgb = RGBColor(0xFF, 0x00, 0x00)
elif variant == "underline-off":
hl = p.add_hyperlink(
url="https://example.com/",
text="Click me",
style=None,
)
run = hl.runs[0]
# python-docx emits <w:u w:val='none'/> when underline is set False
run.font.underline = False
# Confirm the XML-level value is 'none' (the default emitted form).
# If not, force it explicitly.
rPr = run._element.get_or_add_rPr()
u = rPr.find(qn("w:u"))
if u is None:
u = OxmlElement("w:u")
rPr.append(u)
u.set(qn("w:val"), "none")
else:
raise ValueError(f"Unknown --variant {variant!r}")
out.parent.mkdir(parents=True, exist_ok=True)
doc.save(out, reproducible=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--variant",
required=True,
choices=("style-only", "explicit-color", "underline-off"),
)
parser.add_argument("--out", required=True)
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())
{
"$schema": "../manifest.schema.json",
"id": "docx/hyperlink-run-properties--explicit-color",
"kind": "literal",
"title": "Hyperlink run properties (rPr / Hyperlink style)",
"format": "docx",
"category": "references",
"summary": "A w:hyperlink's inner run can either inherit its visible formatting from the built-in 'Hyperlink' character style via w:rStyle, or override it directly with an inline w:rPr (color, underline, etc.). This family exercises three variants: style-only (default), explicit-color override, and explicit underline=none override.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.16.22",
"element": "w:hyperlink",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "Separate from docx/hyperlink-target-modes (which asserts the relationship side) and docx/hyperlink (which asserts only the element exists). Here we assert what sits INSIDE the hyperlink's w:r/w:rPr. The 'Hyperlink' character style is a built-in character style referenced via <w:rStyle w:val='Hyperlink'/>; explicit overrides appear as sibling elements inside the same w:rPr. Word's Insert Hyperlink dialog emits the style-only form; the override forms appear after the user manually recolors or un-underlines the link text."
},
"fixtures": {
"machine": "docx/hyperlink-run-properties--explicit-color"
},
"generator": {
"python": "scripts/gen_hyperlink_run_properties.py",
"arg_template": "--variant {variant.id} --out fixtures/docx/hyperlink-run-properties--{variant.id}.docx"
},
"_expansion": {
"parent_id": "docx/hyperlink-run-properties",
"bindings": {
"variant": "explicit-color"
}
},
"assertions": [
{
"id": "has-hyperlink-explicit-color",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
},
"xpath": "//w:hyperlink[@r:id]",
"must": "exist",
"description": "The generated fixture must contain one external-URL w:hyperlink."
},
{
"id": "rstyle-explicit-color",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:hyperlink/w:r/w:rPr/w:rStyle[@w:val='Hyperlink']",
"must": "absent",
"description": "In the style-only variant the run carries <w:rStyle w:val='Hyperlink'/>. Override variants suppress that so the override can take effect cleanly."
},
{
"id": "color-explicit-color",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:hyperlink/w:r/w:rPr/w:color[@w:val='FF0000']",
"must": "exist",
"description": "The explicit-color variant overrides the theme hyperlink color with w:color w:val='FF0000' (pure red)."
},
{
"id": "underline-off-explicit-color",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:hyperlink/w:r/w:rPr/w:u[@w:val='none']",
"must": "absent",
"description": "The underline-off variant suppresses the default hyperlink underline with w:u w:val='none'."
}
]
}
No rendered reference is available for this case.