Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Create, edit, and inspect PowerPoint presentations with professional design and automated visual QA
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/office/pack.py
1"""Pack a directory into a DOCX, PPTX, or XLSX file.23Validates with auto-repair, condenses XML formatting, and creates the Office file.45Usage:6python pack.py <input_directory> <output_file> [--original <file>] [--validate true|false]78Examples:9python pack.py unpacked/ output.docx --original input.docx10python pack.py unpacked/ output.pptx --validate false11"""1213import argparse14import sys15import shutil16import tempfile17import zipfile18from pathlib import Path1920import defusedxml.minidom2122from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator2324def pack(25input_directory: str,26output_file: str,27original_file: str | None = None,28validate: bool = True,29infer_author_func=None,30) -> tuple[None, str]:31input_dir = Path(input_directory)32output_path = Path(output_file)33suffix = output_path.suffix.lower()3435if not input_dir.is_dir():36return None, f"Error: {input_dir} is not a directory"3738if suffix not in {".docx", ".pptx", ".xlsx"}:39return None, f"Error: {output_file} must be a .docx, .pptx, or .xlsx file"4041if validate and original_file:42original_path = Path(original_file)43if original_path.exists():44success, output = _run_validation(45input_dir, original_path, suffix, infer_author_func46)47if output:48print(output)49if not success:50return None, f"Error: Validation failed for {input_dir}"5152with tempfile.TemporaryDirectory() as temp_dir:53temp_content_dir = Path(temp_dir) / "content"54shutil.copytree(input_dir, temp_content_dir)5556for pattern in ["*.xml", "*.rels"]:57for xml_file in temp_content_dir.rglob(pattern):58_condense_xml(xml_file)5960output_path.parent.mkdir(parents=True, exist_ok=True)61with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:62for f in temp_content_dir.rglob("*"):63if f.is_file():64zf.write(f, f.relative_to(temp_content_dir))6566return None, f"Successfully packed {input_dir} to {output_file}"676869def _run_validation(70unpacked_dir: Path,71original_file: Path,72suffix: str,73infer_author_func=None,74) -> tuple[bool, str | None]:75output_lines = []76validators = []7778if suffix == ".docx":79author = "Claude"80if infer_author_func:81try:82author = infer_author_func(unpacked_dir, original_file)83except ValueError as e:84print(f"Warning: {e} Using default author 'Claude'.", file=sys.stderr)8586validators = [87DOCXSchemaValidator(unpacked_dir, original_file),88RedliningValidator(unpacked_dir, original_file, author=author),89]90elif suffix == ".pptx":91validators = [PPTXSchemaValidator(unpacked_dir, original_file)]9293if not validators:94return True, None9596total_repairs = sum(v.repair() for v in validators)97if total_repairs:98output_lines.append(f"Auto-repaired {total_repairs} issue(s)")99100success = all(v.validate() for v in validators)101102if success:103output_lines.append("All validations PASSED!")104105return success, "\n".join(output_lines) if output_lines else None106107108def _condense_xml(xml_file: Path) -> None:109try:110with open(xml_file, encoding="utf-8") as f:111dom = defusedxml.minidom.parse(f)112113for element in dom.getElementsByTagName("*"):114if element.tagName.endswith(":t"):115continue116117for child in list(element.childNodes):118if (119child.nodeType == child.TEXT_NODE120and child.nodeValue121and child.nodeValue.strip() == ""122) or child.nodeType == child.COMMENT_NODE:123element.removeChild(child)124125xml_file.write_bytes(dom.toxml(encoding="UTF-8"))126except Exception as e:127print(f"ERROR: Failed to parse {xml_file.name}: {e}", file=sys.stderr)128raise129130131if __name__ == "__main__":132parser = argparse.ArgumentParser(133description="Pack a directory into a DOCX, PPTX, or XLSX file"134)135parser.add_argument("input_directory", help="Unpacked Office document directory")136parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)")137parser.add_argument(138"--original",139help="Original file for validation comparison",140)141parser.add_argument(142"--validate",143type=lambda x: x.lower() == "true",144default=True,145metavar="true|false",146help="Run validation with auto-repair (default: true)",147)148args = parser.parse_args()149150_, message = pack(151args.input_directory,152args.output_file,153original_file=args.original,154validate=args.validate,155)156print(message)157158if "Error" in message:159sys.exit(1)160