#!/usr/bin/env python3
"""Compare the words on each slide of two .pptx files.

Use it to check that a redesigned deck kept every word and number of the original.

    pip install python-pptx
    python compare-pptx-text.py original.pptx redesigned.pptx

For each slide it prints "same words", or the words that are missing from the
second file and the words that were added. Slides are matched by position.

What it ignores: spacing, line breaks, bullet symbols (a sentence split over two
lines is not flagged). What it does not ignore: capital letters and punctuation.
What it can't read: text drawn into pictures, speaker notes. It does not check
the order of the slides, so compare the slide count and order yourself.

BetterPPT, 2026-10-27. Free to use and copy.
"""
import re
import sys
import unicodedata
from collections import Counter

from pptx import Presentation

# Chinese / Japanese / Korean characters count as one word each; everything else splits on spaces.
CJK = "㐀-鿿぀-ヿ가-힯"
TOKEN = re.compile(f"[{CJK}]|[^\\s{CJK}]+")
BULLET_ONLY = re.compile("^[•·◦▪■●○‣∙▫◆◇\\-–—*]+$")


def shape_lines(shape):
    """All text in a shape: text boxes, table cells, and shapes inside groups."""
    lines = []
    if shape.shape_type == 6:  # group shape
        for inner in shape.shapes:
            lines += shape_lines(inner)
    if shape.has_text_frame:
        lines += [p.text for p in shape.text_frame.paragraphs if p.text.strip()]
    if getattr(shape, "has_table", False) and shape.has_table:
        for row in shape.table.rows:
            for cell in row.cells:
                lines += [p.text for p in cell.text_frame.paragraphs if p.text.strip()]
    return lines


def slide_words(slide):
    text = " ".join(line for shape in slide.shapes for line in shape_lines(shape))
    words = TOKEN.findall(unicodedata.normalize("NFKC", text))
    return [w for w in words if not BULLET_ONLY.match(w)]


def main(before_path, after_path):
    before = [slide_words(s) for s in Presentation(before_path).slides]
    after = [slide_words(s) for s in Presentation(after_path).slides]
    print(f"slides: original {len(before)}, redesigned {len(after)}")
    if len(before) != len(after):
        print("WARNING: the slide counts differ, so slides are compared by position only up to the shorter deck.")
    differences = 0
    for i in range(min(len(before), len(after))):
        a, b = Counter(before[i]), Counter(after[i])
        missing, added = list((a - b).elements()), list((b - a).elements())
        if not missing and not added:
            print(f"slide {i + 1}: same words")
            continue
        differences += 1
        print(f"slide {i + 1}: DIFFERENT")
        if missing:
            print("  in the original but not the redesigned file:", " ".join(missing))
        if added:
            print("  in the redesigned file but not the original:", " ".join(added))
    print(f"{differences} slide(s) with differences.")


if __name__ == "__main__":
    if len(sys.argv) != 3:
        sys.exit("usage: python compare-pptx-text.py original.pptx redesigned.pptx")
    main(sys.argv[1], sys.argv[2])
