docx/unicode-bmp-round-trip--emoji-bmp

A single paragraph carries a text string in each target script/range, including BMP, supplementary planes (emoji), RTL, combining marks, and ZWJ sequences. The run's <w:t> child must carry the text verbatim with no surrogate-pair corruption.

Library verdicts: python-docx: — docxjs: —

Metadata

Feature iddocx/unicode-bmp-round-trip--emoji-bmp
Formatdocx
Categorytext-content
Familydocx/unicode-bmp-round-trip
Axis valuesscript=emoji-bmp
Spececma-376-5-part-1 § 17.3.3.31 w:t

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
text-carries-sample-emoji-bmp
word/document.xml
exist
The run's <w:t> element must contain the characteristic sample substring for this script family.
//w:t[contains(., '✨')]

Render assertions

No render assertions declared.

Generator source

scripts/gen_unicode_bmp_round_trip.py

#!/usr/bin/env python3
"""Generate a ``fixtures/docx/unicode-bmp-round-trip--<script>.docx`` fixture.

Parameterised generator for the ``docx/unicode-bmp-round-trip`` family.
One invocation writes a minimal one-paragraph document carrying a
characteristic text sample from one Unicode range. The assertions in
the manifest check that the text survives to the ``<w:t>`` element
verbatim via XPath ``contains()``.

The text table is held inline here rather than passed on the command
line because shell quoting for bidirectional RTL text, combining-mark
sequences, emoji ZWJ sequences, and the like is fragile across the
corpus runner, pytest subprocess harness, and interactive invocation.
Keeping the table in one place (this file) lets the manifest refer
only to stable ``id`` + ``sample`` fields while the actual payload
stays in Python source where encoding is unambiguous.

Usage::

    python scripts/gen_unicode_bmp_round_trip.py \\
        --script chinese \\
        --out fixtures/docx/unicode-bmp-round-trip--chinese.docx
"""

from __future__ import annotations

import argparse
from pathlib import Path

from docx import Document

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

# -- Unicode payload table. Keys mirror the ``script.id`` axis in
#    features/docx/unicode-bmp-round-trip.json; values are the full text
#    written into the fixture's single run. The ``sample`` substring in
#    the manifest is chosen to survive any XML entity-encoding or
#    normalisation the library may apply, so both "literal UTF-8" and
#    "&#NNNN;" forms satisfy the assertion at the XML infoset layer.
_PAYLOADS: dict[str, str] = {
    "ascii":      "Hello world",
    "latin1":     "café naïve résumé",
    "cyrillic":   "Привет мир",
    "greek":      "Γειά σου κόσμε",
    "hebrew":     "שלום עולם",
    "arabic":     "مرحبا بالعالم",
    "chinese":    "你好世界",
    "japanese":   "こんにちは世界",
    "korean":     "안녕하세요 세계",
    "devanagari": "नमस्ते दुनिया",
    "thai":       "สวัสดีชาวโลก",
    "emoji-bmp":  "Star ✨ ok",
    "emoji-smp":  "Grin \U0001F600 ok",
    # U+00E0 (precomposed à) then U+0061 U+0300 (a + combining grave).
    "combining":  "combined à decomposed à",
    # ZWJ (U+200D) family sequence: man + ZWJ + woman + ZWJ + boy.
    "zwj-family": "family \U0001F468‍\U0001F469‍\U0001F466",
}


def _write(script: str, out: Path) -> None:
    if script not in _PAYLOADS:
        raise SystemExit(
            f"Unknown script id {script!r}; expected one of {sorted(_PAYLOADS)}"
        )
    doc = Document()
    paragraph = doc.add_paragraph()
    paragraph.add_run(_PAYLOADS[script])
    out.parent.mkdir(parents=True, exist_ok=True)
    doc.save(out, reproducible=True)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--script", required=True, help="Script id key in the payload table.")
    parser.add_argument("--out", required=True, help="Output path for the .docx fixture.")
    args = parser.parse_args()

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


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

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/unicode-bmp-round-trip--emoji-bmp",
  "kind": "literal",
  "title": "Unicode round-trip (docx)",
  "format": "docx",
  "category": "text-content",
  "summary": "A single paragraph carries a text string in each target script/range, including BMP, supplementary planes (emoji), RTL, combining marks, and ZWJ sequences. The run's <w:t> child must carry the text verbatim with no surrogate-pair corruption.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "17.3.3.31",
    "element": "w:t",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
    "notes": "WordprocessingML parts are stored as UTF-8 XML. Every Unicode code point in the text payload may appear either as a literal UTF-8 byte sequence or as a numeric character reference (&#xNNNN;) — both are spec-legal. python-docx and Microsoft Word both emit literal UTF-8, which is the interoperability-friendly form. This parameterised family covers the principal Unicode ranges that stress encoders: ASCII, Latin-1 with accents, Cyrillic, Greek, Hebrew (RTL), Arabic (RTL with kashida), CJK (Chinese + Japanese + Korean), Devanagari, Thai, BMP emoji (U+2728), supplementary-plane emoji (U+1F600, requires UTF-16 surrogate pairs internally in Python but UTF-8 on the wire), a precomposed + decomposed accent pair, and a ZWJ emoji family sequence. Assertions use XPath contains() on <w:t> so literal vs. entity encoding is immaterial at the XML infoset layer; lxml normalises both on parse."
  },
  "fixtures": {
    "machine": "docx/unicode-bmp-round-trip--emoji-bmp"
  },
  "generator": {
    "python": "scripts/gen_unicode_bmp_round_trip.py",
    "arg_template": "--script {script.id} --out fixtures/docx/unicode-bmp-round-trip--{script.id}.docx"
  },
  "_expansion": {
    "parent_id": "docx/unicode-bmp-round-trip",
    "bindings": {
      "script": "emoji-bmp"
    }
  },
  "assertions": [
    {
      "id": "text-carries-sample-emoji-bmp",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:t[contains(., '✨')]",
      "must": "exist",
      "description": "The run's <w:t> element must contain the characteristic sample substring for this script family."
    }
  ]
}

Fixture

Download unicode-bmp-round-trip--emoji-bmp.docx (18.6 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

WordprocessingML parts are stored as UTF-8 XML. Every Unicode code point in the text payload may appear either as a literal UTF-8 byte sequence or as a numeric character reference (&#xNNNN;) — both are spec-legal. python-docx and Microsoft Word both emit literal UTF-8, which is the interoperability-friendly form. This parameterised family covers the principal Unicode ranges that stress encoders: ASCII, Latin-1 with accents, Cyrillic, Greek, Hebrew (RTL), Arabic (RTL with kashida), CJK (Chinese + Japanese + Korean), Devanagari, Thai, BMP emoji (U+2728), supplementary-plane emoji (U+1F600, requires UTF-16 surrogate pairs internally in Python but UTF-8 on the wire), a precomposed + decomposed accent pair, and a ZWJ emoji family sequence. Assertions use XPath contains() on <w:t> so literal vs. entity encoding is immaterial at the XML infoset layer; lxml normalises both on parse.