Consistent behavior of markdownify for single/multiple paragaphs

Hi!

Is there a good/recommended way to force consistent behavior (w.r.t. wrapping in <p>...</p> tags) of markdownify, independent of whether its called on a single paragraph or multiple paragraphs of input?

Right now (in v0.29) single paragraphs are not enclosed in <p>...</p>, while if there are multiple paragraphs each of them is. I’m open to any suggestions on how to deal with this when writing shortcodes not knowing whether to expect one or multiple paragraphs.

I read a couple of posts/discussions about this behavior, but didn’t find a solution.

Thanks!

I agree. I’d prefer that paragraphs are always enclosed in <p> tags for consistency.

(I don’t know of a solution, sorry.)

I suggested we add a blocks keyword or something, but I was voted down; so the current behaviour is that we keep the Ps if more than one. I mainly use this for titles etc. so that is what I want.

We should improve on this, but you could to something like this (not tested):

{{ $markdown := "Hello **world**" | markdownify }}
{{ $hasP := strings.Contains $markdown "<p>" }}
{{ if not $hasP }}<p>{{ end }}
{{ $markdown }}
{{ if not $hasP }}</p>{{ end }}

If you pull the above into a partial you could reuse it.

You were right in my opinion. It was a tight vote and democracy can be a *****. I missed the voting. What seem obvious to me is that there must be a way to handle this situation without workarounds.

Talking about the workaround, testing only for a paragraph presence is not enough in some edge cases. What about if the provided markdown is something like this:

# Title

| col | col |
| --- | --- |
| 1   | 2   |

No paragraph, so it will be wrapped in a p tag. A more robust solution would be to use findRE, testing for more tags (using ‘^<(p|h[0-9]+|…)>’).

I still think this would fit better in your “blocks” functionality. I would just suggest a more self explanatory name, like blockify (if I remember well you throw this name in the discussion) or forceBlock (like “add a block, a paragraph, if not already wrapped”).

This function would be useful in more cases, not only for markdownify, but for others parser or even for raw HTML. It’s easy way to create flexible content placeholders.

In case anyone needs a workaround which handles most of the cases, I offer the following partial (called markdownify-block.html in my project):

{{/*
    The 'markdownify' function removes the surrounding <p> and </p> tags
    if and only if the result is a single-line/block.  But this means that
    the results are inconsistent: sometimes the result is flow-level (for
    simple cases), and sometimes it's block-level (for multi-line or complex
    cases).

    There's no "leave the wrapping <p> in place" option to markdownify, so we
    have to work around it ourselves.  We attempt to recognize all possible
    block-level tags to know whether the result "looks like blocks" or not, and
    then add a wrapping <p> if we think it's needed.

    To use: {{ partial "markdownify-block" .value.with.markdown }}
*/ -}}
{{- $raw := (markdownify . | chomp) -}}
{{- /*
    Any of the following tags mean we should consider this an already-
    blockified result
    (https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements):

        <address>
        <article>
        <aside>
        <blockquote>
        <canvas>
        <dd>
        <div>
        <dl>
        <dt>
        <fieldset>
        <figcaption>
        <figure>
        <footer>
        <form>
        <h1>, <h2>, <h3>, <h4>, <h5>, <h6>
        <header>
        <hgroup>
        <hr>
        <li>
        <main>
        <nav>
        <noscript>
        <ol>
        <output>
        <p>
        <pre>
        <section>
        <table>
        <tfoot>
        <ul>
        <video>

    Markdownify can't generate all of these, but it's safer to assume it could.
*/ -}}
{{- $block := findRE "(?is)^<(?:address|article|aside|blockquote|canvas|dd|div|dl|dt|fieldset|figcaption|figure|footer|form|h(?:1|2|3|4|5|6)|header|hgroup|hr|li|main|nav|noscript|ol|output|p|pre|section|table|tfoot|ul|video)\\b" $raw 1 -}}
{{- /*
    The variable $block is now either an empty (length zero) list, or contains
    the beginning of the block tag that starts the generated HTML.  If it's
    empty, we know we need to reinstate the <p></p> that markdownify removed.

    There's no "length" function, but we can abuse "range" to get the same
    effect.
*/ -}}
{{range $block }}{{ $raw }}{{ else }}<p>{{ $raw }}</p>{{ end -}}

This is working great (for me/us)… in those contexts where we want to ensure that the markdown results in a block-level element, we just call {{ partial "markdownify-block" .contextValue }} instead of {{ markdownify .contextValue }}.

2 Likes

Hi Jared, nice workaround!

There is additional condition that could be added there to check that the $raw is not empty:

{{ if or $block (not $raw) }}{{ $raw }}{{ else }}<p>{{ $raw }}</p>{{ end -}}

This will ensure that when the input text is empty, it will not wrap it with empty <p></p>.

1 Like