pub type Postprocessor<'f> = dyn Fn(&mut Context, &mut MarkdownEvents<'_>) -> PostprocessorResult + Send + Sync + 'f;
Expand description

A post-processing function that is to be called after an Obsidian note has been fully parsed and converted to regular markdown syntax.

Postprocessors are called in the order they’ve been added through Exporter::add_postprocessor just before notes are written out to their final destination. They may be used to achieve the following:

  1. Modify a note’s Context, for example to change the destination filename or update its Frontmatter (see Context::frontmatter).
  2. Change a note’s contents by altering MarkdownEvents.
  3. Prevent later postprocessors from running (PostprocessorResult::StopHere) or cause a note to be skipped entirely (PostprocessorResult::StopAndSkipNote).

Postprocessors and embeds

Postprocessors normally run at the end of the export phase, once notes have been fully parsed. This means that any embedded notes have been resolved and merged into the final note already.

In some cases it may be desirable to change the contents of these embedded notes before they are inserted into the final document. This is possible through the use of Exporter::add_embed_postprocessor. These “embed postprocessors” run much the same way as regular postprocessors, but they’re run on the note that is about to be embedded in another note. In addition:

  • Changes to context carry over to later embed postprocessors, but are then discarded. This means that changes to frontmatter do not propagate to the root note for example.
  • PostprocessorResult::StopAndSkipNote prevents the embedded note from being included (it’s replaced with a blank document) but doesn’t affect the root note.

It’s possible to pass the same functions to Exporter::add_postprocessor and Exporter::add_embed_postprocessor. The Context::note_depth method may be used to determine whether a note is a root note or an embedded note in this situation.

Examples

Update frontmatter

This example shows how to make changes a note’s frontmatter. In this case, the postprocessor is defined inline as a closure.

use obsidian_export::serde_yaml::Value;
use obsidian_export::{Exporter, PostprocessorResult};

let mut exporter = Exporter::new(source, destination);

// add_postprocessor registers a new postprocessor. In this example we use a closure.
exporter.add_postprocessor(&|context, _events| {
    // This is the key we'll insert into the frontmatter. In this case, the string "foo".
    let key = Value::String("foo".to_string());
    // This is the value we'll insert into the frontmatter. In this case, the string "bar".
    let value = Value::String("baz".to_string());

    // Frontmatter can be updated in-place, so we can call insert on it directly.
    context.frontmatter.insert(key, value);

    // This return value indicates processing should continue.
    PostprocessorResult::Continue
});

exporter.run().unwrap();

Change note contents

In this example a note’s markdown content is changed by iterating over the MarkdownEvents and changing the text when we encounter a text element.

Instead of using a closure like above, this example shows how to use a separate function definition.

/// This postprocessor replaces any instance of "foo" with "bar" in the note body.
fn foo_to_bar(context: &mut Context, events: &mut MarkdownEvents) -> PostprocessorResult {
    for event in events.iter_mut() {
        if let Event::Text(text) = event {
            *event = Event::Text(CowStr::from(text.replace("foo", "bar")))
        }
    }
    PostprocessorResult::Continue
}

exporter.add_postprocessor(&foo_to_bar);