返回 ppt-master
check_annotations.py
根目录 / skills / ppt-master / scripts / check_annotations.py
1 #!/usr/bin/env python3
2 """
3 PPT Master - SVG Annotation Checker
4
5 Scans SVG files for edit annotations (data-edit-target / data-edit-annotation attributes)
6 and prints a human-readable summary. Used by AI agents to discover pending annotations.
7
8 Usage:
9 python3 scripts/check_annotations.py <project_dir>
10 python3 scripts/check_annotations.py <svg_file>
11
12 Examples:
13 python3 scripts/check_annotations.py projects/my-project
14 python3 scripts/check_annotations.py projects/my-project/svg_output/slide_01.svg
15
16 Dependencies:
17 None (only uses standard library)
18 """
19
20 import argparse
21 import sys
22 import xml.etree.ElementTree as ET
23 from pathlib import Path
24 from typing import Optional
25
26 from console_encoding import configure_utf8_stdio
27 from slide_roster import discover_slide_svgs
28
29 configure_utf8_stdio()
30
31 def scan_svg_file(svg_path: Path) -> list[dict]:
32 """Scan a single SVG file for edit annotations.
33
34 Propagates ``ET.ParseError``: whether one unreadable slide is fatal is
35 the caller's call, and an empty annotation list must never stand in for
36 a file that could not be read.
37 """
38 tree = ET.parse(svg_path)
39
40 root = tree.getroot()
41 annotations = []
42
43 for elem in root.iter():
44 if elem.get('data-edit-target') == 'true':
45 tag = elem.tag
46 if '}' in tag:
47 tag = tag.split('}', 1)[1]
48
49 content_preview = ''
50 if tag == 'text' and elem.text:
51 content_preview = elem.text.strip()[:50]
52
53 annotations.append({
54 'element_id': elem.get('id', '(no id)'),
55 'tag': tag,
56 'annotation': elem.get('data-edit-annotation', ''),
57 'content_preview': content_preview,
58 })
59
60 return annotations
61
62
63 def scan_directory(dir_path: Path) -> tuple[dict[str, list[dict]], list[str]]:
64 """Scan all SVG files in svg_output/ for edit annotations.
65
66 Returns the annotations found plus one message per slide that could not
67 be parsed, so a broken page cannot pass as a page with no annotations.
68 """
69 svg_dir = dir_path / 'svg_output'
70 if not svg_dir.exists():
71 return {}, []
72
73 results = {}
74 unreadable = []
75 for svg_file in discover_slide_svgs(svg_dir):
76 try:
77 annotations = scan_svg_file(svg_file)
78 except ET.ParseError as exc:
79 unreadable.append(f"{svg_file}: {exc}")
80 continue
81 if annotations:
82 results[svg_file.name] = annotations
83
84 return results, unreadable
85
86
87 def print_results(results: dict[str, list[dict]]) -> None:
88 """Print annotation results in human-readable format."""
89 if not results:
90 print("[OK] No annotations found.")
91 return
92
93 total = sum(len(anns) for anns in results.values())
94 file_count = len(results)
95 ann_word = "annotation" if total == 1 else "annotations"
96 file_word = "file" if file_count == 1 else "files"
97 print(f"Found {total} {ann_word} in {file_count} {file_word}:\n")
98
99 for filename, annotations in results.items():
100 print(f"{filename}")
101 for i, ann in enumerate(annotations, 1):
102 content = f' "{ann["content_preview"]}"' if ann['content_preview'] else ''
103 print(f" [{i}] <{ann['tag']} id=\"{ann['element_id']}\">{content}")
104 print(f" → {ann['annotation']}")
105 print()
106
107
108 def build_parser() -> argparse.ArgumentParser:
109 parser = argparse.ArgumentParser(
110 description='Check SVG files for edit annotations',
111 formatter_class=argparse.RawDescriptionHelpFormatter,
112 )
113 parser.add_argument('path', help='Project directory or single SVG file path')
114 return parser
115
116
117 def main(argv: Optional[list[str]] = None) -> int:
118 parser = build_parser()
119 args = parser.parse_args(argv)
120 unreadable: list[str] = []
121
122 target = Path(args.path).resolve()
123
124 if not target.exists():
125 print(f"Error: Path not found: {target}", file=sys.stderr)
126 return 1
127
128 if target.is_file() and target.suffix == '.svg':
129 try:
130 annotations = scan_svg_file(target)
131 except ET.ParseError as exc:
132 unreadable.append(f"{target}: {exc}")
133 annotations = []
134 results = {target.name: annotations} if annotations else {}
135 elif target.is_dir():
136 results, unreadable = scan_directory(target)
137 else:
138 print(f"Error: Expected a project directory or .svg file, got: {target}", file=sys.stderr)
139 return 1
140
141 for message in unreadable:
142 print(f"[ERROR] Failed to parse SVG {message}", file=sys.stderr)
143
144 if results or not unreadable:
145 print_results(results)
146 return 1 if unreadable else 0
147
148
149 if __name__ == '__main__':
150 raise SystemExit(main())
151
151 lines PYTHON