Skip to main content

ParserConfig

Struct ParserConfig 

Source
pub struct ParserConfig {
    pub extract_images: bool,
    pub compress_images: bool,
    pub quality: u8,
    pub image_handling_mode: ImageHandlingMode,
    pub image_output_path: Option<PathBuf>,
    pub include_slide_number_as_comment: bool,
    pub include_speaker_notes: bool,
    pub include_comments: bool,
    pub include_presentation_metadata: bool,
}
Expand description

Configuration options for the PPTX parser.

Use ParserConfig::builder() to create a configuration instance. This allows you to customize only the desired fields while falling back to sensible defaults for the rest.

§Configuration Options

ParameterTypeDefaultDescription
extract_imagesbooltrueWhether images are extracted from slides or not. If false, images can not be extracted manually either
compress_imagesbooltrueWhether images are compressed before encoding or not. Effects manually extracted images too
qualityu880Compression level (0-100);
higher values retain more detail but increase file size
image_handling_modeImageHandlingModeInMarkdownDetermines how images are handled during content export
image_output_pathOption<PathBuf>NoneOutput directory path for ImageHandlingMode::Save (mandatory for the saving mode)
include_slide_number_as_commentbooltrueWhether the slide number comment is included (<!-- Slide [n] -->)
include_speaker_notesboolfalseWhether speaker notes are appended to Markdown as blockquotes
include_commentsboolfalseWhether presentation comments are appended to Markdown as blockquotes
include_presentation_metadatabooltrueWhether presentation-wide Markdown starts with a metadata comment

§Example

use std::path::PathBuf;
use pptx_to_md::{ImageHandlingMode, ParserConfig};

let config = ParserConfig::builder()
    .extract_images(true)
    .compress_images(true)
    .quality(75)
    .image_handling_mode(ImageHandlingMode::Save)
    .image_output_path(PathBuf::from("/path/to/output/dir/"))
    .build();

Fields§

§extract_images: bool§compress_images: bool§quality: u8§image_handling_mode: ImageHandlingMode§image_output_path: Option<PathBuf>§include_slide_number_as_comment: bool§include_speaker_notes: bool§include_comments: bool§include_presentation_metadata: bool

Implementations§

Source§

impl ParserConfig

Source

pub fn builder() -> ParserConfigBuilder

Examples found in repository?
examples/save_images.rs (line 25)
11fn main() -> Result<()> {
12    let args: Vec<String> = env::args().collect();
13    let Some(input_path) = args.get(1) else {
14        eprintln!(
15            "Usage: cargo run --example save_images <presentation.pptx|presentation.odp> [image-directory] [output.md]"
16        );
17        return Ok(());
18    };
19    let image_directory = args
20        .get(2)
21        .map(PathBuf::from)
22        .unwrap_or_else(|| PathBuf::from("extracted_images"));
23    let output_path = args.get(3).map(String::as_str).unwrap_or("output.md");
24
25    let config = ParserConfig::builder()
26        .image_handling_mode(ImageHandlingMode::Save)
27        .image_output_path(image_directory)
28        .build();
29    let mut presentation = PresentationContainer::open(Path::new(input_path), config)?;
30
31    fs::write(output_path, presentation.convert_to_md()?)?;
32    println!("Saved Markdown to {output_path}");
33    Ok(())
34}
More examples
Hide additional examples
examples/basic_usage.rs (line 26)
10fn main() -> Result<()> {
11    let args: Vec<String> = env::args().collect();
12    let pptx_path = if args.len() > 1 {
13        &args[1]
14    } else {
15        eprintln!("Usage: cargo run --example basic_usage <presentation.pptx|presentation.odp> <extract_images>\ncargo run --example basic_usage sample.pptx true");
16        return Ok(());
17    };
18
19    // Tries to read if the extract_images flag is false else set to true
20    let extract_images = if args.len() > 2 {
21        !(args[2] == "false" || args[2] == "False" || args[2] == "0")
22    } else {
23        true
24    };
25
26    let config = ParserConfig::builder()
27        .extract_images(extract_images)
28        .include_presentation_metadata(true)
29        .include_comments(true)
30        .include_speaker_notes(true)
31        .build();
32    let mut container = PresentationContainer::open(Path::new(pptx_path), config)?;
33    let markdown = container.convert_to_md()?;
34
35    fs::write("output.md", markdown)?;
36    println!("Converted {:?} presentation to output.md", container.format());
37    Ok(())
38}
examples/manual_image_extraction.rs (line 26)
14fn main() -> Result<()> {
15    let args: Vec<String> = env::args().collect();
16    let Some(input_path) = args.get(1) else {
17        eprintln!(
18            "Usage: cargo run --example manual_image_extraction <presentation.pptx|presentation.odp>"
19        );
20        return Ok(());
21    };
22
23    println!("Processing presentation: {input_path}");
24
25    // Use the config builder to build your config
26    let config = ParserConfig::builder()
27        .extract_images(true)
28        .compress_images(true)
29        .quality(75)
30        .image_handling_mode(ImageHandlingMode::Manually)
31        .build();
32
33    let mut container = PresentationContainer::open(Path::new(input_path), config)?;
34
35    // Parse all slides
36    let slides = container.parse_all()?;
37
38    println!("Found {} slides", slides.len());
39
40    // create a new Markdown file
41    let mut md_file = File::create("output.md")?;
42
43    // Create output directory
44    let output_dir = "extracted_images";
45    fs::create_dir_all(output_dir)?;
46
47    let mut image_count = 1;
48
49    // Convert each slide to Markdown and save
50    for slide in slides {
51        writeln!(md_file, "{}", slide.convert_to_md()?)?;
52
53        // Manually load the base64 encoded image strings from the slide
54        if let Some(images) = slide.load_images_manually() {
55            for image in images {
56                // Decode the base64 strings back to raw image data
57                let image_data = general_purpose::STANDARD
58                    .decode(image.base64_content.clone())
59                    .expect("parser returned invalid base64 image data");
60
61                // Extract image extension if the image is not compressed, otherwise its always `.jpg`
62                let ext = if slide.config.compress_images {
63                    "jpg".to_string()
64                } else {
65                    slide.get_image_extension(&image.img_ref.target.clone())
66                };
67
68                // Construct a unique file name
69                let file_name = format!(
70                    "slide{}_image{}_{}",
71                    slide.slide_number, image_count, &image.img_ref.id
72                );
73
74                // Save the image
75                let output_path = format!("{}/{}.{}", output_dir, &file_name, ext);
76                fs::write(&output_path, image_data)?;
77                println!("Saved image to {}", output_path);
78
79                // Write the image data into the Markdown file
80                writeln!(
81                    md_file,
82                    "![{}](data:image/{};base64,{})",
83                    file_name, ext, image.base64_content
84                )?;
85
86                image_count += 1;
87            }
88        }
89    }
90
91    println!("All slides converted successfully!");
92
93    Ok(())
94}
examples/performance_test.rs (line 103)
70fn main() -> Result<()> {
71    // Get the PPTX file path and optional iteration count from command line arguments
72    let args: Vec<String> = env::args().collect();
73    let pptx_path = if args.len() > 1 {
74        &args[1]
75    } else {
76        eprintln!(
77            "Usage: cargo run --example performance_test <presentation.pptx|presentation.odp> [iterations]"
78        );
79        return Ok(());
80    };
81
82    let iterations = if args.len() > 2 {
83        args[2].parse().unwrap_or(5)
84    } else {
85        10 // Default to 10 iterations
86    };
87
88    println!(
89        "Performance testing with {} iterations on: {}",
90        iterations, pptx_path
91    );
92
93    // =========== Single-threaded Approach ===========
94    let mut single_thread_bench = Benchmark::new("Single-threaded parsing");
95
96    let mut total_slides = 0;
97
98    for i in 0..iterations {
99        println!("\nIteration {} (Single-threaded)", i + 1);
100
101        // Measure container creation
102        let mut container = single_thread_bench.measure(|| {
103            let config = ParserConfig::builder().extract_images(true).build();
104            PresentationContainer::open(Path::new(pptx_path), config)
105                .expect("Failed to open presentation")
106        });
107
108        // Measure parsing
109        let slides =
110            single_thread_bench.measure(|| container.parse_all().expect("Failed to parse slides"));
111        println!("  Found {} slides in the presentation", slides.len());
112
113        // Measure conversion
114        let _md_content = single_thread_bench.measure(|| {
115            slides
116                .iter()
117                .filter_map(|slide| slide.convert_to_md().ok())
118                .collect::<Vec<String>>()
119        });
120
121        total_slides += slides.len();
122    }
123
124    single_thread_bench.report();
125    println!(
126        "Average slides per presentation: {}",
127        total_slides / iterations
128    );
129
130    // =========== Single-threaded Streamed Approach ===========
131    let mut single_thread_streamed_bench = Benchmark::new("Single-threaded streamed parsing");
132
133    total_slides = 0;
134
135    for i in 0..iterations {
136        println!("\nIteration {} (Single-threaded streamed)", i + 1);
137
138        // Measure container creation
139        let mut container = single_thread_streamed_bench.measure(|| {
140            let config = ParserConfig::builder().extract_images(true).build();
141            PresentationContainer::open(Path::new(pptx_path), config)
142                .expect("Failed to open presentation")
143        });
144
145        // Measure slide processing (including parsing and conversion)
146        let slides_processed = single_thread_streamed_bench.measure(|| {
147            let mut processed = 0;
148
149            // Process slides one by one using the iterator
150            for slide_result in container.iter_slides() {
151                match slide_result {
152                    Ok(slide) => {
153                        // Konvertiere den Slide zu Markdown
154                        let _md_content = slide.convert_to_md();
155                        processed += 1;
156                    }
157                    Err(e) => {
158                        eprintln!("Error processing slide: {:?}", e);
159                    }
160                }
161            }
162
163            processed
164        });
165
166        println!("  Processed {} slides", slides_processed);
167        total_slides += slides_processed;
168    }
169
170    single_thread_streamed_bench.report();
171    println!(
172        "Average slides per presentation: {}",
173        total_slides / iterations
174    );
175
176    // =========== Optimized Multi-threaded Approach ===========
177    let mut optimized_multi_thread_bench = Benchmark::new("Optimized Multi-threaded parsing");
178
179    total_slides = 0;
180
181    for i in 0..iterations {
182        println!("\nIteration {} (Optimized Multi-threaded)", i + 1);
183
184        // Container öffnen mit der gewünschten Konfiguration
185        let mut container = optimized_multi_thread_bench.measure(|| {
186            let config = ParserConfig::builder().extract_images(true).build();
187            PresentationContainer::open(Path::new(pptx_path), config)
188                .expect("Failed to open presentation")
189        });
190
191        let slides = optimized_multi_thread_bench.measure(|| {
192            container
193                .parse_all_multi_threaded()
194                .expect("Failed to parse slides")
195        });
196
197        println!("  Successfully processed {} slides", slides.len());
198
199        // Parallel zu Markdown konvertieren (bleibt unverändert)
200        let _md_content = optimized_multi_thread_bench.measure(|| {
201            slides
202                .par_iter()
203                .filter_map(|slide| slide.convert_to_md().ok())
204                .collect::<Vec<String>>()
205        });
206
207        total_slides += slides.len();
208    }
209
210    optimized_multi_thread_bench.report();
211    println!(
212        "Average slides per presentation: {}",
213        total_slides / iterations
214    );
215
216    // =========== Performance Comparison ===========
217    if !single_thread_bench.results.is_empty()
218        && !single_thread_streamed_bench.results.is_empty()
219        && !optimized_multi_thread_bench.results.is_empty()
220    {
221        let single_avg: Duration = single_thread_bench.results.iter().sum::<Duration>()
222            / single_thread_bench.results.len() as u32;
223        let single_streamed_avg: Duration = single_thread_streamed_bench
224            .results
225            .iter()
226            .sum::<Duration>()
227            / single_thread_streamed_bench.results.len() as u32;
228        let optimized_multi_avg: Duration = optimized_multi_thread_bench
229            .results
230            .iter()
231            .sum::<Duration>()
232            / optimized_multi_thread_bench.results.len() as u32;
233
234        println!("\nPerformance Comparison");
235        println!("=====================");
236        println!("Single-threaded average: {:?}", single_avg);
237        println!(
238            "Single-threaded streaming average: {:?}",
239            single_streamed_avg
240        );
241        println!(
242            "Optimized multi-threaded average: {:?}",
243            optimized_multi_avg
244        );
245
246        // Compare single-threaded vs single-threaded streaming
247        if single_avg > single_streamed_avg {
248            let speedup = single_avg.as_secs_f64() / single_streamed_avg.as_secs_f64();
249            println!(
250                "Single-threaded streaming is {:.2}x faster than single-threaded",
251                speedup
252            );
253        } else {
254            let slowdown = single_streamed_avg.as_secs_f64() / single_avg.as_secs_f64();
255            println!(
256                "Single-threaded streaming is {:.2}x slower than single-threaded",
257                slowdown
258            );
259        }
260
261        // Compare single-threaded vs optimized multithreaded
262        if single_avg > optimized_multi_avg {
263            let speedup = single_avg.as_secs_f64() / optimized_multi_avg.as_secs_f64();
264            println!(
265                "Optimized multi-threaded is {:.2}x faster than single-threaded",
266                speedup
267            );
268        } else {
269            let slowdown = optimized_multi_avg.as_secs_f64() / single_avg.as_secs_f64();
270            println!(
271                "Optimized multi-threaded is {:.2}x slower than single-threaded",
272                slowdown
273            );
274        }
275
276        // Compare single-threaded streaming vs optimized multithreaded
277        if single_streamed_avg > optimized_multi_avg {
278            let speedup = single_streamed_avg.as_secs_f64() / optimized_multi_avg.as_secs_f64();
279            println!(
280                "Optimized multi-threaded is {:.2}x faster than single-threaded streaming",
281                speedup
282            );
283        } else {
284            let slowdown = optimized_multi_avg.as_secs_f64() / single_streamed_avg.as_secs_f64();
285            println!(
286                "Optimized multi-threaded is {:.2}x slower than single-threaded streaming",
287                slowdown
288            );
289        }
290
291        // Determine the overall fastest approach
292        let fastest_approach = if single_avg <= single_streamed_avg
293            && single_avg <= optimized_multi_avg
294        {
295            "Single-threaded"
296        } else if single_streamed_avg <= single_avg && single_streamed_avg <= optimized_multi_avg {
297            "Single-threaded streaming"
298        } else {
299            "Optimized multi-threaded"
300        };
301
302        println!(
303            "\nOverall result: {} approach is the fastest for this workload.",
304            fastest_approach
305        );
306    }
307
308    Ok(())
309}

Trait Implementations§

Source§

impl Clone for ParserConfig

Source§

fn clone(&self) -> ParserConfig

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ParserConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ParserConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<R, P> ReadPrimitive<R> for P
where R: Read + ReadEndian<P>, P: Default,

Source§

fn read_from_little_endian(read: &mut R) -> Result<Self, Error>

Read this value from the supplied reader. Same as ReadEndian::read_from_little_endian().
Source§

fn read_from_big_endian(read: &mut R) -> Result<Self, Error>

Read this value from the supplied reader. Same as ReadEndian::read_from_big_endian().
Source§

fn read_from_native_endian(read: &mut R) -> Result<Self, Error>

Read this value from the supplied reader. Same as ReadEndian::read_from_native_endian().
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.