extract_pptx
Extract slides, speaker notes, and comments from a PowerPoint (.pptx) deck.
Presentation Order
ppt/presentation.xml and ppt/_rels/presentation.xml.rels are required. The
presentation's slide list determines membership and order in every output
format. slide_index is the one-based position in that list, not a part filename
suffix or the presentation's internal slide ID. Hidden slides that remain in the
list are included. Orphan slide, note, and comment parts are not extracted.
Slide parts may be renamed or stored in other package directories. Notes and
legacy comments are followed through each slide's actual relationship part;
comment authors are followed through the presentation's commentAuthors
relationship. Image relationships are also relative to the actual slide part.
An absent notes/comments relationship means no associated content, even if a
similarly numbered file exists in the ZIP.
Missing/empty manifests, duplicate slide IDs, relationship IDs or normalized slide targets, missing referenced parts, wrong slide relationship types, and external slide/notes/comments/authors targets are errors. Multiple notes, comments, or author-list relationships on their respective source are rejected as ambiguous. There is no filename-based fallback. Slide, note, comment, and author-list references are checked before slide media is published; later content or output errors still do not roll back already published artifacts.
Presentation and slide-reference namespaces accept transitional and strict OOXML URIs with arbitrary prefixes. Relationship XML accepts the OPC namespace and legacy unqualified elements, but not foreign namespaces. Presentation nesting is capped at 128 elements. This is structural extraction, not slide rendering or a complete OOXML schema validator; visual reading order within a slide and modern comments are outside this contract.
Text Fidelity
Slide text, table cells, speaker notes, metadata and comments decode built-in XML entities and numeric references once and preserve literal CDATA. Adjacent events and runs do not introduce spaces or newlines; speaker-note paragraphs are separated by newlines. Metadata outer whitespace is trimmed after accumulation. Picture descriptions, embed IDs, list levels and comment attributes use the part's declared XML-version normalization (1.0 by default).
Unknown/invalid text references and DTDs return errors; no custom or external entities are expanded. Decoding and retained attribute values are admitted before allocation against existing output budgets. Table-text copies, generated separators and comment fan-out are charged. ZIP byte/entry, XML event/item, deadline, and final serialized-output checks are unchanged.
Parameters
- pathstringone of
pathorsource_key - File path to the
.pptxfile; supports${ctx.key}interpolation. - source_keystringone of
pathorsource_key - Context key containing a file path, artifact URI, or artifact descriptor.
- formatstringdefault "text"
- Output format:
"text"(flattened slide text),"markdown"(one section per slide), or"json"(structured IR). - output_keystringdefault "content"
- Context key where the extracted output is stored. For
text/markdownthe value is a string; forjsonit is an object. - metadata_keystring
- If set, deck metadata (slide count + Dublin Core fields) is stored under this context key.
- comments_keystring
- If set, relationship-linked legacy slide comments and their author lookup are stored under this context key as a flat array in presentation order. Comments are also attached per-slide in the JSON output.
- media_modestringdefault "none"
- Embedded-media handling:
"none"keeps relationship metadata only;"artifact"streams each resolved image to the disk-backed artifact store and adds anartifactdescriptor. Artifact mode requiresformat = "json". - include_image_bytesbooleandefault false
- Deprecated compatibility input.
falseis accepted;trueis rejected with a migration error because extraction no longer materializes inline Base64 media.
Providing both
pathandsource_keyis an error.formataccepts"text","markdown", or"json". Artifact inputs are opened and SHA-256 verified inside the tracked blocking worker; OOXML parsing consumes that same rewound handle rather than a resolved store pathname. Presentformat,media_mode,output_key,metadata_key, andcomments_keyvalues must be strings. The deprecatedinclude_image_bytesinput accepts a native boolean or an interpolated boolean spelling (true/false,yes/no,on/off, or1/0, case-insensitive). Present values of the wrong type are rejected instead of being treated as absent.output_key,metadata_key, andcomments_keymust be pairwise distinct when the optional keys are set.media_mode = "artifact"is rejected unlessformat = "json".
Context Output
<output_key>(defaultcontent) — slides as text / Markdown / JSON IR.<metadata_key>(when set) — object withslide_countand (when present)title,author,subject,description,keywords,last_modified_by,created,modified,revision,category.<comments_key>(when set) — flat array ofPptxComment(see below).
JSON output shape
{
"slides": [
{
"slide_index": 1,
"title": "STIMULUS 1A",
"elements": [
{
"type": "image",
"alt_text": "Clinical scan",
"embed_id": "rId3",
"embedded_path": "ppt/media/image1.png",
"artifact": {
"artifact_uri": "artifact://sha256/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"size_bytes": 184322,
"mime_type": "image/png"
}
}
],
"speaker_notes": "Moderator: probe on rationale per group.",
"comments": [
{
"slide_index": 1,
"idx": "1",
"author_id": "0",
"author": "Reviewer A",
"initials": "RA",
"date": "2026-04-02T14:20:00",
"text": "Make sure to clarify 'group' wording."
}
]
},
{
"slide_index": 2,
"title": "Patient Profile A",
"elements": [
{
"type": "table",
"rows": [
["Field", "Value"],
["Age", "75"],
["Diagnosis", "Bilateral GA, juxtafoveal"]
]
}
]
}
]
}
Element types
text_block— a non-title shape's text.placeholdercarries the OOXML placeholder type (subTitle,body, etc.) when present and is omitted otherwise.paragraphs[]carrytextand optionallist_level(0-based indent for bulleted items).table— rendered asrows: [[string]].image— picture shape metadata.alt_textandembed_idare included when available.embedded_pathis included only when that ID resolves through an internal OOXML image relationship. Withmedia_mode = "artifact", a resolved embedded image also has anartifactobject containing its canonical URI, SHA-256 digest, decoded byte size, and MIME type. Binary bytes are not placed inNodeOutput.
The slide's title is taken from the placeholder with type="title" or type="ctrTitle". If no title placeholder exists, the title field is omitted.
Comments
The node currently parses legacy comments through comments relationships
and authors through the presentation's commentAuthors relationship. Conventional
part names such as ppt/comments/comment3.xml and ppt/commentAuthors.xml are
not required. PowerPoint's newer "modern comments" format is not yet supported.
slide_index on each comment is the referring slide's one-based presentation
position, in both the flat comments array and the per-slide JSON output.
Resource and cancellation contract
- The input must be a regular file. Its raw size and central directory are
bounded by
IRONFLOW_MAX_FILE_BYTES(default52428800, 50 MiB) before the ZIP library allocates archive metadata. On Unix and Windows, IronFlow also refuses to follow a final path-component symlink/reparse point; other platforms enforce the opened-handle regular-file check. - The EOCD/ZIP64 preflight validates central-directory bounds and
IRONFLOW_MAX_ZIP_ENTRIES(default10000). IronFlow then rejects duplicate part names, symlink or special-file parts, and a cumulative declared uncompressed size aboveIRONFLOW_MAX_ZIP_UNCOMPRESSED_BYTES(default536870912, 512 MiB). These archive-wide admission checks include orphan parts, even though their XML and media are not read during extraction. - XML parts are parsed directly from bounded ZIP-entry readers; IronFlow does
not first materialize a complete part as a byte vector or string. Each part
is capped by the smaller of the remaining cumulative ZIP budget and the
extraction-output budget, and actual decoded bytes accumulate across every
part read. All read, UTF-8, and XML parse errors in the manifest and consumed
slide, notes, relationship, comment, author, and core-properties parts propagate.
Unlinked notes/comments/authors and missing optional metadata remain absent or
empty; linked notes/comments/authors must exist. Errors and limit
breaches never become partial output.
quick_xmlstill buffers its current event, so one unusually large text or CDATA token can require memory proportional to that token, but not to the complete XML part. IRONFLOW_MAX_EXTRACT_ITEMS(default250000) is shared across presentation membership, presentation/slide/relationship/content-type XML events, content-type definitions, slides, elements, paragraphs, table rows and cells, relationships, comments and authors, metadata fields, and embedded media occurrences. Content-type work occurs only when artifact media is requested.IRONFLOW_MAX_EXTRACT_OUTPUT_BYTES(default52428800, 50 MiB) bounds retained fields and formatting work and, authoritatively, the complete serializedNodeOutput. That final result includes the slide output, metadata, and the optional flat comments copy. This is a logical result limit, not a process-RSS limit and not the laterIRONFLOW_MAX_TASK_OUTPUT_BYTESpersistence limit.- Embedded media is decoded only when
format = "json"andmedia_mode = "artifact". It is copied from the ZIP entry to a private artifact-store staging file in bounded chunks while cancellation, cumulative decoded ZIP bytes, and the per-part limit are enforced. The bytes are hashed during that copy and published atomically underIRONFLOW_ARTIFACT_DIR(defaultdata/artifacts) assha256/<digest>. Repeated references to the same normalized package part reuse one descriptor and one stored file. Relationship attributes are XML decoded before use, duplicate relationship IDs are rejected, and only the transitional or strict OOXMLimagerelationship type is eligible forembedded_pathor artifact publication. For image elements, non-image relationships and external targets are ignored and never opened or fetched. In artifact mode, a referenced internal image whose media part is missing is an error; metadata-only mode does not open the image bytes. Relationship elements must contain non-emptyId,Type, andTargetattributes; an unknownTargetModeis rejected instead of being treated as internal. Internal targets resolve relative to their actual source part; a single leading/denotes the package root, never the host filesystem. Paths cannot escape the package root or contain URI schemes, backslashes, ASCII control characters, queries, fragments, empty segments, or terminal dot segments. Case, Unicode, and percent-looking sequences are preserved; targets are not URL-decoded before ZIP member lookup. Relationship fields, slide identities, and normalized retained paths share the output budget. - In artifact mode, the descriptor MIME type comes from the matching
[Content_Types].xmloverride or extension default when one is declared; both lookups follow the package's ASCII case-insensitive matching rules and an override takes precedence. IronFlow falls back to its built-in image-extension mapping when the package omits a matching declaration, and toapplication/octet-streamwhen neither source recognizes the extension. MIME content is not inferred from the binary payload. A present malformed content-types part is an extraction error. - Artifact descriptors are small JSON values; the media itself is never Base64-encoded or retained in the extraction model. Artifacts are immutable and content-addressed. The local backend does not automatically expire or garbage-collect them, so operators own retention. A recovered or multi-host workflow must mount the same artifact directory on every worker; Redis or PostgreSQL context persistence does not copy local artifact files. Artifact publication is not rolled back if a later slide or output check fails, so a retention sweep must also reclaim unreferenced files.
- ZIP and XML work runs on a tracked blocking worker. Archive reads and parser loops check cancellation and the step/run deadline between chunks and XML events; synchronous ZIP-library construction cannot be interrupted inside the call and is bracketed by checkpoints. Task and run admission remain occupied until the physical worker stops.
Examples
Extract a stimulus deck as JSON
flow:step("extract_deck", nodes.extract_pptx({
path = "${ctx.deck_path}",
format = "json",
media_mode = "artifact",
output_key = "deck",
comments_key = "deck_comments",
metadata_key = "deck_meta"
}))
flow:step("show", nodes.log({
message = "Deck '${ctx.deck_meta.title}' has ${ctx.deck_meta.slide_count} slides"
})):depends_on("extract_deck")
Markdown for previewing
flow:step("preview", nodes.extract_pptx({
path = "/data/stimulus.pptx",
format = "markdown",
output_key = "md"
}))
Produces one ## Slide N section per slide with the title as an ### header, body paragraphs (bullets if list-leveled), tables as Markdown pipe tables, and speaker notes appended.