office-toolkit

"[tool] Scrape and patch Office documents (docx, pptx,

office-toolkit

Scrape and patch Office documents and PDF into structured JSON via direct XML parsing.

Boundary

  • Does:
    • Scrape docx body into flat JSON keyed by body element index.
    • Scrape pptx slides into flat JSON keyed by slide index.
    • Scrape xlsx sheets into flat JSON keyed by sheet name.
    • Scrape PDF pages into flat JSON keyed by page index (block-level with table detection).
    • Patch docx XML in-place via JSON instructions.
    • Patch pptx XML in-place via JSON instructions (planned).
    • Preserve everything not explicitly targeted during patch.
  • Does not:
    • Edit XML directly — always use patch commands.
    • Extract image pixel content or render diagrams.
    • Handle Confluence pages or plain text files.
    • Use python-docx or python-pptx — always use zipfile + lxml.

Interface

  • Commands:
    • scrape.py — document to JSON. Auto-detects format by extension.
    • patch.py — JSON instructions to document. Auto-detects format by extension.
  • Input:
    • scrape.py: .docx, .pptx, .xlsx, or .pdf file.
    • patch.py: document file + JSON instruction array.
  • Output:
    • scrape.py: JSON to stdout or file (-o).
    • patch.py: modified document (in-place or to output path).

Behavior

Run python3.12 scrape.py --help or python3.12 patch.py --help for usage.

patch.py

Usage: python3.12 patch.py <file> <instructions.json> [-o output]

Instructions are a JSON array of op dicts. Each op has "op" and "idx" (body element index from scrape output).

Execution order: modification ops first (body length unchanged), then structural ops in descending idx order (prevents drift), then comment ops.

Modification ops

OpRequired fieldsEffect
update_textidx, run, textChange text of a specific run by index
update_runsidx, runsReplace all runs in a paragraph
rename_headingidx, textChange heading text, keep first run formatting
update_cellidx, row, col, runsReplace cell content in a table
add_rowidx, after_row, cellsInsert row after specified row
delete_rowidx, rowDelete a row from a table

Structural ops

OpRequired fieldsEffect
deleteidxRemove body element
add_afteridx, runsInsert paragraph after element
add_table_afteridx, rowsInsert table after element
moveidx, afterMove element to after another
insert_imageidx, image_path, width_emu, height_emuInsert image paragraph after element

Comment ops

OpRequired fieldsEffect
reply_commenttextAppend comment to comments.xml

Optional: author (default "AI").

Run dict format

{"text": "hello", "bold": true, "italic": true, "hidden": true}

Hyperlink run: {"text": "click", "hyperlink": "https://..."}.

Text supports \n (line break) and \t (tab).

Optional fields

  • clone_style_from: body index to copy style from (add_after, add_table_after).
  • cells: array of {"runs": [...]} for add_row.
  • rows: array of arrays of {"runs": [...]} for add_table_after.

docx fields

Node fieldDescription
tagp, tbl, or sdt
textPlain text (runs concatenated)
runsText runs with formatting flags
styleParagraph or table style name
levelHeading level (1-9)
sectionFull section path (headings only)
cellsTable cells with row, col, text, runs
listtrue if list paragraph
commentsInline comments: id, author, date, text

Image runs: image, rId, target, alt, width_emu, height_emu.

pptx fields

Shape typeDescription
textText box or auto shape with text
tableTable shape
imagePicture (position only)
chartEmbedded chart
groupGrouped shapes (recursive children)
otherSmartArt, media, unknown

Run fields: text, bold, italic, size, color, hyperlink.

Cell fields: row, col, text, runs, shading.

Gotchas

PitfallCorrect approach
Slide order ≠ file numberingRead presentation.xml rId order
Grouped shapes hide textRecurse into p:grpSp
SmartArt text in separate XMLMark as other, text may be incomplete
pptx placeholder titlesCheck p:ph type attribute
docx update_runs drops formattingInclude bold/italic in run dicts
Multiple add_after on same idxUse different target indices