Working with presentation slides

A slide is stored as a separate PresentationML part, usually under ppt/slides/. In ooxmlsdk, slide parts are reached from the presentation part with slide_parts(&document).

The slide list in p:presentation defines normal presentation order. Custom shows can define a separate ordering by listing slide relationship IDs in a p:custShow slide list.

Slide structure

The root element of a slide part is <p:sld/>. It contains common slide data, optional transition and timing data, color map overrides, and extension data.

For a shape, p:nvSpPr and p:spPr are required by the generated schema. The text body p:txBody is optional, but when it is present it must contain a:bodyPr; a:lstStyle is optional.

<p:sld
  xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
  xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
  <p:cSld>
    <p:spTree>
      <p:nvGrpSpPr>
        <p:cNvPr id="1" name=""/>
        <p:cNvGrpSpPr/>
        <p:nvPr/>
      </p:nvGrpSpPr>
      <p:grpSpPr/>
      <p:sp>
        <p:nvSpPr>
          <p:cNvPr id="2" name="Title 1"/>
          <p:cNvSpPr/>
          <p:nvPr/>
        </p:nvSpPr>
        <p:spPr/>
        <p:txBody>
          <a:bodyPr/>
          <a:lstStyle/>
          <a:p><a:r><a:t>Slide title</a:t></a:r></a:p>
        </p:txBody>
      </p:sp>
    </p:spTree>
  </p:cSld>
</p:sld>

Common child elements include:

PresentationML elementPurpose
<p:cSld/>Common slide data, including shapes and text
<p:clrMapOvr/>Color map override for this slide
<p:transition/>Transition from the previous slide
<p:timing/>Animation and timing data
<p:extLst/>Extension data

p:cSld stores the slide-specific common data, including the shape tree. p:clrMapOvr can inherit the master color mapping with a:masterClrMapping or define an override mapping.

Reading slide content

For inspection tasks, read each SlidePart through the package and parse the text or relationships you need:

#![allow(unused)]
fn main() {
pub fn get_slide_text(
  path: &Path,
  slide_index: usize,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
  let document = PresentationDocument::new_from_file_with_settings(path, lazy_settings())?;
  let presentation_part = document.presentation_part()?;
  let Some(slide_part) = presentation_part.slide_parts(&document).nth(slide_index) else {
    return Ok(Vec::new());
  };
  let xml = slide_part.data_as_str(&document)?.unwrap_or_default();

  Ok(extract_drawing_text(xml))
}
}

The companion example that reads every slide uses the same traversal:

#![allow(unused)]
fn main() {
pub fn get_all_slide_text(path: &Path) -> Result<Vec<Vec<String>>, Box<dyn std::error::Error>> {
  let document = PresentationDocument::new_from_file_with_settings(path, lazy_settings())?;
  let presentation_part = document.presentation_part()?;
  let mut slides = Vec::new();

  let slide_parts: Vec<_> = presentation_part.slide_parts(&document).collect();
  for slide_part in slide_parts {
    let xml = slide_part.data_as_str(&document)?.unwrap_or_default();
    slides.push(extract_drawing_text(xml));
  }

  Ok(slides)
}
}

Relationship model

Slide parts can own relationships to layouts, images, charts, embedded packages, media, comments, and hyperlinks. Prefer typed accessors such as slide_layout_part(&document) when the target is a known part type. For reference relationships like external hyperlinks, use relationship iterators such as hyperlink_relationships(&document).

When a feature is not yet represented by a high-level helper in ooxmlsdk, keep the package model intact: read the part data, make a focused XML change, and save the package only after the resulting part graph is still valid.

A valid slide is normally associated with a slide layout, and that layout is associated with a slide master. When creating or copying slides, preserve those relationships rather than copying only ppt/slides/slideN.xml.