docx

Create, read, edit, template, and review Word .docx files.

  • word
  • docx
  • documents
  • office
  • templates
  • revisions
  • comments

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/skills/productivity/docx'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md
#!/usr/bin/env python3# MIT License. Shared helpers for the docx skill scripts."""Shared helpers: paragraph iteration and run-preserving text replacement."""from __future__ import annotations  def iter_all_paragraphs(doc, include_headers_footers: bool = True):    """Yield every paragraph in body, tables (recursively), headers, footers."""    yield from _iter_container(doc)    if include_headers_footers:        for section in doc.sections:            for part in (                section.header, section.footer,                section.first_page_header, section.first_page_footer,                section.even_page_header, section.even_page_footer,            ):                if part is not None:                    yield from _iter_container(part)  def _iter_container(container):    for para in container.paragraphs:        yield para    for table in container.tables:        yield from _iter_table(table)  def _iter_table(table):    for row in table.rows:        for cell in row.cells:            for para in cell.paragraphs:                yield para            for nested in cell.tables:                yield from _iter_table(nested)  def iter_part_roots(doc):    """Yield the XML root of the body plus every header/footer part."""    yield doc.element.body    seen = set()    for section in doc.sections:        for part in (            section.header, section.footer,            section.first_page_header, section.first_page_footer,            section.even_page_header, section.even_page_footer,        ):            if part is not None and id(part._element) not in seen:                seen.add(id(part._element))                yield part._element  def replace_in_paragraph(para, old: str, new: str) -> int:    """Replace `old` with `new` in a paragraph, preserving run formatting.     Strategy: first replace occurrences fully contained in a single run    (formatting fully preserved). If the needle spans multiple runs, the    matched runs are collapsed: the replacement inherits the formatting of    the run where the match starts. Returns number of replacements made.    """    if not old or old not in para.text:        return 0    count = 0    # Pass 1: within-run replacements.    for run in para.runs:        if old in run.text:            count += run.text.count(old)            run.text = run.text.replace(old, new)    # Pass 2: cross-run occurrences.    while old in para.text:        runs = para.runs        # Map paragraph text offsets to (run_index, offset_in_run).        full = "".join(r.text for r in runs)        start = full.find(old)        if start < 0:            break        end = start + len(old)        pos = 0        spans = []  # (run_idx, cut_start, cut_end) portions inside the match        for i, r in enumerate(runs):            r_start, r_end = pos, pos + len(r.text)            if r_end > start and r_start < end:                spans.append((i, max(start, r_start) - r_start,                              min(end, r_end) - r_start))            pos = r_end        first = True        for i, cs, ce in spans:            t = runs[i].text            if first:                runs[i].text = t[:cs] + new + t[ce:]                first = False            else:                runs[i].text = t[:cs] + t[ce:]        count += 1    return count