Files
anthropics_skills/skills/pptx/scripts/office/helpers/pptx_slide.py
T
Peter Lai fa0fa64bdc Update docx, pptx, and xlsx skills (#1447)
Add support for template formats (.dotx, .potx, .xltx) across validation
and the helper scripts.

Consolidate the shared office helpers into a single module, replacing the
pack/unpack pipeline with explicit zip/unzip steps. Extraction now rejects
symlink and path-traversal archive entries. Move run merging to a
standalone docx script, since it only ever applied to Word documents.

Fix the redlining validator so it compares against the original even when
a document has no tracked changes, which is when an untracked edit would
otherwise go unreported.

Provision a LibreOffice user profile per invocation so conversions work in
sandboxed environments.

Trim the skill docs to the guidance that earns its place.
2026-07-16 19:47:37 -07:00

61 lines
1.6 KiB
Python

"""Pick the slide-XML schema errors PowerPoint refuses the file over.
A denylist over lxml's messages, so an unrecognised error class is a miss rather
than a false alarm.
"""
from __future__ import annotations
import re
SLIDE_PART_RE = re.compile(
r"ppt/(slides|slideLayouts|slideMasters|notesSlides|notesMasters|handoutMasters)"
r"/[^/]+\.xml"
)
FATAL_SLIDE_ERRORS: tuple[tuple[re.Pattern[str], str], ...] = (
(
re.compile(r"\}tableStyleId': This element is not expected"),
"two <a:tableStyleId> in one <a:tblPr> (the schema allows one)",
),
(
re.compile(r"\}srgbClr', attribute 'val'"),
"a colour that is not six hex digits",
),
(
re.compile(r"\}txBody': Missing child element"),
"a <p:txBody> with no children",
),
(
re.compile(r"\}miter', attribute 'lim'"),
'a line join with lim="NaN"',
),
(
re.compile(r"\}uLnTx': This element is not expected"),
"<a:uLnTx> in a position the schema forbids",
),
(
re.compile(r"\}overrideClrMapping': This element is not expected"),
"<p:overrideClrMapping> in a position the schema forbids",
),
(
re.compile(r"\}nvGrpSpPr': Missing child element"),
"a <p:nvGrpSpPr> with no children",
),
)
def is_schema_verdict(error: str) -> bool:
return error.startswith("Element ")
def fatal_slide_errors(errors: set[str]) -> list[str]:
out = []
for error in sorted(errors):
for pattern, meaning in FATAL_SLIDE_ERRORS:
if pattern.search(error):
out.append(f"{meaning}: {error}")
break
return out