PPTX Reference Deck Analysis
Seth HobsonCommunityCreated by kimtthDistributed by Seth Hobson
- Skill
- Research and analysis
- Documents and content
- MIT
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a secure, read-only framework for inspecting PowerPoint (.pptx) files to extract design evidence and structural metadata. It is designed to help users understand the layout, typography, theme, and OOXML package integrity of a reference deck, facilitating the creation of new presentations based on existing design patterns without directly copying or mutating the source files.
What it does
- Extracts slide counts, text summaries, and shape counts from PPTX files.
- Analyzes design elements including theme colors, fonts, and layout rhythm.
- Performs OOXML package diagnostics to identify malformed XML or broken relationships.
- Generates a derived template catalog from source deck masters and layouts.
- Provides tools for raw package inspection, including relationship mapping and media identification.
- Maintains a strict read-only policy to ensure source deck integrity.
When to use it
- When you need to reverse-engineer the design system of an existing presentation.
- When auditing a deck for structural errors or broken internal relationships.
- When you need to extract specific layout or theme data to inform a new presentation project.
- When performing security-conscious inspection of untrusted OOXML packages.
Advantages
- Ensures source files remain unmodified and safe from accidental changes.
- Provides granular visibility into the underlying OOXML structure.
- Includes built-in safety measures against malicious XML content.
- Facilitates consistent design by extracting reusable layout and theme tokens.
Usage examples
- “Analyze the structure and theme of corporate-deck.pptx to create a style guide.”
- The agent will output a report detailing the color scheme, font usage, and layout patterns found in the deck, providing a basis for a new template.
- “Check the integrity of legacy-presentation.pptx for broken links or malformed XML.”
- The agent will run a validation script and return a manifest highlighting any orphaned parts or content-type gaps within the package.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/wshobson/pptx-reference-deck-analysis/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip pptx-reference-deck-analysis.zip -d .agents/skills
Documentation by kimtth
PPTX Reference Deck Analysis
Inspect a reference deck as design evidence. This skill never copies, clones, or mutates a source deck.
Contract
Implement required extraction on demand with a small task-local python-pptx script. Capture only the information required for the new deck:
compact prompt context: slide count and size, text summaries, shape counts, styles, brand signals, template use, and layout rhythm;
full extraction:
summary, slides, and read-onlylayout_treeevidence;folder diagnostics: one result per deck plus a manifest;
style-master analysis: colors, fonts, size distribution, master/layout use, and flow patterns;
derived template catalog: zero-based source indices, layout roles, usable regions, placeholders, visual structures, and constraints.
Rules
Keep the source deck read-only and independently author every target-slide coordinate.
Use the bundled OOXML utilities only for raw themes, relationships, notes, comments, animations, media, masters, or layouts that high-level extraction cannot expose.
Record inspected parts and parsing exceptions in the analysis manifest.
Do not use extracted content, fonts, images, or proprietary assets in a generated deck without explicit permission and license evidence.
OOXML package inspection
Install defusedxml from requirements.txt before using the bundled utilities.
Run
scripts/inspect.py <deck.pptx>for a compact JSON report of slide order, text, theme tokens, relationships, notes, comments, animations, and media.Run
scripts/validate_package.py <deck.pptx> --output <report.json>for malformed XML, broken internal relationships, content-type gaps, duplicate layout links, and orphaned parts.Run
scripts/unpack.py <deck.pptx> <output-dir>only when raw-package evidence is necessary.Resolve relationship targets relative to the
.relsowner; never infer slide order from filenames.
Safety
Never modify a supplied deck or blindly copy package parts into a new deck.
Run the scripts from a trusted workspace; they reject path traversal, symlinks, oversized members, and archive bombs.
Parse untrusted XML with
defusedxml; do not enable entity expansion, DTD loading, or network access.Treat theme colors as tokens unless fully resolved against the color scheme.
See references/reference-deck-analysis.md for output shapes, references/reference-deck-analysis-patterns.md for documentation-only patterns, and references/ooxml-parsing.md for package part maps.
Files
- ATTRIBUTION.mdAdded by AutoGineer1015 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md2.7 KiB
- references/ooxml-parsing.md1.1 KiB
- references/reference-deck-analysis-patterns.md1.2 KiB
- references/reference-deck-analysis.md1.5 KiB
- requirements.txt19 B
- scripts/inspect.pyScript5.9 KiB
- scripts/unpack.pyScript3.0 KiB
- scripts/validate_package.pyScript9.5 KiB
scripts/validate_package.py
#!/usr/bin/env python3
"""Validate PPTX package integrity without modifying the archive."""
from __future__ import annotations
import argparse
import json
import posixpath
import stat
import sys
import zipfile
from collections import Counter
from pathlib import Path, PurePosixPath
from typing import Any
from defusedxml import ElementTree as ET
P = "{http://schemas.openxmlformats.org/presentationml/2006/main}"
R = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}"
PR = "{http://schemas.openxmlformats.org/package/2006/relationships}"
CT = "{http://schemas.openxmlformats.org/package/2006/content-types}"
SLIDE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"
MAX_MEMBERS, MAX_MEMBER_SIZE, MAX_TOTAL_SIZE, MAX_COMPRESSION_RATIO = 5_000, 100 * 1024 * 1024, 512 * 1024 * 1024, 1_000
def _write_stdout(value: str) -> None:
"""Write UTF-8 JSON without depending on the console code page."""
sys.stdout.buffer.write(value.encode("utf-8"))
def _workspace_path(value: str) -> Path:
root = Path.cwd().resolve()
path = Path(value).expanduser().resolve()
if not path.is_relative_to(root):
raise ValueError(f"Path escapes the current workspace: {value}")
return path
def _validate_archive(archive: zipfile.ZipFile) -> None:
members, total = archive.infolist(), 0
if len(members) > MAX_MEMBERS:
raise ValueError("Archive contains too many entries")
for member in members:
if stat.S_ISLNK(member.external_attr >> 16):
raise ValueError(f"Archive contains a symlink: {member.filename}")
if member.file_size > MAX_MEMBER_SIZE:
raise ValueError(f"Archive entry is too large: {member.filename}")
total += member.file_size
if total > MAX_TOTAL_SIZE:
raise ValueError("Archive uncompressed size is too large")
if member.compress_size and member.file_size / member.compress_size > MAX_COMPRESSION_RATIO:
raise ValueError(f"Suspicious compression ratio: {member.filename}")
def _source_part(rels_name: str) -> str | None:
path = PurePosixPath(rels_name)
if str(path) == "_rels/.rels":
return ""
if path.parent.name != "_rels" or not path.name.endswith(".rels"):
return None
return str(path.parent.parent / path.name.removesuffix(".rels"))
def _target(rels_name: str, value: str) -> str | None:
source = _source_part(rels_name)
if source is None:
return None
if value.startswith("/"):
target = posixpath.normpath(value.lstrip("/"))
else:
target = posixpath.normpath(posixpath.join(posixpath.dirname(source), value))
return target if target not in {"", ".", ".."} and not target.startswith("../") else None
def _content_types(archive: zipfile.ZipFile) -> tuple[dict[str, str], dict[str, str]]:
root = ET.fromstring(archive.read("[Content_Types].xml"))
return (
{item.get("Extension", "").lower(): item.get("ContentType", "") for item in root.findall(f"{CT}Default")},
{item.get("PartName", "").lstrip("/"): item.get("ContentType", "") for item in root.findall(f"{CT}Override")},
)
def validate(path: str) -> dict[str, Any]:
errors: list[dict[str, str]] = []
warnings: list[dict[str, str]] = []
with zipfile.ZipFile(path) as archive:
_validate_archive(archive)
names = {item.filename for item in archive.infolist() if not item.is_dir()}
for name in sorted(name for name in names if name.endswith((".xml", ".rels"))):
try:
ET.fromstring(archive.read(name))
except Exception as exc:
errors.append({"part": name, "check": "xml_well_formed", "message": str(exc)})
if "[Content_Types].xml" not in names:
errors.append({"part": "[Content_Types].xml", "check": "content_types", "message": "missing content types part"})
defaults, overrides = {}, {}
else:
try:
defaults, overrides = _content_types(archive)
except Exception as exc:
errors.append({"part": "[Content_Types].xml", "check": "content_types", "message": str(exc)})
defaults, overrides = {}, {}
referenced: set[str] = set()
relationships: dict[str, dict[str, dict[str, str]]] = {}
for rels_name in sorted(name for name in names if name.endswith(".rels")):
try:
root = ET.fromstring(archive.read(rels_name))
except Exception:
continue
rels: dict[str, dict[str, str]] = {}
for rel in root.findall(f"{PR}Relationship"):
rel_id, target, mode = rel.get("Id", ""), rel.get("Target", ""), rel.get("TargetMode", "Internal")
rels[rel_id] = {"type": rel.get("Type", ""), "target": target, "mode": mode}
if mode != "External":
resolved = _target(rels_name, target)
if not resolved or resolved not in names:
errors.append({"part": rels_name, "check": "internal_relationship", "message": f"{rel_id} targets missing or unsafe part: {target}"})
else:
referenced.add(resolved)
relationships[rels_name] = rels
presentation, declared_slides = "ppt/presentation.xml", set()
presentation_rels = relationships.get("ppt/_rels/presentation.xml.rels", {})
if presentation not in names:
errors.append({"part": presentation, "check": "slide_order", "message": "missing presentation part"})
else:
try:
root = ET.fromstring(archive.read(presentation))
ids = [item.get("id", "") for item in root.findall(f".//{P}sldId")]
for value, count in Counter(ids).items():
if value and count > 1:
errors.append({"part": presentation, "check": "slide_id_unique", "message": f"duplicate slide id: {value}"})
for item in root.findall(f".//{P}sldId"):
rel_id = item.get(f"{R}id", "")
rel = presentation_rels.get(rel_id)
if rel is None or not rel["type"].endswith("/slide"):
errors.append({"part": presentation, "check": "slide_relationship", "message": f"slide id references missing/non-slide relationship: {rel_id}"})
else:
target = _target("ppt/_rels/presentation.xml.rels", rel["target"])
if target:
declared_slides.add(target)
except Exception as exc:
errors.append({"part": presentation, "check": "slide_order", "message": str(exc)})
for slide in sorted(name for name in names if name.startswith("ppt/slides/slide") and name.endswith(".xml")):
if overrides.get(slide) != SLIDE_CONTENT_TYPE:
errors.append({"part": slide, "check": "content_type", "message": "missing or incorrect slide content type override"})
if slide not in declared_slides:
warnings.append({"part": slide, "check": "unlisted_slide", "message": "slide part is not listed in presentation.xml"})
rels_name = f"{PurePosixPath(slide).parent}/_rels/{PurePosixPath(slide).name}.rels"
layouts = [item for item in relationships.get(rels_name, {}).values() if item["type"].endswith("/slideLayout")]
if len(layouts) != 1:
errors.append({"part": rels_name, "check": "slide_layout_relationship", "message": f"expected exactly one slideLayout relationship, found {len(layouts)}"})
for check, candidates in {"orphaned_media": (item for item in names if item.startswith("ppt/media/")), "orphaned_notes": (item for item in names if item.startswith("ppt/notesSlides/notesSlide") and item.endswith(".xml"))}.items():
for name in sorted(candidates):
if name not in referenced:
warnings.append({"part": name, "check": check, "message": "part has no inbound internal relationship"})
return {"deck": path, "ok": not errors, "error_count": len(errors), "warning_count": len(warnings), "errors": errors, "warnings": warnings}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Validate PPTX package integrity without modifying it.")
parser.add_argument("deck", help="PPTX file inside the current workspace")
parser.add_argument("--output", help="Optional JSON report path inside the current workspace")
args = parser.parse_args(argv)
try:
deck = _workspace_path(args.deck)
if not deck.is_file() or deck.suffix.lower() != ".pptx":
raise ValueError("deck must be an existing .pptx file")
report = validate(str(deck))
payload = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
if args.output:
output = _workspace_path(args.output)
if output == deck:
raise ValueError("output must not overwrite the input deck")
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(payload, encoding="utf-8")
_write_stdout(payload)
return 0 if report["ok"] else 1
except (OSError, ValueError, zipfile.BadZipFile) as exc:
_write_stdout(
json.dumps(
{"ok": False, "errors": [{"check": "input", "message": str(exc)}]},
ensure_ascii=False,
indent=2,
)
+ "\n"
)
return 2
if __name__ == "__main__":
raise SystemExit(main())
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Python
Security
What our scanner noticed· Low risk
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- kimtth
- Publisher
- Seth Hobson (Community)
- Distributed by
- Seth Hobson
- Source
- wshobson/agents · plugins/pptx-deck-creation/skills/pptx-reference-deck-analysis · commit 4bde316
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by kimtth or Seth Hobson.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download pptx-reference-deck-analysis.zip