docx/math-equation--radical

A family covering common Office Math (OMML) constructs — fraction, radical, super/subscript, n-ary summation and integral, matrix, and a plain identifier run.

Library verdicts: python-docx: — docxjs: —

Metadata

Feature iddocx/math-equation--radical
Formatdocx
Categorymath
Familydocx/math-equation
Axis valuesequation=radical
Spececma-376-5-part-1 § 22 m:oMath

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
omath-present-radical
word/document.xml
exist
At least one <m:oMath> element must be present in the body. Every OMML construct is hosted inside an <m:oMath> (inline) or <m:oMathPara> (display).
//m:oMath
omml-shape-radical
word/document.xml
exist
The OMML expression must contain the shape specific to this case (fraction / radical / super / sub / summation / integral / matrix / identifier).
//m:oMath//m:rad

Render assertions

No render assertions declared.

Generator source

scripts/gen_math_equation.py

#!/usr/bin/env python3
"""Generate ``fixtures/docx/math-equation--<shape>.docx`` fixtures.

Each run builds one paragraph hosting one OMML construct. The ``--shape``
argument selects which OMML element family is emitted:

- ``fraction``    — ``m:f``
- ``radical``     — ``m:rad``
- ``superscript`` — ``m:sSup``
- ``subscript``   — ``m:sSub``
- ``summation``   — ``m:nary`` with ``m:chr m:val='∑'``
- ``integral``    — ``m:nary`` with ``m:chr m:val='∫'``
- ``matrix``      — ``m:m`` 2x2
- ``identifier``  — a plain ``m:r``/``m:t`` run of ``y``

The first five go through the fork's high-level builder functions from
``docx.equations``. The summation, integral, and matrix shapes use
hand-authored OMML XML strings passed to ``Paragraph.add_equation`` —
the fork currently has no n-ary or matrix builder.

The manifest at ``features/docx/math-equation.json`` drives the ``--shape``
choice via its ``generator.arg_template`` at expansion time.
"""

from __future__ import annotations

import argparse
from pathlib import Path

from docx import Document
from docx.equations import (
    build_fraction,
    build_identifier,
    build_radical,
    build_subscript,
    build_superscript,
)

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

# -- OMML namespace URI for hand-authored OMML XML fragments. --
_M_NS = "http://schemas.openxmlformats.org/officeDocument/2006/math"


def _summation_omml() -> str:
    """Return an ``m:oMath`` expressing Σ from i=1 to n of i.

    Uses ``m:nary`` with ``m:naryPr/m:chr m:val='∑'`` and limLoc=undOvr so
    the sub/sup limits sit under and over the sigma.
    """
    return (
        '<m:oMath xmlns:m="%s">'
        "<m:nary>"
        '<m:naryPr><m:chr m:val="∑"/><m:limLoc m:val="undOvr"/></m:naryPr>'
        "<m:sub><m:r><m:t>i=1</m:t></m:r></m:sub>"
        "<m:sup><m:r><m:t>n</m:t></m:r></m:sup>"
        "<m:e><m:r><m:t>i</m:t></m:r></m:e>"
        "</m:nary>"
        "</m:oMath>"
    ) % _M_NS


def _integral_omml() -> str:
    """Return an ``m:oMath`` expressing ∫ from 0 to 1 of x.

    Uses ``m:nary`` with ``m:chr m:val='∫'``. Default limLoc (subSup)
    places limits to the right of the sign, matching Word's behaviour
    for integrals.
    """
    return (
        '<m:oMath xmlns:m="%s">'
        "<m:nary>"
        '<m:naryPr><m:chr m:val="∫"/></m:naryPr>'
        "<m:sub><m:r><m:t>0</m:t></m:r></m:sub>"
        "<m:sup><m:r><m:t>1</m:t></m:r></m:sup>"
        "<m:e><m:r><m:t>x</m:t></m:r></m:e>"
        "</m:nary>"
        "</m:oMath>"
    ) % _M_NS


def _matrix_omml() -> str:
    """Return an ``m:oMath`` expressing a 2x2 matrix of a,b / c,d.

    Uses ``m:m`` with one ``m:mcs`` carrying a single ``m:mc`` that sets
    column count to 2 and center justification, then two ``m:mr`` rows of
    two ``m:e`` cells each.
    """
    return (
        '<m:oMath xmlns:m="%s">'
        "<m:m>"
        "<m:mPr><m:mcs><m:mc><m:mcPr>"
        '<m:count m:val="2"/><m:mcJc m:val="center"/>'
        "</m:mcPr></m:mc></m:mcs></m:mPr>"
        "<m:mr>"
        "<m:e><m:r><m:t>a</m:t></m:r></m:e>"
        "<m:e><m:r><m:t>b</m:t></m:r></m:e>"
        "</m:mr>"
        "<m:mr>"
        "<m:e><m:r><m:t>c</m:t></m:r></m:e>"
        "<m:e><m:r><m:t>d</m:t></m:r></m:e>"
        "</m:mr>"
        "</m:m>"
        "</m:oMath>"
    ) % _M_NS


_SHAPES: dict[str, callable] = {  # type: ignore[type-arg]
    "fraction":    lambda: build_fraction("a", "b"),
    "radical":     lambda: build_radical("x", degree_text="2"),
    "superscript": lambda: build_superscript("a", "2"),
    "subscript":   lambda: build_subscript("x", "1"),
    "summation":   _summation_omml,
    "integral":    _integral_omml,
    "matrix":      _matrix_omml,
    "identifier":  lambda: build_identifier("y"),
}


def _write(shape: str, out: Path) -> None:
    if shape not in _SHAPES:
        raise SystemExit(
            "Unknown shape %r; expected one of %s"
            % (shape, ", ".join(sorted(_SHAPES)))
        )
    doc = Document()
    p = doc.add_paragraph()
    p.add_equation(_SHAPES[shape]())
    out.parent.mkdir(parents=True, exist_ok=True)
    doc.save(out, reproducible=True)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--shape",
        default="fraction",
        choices=sorted(_SHAPES),
        help="Which OMML construct to emit.",
    )
    parser.add_argument(
        "--out",
        default=str(_REPO_ROOT / "fixtures" / "docx" / "math-equation.docx"),
    )
    args = parser.parse_args()

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


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

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/math-equation--radical",
  "kind": "literal",
  "title": "Math equation (OMML)",
  "format": "docx",
  "category": "math",
  "summary": "A family covering common Office Math (OMML) constructs — fraction, radical, super/subscript, n-ary summation and integral, matrix, and a plain identifier run.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "22",
    "element": "m:oMath",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/shared-math.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/shared-math.xsd",
    "notes": "Each expanded case writes one paragraph containing an inline <m:oMath> with a distinct OMML shape: fraction (m:f), radical (m:rad), superscript (m:sSup), subscript (m:sSub), summation (m:nary with m:chr val=U+2211), integral (m:nary with m:chr val=U+222B), matrix (m:m with two rows of two cells), and a plain identifier (m:r/m:t). Eight cases total. The fork's python-docx equation builders (build_fraction, build_radical, build_superscript, build_subscript, build_identifier) cover five of them; the summation / integral / matrix shapes go through Paragraph.add_equation with hand-written OMML XML since the fork has no high-level n-ary / matrix builder. The summation/integral shape_xpath uses m:naryPr/m:chr/@m:val to distinguish the two nary glyphs. All assertions run against word/document.xml using the m namespace."
  },
  "fixtures": {
    "machine": "docx/math-equation--radical",
    "office": "docx/math-equation--radical"
  },
  "generator": {
    "python": "scripts/gen_math_equation.py",
    "arg_template": "--shape {equation.id} --out fixtures/docx/math-equation--{equation.id}.docx"
  },
  "_expansion": {
    "parent_id": "docx/math-equation",
    "bindings": {
      "equation": "radical"
    }
  },
  "assertions": [
    {
      "id": "omath-present-radical",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
        "m": "http://schemas.openxmlformats.org/officeDocument/2006/math"
      },
      "xpath": "//m:oMath",
      "must": "exist",
      "description": "At least one <m:oMath> element must be present in the body. Every OMML construct is hosted inside an <m:oMath> (inline) or <m:oMathPara> (display)."
    },
    {
      "id": "omml-shape-radical",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
        "m": "http://schemas.openxmlformats.org/officeDocument/2006/math"
      },
      "xpath": "//m:oMath//m:rad",
      "must": "exist",
      "description": "The OMML expression must contain the shape specific to this case (fraction / radical / super / sub / summation / integral / matrix / identifier)."
    }
  ]
}

Fixture

Download math-equation--radical.docx (18.6 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

Each expanded case writes one paragraph containing an inline <m:oMath> with a distinct OMML shape: fraction (m:f), radical (m:rad), superscript (m:sSup), subscript (m:sSub), summation (m:nary with m:chr val=U+2211), integral (m:nary with m:chr val=U+222B), matrix (m:m with two rows of two cells), and a plain identifier (m:r/m:t). Eight cases total. The fork's python-docx equation builders (build_fraction, build_radical, build_superscript, build_subscript, build_identifier) cover five of them; the summation / integral / matrix shapes go through Paragraph.add_equation with hand-written OMML XML since the fork has no high-level n-ary / matrix builder. The summation/integral shape_xpath uses m:naryPr/m:chr/@m:val to distinguish the two nary glyphs. All assertions run against word/document.xml using the m namespace.