Article · 2024-10-06

Parsing Complex Keynote Presentations: Direct Extraction and PDF-Based Approaches

Path A: Custom Reader for Direct .key Parsing

A Keynote .key file is not text-based. The format is Apple's proprietary compressed binary—fundamentally a ZIP archive containing multiple resources: images, videos, and Snappy-compressed Protobuf (.iwa) data that stores document metadata, text, and structural definitions. This means standard text parsing libraries fail entirely; extracting content requires a specialized tool or library to unpack and read the internals.

The open-source project keynote-parser provides one viable approach. It decompresses and decodes Keynote files into readable text form (typically YAML) suitable for extraction. Specifically, keynote-parser unpacks a .key file into a folder structure containing parsed YAML files from which you can extract slide text, speaker notes, table contents, and other data. A basic workflow looks like this:

# 安装 keynote-parser 工具
pip install keynote-parser

# 将 Keynote 文件解包为文件夹结构(生成 ./MySlides/ 目录)
keynote-parser unpack MySlides.key

After execution, a folder named MySlides/ contains the parsed YAML and resources. Each slide may have a corresponding Slide-XXXX.yaml recording object properties and text. A script can then read these YAML files to extract needed text and structure. For tables, locate cell text within the YAML; for shapes and flowcharts, extract text labels (though the line connections between shapes may not translate directly to plain text).

When implementing a custom reader, you can assemble extracted content into structured text (Markdown or custom markup for tables) or generate text blocks for indexing. Frameworks like LangChain or LlamaIndex support custom document loaders by inheriting their base classes. In LangChain, you might subclass BaseLoader to create a KeynoteLoader whose load() method implements the parsing logic:

from langchain.docstore.document import Document
from langchain.document_loaders import BaseLoader
import subprocess, os

class KeynoteLoader(BaseLoader):
    def __init__(self, file_path: str):
        self.file_path = file_path

    def load(self) -> list[Document]:
        # 使用 keynote-parser 将 .key 解包
        output_dir = os.path.splitext(self.file_path)[0]  # 去掉扩展名作为文件夹名
        subprocess.run(["keynote-parser", "unpack", self.file_path, "-o", output_dir])
        text_chunks = []
        # 遍历解包后的目录,读取所有 YAML 文件中的文本内容
        for root, dirs, files in os.walk(output_dir):
            for fname in files:
                if fname.endswith(".yaml"):
                    text_chunks.append(extract_text_from_yaml(os.path.join(root, fname)))
        # 将提取的文本合并为一个字符串,或根据需要拆分
        full_text = "\n".join(text_chunks)
        # 封装为 Document 对象返回
        return [Document(page_content=full_text, metadata={"source": self.file_path})]

The extract_text_from_yaml function (which you implement) parses YAML and retrieves text strings, excluding style and other irrelevant metadata. This loader can then handle .key files directly. When indexing, you obtain native text content from slide descriptions, table data, and other elements without intermediate conversion.

Advantages: Path A avoids information loss during conversion. Theoretically, it extracts richer structural information—tables retain row-column correspondence, slide notes become accessible (if stored in the .key file), and even flowchart text labels are retrievable. Custom parsing also allows special handling: marking table text with Markdown syntax to preserve structure for later answer generation.

Challenges: Path A demands engineering effort. While keynote-parser handles the format decoding, you still need to understand its output structure to extract content correctly. The approach depends on specific Keynote format definitions; newer Keynote versions may require parser updates. That said, as of 2025 the format evolves infrequently, and existing tools support current versions. Path A suits teams with high parsing-quality requirements and development capability, enabling maximum customization for complex content.

Path B: Keynote to PDF with Mature Parsing Tools

Path B is more direct and general: convert Keynote files to PDF in batch, then extract information using established document parsing tools. PDF parsing libraries and services excel at layout analysis, text extraction, and table recognition. Keynote itself offers PDF export; you can automate batch conversion via AppleScript or Keynote's command-line tools.

Automated PDF Export from Keynote

On macOS, AppleScript provides scripting control over Keynote. Write a script to open each .key file and execute the export-to-PDF action in the background. Here is a simple example exporting a single Keynote document:

-- 假设 Keynote 已安装在本机
set keynoteFile to POSIX file "/路径/至/文件/MySlides.key" as alias
set exportPath to POSIX file "/路径/至/导出/MySlides.pdf" as text

tell application "Keynote"
    open keynoteFile
    -- 将当前打开的文档导出为 PDF
    export document 1 to file exportPath as PDF
    close document 1
end tell

This invokes Keynote's export command to save the first document as PDF. You can batch-process all .key files in a directory via the terminal using osascript:

# 假设 current_dir 下有多个 .key 文件
for f in *.key; do
  echo "Converting $f to PDF..."
  osascript -e "tell application \"Keynote\" to export document 1 of (open POSIX file \"$(pwd)/$f\" as alias) to POSIX file \"$(pwd)/${f%.key}.pdf\" as PDF"
done

The shell script uses a one-liner AppleScript to open and export each Keynote file (note: $(pwd) expands to the current directory's full path; ${f%.key}.pdf replaces the extension). This requires no manual work and efficiently converts batch Keynote files to PDF. Several practical considerations apply:

Once you have PDF files, the remainder is standard PDF parsing.

Parsing PDF Content

Numerous tools efficiently parse PDF document content. Common options include:

Using Unstructured as an example:

from unstructured.partition.pdf import partition_pdf

pdf_file = "MySlides.pdf"
elements = partition_pdf(filename=pdf_file)
# elements 是文档元素列表,可包含 Title, NarrativeText, Table 等不同类型
text_segments = [elem.text for elem in elements if hasattr(elem, "text")]
full_text = "\n".join(text_segments)
print(full_text[:500])  # 打印前500字符预览

This code uses partition_pdf to decompose the PDF into an element list, then concatenates text. For table elements, Unstructured typically converts them to Markdown table syntax, preserving structure. You can further process by element type—adding slide identifiers to content or treating each slide as a separate Document to maintain segmented indexing.

With LangChain's loader, the code is simpler:

from langchain.document_loaders import PyPDFLoader

loader = PyPDFLoader("MySlides.pdf")
docs = loader.load()  # 将PDF按页面等切分成多个Document
print(docs[0].page_content[:200])  # 打印第一个Document的一部分文本

LangChain's loader splits the PDF into one or more Documents (usually page-by-page, or per a custom strategy), ready for vector embedding and indexing. To keep each slide as a separate segment, export the PDF with one slide per page, so each page becomes one Document.

Parsing quality depends on PDF content representation. Most slide text remains extractable (selectable, copyable) in PDF, so parsing tools retrieve all text. Flowcharts translate their text labels to plain text but lose arrow relationships and semantic connections. A slide with flowchart nodes yields extracted node titles and descriptions; the "points-to" relationships vanish. Tables usually appear as cell text in PDF; parsing tools may output continuous text lines or structured tables (from advanced services). Path B quickly extracts text but expresses structure simply, losing layout information. For RAG applications, text retrieval matters more than structure, so this suffices for most retrieval needs.

Implementation tips: If post-processing results for a vector database, add metadata during parsing. Include the slide number, title, or label data (e.g., "from table on page X") so retrieval results trace back to source or render context. With LlamaIndex, pass parsed text directly into indexing; with vector database + LangChain, store Document lists in the database with metadata like {"source": "slides1.pdf", "page": 2}.

Common Pitfalls and Considerations

Parsing complex documents like Keynote presentations for indexing runs into predictable traps:

Implementation Strategy

For Keynote documents with complex structures (flowcharts, tables, notes), building a high-quality index to support RAG applications demands effort at the parsing stage. Two practical paths emerge: one customizes a reader to directly parse .key internals and extract original content; the other converts to universal PDF and applies mature parsing tools. Each suits different scenarios—custom parsing yields richer structured information but costs more to implement; PDF conversion is fast, reliable, and uses existing tools.

For data engineering and AI application development, a hybrid approach works best: invest in custom parsing logic (Path A) for particularly important or complex files to ensure critical content doesn't disappear and structure is preserved; use conversion and parsing workflows (Path B) for large batches of standard files to achieve good cost-effectiveness. Avoid common pitfalls, respect tool capabilities and conversion details, and you substantially improve the parseability and retrieval quality of complex documents.

With this guidance, you can confidently address Keynote document parsing. From export script writing to parser implementation, concrete examples and lessons learned are provided. Select the approach that fits your project, iteratively improve the parsing results, and you establish a solid data foundation for subsequent RAG applications.

© 2026 Yuxu Ge ·