Mdbook preprocessor: how to apply classes to markdown items?

I'd like to be able to put markdown items within a <div class=...> container, or just to tag them with custom classes.

To achieve that, I'm trying to update mdbook-classy to mdbook 0.5.4, but I know little about how mdbook works internally. I've succeeded to make it compile and work again, except for the <div class=...> added by this preprocessor around the element that follows.

For example,

{:.red big}
You can write **Markdown** paragraphs

should produce this (with or without the paragraph tags):

<div class="red big">
<p>You can write <strong>Markdown</strong> paragraphs</p>
</div>

(links to the code at the end of the post)

Below are two attempts that failed:


(1) If I keep the code's original idea, inserting an Event::Html before and another one after, respectively for <div> and </div>, I get this HTML at the end: (plus warnings about unclosed div tags)

<p>to get this:
<div class="red big"></div>
</p>
<p>You can write <strong>Markdown</strong> paragraphs</p>

even though the modified sequence of events seems fine before they're sent to pulldown_cmark_to_cmark::cmark(...):

Start(Paragraph)
 Text(Borrowed("to get this:"))
End(Paragraph)
Html(Boxed("<div class=\"red big\">\n"))
Start(Paragraph)
 Text(Borrowed("You can write "))
 Start(Strong)
  Text(Borrowed("Markdown"))
 End(Strong)
 Text(Borrowed(" paragraphs with red text anytime you want."))
End(Paragraph)
Html(Borrowed("</div>\n"))

(2) If I enclose the divs between Tag::HtmlBlocks (commented out in the fork) or Tag::Paragraphs, the div tags surround the text correctly, but that text isn't evaluated by mdbook any more[1], as if it was a code block: (this is with the extra Paragraph tag)

<p>to get this:</p>
<div class="red big">
You can write **Markdown** paragraphs with red text anytime you want.</div>

(you can see the strong tags not being applied around "Markdown" on the last line)

even though the sequence looks fine, too:

Start(Paragraph)
 Text(Borrowed("to get this:"))
End(Paragraph)
Start(Paragraph)
 Html(Boxed("<div class=\"red big\">\n"))
 Start(Paragraph)
  Text(Borrowed("You can write "))
  Start(Strong)
   Text(Borrowed("Markdown"))
  End(Strong)
  Text(Borrowed(" paragraphs with red text anytime you want."))
 End(Paragraph)
 Html(Borrowed("</div>\n"))
End(Paragraph)

Actually, it's possible to achieve the same result as above with the standard mdbook by enclosing items between <div> and </div>. And that's what the preprocessor above outputs, in fact, so it doesn't seem to be the correct way to preprocess the markdown.


In conclusion, I don't see any other way to enclose items in a container or to "mark" them with class names. Is it possible at all?

EDIT: I'm starting to think it requires a combination of preprocessor for the custom syntax and backend to transform something into the final <div class=...> ... </div>, without mdbook knowing the items in-between are in a div container. Though I don't know if it's possible to append that after the default renderer (e.g. transforming HTML comments to div tags)...

Links:


  1. EDIT: which is apparently in line with the Markdown specs ↩︎

It doesn't look possible to chain backend renderers, so the idea of inserting comments with the preprocessor and transforming them to HTML at the output won't work. Or I'd have to re-process the HTML output with another executable, but that's not very handy.

Unless I'm missing something, it's not really possible to apply a class to markdown items without modifying mdbook itself, then. I find that strange.

I not so much. Markdown has the very powerful escape hatch of inline html and I have used that mechanism for styling before. Not sure if supporting

{:.red big}
You can write **Markdown** paragraphs

in favour of

<div class="red big">
You can write **Markdown** paragraphs
</div>

is really worth the trouble.

I have seen this {:.red big} syntax before though. But whether there exist an actual markdown flavour supporting this or if this was only a proposal eludes my mind right now.

To clarify, that below is not what I want to achieve, since it doesn't transform the inner Markdown. That's the whole point of my question.

<div class="red big">
You can write **Markdown** paragraphs
</div>

What I'd like to get is this:

<div class="red big">
<p>You can write <strong>Markdown</strong> paragraphs</p>
</div>

For example, I'd like to apply different custom styles to quotes.

It does, if you separate inline HTML with blank lines:

<div class="red big">

You can write **Markdown** paragraphs

</div>

will be turned into what you expect (modulo the same blank lines).

The CommonMark homepage has an online playground for the reference implementation. I created a snippet to illustrate the example:

Heh. That's unexpected. The initial version did put an extra line, but I suppose it wasn't expressed correctly.

Thanks a lot! I'll try to use that trick.

EDIT:

I needed to put an extra EOL after both tags:

  • Before:
    Event::InlineHtml(format!("<div class=\"{class}\">\n\n").into())
    
  • After:
    Event::InlineHtml("\n</div>\n\n".into())
    

Or, of course, I could simply put the <div> manually. I don't really mind.

EDIT 2:

I misunderstood the Markdown specification. What's interpreted as raw HTML isn't the Markdown between the two inserted tags, but what follows any tag until a blank line (the actual conditions are more complex, but this is true in the case of div). So following each tag with a blank line allows to use Markdown inside the two extra tags.

mdbook-admonish is worth a look for the wrap-in-div pattern. for custom class markers like {:.red big} you usually need a preprocessor that walks the pulldown-cmark events and inserts the open/close tags around the next block, classy-style. updating classy for 0.5.x is mostly matching the new preprocessor hook signatures, the event walk stays the same

I've already made a PR with the 0.5.x update, since I had it. It's limited to simple paragraphs, though; it doesn't support anything else like quotes, blocks, and so on. For that, it'd need to be adapted. The main loop is a little convoluted, to be honest.

I briefly looked at admonish, but it's not compatible with mdbook 0.5 either, and I don't want to have to redefine all the styles anyway. For me, simply inserting div tags is perfectly fine; I need only a few of them. :slight_smile: