Document processing
The definitive collection of cross-platform Agent Skills. Compatible with Claude Code, Codex, Cursor, OpenClaw, Gemini CLI, Copilot, Hermes. Curated weekly. Higher quality than any alternative.
npx -y skills add JPeetz/agent-skills --skill document-processingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
End-to-end document processing agent skill covering PDF manipulation (extract, merge, split, rotate, watermark, form-fill, OCR), DOCX creation and editing (templates, mail-merge, style management), XLSX spreadsheet handling (formulas, charts, pivot tables, data analysis), PPTX presentation generation (layouts, charts, media, speaker notes), and cross-format document conversion via pandoc and native libraries. Primary keyword clusters: PDF manipulation automation, DOCX generation Python, Excel spreadsheet automation openpyxl, PowerPoint generation python-pptx, document format conversion pandoc, mail merge automation, PDF OCR extraction, batch document processing, office automation Python, template-driven document generation. Designed for agentic platforms — Claude Code, Codex, Cursor, Gemini CLI, OpenClaw, GitHub Copilot, Windsurf, and OpenCode.
SKILL.md
28.5 KB, ~7.3k tokens by cl100k_base, as published. Nobody here has run it
Document Processing — Agent Skill
End-to-end document processing for agentic workflows. Create, read, edit, convert, and analyze documents across the five major office formats: PDF, DOCX, XLSX, PPTX, and cross-format conversion.
Quick Reference
| Task | Library/Tool | Key Command/Pattern |
|---|---|---|
| Extract text (simple) | pypdf | PdfReader("f.pdf").pages[0].extract_text() |
| Extract text (layout) | pdfplumber | pdf.pages[i].extract_text() |
| Extract tables | pdfplumber | pdf.pages[i].extract_tables() |
| Merge PDFs | pypdf | PdfWriter().append(…).write("out.pdf") |
| Split PDFs | pypdf | Write page slices to separate files |
| Rotate pages | pypdf | page.rotate(90) |
| Add watermark | pypdf | Overlay PDF onto target |
| Fill form fields | pypdf | update_page_form_field_values(page, data) |
| OCR scanned PDF | pytesseract + pdf2image | Convert to images → Tesseract |
| Create DOCX from scratch | python-docx | Document().add_heading(…) |
| Mail merge (template) | python-docx + CSV | Replace {{PLACEHOLDER}} in runs |
| DOCX → PDF | LibreOffice headless | soffice --headless --convert-to pdf |
| Create XLSX with charts | openpyxl | Workbook() → add BarChart() |
| Pivot table | openpyxl | PivotTable() on new sheet |
| XLSX → CSV | pandas | read_excel().to_csv() |
| Create PPTX | python-pptx | Presentation().slides.add_slide(…) |
| Speaker notes | python-pptx | slide.notes_slide.notes_text_frame.text |
| Cross-format convert | pandoc | pandoc input.docx -o output.md |
| Validate tools | doc_tools_check.py | Run at start of every session |
When to Activate
Activate this skill when the user needs to do something with a document as opposed to merely viewing it. Activation triggers include:
| Trigger | Example phrasing |
|---|---|
| PDF manipulation | "extract pages from this PDF", "merge these PDFs", "fill out this form", "rotate page 3", "add a watermark", "OCR this scanned document", "split this PDF into chapters" |
| DOCX creation / editing | "write a report as a Word doc", "update the template", "mail-merge these names", "convert my markdown to DOCX", "generate an invoice as .docx" |
| XLSX spreadsheet work | "create a budget spreadsheet", "add a chart to the Excel file", "calculate column totals", "generate a pivot table", "format this data as a table" |
| PPTX presentation | "make a slide deck from these notes", "add speaker notes", "generate charts in PowerPoint", "create a presentation from this outline" |
| Format conversion | "convert PDF to DOCX", "turn this Word doc into Markdown", "XLSX to CSV", "DOCX to PDF", "PPTX to images" |
Non-triggers (do NOT activate):
- "Read this PDF to me" (pure text extraction for viewing — use a simpler read/extract path, not this full skill)
- "Edit this paragraph" (general text editing, not document-format editing)
- "What does this image look like?" (image analysis, not document processing)
- "Summarise this article" (content summarisation, not document manipulation)
- "Can you crop this photo?" (image editing, not document processing)
Common Pitfalls & Anti-Patterns
❌ NEVER do these
-
Overwriting the source file — you'll lose the original
- Always write to
{original}_processed.{ext}or{original}_{operation}.{ext} - If the user insists on overwrite, show both paths and confirm first
- Always write to
-
Forgetting to run
doc_tools_check.pybefore operations- Missing libraries cause obscure errors. Run the checker first in every session.
-
Using
pypdffor table extraction — it'll give you garbagepypdfextracts raw text with no layout awareness. Usepdfplumberfor tables and columnar text.
-
Loading a 500MB file entirely into memory
- For large files (>100MB): use streaming/chunking. For PDFs >1,000 pages: extract page ranges.
-
Assuming
cell.valuereturns a formatted string when it returnsNone- XLSX formulas return
Noneunless you load withdata_only=True. Even then, cached values may be stale.
- XLSX formulas return
-
Using hardcoded slide layout indices (
prs.slide_layouts[0])- Layout order differs between templates. List dynamically:
[ly.name for ly in prs.slide_layouts]
- Layout order differs between templates. List dynamically:
-
Running Pandoc without checking it's installed
- Pandoc is a system dependency, not a Python package.
brew install pandoc/apt install pandoc.
- Pandoc is a system dependency, not a Python package.
-
Trying to fill XFA (XML Forms Architecture) forms with pypdf
pypdfonly handles AcroForm fields. XFA forms require different tools — checkreader.get_fields()first.
-
Embedding 4000px images into DOCX files
- Resize images before embedding:
img.thumbnail((1920, 1080)). Unscaled images balloon file sizes.
- Resize images before embedding:
-
Mixing document formats without checking conversion quality
- PDF → DOCX conversion loses structure. DOCX → PDF via LibreOffice preserves it. Check
references/conversion-matrix.mdfor quality ratings.
- PDF → DOCX conversion loses structure. DOCX → PDF via LibreOffice preserves it. Check
✅ Decision Matrix: Which Library When?
| Outcome | DOCX | XLSX | PPTX | |
|---|---|---|---|---|
| Simple text extraction | pypdf | N/A | N/A | N/A |
| Layout-aware text | pdfplumber | python-docx | openpyxl | python-pptx |
| Table extraction | pdfplumber | python-docx | openpyxl | N/A |
| Create from scratch | N/A | python-docx | openpyxl | python-pptx |
| Template filling | pypdf (forms) | python-docx | openpyxl | python-pptx |
| Charts | N/A | N/A | openpyxl | python-pptx |
| Images/OCR | pytesseract | python-docx | openpyxl | python-pptx |
| Conversion to other | LibreOffice | pandoc/LibreOffice | pandas/LibreOffice | LibreOffice |
Safety Rules (Mandatory)
-
Never overwrite the source file. Always write output to a new path. Default naming:
{original_name}_processed.{ext}or{original_name}_{operation}.{ext}. -
Validate output integrity. After every write operation, read back the output file and confirm:
- The file exists and is non-empty.
- Page / row / slide counts are correct.
- Critical content (text, data, images) survived the operation.
-
Warn on destructive operations. If a user explicitly asks to overwrite the source, confirm before proceeding. Show both paths and ask.
-
Handle missing dependencies gracefully. Run
scripts/doc_tools_check.pybefore any document operation. If a required library is missing, install it (with user approval) or suggest the command. -
Preserve metadata when possible. Author, creation date, and custom properties should survive transformations unless the user explicitly asks to strip them.
-
Respect file size limits. For files >100 MB, warn the user and suggest streaming / chunking approaches. For PDFs >1,000 pages, use page-range extraction instead of loading the entire document.
Pre-Flight: Tool Validation
Before any document operation, validate the tooling:
python3 scripts/doc_tools_check.py
The script checks for:
pypdf— PDF manipulation (merge, split, rotate, metadata)pdfplumber— PDF text/table extraction with layout awarenesspython-docx— DOCX read/writeopenpyxl— XLSX read/write, formulas, chartspython-pptx— PPTX read/write, slide generationpandoc— universal format conversion (CLI)
If a tool is missing, the script outputs the exact pip install or brew install command. Run it first in every document-processing session.
Workflow: PDF Manipulation
1. Assess the Document
from pypdf import PdfReader
reader = PdfReader("input.pdf")
print(f"Pages: {len(reader.pages)}")
print(f"Metadata: {reader.metadata}")
print(f"Encrypted: {reader.is_encrypted}")
If encrypted and the user provides a password:
reader.decrypt("the_password")
2. Choose the Right Tool
| Operation | Tool | Why |
|---|---|---|
| Extract text (simple) | pypdf | Fast, lightweight |
| Extract text (layout-aware) | pdfplumber | Preserves columns, tables, positioning |
| Extract tables | pdfplumber | Direct .extract_tables() |
| Merge PDFs | pypdf | PdfWriter.append() |
| Split PDFs | pypdf | Write page ranges to separate files |
| Rotate pages | pypdf | page.rotate(90) |
| Add watermark | pypdf | Overlay one PDF onto another |
| Fill form fields | pypdf | reader.get_fields() → writer.update_page_form_field_values() |
| OCR scanned docs | pytesseract + pdf2image | Tesseract OCR pipeline |
| Add annotations | pypdf | Free-text, highlight, link annotations |
| Extract images | pypdf | Iterate /XObject resources |
3. Execute with Validation
Text extraction example:
import pdfplumber
with pdfplumber.open("input.pdf") as pdf:
for i, page in enumerate(pdf.pages):
text = page.extract_text()
tables = page.extract_tables()
print(f"--- Page {i+1} ---")
print(text)
Merge example:
from pypdf import PdfWriter, PdfReader
writer = PdfWriter()
for path in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
writer.append(path)
writer.write("merged_output.pdf")
# Validate
assert len(PdfReader("merged_output.pdf").pages) == expected_pages
Form filling example:
from pypdf import PdfReader, PdfWriter
reader = PdfReader("form.pdf")
writer = PdfWriter()
writer.append(reader)
fields = reader.get_fields()
# Show user available fields, collect values
writer.update_page_form_field_values(writer.pages[0], {
"Name": "Jane Doe",
"Date": "2026-05-28",
"Amount": "150.00"
})
writer.write("form_filled.pdf")
OCR example:
import pytesseract
from pdf2image import convert_from_path
images = convert_from_path("scanned.pdf", dpi=300)
text = "\n\n".join(pytesseract.image_to_string(img) for img in images)
with open("scanned_ocr.txt", "w") as f:
f.write(text)
4. Common Issues & Remedies
| Issue | Remedy |
|---|---|
| Encrypted PDF without password | Ask user for password; if unavailable, report and stop |
| Scanned PDF (image-only) | Use OCR path (pdf2image + pytesseract) |
| Corrupt PDF | Try pypdf.PdfReader(strict=False), or use mutool repair |
| Extracted text is garbled | Use pdfplumber instead of pypdf for layout-aware extraction |
| Form fields not found | Check reader.get_fields() — some PDFs use XFA forms (unsupported) |
Workflow: DOCX Creation & Editing
1. Decide: Template or Blank
- Template approach: Start from an existing
.docxwith placeholders like{{NAME}},{{DATE}},{{AMOUNT}}. Best for reports, invoices, letters. - Blank approach: Build from
Document()usingpython-docx. Best for simple documents or when no template exists.
2. Template Pattern
from docx import Document
doc = Document("template.docx")
# Replace placeholders across all paragraphs, tables, headers, footers
placeholders = {
"{{NAME}}": "Jane Doe",
"{{DATE}}": "28 May 2026",
"{{AMOUNT}}": "€1,500.00"
}
def replace_in_paragraphs(paragraphs):
for para in paragraphs:
for key, val in placeholders.items():
if key in para.text:
for run in para.runs:
if key in run.text:
run.text = run.text.replace(key, val)
replace_in_paragraphs(doc.paragraphs)
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
replace_in_paragraphs(cell.paragraphs)
for section in doc.sections:
replace_in_paragraphs(section.header.paragraphs)
replace_in_paragraphs(section.footer.paragraphs)
doc.save("output.docx")
3. Building from Scratch
from docx import Document
from docx.shared import Inches, Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
doc = Document()
# Styles
style = doc.styles['Normal']
style.font.name = 'Calibri'
style.font.size = Pt(11)
# Heading
doc.add_heading('Quarterly Report', level=1)
# Paragraph
p = doc.add_paragraph('Executive summary of Q1 2026 results.')
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
# Bold run
run = p.add_run(' Key highlights:')
run.bold = True
# Bullet list
for item in ['Revenue up 12%', 'Costs down 4%', '3 new markets entered']:
doc.add_paragraph(item, style='List Bullet')
# Table
table = doc.add_table(rows=3, cols=3, style='Light Grid Accent 1')
table.alignment = WD_TABLE_ALIGNMENT.CENTER
data = [['Metric', 'Q4 2025', 'Q1 2026'],
['Revenue', '€2.1M', '€2.35M'],
['Profit', '€420K', '€510K']]
for i, row_data in enumerate(data):
for j, cell_val in enumerate(row_data):
table.rows[i].cells[j].text = cell_val
# Image
doc.add_picture('chart.png', width=Inches(5.5))
doc.save('report.docx')
4. Mail Merge for Bulk Documents
from docx import Document
import csv
doc = Document("letter_template.docx")
with open("recipients.csv") as f:
recipients = list(csv.DictReader(f))
for i, recipient in enumerate(recipients):
doc_copy = Document("letter_template.docx")
for para in doc_copy.paragraphs:
for key, val in recipient.items():
if f"{{{{{key}}}}}" in para.text:
for run in para.runs:
run.text = run.text.replace(f"{{{{{key}}}}}", val)
doc_copy.save(f"letter_{i+1:03d}.docx")
5. DOCX → PDF Conversion
# Option A: LibreOffice headless (best fidelity)
# soffice --headless --convert-to pdf report.docx
# Option B: python-docx + reportlab (programmatic)
# Option C: pandoc + wkhtmltopdf (Markdown intermediary)
Workflow: XLSX Spreadsheet Handling
1. Read & Analyse Existing Data
import openpyxl
from openpyxl.utils import get_column_letter
wb = openpyxl.load_workbook("data.xlsx", data_only=True)
ws = wb.active
print(f"Sheet: {ws.title}, Rows: {ws.max_row}, Cols: {ws.max_column}")
# Read headers
headers = [cell.value for cell in ws[1]]
print(f"Columns: {headers}")
# Read all data as list of dicts
data = []
for row in ws.iter_rows(min_row=2, values_only=True):
data.append(dict(zip(headers, row)))
2. Create from Scratch with Formatting
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side, numbers
from openpyxl.chart import BarChart, Reference
from openpyxl.formatting.rule import DataBarRule
wb = Workbook()
ws = wb.active
ws.title = "Q1 Budget"
# Header styling
header_font = Font(name="Calibri", size=12, bold=True, color="FFFFFF")
header_fill = PatternFill(start_color="2F5496", end_color="2F5496", fill_type="solid")
header_align = Alignment(horizontal="center", vertical="center")
thin_border = Border(
left=Side(style='thin'), right=Side(style='thin'),
top=Side(style='thin'), bottom=Side(style='thin')
)
headers = ["Category", "Budget", "Actual", "Variance", "Variance %"]
for col, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_align
cell.border = thin_border
# Data
data = [
["Marketing", 50000, 48500],
["R&D", 120000, 118000],
["Sales", 75000, 72000],
["Operations", 90000, 93500],
["Support", 45000, 44200],
]
for row_idx, (cat, budget, actual) in enumerate(data, 2):
ws.cell(row=row_idx, column=1, value=cat).border = thin_border
ws.cell(row=row_idx, column=2, value=budget).border = thin_border
ws.cell(row=row_idx, column=3, value=actual).border = thin_border
# Variance = Actual - Budget
ws.cell(row=row_idx, column=4,
value=actual - budget).border = thin_border
ws.cell(row=row_idx, column=4).number_format = '#,##0'
# Variance %
if budget != 0:
ws.cell(row=row_idx, column=5,
value=(actual - budget) / budget).border = thin_border
ws.cell(row=row_idx, column=5).number_format = '0.0%'
# Auto-width columns
for col in range(1, len(headers) + 1):
ws.column_dimensions[get_column_letter(col)].width = 16
# Totals row
total_row = len(data) + 2
ws.cell(row=total_row, column=1, value="TOTAL").font = Font(bold=True)
for col in [2, 3, 4]:
col_letter = get_column_letter(col)
ws.cell(row=total_row, column=col,
value=f"=SUM({col_letter}2:{col_letter}{total_row-1})")
ws.cell(row=total_row, column=col).font = Font(bold=True)
# Bar chart
chart = BarChart()
chart.title = "Budget vs Actual"
chart.x_axis.title = "Category"
chart.y_axis.title = "Amount (€)"
chart.style = 10
cats = Reference(ws, min_col=1, min_row=2, max_row=total_row-1)
budget_data = Reference(ws, min_col=2, min_row=1, max_row=total_row-1)
actual_data = Reference(ws, min_col=3, min_row=1, max_row=total_row-1)
chart.add_data(budget_data, titles_from_data=True)
chart.add_data(actual_data, titles_from_data=True)
chart.set_categories(cats)
chart.width = 20
chart.height = 12
ws.add_chart(chart, f"A{total_row + 2}")
# Conditional formatting — data bars on Actual
rule = DataBarRule(start_type='min', end_type='max',
color='2F5496', showValue=True)
ws.conditional_formatting.add(f"C2:C{total_row-1}", rule)
# Freeze header row
ws.freeze_panes = 'A2'
wb.save("budget_report.xlsx")
3. Pivot Tables
from openpyxl import load_workbook
from openpyxl.pivot import PivotTable
wb = load_workbook("sales_data.xlsx")
ws = wb.active
# Create pivot on a new sheet
pivot_sheet = wb.create_sheet("Pivot")
pivot = PivotTable()
pivot.add_field("Region")
pivot.add_field("Product")
pivot.add_field("Revenue")
pivot_sheet.add_pivot(pivot, "A3")
wb.save("sales_with_pivot.xlsx")
4. Common XLSX Pitfalls
| Pitfall | Fix |
|---|---|
Formula results return None | Use data_only=True when loading, or compute values manually |
| Large file (>50MB) | Use read_only=True mode or chunk with iter_rows() |
| Merged cells lose data | Read from top-left cell; ws.merged_cells.ranges to detect |
| Date values appear as numbers | Apply date number format: cell.number_format = 'YYYY-MM-DD' |
Workflow: PPTX Presentation Generation
1. Read & Analyse Existing Deck
from pptx import Presentation
prs = Presentation("deck.pptx")
print(f"Slides: {len(prs.slides)}")
print(f"Slide width: {prs.slide_width}, height: {prs.slide_height}")
for i, slide in enumerate(prs.slides):
print(f"\n--- Slide {i+1} ({slide.slide_layout.name}) ---")
for shape in slide.shapes:
if shape.has_text_frame:
print(f" [{shape.shape_type}] {shape.text[:100]}")
if shape.has_table:
print(f" [TABLE] {shape.table.rows.__len__()} rows")
2. Build from Scratch
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.chart import XL_CHART_TYPE
from pptx.chart.data import CategoryChartData
prs = Presentation()
prs.slide_width = Inches(13.333) # 16:9
prs.slide_height = Inches(7.5)
# --- Title Slide ---
slide = prs.slides.add_slide(prs.slide_layouts[0]) # Title layout
slide.shapes.title.text = "Q1 2026 Business Review"
if slide.placeholders[1].has_text_frame:
slide.placeholders[1].text = "Strategy & Operations\n28 May 2026"
# --- Content Slide ---
slide = prs.slides.add_slide(prs.slide_layouts[1]) # Title + Content
slide.shapes.title.text = "Key Highlights"
tf = slide.placeholders[1].text_frame
tf.clear()
for highlight in [
"Revenue up 12% YoY to €2.35M",
"Operating margin improved to 21.7%",
"3 new markets: Spain, Portugal, Italy",
"Customer NPS score: 72 (up from 64)"
]:
p = tf.add_paragraph()
p.text = highlight
p.level = 0
p.font.size = Pt(18)
p.space_after = Pt(8)
# --- Chart Slide ---
slide = prs.slides.add_slide(prs.slide_layouts[5]) # Blank
slide.shapes.title.text = "Revenue Trend"
chart_data = CategoryChartData()
chart_data.categories = ['Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar']
chart_data.add_series('Revenue (€K)', [680, 710, 750, 770, 790, 810])
chart_frame = slide.shapes.add_chart(
XL_CHART_TYPE.LINE_MARKERS, Inches(1), Inches(1.5),
Inches(11), Inches(5.5), chart_data
)
chart = chart_frame.chart
chart.has_legend = True
chart.has_title = False
# --- Image Slide ---
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.shapes.title.text = "Market Expansion Map"
slide.shapes.add_picture("map.png", Inches(2), Inches(1.5),
Inches(9), Inches(5))
# --- Speaker Notes ---
for slide in prs.slides:
notes_slide = slide.notes_slide
notes_slide.notes_text_frame.text = (
f"Speaker notes for: {slide.shapes.title.text}"
)
prs.save("quarterly_review.pptx")
3. Template-Driven Slides
from pptx import Presentation
import json
prs = Presentation("template.pptx")
with open("slide_content.json") as f:
content = json.load(f)
for slide_data in content["slides"]:
slide = prs.slides.add_slide(
prs.slide_layouts[slide_data["layout_index"]]
)
slide.shapes.title.text = slide_data["title"]
if "body" in slide_data:
slide.placeholders[1].text = slide_data["body"]
prs.save("generated_deck.pptx")
4. PPTX Common Issues
| Issue | Fix |
|---|---|
| Layout index not found | List available: [ly.name for ly in prs.slide_layouts] |
| Placeholder missing | Check slide.placeholders; not all layouts have placeholder[1] |
| Images too large | Resize with PIL first, or use Inches() to set exact dimensions |
| Font not available on system | Use common fonts (Calibri, Arial) or embed fonts |
Workflow: Format Conversion
The Conversion Matrix
See references/conversion-matrix.md for the full cross-reference. Quick
reference for the most common paths:
| From | To | Tool | Quality |
|---|---|---|---|
| DOCX | LibreOffice headless | ⭐⭐⭐⭐⭐ Best | |
| DOCX | Markdown | pandoc -i input.docx -o output.md | ⭐⭐⭐⭐ Good |
| Markdown | DOCX | pandoc -i input.md -o output.docx | ⭐⭐⭐⭐ Good |
| Markdown | pandoc --pdf-engine=xelatex | ⭐⭐⭐⭐⭐ Best | |
| DOCX | pdf2docx library or LibreOffice | ⭐⭐⭐ Moderate | |
| Text | pdfplumber or pandoc | ⭐⭐⭐⭐ Good | |
| XLSX | CSV | pandas.read_excel().to_csv() | ⭐⭐⭐⭐⭐ Lossless |
| XLSX | LibreOffice headless | ⭐⭐⭐⭐ Good | |
| PPTX | LibreOffice headless | ⭐⭐⭐⭐ Good | |
| PPTX | Images | pptx → PDF → pdf2image | ⭐⭐⭐ Moderate |
| HTML | DOCX | pandoc -i input.html -o output.docx | ⭐⭐⭐ Moderate |
Generic Pandoc Pattern
# Basic conversion
pandoc input.docx -o output.md
# With template and metadata
pandoc input.md \
--template=corporate.latex \
--metadata title="Q1 Report" \
--metadata author="Jane Doe" \
--pdf-engine=xelatex \
-o output.pdf
# Convert with reference document for styling
pandoc input.md --reference-doc=styles.docx -o output.docx
# Batch conversion
for file in *.md; do
pandoc "$file" -o "${file%.md}.docx"
done
PDF → DOCX (Best Available)
# Using pdf2docx (best fidelity for text-heavy PDFs)
from pdf2docx import Converter
cv = Converter("input.pdf")
cv.convert("output.docx", start=0, end=None)
cv.close()
# Fallback: extract text, rebuild in python-docx
import pdfplumber
from docx import Document
doc = Document()
with pdfplumber.open("input.pdf") as pdf:
for page in pdf.pages:
text = page.extract_text()
if text:
doc.add_paragraph(text)
doc.save("output.docx")
XLSX → CSV / JSON
import pandas as pd
df = pd.read_excel("data.xlsx", sheet_name=None) # All sheets
for sheet_name, sheet_df in df.items():
sheet_df.to_csv(f"data_{sheet_name}.csv", index=False)
sheet_df.to_json(f"data_{sheet_name}.json", orient="records")
Platform Compatibility Notes
Claude Code / Codex
- Full Python scripting capability — all libraries available.
- Use
subprocessfor pandoc and LibreOffice calls. - Recommend
pip installfor missing packages.
Cursor
- Works identically; execute scripts via integrated terminal.
- Use the
doc_tools_check.pyscript as a pre-commit or task runner.
Gemini CLI
- Can execute Python but may have restricted subprocess access.
- Prefer pure-Python libraries (pypdf, pdfplumber, python-docx, openpyxl, python-pptx) over CLI tools when possible.
- Pandoc may not be available; fall back to Python-only conversion paths.
OpenClaw
- Full tool access. Can install packages and run both Python scripts and external CLI tools.
- Recommended: run
doc_tools_check.pyon first invocation to set up the environment.
GitHub Copilot
- Chat context; provide complete, self-contained Python snippets.
- Include dependency installation comments.
- Tip: reference
scripts/doc_tools_check.pyfor the install list.
Windsurf
- Full Python execution in IDE terminal; all libraries supported.
- Use
doc_tools_check.pyfor environment validation. - Store reusable template files in project workspace.
OpenCode
- Execute scripts via terminal with full Python access.
- Prefer pure-Python libraries for portability.
- Run
doc_tools_check.pyto validate environment before any operation.
Integration Pattern
A typical document-processing session follows this sequence:
1. User Request
↓
2. Trigger Detection (match phrases → activate skill)
↓
3. Tool Validation → scripts/doc_tools_check.py
↓
4. Document Assessment → Inspect input file(s)
↓
5. Operation Execution → Follow workflow above
↓
6. Output Validation → Read back, verify integrity
↓
7. Deliver Output → Present result path + summary
Reference Files
| File | Contents |
|---|---|
references/pdf-processing-guide.md | Deep dive: extraction, merging, splitting, rotation, watermarking, form filling, OCR |
references/office-formats-guide.md | DOCX templates, XLSX formatting, PPTX layouts, style consistency |
references/conversion-matrix.md | Full conversion pair matrix with tool recommendations and quality ratings |
scripts/doc_tools_check.py | PEP 723 pre-flight validation of all required tooling |
evals/eval_cases.json | 5 positive + 3 near-miss negative test cases |
Troubleshooting Quick Reference
| Problem | Likely Cause | Solution |
|---|---|---|
ModuleNotFoundError | Missing Python package | Run doc_tools_check.py, install missing packages |
pandoc: command not found | pandoc not installed | brew install pandoc (macOS) or apt install pandoc (Linux) |
| PDF text extraction empty | Scanned/image PDF | Use OCR path: pdf2image + pytesseract |
| DOCX styles look wrong | Template mismatch | Use --reference-doc with pandoc or LibreOffice conversion |
XLSX formula error #REF! | Sheet/cell references broken | Check sheet names and cell ranges after row insertion |
| PPTX layout index changes | Template differs from code assumption | List layouts dynamically with [ly.name for ly in prs.slide_layouts] |
| File size ballooning | Embedded high-res images | Resize images before embedding: img.thumbnail((1920, 1080)) |
| Character encoding issues | Non-UTF-8 content | Specify encoding: encoding='utf-8' in all file operations |