From 0414187424318b690cfdd083b39d1c8df9d00cbf Mon Sep 17 00:00:00 2001 From: nilsk Date: Fri, 17 Jul 2026 23:00:42 +0200 Subject: [PATCH 1/4] migrated examples to new top-level api --- examples/manual_image_extraction.rs | 31 ++++------ examples/memory_efficient_streaming.rs | 59 ++++++------------ examples/performance_test.rs | 40 ++++-------- examples/save_images.rs | 55 ++++++----------- examples/slide_elements.rs | 85 +++++++++++--------------- 5 files changed, 96 insertions(+), 174 deletions(-) diff --git a/examples/manual_image_extraction.rs b/examples/manual_image_extraction.rs index 3a6aeac..368d9e5 100644 --- a/examples/manual_image_extraction.rs +++ b/examples/manual_image_extraction.rs @@ -1,28 +1,26 @@ -//! Basic usage example for the pptx-to-md crate +//! Manually extract images while parsing either a PPTX or ODP presentation. //! -//! This example demonstrates how to open a PPTX file and convert all slides to Markdown. -//! -//! Run with: cargo run --example manual_image_extraction +//! Run with: +//! cargo run --example manual_image_extraction use base64::Engine; use base64::engine::general_purpose; -use pptx_to_md::{ImageHandlingMode, ParserConfig, PptxContainer, Result}; +use pptx_to_md::{ImageHandlingMode, ParserConfig, PresentationContainer, Result}; use std::fs::File; use std::io::Write; use std::path::Path; use std::{env, fs}; fn main() -> Result<()> { - // Get the PPTX file path from command line arguments let args: Vec = env::args().collect(); - let pptx_path = if args.len() > 1 { - &args[1] - } else { - eprintln!("Usage: cargo run --example manual_image_extraction "); + let Some(input_path) = args.get(1) else { + eprintln!( + "Usage: cargo run --example manual_image_extraction " + ); return Ok(()); }; - println!("Processing PPTX file: {}", pptx_path); + println!("Processing presentation: {input_path}"); // Use the config builder to build your config let config = ParserConfig::builder() @@ -32,8 +30,7 @@ fn main() -> Result<()> { .image_handling_mode(ImageHandlingMode::Manually) .build(); - // Open the PPTX file - let mut container = PptxContainer::open(Path::new(pptx_path), config)?; + let mut container = PresentationContainer::open(Path::new(input_path), config)?; // Parse all slides let slides = container.parse_all()?; @@ -47,12 +44,11 @@ fn main() -> Result<()> { let output_dir = "extracted_images"; fs::create_dir_all(output_dir)?; - // Process slides one by one using the iterator let mut image_count = 1; // Convert each slide to Markdown and save for slide in slides { - writeln!(md_file, "{}", slide.convert_to_md()?).expect("Couldn't write to file"); + writeln!(md_file, "{}", slide.convert_to_md()?)?; // Manually load the base64 encoded image strings from the slide if let Some(images) = slide.load_images_manually() { @@ -60,7 +56,7 @@ fn main() -> Result<()> { // Decode the base64 strings back to raw image data let image_data = general_purpose::STANDARD .decode(image.base64_content.clone()) - .unwrap(); + .expect("parser returned invalid base64 image data"); // Extract image extension if the image is not compressed, otherwise its always `.jpg` let ext = if slide.config.compress_images { @@ -85,8 +81,7 @@ fn main() -> Result<()> { md_file, "![{}](data:image/{};base64,{})", file_name, ext, image.base64_content - ) - .expect("Couldn't write to file"); + )?; image_count += 1; } diff --git a/examples/memory_efficient_streaming.rs b/examples/memory_efficient_streaming.rs index c10cdc9..e71cf55 100644 --- a/examples/memory_efficient_streaming.rs +++ b/examples/memory_efficient_streaming.rs @@ -1,61 +1,38 @@ -//! Memory-efficient streaming example for the pptx-to-md crate +//! Process one PPTX or ODP slide at a time instead of retaining all slides. //! -//! This example demonstrates how to use the streaming API to process large -//! PPTX files with minimal memory usage. -//! -//! Run with: cargo run --example memory_efficient_streaming +//! Run with: +//! cargo run --example memory_efficient_streaming -use pptx_to_md::{ParserConfig, PptxContainer, Result}; +use pptx_to_md::{ParserConfig, PresentationContainer, Result}; use std::env; use std::fs; use std::path::Path; fn main() -> Result<()> { - // Get the PPTX file path from command line arguments let args: Vec = env::args().collect(); - let pptx_path = if args.len() > 1 { - &args[1] - } else { + let Some(input_path) = args.get(1) else { eprintln!( - "Usage: cargo run --example memory_efficient_streaming " + "Usage: cargo run --example memory_efficient_streaming " ); return Ok(()); }; - println!("Processing PPTX file: {}", pptx_path); - - // Use the config builder to build your config - let config = ParserConfig::builder().extract_images(true).build(); - - // Open the PPTX file with the streaming API - let mut streamer = PptxContainer::open(Path::new(pptx_path), config)?; - - // Create output directory + let mut presentation = + PresentationContainer::open(Path::new(input_path), ParserConfig::default())?; let output_dir = "output_streaming"; fs::create_dir_all(output_dir)?; - // Process slides one by one using the iterator - for slide_result in streamer.iter_slides() { - match slide_result { - Ok(slide) => { - println!( - "Processing slide {} ({} elements)", - slide.slide_number, - slide.elements.len() - ); - - let md_content = slide.convert_to_md()?; - let output_path = format!("{}/slide_{}.md", output_dir, slide.slide_number); - fs::write(&output_path, md_content)?; - println!("Saved slide {} to {}", slide.slide_number, output_path); - } - Err(e) => { - eprintln!("Error processing slide: {:?}", e); - } - } + // Unlike parse_document(), the iterator only retains the current slide. + for slide_result in presentation.iter_slides() { + let slide = slide_result?; + let output_path = format!("{output_dir}/slide_{}.md", slide.slide_number); + fs::write(&output_path, slide.convert_to_md()?)?; + println!( + "Saved slide {} ({} semantic blocks) to {output_path}", + slide.slide_number, + slide.blocks.len() + ); } - println!("All slides processed successfully!"); - Ok(()) } diff --git a/examples/performance_test.rs b/examples/performance_test.rs index 10dce09..e6f3a73 100644 --- a/examples/performance_test.rs +++ b/examples/performance_test.rs @@ -7,9 +7,9 @@ //! //! It also provides timing for individual operations to identify bottlenecks. //! -//! Run with: cargo run --release --example performance_test [iterations] +//! Run with: cargo run --release --example performance_test [iterations] -use pptx_to_md::{ParserConfig, PptxContainer, Result}; +use pptx_to_md::{ParserConfig, PresentationContainer, Result}; use rayon::prelude::*; use std::env; use std::path::Path; @@ -74,7 +74,7 @@ fn main() -> Result<()> { &args[1] } else { eprintln!( - "Usage: cargo run --example performance_test [iterations]" + "Usage: cargo run --example performance_test [iterations]" ); return Ok(()); }; @@ -101,17 +101,14 @@ fn main() -> Result<()> { // Measure container creation let mut container = single_thread_bench.measure(|| { let config = ParserConfig::builder().extract_images(true).build(); - PptxContainer::open(Path::new(pptx_path), config).expect("Failed to open PPTX") + PresentationContainer::open(Path::new(pptx_path), config) + .expect("Failed to open presentation") }); - println!( - " Found {} slides in the presentation", - container.slide_count - ); - // Measure parsing let slides = single_thread_bench.measure(|| container.parse_all().expect("Failed to parse slides")); + println!(" Found {} slides in the presentation", slides.len()); // Measure conversion let _md_content = single_thread_bench.measure(|| { @@ -141,17 +138,10 @@ fn main() -> Result<()> { // Measure container creation let mut container = single_thread_streamed_bench.measure(|| { let config = ParserConfig::builder().extract_images(true).build(); - PptxContainer::open(Path::new(pptx_path), config).expect("Failed to open PPTX") + PresentationContainer::open(Path::new(pptx_path), config) + .expect("Failed to open presentation") }); - println!( - " Found {} slides in the presentation", - container.slide_count - ); - - // Zähle die Slides im Voraus für die statistische Auswertung - let expected_slides = container.slide_count; - // Measure slide processing (including parsing and conversion) let slides_processed = single_thread_streamed_bench.measure(|| { let mut processed = 0; @@ -173,10 +163,7 @@ fn main() -> Result<()> { processed }); - println!( - " Processed {} out of {} slides", - slides_processed, expected_slides - ); + println!(" Processed {} slides", slides_processed); total_slides += slides_processed; } @@ -197,15 +184,10 @@ fn main() -> Result<()> { // Container öffnen mit der gewünschten Konfiguration let mut container = optimized_multi_thread_bench.measure(|| { let config = ParserConfig::builder().extract_images(true).build(); - PptxContainer::open(Path::new(pptx_path), config).expect("Failed to open PPTX") + PresentationContainer::open(Path::new(pptx_path), config) + .expect("Failed to open presentation") }); - println!( - " Found {} slides in the presentation", - container.slide_count - ); - - // Verwende die neue optimierte Multi-Threading-Methode let slides = optimized_multi_thread_bench.measure(|| { container .parse_all_multi_threaded() diff --git a/examples/save_images.rs b/examples/save_images.rs index d7a8c6d..7ec64b2 100644 --- a/examples/save_images.rs +++ b/examples/save_images.rs @@ -1,53 +1,34 @@ -//! Basic usage example for the pptx-to-md crate +//! Save presentation images next to the generated Markdown instead of embedding them. //! -//! This example demonstrates how to open a PPTX file and convert all slides to Markdown. -//! -//! Run with: cargo run --example save_images +//! Run with: +//! cargo run --example save_images [image-directory] [output.md] -use pptx_to_md::{ImageHandlingMode, ParserConfig, PptxContainer, Result}; +use pptx_to_md::{ImageHandlingMode, ParserConfig, PresentationContainer, Result}; use std::env; -use std::fs::File; -use std::io::Write; +use std::fs; use std::path::{Path, PathBuf}; fn main() -> Result<()> { - // Get the PPTX file path from command line arguments and provide the mandatory output path let args: Vec = env::args().collect(); - let pptx_path = if args.len() > 1 { - &args[1] - } else { - eprintln!("Usage: cargo run --example save_images "); + let Some(input_path) = args.get(1) else { + eprintln!( + "Usage: cargo run --example save_images [image-directory] [output.md]" + ); return Ok(()); }; + let image_directory = args + .get(2) + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("extracted_images")); + let output_path = args.get(3).map(String::as_str).unwrap_or("output.md"); - println!("Processing PPTX file: {}", pptx_path); - - // Use the config builder to build your config let config = ParserConfig::builder() - .extract_images(true) - .compress_images(true) - .quality(75) .image_handling_mode(ImageHandlingMode::Save) - .image_output_path(PathBuf::from("C:/Users/nilsk/Downloads/extracted_images")) + .image_output_path(image_directory) .build(); + let mut presentation = PresentationContainer::open(Path::new(input_path), config)?; - // Open the PPTX file - let mut container = PptxContainer::open(Path::new(pptx_path), config)?; - - // Parse all slides - let slides = container.parse_all()?; - - println!("Found {} slides", slides.len()); - - // create a new Markdown file - let mut md_file = File::create("output.md")?; - - // Convert each slide to Markdown and save the images automatically - for slide in slides { - writeln!(md_file, "{}", slide.convert_to_md()?).expect("Couldn't write to file"); - } - - println!("All slides converted successfully!"); - + fs::write(output_path, presentation.convert_to_md()?)?; + println!("Saved Markdown to {output_path}"); Ok(()) } diff --git a/examples/slide_elements.rs b/examples/slide_elements.rs index c7acc79..3209021 100644 --- a/examples/slide_elements.rs +++ b/examples/slide_elements.rs @@ -1,66 +1,53 @@ -//! Working with the parsed slide elements example for the pptx-to-md crate +//! Inspect the semantic document model instead of rendering Markdown directly. //! -//! This example demonstrates how to use the slide elements before the elements are parsed to Markdown, -//! to do your conversion logic. -//! -//! Run with: cargo run --example slide_elements +//! Run with: +//! cargo run --example slide_elements -use pptx_to_md::{ParserConfig, PptxContainer, Result, SlideBlockContent}; +use pptx_to_md::{ParserConfig, PresentationContainer, Result, SlideBlockContent}; use std::env; use std::path::Path; fn main() -> Result<()> { - // Get the PPTX file path from command line arguments let args: Vec = env::args().collect(); - let pptx_path = if args.len() > 1 { - &args[1] - } else { - eprintln!("Usage: cargo run --example slide_elements "); + let Some(input_path) = args.get(1) else { + eprintln!("Usage: cargo run --example slide_elements "); return Ok(()); }; - println!("Processing PPTX file: {}", pptx_path); - - // Use the config builder to build your config - let config = ParserConfig::builder().extract_images(true).build(); - - // Open the PPTX file with the streaming API - let mut streamer = PptxContainer::open(Path::new(pptx_path), config)?; - - // Process slides one by one using the iterator - for slide_result in streamer.iter_slides() { - match slide_result { - Ok(slide) => { - println!( - "Processing slide {} ({} semantic blocks)", - slide.slide_number, - slide.blocks.len() - ); - - for block in &slide.blocks { - match &block.content { - SlideBlockContent::Text(text) => { - println!("{:?}\t{:?}\n", text, block.bounds) - } - SlideBlockContent::Table(table) => { - println!("{:?}\t{:?}\n", table, block.bounds) - } - SlideBlockContent::Image(image) => { - println!("{:?}\t{:?}\n", image, block.bounds) - } - SlideBlockContent::Unsupported(unsupported) => { - println!("Unsupported: {:?}\n", unsupported) - } - } + let mut presentation = + PresentationContainer::open(Path::new(input_path), ParserConfig::default())?; + let document = presentation.parse_document()?; + + println!( + "Parsed {:?} presentation with {} slides and {} diagnostics", + presentation.format(), + document.slides.len(), + document.diagnostics.len() + ); + + for slide in &document.slides { + println!( + "Slide {} ({} semantic blocks)", + slide.slide_number, + slide.blocks.len() + ); + for block in &slide.blocks { + match &block.content { + SlideBlockContent::Text(text) => { + println!(" {:?} text at {:?}: {:?}", text.role, block.bounds, text) + } + SlideBlockContent::Table(table) => { + println!(" Table at {:?}: {:?}", block.bounds, table) + } + SlideBlockContent::Image(image) => { + println!(" Image at {:?}: {:?}", block.bounds, image) + } + SlideBlockContent::Unsupported(unsupported) => { + println!(" Unsupported at {:?}: {:?}", block.bounds, unsupported) } - } - Err(e) => { - eprintln!("Error processing slide: {:?}", e); } } } - println!("All slides processed successfully!"); - Ok(()) } From ba561e3600d9b8033ff450fd10cf59cbf713bdf8 Mon Sep 17 00:00:00 2001 From: nilsk Date: Fri, 17 Jul 2026 23:01:27 +0200 Subject: [PATCH 2/4] added a legacy example for documentation purposes --- examples/legacy_pptx_api.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 examples/legacy_pptx_api.rs diff --git a/examples/legacy_pptx_api.rs b/examples/legacy_pptx_api.rs new file mode 100644 index 0000000..84d0651 --- /dev/null +++ b/examples/legacy_pptx_api.rs @@ -0,0 +1,35 @@ +//! Legacy PPTX-only API retained for existing integrations. +//! +//! New code should use `PresentationContainer`; see `basic_usage.rs`. +//! +//! Run with: +//! cargo run --example legacy_pptx_api [output.md] + +use pptx_to_md::{ParserConfig, PptxContainer, Result}; +use std::env; +use std::fs; +use std::path::Path; + +fn main() -> Result<()> { + let args: Vec = env::args().collect(); + let Some(input_path) = args.get(1) else { + eprintln!("Usage: cargo run --example legacy_pptx_api [output.md]"); + return Ok(()); + }; + let output_path = args.get(2).map(String::as_str).unwrap_or("output.md"); + + // This is the pre-PresentationContainer flow: open a PPTX-specific + // container, parse every slide, and render each slide separately. It does + // not add the presentation-level metadata header. + let mut container = PptxContainer::open(Path::new(input_path), ParserConfig::default())?; + let markdown = container + .parse_all()? + .into_iter() + .map(|slide| slide.convert_to_md()) + .collect::>>()? + .join("\n"); + fs::write(output_path, markdown)?; + + println!("Converted PPTX with the legacy entry point to {output_path}"); + Ok(()) +} From a011e44880aebb85a2e228bec2c266a96cefbcbd Mon Sep 17 00:00:00 2001 From: nilsk Date: Fri, 17 Jul 2026 23:01:54 +0200 Subject: [PATCH 3/4] fixed config doc issues --- src/parser_config.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parser_config.rs b/src/parser_config.rs index 7131925..94fb680 100644 --- a/src/parser_config.rs +++ b/src/parser_config.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; /// |-----------------------|-----------------------------------------------------------------------------------------------------------------------------------| /// | `InMarkdown` | Images are embedded directly in the Markdown output using standard syntax as `base64` data (`![]()`) | /// | `Manually` | Image handling is delegated to the user, requiring manual copying or referencing (as `base64` encoded string) | -/// | `Save` | Images will be saved in a provided output directory and integrated using `` tag syntax (``) | +/// | `Save` | Images are saved in a provided output directory and referenced using Markdown image syntax with a `file://` URL | #[derive(Debug, Clone, PartialEq, Eq)] pub enum ImageHandlingMode { InMarkdown, @@ -27,10 +27,10 @@ pub enum ImageHandlingMode { /// |---------------------------|-----------------------|---------------|-----------------------------------------------------------------------------------------------------------| /// | `extract_images` | `bool` | `true` | Whether images are extracted from slides or not. If false, images can not be extracted manually either | /// | `compress_images` | `bool` | `true` | Whether images are compressed before encoding or not. Effects manually extracted images too | -/// | `image_quality` | `u8` | `80` | Compression level (0-100);
higher values retain more detail but increase file size | +/// | `quality` | `u8` | `80` | Compression level (0-100);
higher values retain more detail but increase file size | /// | `image_handling_mode` | `ImageHandlingMode` | `InMarkdown` | Determines how images are handled during content export | /// | `image_output_path` | `Option` | `None` | Output directory path for `ImageHandlingMode::Save` (mandatory for the saving mode) | -/// | `include_slide_number_as_comment` | `bool` | `true` | Weather the slide number comment is included or not (``) | +/// | `include_slide_number_as_comment` | `bool` | `true` | Whether the slide number comment is included (``) | /// | `include_speaker_notes` | `bool` | `false` | Whether speaker notes are appended to Markdown as blockquotes | /// | `include_comments` | `bool` | `false` | Whether presentation comments are appended to Markdown as blockquotes | /// | `include_presentation_metadata` | `bool` | `true` | Whether presentation-wide Markdown starts with a metadata comment | From 5055a13781fc956e7c760ec094ee3feca20269e5 Mon Sep 17 00:00:00 2001 From: nilsk Date: Fri, 17 Jul 2026 23:08:35 +0200 Subject: [PATCH 4/4] documented the different API entry points in the README.md --- README.md | 139 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 4de7f36..ed856a3 100644 --- a/README.md +++ b/README.md @@ -26,116 +26,119 @@ It also supports OpenDocument Presentation (`.odp`). - 🪄 **Embedding:** Used to provide pptx content and meta-information in a form that is useful for embeddings --- -## 👨‍💻 Example Usage +## 📌 Quickstart -`PresentationContainer` is the recommended entry point for new code. It detects whether the input is a PowerPoint (`.pptx`) or OpenDocument Presentation (`.odp`) file and exposes the same parsing API for both formats. +`PresentationContainer` is the preferred entry point for all new code. It +detects PPTX and ODP automatically. The default configuration produces complete +Markdown with presentation metadata, slide markers, spatial reading order, and +embedded images. ```rust -use pptx_to_md::{ImageHandlingMode, ParserConfig, PresentationContainer}; +use pptx_to_md::{ParserConfig, PresentationContainer, Result}; use std::path::Path; -fn main() -> Result<(), Box> { - let config = ParserConfig::builder() - .include_presentation_metadata(true) - .compress_images(true) - .quality(80) - .image_handling_mode(ImageHandlingMode::InMarkdown) - .include_slide_number_as_comment(true) - .include_speaker_notes(false) - .include_comments(false) - .build(); - - let mut container = PresentationContainer::open( - Path::new("path/to/presentation.pptx"), // or .odp - config, +fn main() -> Result<()> { + let mut presentation = PresentationContainer::open( + Path::new("presentation.pptx"), // also accepts .odp + ParserConfig::default(), )?; - let markdown = container.convert_to_md()?; + let markdown = presentation.convert_to_md()?; std::fs::write("output.md", markdown)?; - Ok(()) } ``` -`PresentationContainer::convert_to_md()` is the preferred API for converting -a complete PPTX or ODP document. It emits presentation metadata once at the -start and then appends all slide Markdown in order. The existing `parse_all()`, -streaming, and per-slide APIs remain available for structured processing. +See [`examples/basic_usage.rs`](examples/basic_usage.rs) for the corresponding +command-line example. -### Semantic document model +## 🎯 Choosing the right API -`PresentationContainer::parse_document()` returns a **semantic** `Presentation` -containing metadata, slides, and non-fatal parser diagnostics. Each slide exposes -ordered `blocks` with bounds and source order. Text blocks preserve title/body -roles, individual paragraphs, list metadata, run formatting, and hyperlinks; -tables preserve merged-cell information and images retain alternative text. +Start with `PresentationContainer` and select the operation based on what the +application needs: -For custom Markdown behavior, call `Slide::to_markdown(&MarkdownOptions)`. -`ReadingOrder::Spatial` is the default and groups content by visual columns; -`ReadingOrder::Source` preserves the package's source order. Per-slide -`convert_to_md()` now returns `Result` so image and filesystem failures -cannot be discarded silently. +| Goal | Preferred API | What it does | +| --- | --- | --- | +| Convert one complete presentation | `convert_to_md()` | Parses every slide and returns one Markdown document, including optional presentation metadata | +| Convert a large PPTX faster | `convert_to_md_multi_threaded()` | Uses parallel slide parsing for PPTX; ODP transparently uses its normal document parser | +| Inspect or transform structured content | `parse_document()` | Returns metadata, semantic slides and blocks, and aggregated diagnostics | +| Work with all slides directly | `parse_all()` | Returns `Vec` without creating presentation-level Markdown | +| Process one slide at a time | `iter_slides()` | Streams `Result` values and avoids retaining every parsed slide | +| Customize one slide's Markdown | `Slide::to_markdown(&MarkdownOptions)` | Controls reading order, slide marker, notes, comments, and unsupported-content comments | +| Read only document properties | `metadata()` | Returns parsed presentation metadata without parsing the slides | -See [Semantic presentation-to-Markdown conversion](docs/SEMANTIC_MARKDOWN_CONVERSION.md) -for the architecture, migration notes, test coverage, and known limitations of -the pull-parser and semantic-model changes. +`ParserConfig` controls parsing, image handling, and the defaults used by +`convert_to_md()`. `MarkdownOptions` is only needed when rendering an individual +parsed slide differently. -To access metadata as structured Rust values without converting slides, see -[`examples/presentation_metadata.rs`](https://github.com/nilskruthoff/pptx-parser/tree/master/examples/presentation_metadata.rs). +Important behavior differences: -For PPTX-only code, `PptxContainer` remains available for backwards compatibility and for callers that explicitly want the old PowerPoint-only entry point. New code that may handle both formats should prefer `PresentationContainer`. -For more usage examples, refer to the [examples](https://github.com/nilskruthoff/pptx-parser/tree/master/examples) directory. +- Presentation-level `convert_to_md*()` methods parse and render the slides and + emit presentation metadata once. Per-slide methods never emit presentation + metadata. +- `parse_document()` and `parse_all*()` retain all returned slides. The former + additionally packages metadata and aggregates slide diagnostics. +- `Slide::convert_to_md()` uses the rendering flags copied from `ParserConfig`. + `Slide::to_markdown()` accepts explicit `MarkdownOptions` for that one call; + image loading and image output mode still come from the slide's + `ParserConfig`. +- `PresentationContainer::open()` performs format detection. Use `format()` to + inspect the result. `open_as()` is only necessary when the caller explicitly + wants to force `PresentationFormat::Pptx` or `PresentationFormat::Odp`. -### PPTX and ODP auto-detection +### Structured content and custom Markdown -Use `PresentationContainer` when the input may be either `.pptx` or `.odp`: +Use `parse_document()` when Markdown is not the only desired output or parser +diagnostics need to be inspected: ```rust -use pptx_to_md::{ParserConfig, PresentationContainer, PresentationFormat}; +use pptx_to_md::{MarkdownOptions, ParserConfig, PresentationContainer, ReadingOrder, Result}; use std::path::Path; -fn main() -> Result<(), Box> { +fn main() -> Result<()> { let mut presentation = PresentationContainer::open( - Path::new("path/to/presentation.odp"), + Path::new("presentation.odp"), ParserConfig::default(), )?; - - assert_eq!(presentation.format(), PresentationFormat::Odp); - let slides = presentation.parse_all()?; + let document = presentation.parse_document()?; + + for diagnostic in &document.diagnostics { + eprintln!("{:?}: {}", diagnostic.severity, diagnostic.message); + } + + let options = MarkdownOptions { + reading_order: ReadingOrder::Source, + include_speaker_notes: true, + ..MarkdownOptions::default() + }; + let first_slide_markdown = document.slides[0].to_markdown(&options)?; + println!("{first_slide_markdown}"); Ok(()) } ``` -If the format is already known, use `open_as` to skip auto-detection: +`ReadingOrder::Spatial` is the default and orders visual columns heuristically. +`ReadingOrder::Source` preserves the order in the source XML. -```rust -use pptx_to_md::{ParserConfig, PresentationContainer, PresentationFormat}; -use std::path::Path; +### Streaming and parallel parsing -fn main() -> Result<(), Box> { - let mut presentation = PresentationContainer::open_as( - Path::new("path/to/presentation.pptx"), - ParserConfig::default(), - PresentationFormat::Pptx, - )?; - - let slides = presentation.parse_all()?; - Ok(()) -} -``` +Use `iter_slides()` for bounded-memory processing. Each slide is parsed when the +iterator advances. Use `parse_all_multi_threaded()` or +`convert_to_md_multi_threaded()` for CPU-parallel PPTX parsing. ODP stores its +pages in one `content.xml`, so its implementation remains sequential. --- -## Config Parameters +## ⚙️ Config Parameters | Parameter | Type | Default | Description | |--------------------------|-----------------------|---------------|-----------------------------------------------------------------------------------------------------------| | `extract_images` | `bool` | `true` | Whether images are extracted from slides or not. If false, images can not be extracted manually either. | | `compress_images` | `bool` | `true` | Whether images are compressed before encoding or not. Effects manually extracted images too. | -| `image_quality` | `u8` | `80` | Defines the image compression quality `(0-100)`. Higher values mean better quality but larger file sizes. | +| `quality` | `u8` | `80` | Defines the image compression quality `(0-100)`. Higher values mean better quality but larger file sizes. | | `image_handling_mode` | `ImageHandlingMode` | `InMarkdown` | Determines how images are handled during content export | | `image_output_path` | `Option` | `None` | Output directory path for `ImageHandlingMode::Save` (mandatory for saving mode) | -| `include_slide_number_as_comment` | `bool` | `true` | Weather the slide number comment is included or not (``) | +| `include_slide_number_as_comment` | `bool` | `true` | Whether the slide number comment is included (``) | | `include_speaker_notes` | `bool` | `false` | Whether speaker notes are appended to Markdown as blockquotes | | `include_comments` | `bool` | `false` | Whether presentation comments are appended to Markdown as blockquotes | | `include_presentation_metadata` | `bool` | `true` | Whether complete-presentation Markdown starts with a metadata HTML comment | @@ -146,7 +149,7 @@ fn main() -> Result<(), Box> { |---------------|---------------------------------------------------------------------------------------------------------------------------------| | `InMarkdown` | Images are embedded directly in the Markdown output using standard syntax as `base64` data (`![]()`) | | `Manually` | Image handling is delegated to the user, requiring manual copying or referencing (as `base64`) | -| `Save` | Images will be saved in a provided output directory and integrated using `` tag syntax (``) | +| `Save` | Images are saved in the configured output directory and referenced with Markdown image syntax and a `file://` URL | ---