agentsclimarketplace

Run2 docx template filling

Skill cxcscmu/SkillLearnBench/skills/b3-teacher-feedback-claude-opus-4-6/offer-letter-generator/run2_docx-template-filling

[COLM'26] SkillLearnBench is the first benchmark for evaluating continual learning methods that automatically generate agent skills.

Install
npx -y skills add cxcscmu/SkillLearnBench --skill run2_docx-template-filling

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

How to open a .docx Word document template with python-docx, replace placeholder tags like {{PLACEHOLDER}} in all document locations (body, headers, footers, tables including nested tables), handle conditional sections, and save the result.

SKILL.md

6.7 KB, as published. Nobody here has run it

Core Approach

Use python-docx to open the template, collect ALL paragraphs from every possible location, perform text replacement, and save.

Collecting ALL Paragraphs (Critical: Must Be Recursive)

The biggest pitfall is missing paragraphs in nested tables, headers, footers, and tables within headers/footers. Use a recursive approach:

from docx import Document

def get_all_paragraphs(doc):
    """Collect every paragraph in the document, including nested tables, headers, and footers."""
    paragraphs = []

    # Body paragraphs
    paragraphs.extend(doc.paragraphs)

    # Tables in body (recursive)
    for table in doc.tables:
        paragraphs.extend(_get_paragraphs_from_table(table))

    # Headers and footers
    for section in doc.sections:
        for header_footer in [section.header, section.footer, section.first_page_header, section.first_page_footer, section.even_page_header, section.even_page_footer]:
            if header_footer is not None:
                paragraphs.extend(header_footer.paragraphs)
                for table in header_footer.tables:
                    paragraphs.extend(_get_paragraphs_from_table(table))

    return paragraphs


def _get_paragraphs_from_table(table):
    """Recursively collect paragraphs from a table, including nested tables within cells."""
    paragraphs = []
    for row in table.rows:
        for cell in row.cells:
            paragraphs.extend(cell.paragraphs)
            # CRITICAL: Recurse into nested tables
            for nested_table in cell.tables:
                paragraphs.extend(_get_paragraphs_from_table(nested_table))
    return paragraphs

Why Recursive?

Templates often have nested tables (a table inside a table cell). doc.tables only gives top-level tables. You MUST call cell.tables on each cell and recurse to find all nested content. Without this, placeholders in nested tables will remain unreplaced.

Replacing Placeholders in Paragraphs (Run-Aware)

Placeholders like {{CANDIDATE_FULL_NAME}} may be split across multiple runs within a paragraph. Always work at the full paragraph text level, then rewrite runs:

def replace_placeholder_in_paragraph(paragraph, placeholder, value):
    """Replace a placeholder in a paragraph, handling split runs."""
    full_text = ''.join(run.text for run in paragraph.runs)
    if placeholder not in full_text:
        return False
    new_text = full_text.replace(placeholder, value)
    # Clear all runs, put new text in first run, preserve formatting
    for i, run in enumerate(paragraph.runs):
        if i == 0:
            run.text = new_text
        else:
            run.text = ''
    return True

Full Replacement Over All Paragraphs

def replace_all(doc, placeholder, value):
    for para in get_all_paragraphs(doc):
        replace_placeholder_in_paragraph(para, placeholder, value)

Handling Conditional Sections

For {{IF_RELOCATION}}...{{END_IF_RELOCATION}}:

  • If the condition is true (e.g., RELOCATION_PACKAGE == "Yes"): remove only the marker lines {{IF_RELOCATION}} and {{END_IF_RELOCATION}}, keep content between them.
  • If the condition is false: remove the markers AND all content between them.
def handle_conditional(doc, tag_name, keep_content):
    """Handle conditional blocks like {{IF_RELOCATION}}...{{END_IF_RELOCATION}}."""
    start_tag = '{{IF_' + tag_name + '}}'
    end_tag = '{{END_IF_' + tag_name + '}}'
    all_paras = get_all_paragraphs(doc)
    
    inside = False
    for para in all_paras:
        full_text = ''.join(run.text for run in para.runs)
        
        if start_tag in full_text:
            inside = True
            # Remove the start tag (or the whole paragraph if it's only the tag)
            replace_placeholder_in_paragraph(para, start_tag, '')
            # Check if paragraph is now empty/whitespace — if so, remove it
            remaining = ''.join(run.text for run in para.runs).strip()
            if not remaining:
                _remove_paragraph(para)
            continue
        
        if end_tag in full_text:
            inside = False
            replace_placeholder_in_paragraph(para, end_tag, '')
            remaining = ''.join(run.text for run in para.runs).strip()
            if not remaining:
                _remove_paragraph(para)
            continue
        
        if inside and not keep_content:
            _remove_paragraph(para)


def _remove_paragraph(paragraph):
    """Remove a paragraph from the document."""
    p = paragraph._element
    p.getparent().remove(p)

Handling Conditional When Tags Are Inline

Sometimes the IF/END_IF markers are inline within a paragraph alongside other text. In that case, just replace them with empty string rather than removing the paragraph:

# Simpler approach: always just replace the tags with ''
# Then remove content paragraphs if not keeping

Complete Workflow

import json
from docx import Document

# Load data
with open('employee_data.json', 'r') as f:
    data = json.load(f)

doc = Document('offer_letter_template.docx')

# Handle conditional FIRST (before replacing other placeholders)
keep_relocation = data.get('RELOCATION_PACKAGE', 'No').strip().lower() == 'yes'
handle_conditional(doc, 'RELOCATION', keep_relocation)

# Replace all placeholders
for key, value in data.items():
    placeholder = '{{' + key + '}}'
    replace_all(doc, placeholder, str(value))

doc.save('/root/offer_letter_filled.docx')

Verification

After saving, reopen and verify no {{ placeholders remain:

doc2 = Document('/root/offer_letter_filled.docx')
for para in get_all_paragraphs(doc2):
    text = ''.join(run.text for run in para.runs)
    assert '{{' not in text, f"Unreplaced placeholder found: {text}"

Common Pitfalls

  1. Nested tables: MUST recurse into cell.tables — this is the #1 cause of unreplaced placeholders
  2. Headers/footers: Must iterate ALL section headers and footers, including first-page and even-page variants
  3. Tables in headers/footers: Headers/footers can contain tables too — must process those
  4. Split runs: A placeholder like {{NAME}} might be split as {{ in one run and NAME}} in another — always join all run texts before matching
  5. Deduplication: When iterating cells, the same cell object can appear multiple times if cells are merged. Consider deduplicating by element identity: seen = set(); ... if id(para._element) in seen: continue

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.