Assemble¶
This article demonstrates how to assemble multiple RTF files into a single RTF or DOCX file using rtflite.
Prerequisites¶
To enable DOCX support, install rtflite with the docx extra:
Define input files¶
Assemble into RTF¶
Assemble into DOCX with toggle fields¶
Open combined-docx.docx in Word and refresh fields to resolve the
INCLUDETEXT placeholders. Without opening in Word, the placeholders
appear as Error! Reference source not found.
because python-docx does not evaluate those fields.
Note
The assemble_docx workflow intentionally mirrors how medical writers
paste hyperlinks into CSR sections. After the link is in place,
toggling fields or running Update Field in Word reconnects the
assembled file to the original TLFs without additional copy-paste work.
This hybrid approach, which relies on Word's field codes, is documented
in the r4csr book.
Another example to assemble into DOCX with mixed orientation pages (portrait, landscape):
Open combined-mixed.docx in Word and update fields to pull in the portrait
and landscape tables. Until the fields are refreshed, you will see the same
Error! Reference source not found. placeholders.
Assemble into DOCX without toggle fields¶
RTFDocument.write_docx (added in rtflite 2.2.0) creates DOCX files for
individual rtflite tables directly.
Use concatenate_docx (added in rtflite 2.3.0, powered by python-docx)
to concatenate the DOCX outputs when you need final files without manual
field refreshes, similar to assemble_rtf.
Generate example DOCX tables:
from importlib.resources import files
import polars as pl
import rtflite as rtf
ae_path = files("rtflite.data").joinpath("adae.parquet")
ae = pl.read_parquet(ae_path)
ae_summary = (
ae.group_by(["TRTA", "AEDECOD"])
.agg(pl.len().alias("n"))
.pivot(values="n", index="AEDECOD", on="TRTA")
.fill_null(0)
.sort("AEDECOD")
)
# Two portrait tables
portrait_doc1 = rtf.RTFDocument(
df=ae_summary.head(12),
rtf_title=rtf.RTFTitle(text="Adverse Events (Portrait)"),
rtf_column_header=rtf.RTFColumnHeader(
text=[
"Adverse Events",
"Placebo (N=86)",
"Xanomeline High Dose (N=84)",
"Xanomeline Low Dose (N=84)",
],
),
rtf_body=rtf.RTFBody(col_rel_width=[4, 2, 2, 2]),
)
portrait_doc1.write_docx("portrait-ae-1.docx")
portrait_doc2 = rtf.RTFDocument(
df=ae_summary.slice(12, 12),
rtf_title=rtf.RTFTitle(text="Adverse Events (Portrait, Part 2)"),
rtf_column_header=rtf.RTFColumnHeader(
text=[
"Adverse Events",
"Placebo (N=86)",
"Xanomeline High Dose (N=84)",
"Xanomeline Low Dose (N=84)",
],
),
rtf_body=rtf.RTFBody(col_rel_width=[4, 2, 2, 2]),
)
portrait_doc2.write_docx("portrait-ae-2.docx")
# One landscape table
landscape_doc = rtf.RTFDocument(
df=ae_summary.head(20),
rtf_page=rtf.RTFPage(
orientation="landscape",
nrow=10,
border_first="dashed",
border_last="dashed",
),
rtf_title=rtf.RTFTitle(text="Adverse Events Summary - Landscape Layout"),
rtf_column_header=rtf.RTFColumnHeader(
text=[
"Adverse Events",
"Placebo (N=86)",
"Xanomeline High Dose (N=84)",
"Xanomeline Low Dose (N=84)",
],
),
rtf_body=rtf.RTFBody(col_rel_width=[4, 2, 2, 2]),
)
landscape_doc.write_docx("landscape-ae.docx")
Concatenate two portrait DOCX files¶
Use concatenate_docx to start from the first DOCX (avoids a blank leading
page) and add a new section per file so each starts on its own page with the
correct orientation.
portrait_files = [
"portrait-ae-1.docx",
"portrait-ae-2.docx",
]
concatenate_docx(
portrait_files,
"combined-python-docx.docx",
)