texlive[70856] Master/texmf-dist: markdown (4apr24)

commits+karl at tug.org commits+karl at tug.org
Thu Apr 4 22:36:41 CEST 2024


Revision: 70856
          https://tug.org/svn/texlive?view=revision&revision=70856
Author:   karl
Date:     2024-04-04 22:36:41 +0200 (Thu, 04 Apr 2024)
Log Message:
-----------
markdown (4apr24)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/generic/markdown/CHANGES.md
    trunk/Master/texmf-dist/doc/generic/markdown/README.md
    trunk/Master/texmf-dist/doc/generic/markdown/VERSION
    trunk/Master/texmf-dist/doc/generic/markdown/markdown.html
    trunk/Master/texmf-dist/doc/generic/markdown/markdown.pdf
    trunk/Master/texmf-dist/scripts/markdown/markdown-cli.lua
    trunk/Master/texmf-dist/source/generic/markdown/markdown.dtx
    trunk/Master/texmf-dist/tex/generic/markdown/markdown.tex
    trunk/Master/texmf-dist/tex/luatex/markdown/markdown.lua

Modified: trunk/Master/texmf-dist/doc/generic/markdown/CHANGES.md
===================================================================
--- trunk/Master/texmf-dist/doc/generic/markdown/CHANGES.md	2024-04-03 23:45:59 UTC (rev 70855)
+++ trunk/Master/texmf-dist/doc/generic/markdown/CHANGES.md	2024-04-04 20:36:41 UTC (rev 70856)
@@ -2,6 +2,41 @@
 
 ## 3.5.0
 
+## 3.4.3
+
+Fixes:
+
+- Remove trailing paragraph/interblock separators in right-open slice
+  intervals. (#408, #419)
+- Do not misinterpret bracketed e-mails as citations. (#424, #426,
+  sponsored by @istqborg)
+- Comply with CommonMark 0.31.2. (#416, 40b516ee, de8d137d, #432,
+  contributed by @lostenderman)
+- Do not end a paragraph before a `:::` in fenced divs.
+  (#407, lostenderman/markdown#157, #427, #428, lostenderman/markdown#158,
+   #431, contributed by @lostenderman)
+
+Documentation:
+
+- Add slides from the defense of projects MUNI/33/1654/2022 and
+  MUNI/33/1658/2022 to `README.md`. (49f01ccf)
+- Remove `<mroot>` from MathML in the user manual. (#420, #422,
+  contributed by @quark67)
+
+Contributed Software:
+
+- Make the documentation of contributions more detailed. (3f928162)
+
+Docker:
+
+- Add support for TeX Live 2024.
+  (#411, bafbb164, #413, 04957eee, 16000aa4, #425, 9549a5d8, 8f8e1315,
+   844beafc, cf592003)
+
+Continuous Integration:
+
+- Style-check tabs and trailing spaces in `markdown.dtx`. (a0c941ca)
+
 ## 3.4.2 (2024-03-09)
 
 Fixes:
@@ -119,7 +154,7 @@
 - Remove the `options.cacheDir` directory if it is empty after conversion.
   (5cfcea6)
 - Allow tables inside lists. (#368, #371, contributed by @lostenderman,
-  sponsored by ISTQB)
+  sponsored by @istqborg)
 - Check that shell access is unrestricted before attempting shell escape.
   (#365, witiko/lt3luabridge#22, latex3/latex3#1339, #372)
 
@@ -152,7 +187,7 @@
 Fixes:
 
 - Make the `import` LaTeX option correctly handle recursive imports.
-  (68c7a2f5, danopolan/istqb_latex#87)
+  (68c7a2f5, istqborg/istqb_shared_documents#87)
 - Support attributes on multi-line setext headings.
   (#315, #355, #356, contributed by @lostenderman)
 - Correctly process the combination of Lua options `fancyLists` and
@@ -160,8 +195,8 @@
 - Properly parse emphasis at line endings in headings.
   (#358, #360, contributed by @lostenderman)
 - Fix fancy lists that use roman numerals as markers.
-  (danopolan/istqb_latex#87, #359, #364,
-   contributed by @lostenderman, sponsored by ISTQB)
+  (istqborg/istqb_shared_documents#87, #359, #364,
+   contributed by @lostenderman, sponsored by @istqborg)
 
 Documentation:
 
@@ -232,7 +267,7 @@
 Fixes:
 
 - Correctly parse paragraphs with trailing spaces.
-  (danopolan/istqb_latex#77, #345, #347)
+  (istqborg/istqb_shared_documents#77, #345, #347)
 
 ## 3.0.0 (2023-08-25)
 

Modified: trunk/Master/texmf-dist/doc/generic/markdown/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/generic/markdown/README.md	2024-04-03 23:45:59 UTC (rev 70855)
+++ trunk/Master/texmf-dist/doc/generic/markdown/README.md	2024-04-04 20:36:41 UTC (rev 70856)
@@ -204,9 +204,10 @@
 6. Talks:
     - [Five Years of Markdown in LaTeX: What, Why, How, and Whereto][pv212-fall2020] (in Czech),
     - [Markdown 2.10.0: LaTeX Themes & Snippets, Two Flavors of Comments, and LuaMetaTeX][tb131-video] ([slides][tb131-slides]),
-    - [A Self-Publisher's Take on Markdown and TeX][tb134-01-video] ([slides][tb134-01-slides]), and
-    - [A Gentle Introduction to Markdown for Writers][tb134-02-video] ([slides][tb134-02-slides], [example][tb134-02-example]), and
-    - [Markdown 3: What's New, What's Next?][tb137-video] ([slides][tb137-slides]).
+    - [A Self-Publisher's Take on Markdown and TeX][tb134-01-video] ([slides][tb134-01-slides]),
+    - [A Gentle Introduction to Markdown for Writers][tb134-02-video] ([slides][tb134-02-slides], [example][tb134-02-example]),
+    - [Markdown 3: What's New, What's Next?][tb137-video] ([slides][tb137-slides]), and
+    - An Implementation of the CommonMark Standard and new Syntax Extensions to the Markdown Package for TeX ([slides][gencur-defense-slides]).
 7. Theses:
     - [Generic TeX Writer for the Pandoc Document Converter][thesis-umhg5]
     - [An implementation of the CommonMark standard into the Markdown package for TeX][thesis-r7z7l]
@@ -248,6 +249,8 @@
 
  [pv212-fall2020]: https://is.muni.cz/elearning/io/?qurl=%2Fel%2Ffi%2Fpodzim2020%2FPV212%2Findex.qwarp;prejit=5595952
 
+ [gencur-defense-slides]: https://docs.google.com/presentation/d/e/2PACX-1vRbgJZ-UJlj5WMOjgWnq0BeNWKdA9ZhFqCKajhCjXtv3OarSKmojl5-X8tDp1ivnKtujuyEDmD2z_Z0/pub "An Implementation of the CommonMark Standard and new Syntax Extensions to the Markdown Package for TeX"
+
  [install]:  https://mirrors.ctan.org/macros/generic/markdown/markdown.html#installation "Markdown Package User Manual"
  [liantze]:  http://liantze.penguinattack.org/                                           "Rants from the Lab"
  [overleaf]: https://www.overleaf.com/                                                   "Overleaf: Real-time Collaborative Writing and Publishing Tools with Integrated PDF Preview"
@@ -271,16 +274,17 @@
 | [<img width="150" src="https://www.fi.muni.cz/images/fi-logo.png">][fimu] | I gratefully acknowledge the funding from the [Faculty of Informatics][fimu] at the [Masaryk University][mu] in Brno, Czech Republic, for the development of the Markdown package in projects [MUNI/33/12/2015][], [MUNI/33/1784/2020][], [MUNI/33/0776/2021][], [MUNI/33/1654/2022][], and [MUNI/33/1658/2022][]. |
 | [<img width="150" src="https://cdn.overleaf.com/img/ol-brand/overleaf_og_logo.png">][overleaf] | Extensive user documentation for the Markdown package was kindly written by [Lian Tze Lim][liantze] and published by [Overleaf][]. |
 | [<img width="150" src="https://pbs.twimg.com/profile_images/1004769879319334912/6Bh1UthD.jpg">][omedym] | Support for content slicing (Lua options [`shiftHeadings`][option-shift-headings] and [`slice`][option-slice]) and pipe tables (Lua options [`pipeTables`][option-pipe-tables] and [`tableCaptions`][option-table-captions]) was graciously sponsored by [David Vins][dvins] and [Omedym][]. |
-| [<img width="150" src="https://www.istqb.org/static/istqb-logo-1b043e800a580724ad223567f9ea57c0.png">][istqb] | Fixes for issues [#359][issue-359] and [#368][issue-368] were graciously sponsored by the [International Software Testing Qualifications Board (ISTQB)][istqb]. |
+| [<img width="150" src="https://www.istqb.org/static/istqb-logo-1b043e800a580724ad223567f9ea57c0.png">][istqb] | Fixes for issues [#359][issue-359], [#368][issue-368], and [#424][issue-424] were graciously sponsored by the [International Software Testing Qualifications Board (ISTQB)][istqb]. |
 
  [dvins]:  https://github.com/dvins             "David Vins"
  [fimu]:   https://www.fi.muni.cz/index.html.en "Faculty of Informatics, Masaryk University"
- [ISTQB]:  https://www.istqb.org/               "International Software Testing Qualifications Board"
+ [ISTQB]:  https://github.com/istqborg          "ISTQB.ORG: Official ISTQB® GitHub account"
  [mu]:     https://www.muni.cz/en               "Masaryk University"
  [Omedym]: https://www.omedym.com/              "Omedym"
 
  [issue-359]: https://github.com/witiko/markdown/issues/359 "First item of a fancy list forms a separate list"
  [issue-368]: https://github.com/witiko/markdown/issues/368 "Tables nested in list items have empty lines"
+ [issue-424]: https://github.com/witiko/markdown/issues/424 "E-mail addresses are incorrectly interpreted as bracketed citations"
 
  [option-pipe-tables]:    https://mirrors.ctan.org/macros/generic/markdown/markdown.html#pipe-tables          "Markdown Package User Manual"
  [option-shift-headings]: https://mirrors.ctan.org/macros/generic/markdown/markdown.html#option-shiftheadings "Markdown Package User Manual"

Modified: trunk/Master/texmf-dist/doc/generic/markdown/VERSION
===================================================================
--- trunk/Master/texmf-dist/doc/generic/markdown/VERSION	2024-04-03 23:45:59 UTC (rev 70855)
+++ trunk/Master/texmf-dist/doc/generic/markdown/VERSION	2024-04-04 20:36:41 UTC (rev 70856)
@@ -1 +1 @@
-3.4.2-0-ga45cf0ed (2024-03-09)
+3.4.3-0-ge2c6be1a (2024-04-04)

Modified: trunk/Master/texmf-dist/doc/generic/markdown/markdown.html
===================================================================
--- trunk/Master/texmf-dist/doc/generic/markdown/markdown.html	2024-04-03 23:45:59 UTC (rev 70855)
+++ trunk/Master/texmf-dist/doc/generic/markdown/markdown.html	2024-04-04 20:36:41 UTC (rev 70856)
@@ -93,7 +93,7 @@
 <header id="title-block-header">
 <h1 class="title">Markdown Package User Manual</h1>
 <p class="author">Vít Starý Novotný</p>
-<p class="date">3.4.2-0-ga45cf0ed 2024-03-09</p>
+<p class="date">3.4.3-0-ge2c6be1a 2024-04-04</p>
 </header>
 <nav id="TOC" role="doc-toc">
 <ul>
@@ -604,8 +604,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt{-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <p>Invoking pdfTeX should have the same effect:</p>
 <div class="sourceCode" id="cb32"><pre
@@ -641,8 +641,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt{-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <p>In this case, we cannot use pdfTeX, because pdfTeX does not define
 the <code>\directlua</code> <span class="tex">T<sub>e</sub>X</span>
@@ -693,8 +693,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt{-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <p>Invoking pdfTeX should have the same effect:</p>
 <div class="sourceCode" id="cb38"><pre
@@ -737,8 +737,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt{-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <p>Invoking pdfTeX should have the same effect:</p>
 <div class="sourceCode" id="cb41"><pre
@@ -785,8 +785,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt{-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <p>Invoking pdfTeX should have the same effect:</p>
 <div class="sourceCode" id="cb44"><pre
@@ -828,8 +828,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt{-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <h2 data-number="2.2" id="options"><span
 class="header-section-number">2.2</span> Options</h2>
@@ -4679,8 +4679,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt {-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <h5 class="unnumbered" id="lua-cli-example-10">Lua CLI Example</h5>
 <p>Using a text editor, create a text document named
@@ -4718,8 +4718,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt {-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <h5 class="unnumbered" id="plain-tex-example-11">Plain <span
 class="tex">T<sub>e</sub>X</span> Example</h5>
@@ -4746,8 +4746,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt {-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <h5 class="unnumbered" id="latex-example-26"><span
 class="latex">L<sup>a</sup>T<sub>e</sub>X</span> Example</h5>
@@ -4774,8 +4774,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt {-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <h5 class="unnumbered" id="context-example-17">Con<span
 class="tex">T<sub>e</sub>X</span>t Example</h5>
@@ -4802,8 +4802,8 @@
 contain the following text:</p>
 <blockquote>
 <p>$\sqrt {-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <h4 data-number="2.2.1.35" id="option-inlinecodeattributes"><span
 class="header-section-number">2.2.1.35</span> Option
@@ -9109,8 +9109,8 @@
 <blockquote>
 <p>$\sqrt{-1}$ *equals* $i$.</p>
 <p>$\sqrt{-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <h5 class="unnumbered" id="latex-example-63"><span
 class="latex">L<sup>a</sup>T<sub>e</sub>X</span> Example</h5>
@@ -9145,8 +9145,8 @@
 <blockquote>
 <p>$\sqrt{-1}$ *equals* $i$.</p>
 <p>$\sqrt{-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <h5 class="unnumbered" id="context-example-40">Con<span
 class="tex">T<sub>e</sub>X</span>t Example</h5>
@@ -9177,8 +9177,8 @@
 <blockquote>
 <p>$\sqrt{-1}$ *equals* $i$.</p>
 <p>$\sqrt{-1}$ <em>equals</em> $i$.</p>
-<p><math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
-<em>equals</em> <math><mi>i</mi></math>.</p>
+<p><math><msqrt><mo>−</mo><mn>1</mn></msqrt></math> <em>equals</em>
+<math><mi>i</mi></math>.</p>
 </blockquote>
 <h4 data-number="2.3.1.8"
 id="code-span-attribute-context-renderers"><span

Modified: trunk/Master/texmf-dist/doc/generic/markdown/markdown.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/scripts/markdown/markdown-cli.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/markdown/markdown-cli.lua	2024-04-03 23:45:59 UTC (rev 70855)
+++ trunk/Master/texmf-dist/scripts/markdown/markdown-cli.lua	2024-04-04 20:36:41 UTC (rev 70856)
@@ -58,7 +58,7 @@
 -- those in the standard .ins files.
 --
 local metadata = {
-    version   = "3.4.2-0-ga45cf0ed",
+    version   = "3.4.3-0-ge2c6be1a",
     comment   = "A module for the conversion from markdown to plain TeX",
     author    = "John MacFarlane, Hans Hagen, Vít Starý Novotný",
     copyright = {"2009-2016 John MacFarlane, Hans Hagen",

Modified: trunk/Master/texmf-dist/source/generic/markdown/markdown.dtx
===================================================================
--- trunk/Master/texmf-dist/source/generic/markdown/markdown.dtx	2024-04-03 23:45:59 UTC (rev 70855)
+++ trunk/Master/texmf-dist/source/generic/markdown/markdown.dtx	2024-04-04 20:36:41 UTC (rev 70856)
@@ -104,7 +104,13 @@
 \newunicodechar{☒}{\markdownRendererTickedBox}
 \newunicodechar{⌛}{\markdownRendererHalfTickedBox}
 \newunicodechar{☐}{\markdownRendererUntickedBox}
-\newunicodechar{😉}{;-)}
+\usepackage{emoji}
+\makeatletter
+\@ifpackagelater{emoji}{2020/03/16}{
+  \newunicodechar{😉}{\emoji{winking-face}}
+}{
+  \newunicodechar{😉}{;-)}
+}
 \makeatother
 
 % Set up the catcodes.
@@ -1881,7 +1887,7 @@
 
 > \$\\sqrt{-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -1924,7 +1930,7 @@
 
 > \$\\sqrt{-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -1977,7 +1983,7 @@
 
 > \$\\sqrt{-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -5563,15 +5569,15 @@
 following text:
 
 > Term 1
-> 
+>
 > :   Definition 1
-> 
+>
 > Term 2 with *inline markup*
-> 
+>
 > :   Definition 2
-> 
+>
 >         { some code, part of Definition 2 }
-> 
+>
 >     Third paragraph of definition 2.
 
 ##### \Hologo{ConTeXt} Example {.unnumbered}
@@ -5605,15 +5611,15 @@
 following text:
 
 > Term 1
-> 
+>
 > :   Definition 1
-> 
+>
 > Term 2 with *inline markup*
-> 
+>
 > :   Definition 2
-> 
+>
 >         { some code, part of Definition 2 }
-> 
+>
 >     Third paragraph of definition 2.
 
 %</manual-options>
@@ -7157,7 +7163,7 @@
 
 > \$\\sqrt {-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -7199,7 +7205,7 @@
 
 > \$\\sqrt {-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -7231,7 +7237,7 @@
 
 > \$\\sqrt {-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -7263,7 +7269,7 @@
 
 > \$\\sqrt {-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -7295,7 +7301,7 @@
 
 > \$\\sqrt {-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -8066,20 +8072,20 @@
 following text:
 
 > Here is a note reference,[^1] and another.[^longnote]
-> 
+>
 > [^1]: Here is the note.
-> 
+>
 > [^longnote]: Here's one with multiple blocks.
-> 
+>
 >     Subsequent paragraphs are indented to show that they
 > belong to the previous note.
-> 
+>
 >         { some.code }
-> 
+>
 >     The whole paragraph can be indented, or just the
 >     first line.  In this way, multi-paragraph notes
 >     work like multi-paragraph list items.
-> 
+>
 > This paragraph won't be part of the note, because it
 > isn't indented.
 
@@ -8120,20 +8126,20 @@
 following text:
 
 > Here is a note reference,[^1] and another.[^longnote]
-> 
+>
 > [^1]: Here is the note.
-> 
+>
 > [^longnote]: Here's one with multiple blocks.
-> 
+>
 >     Subsequent paragraphs are indented to show that they
 > belong to the previous note.
-> 
+>
 >         { some.code }
-> 
+>
 >     The whole paragraph can be indented, or just the
 >     first line.  In this way, multi-paragraph notes
 >     work like multi-paragraph list items.
-> 
+>
 > This paragraph won't be part of the note, because it
 > isn't indented.
 
@@ -9123,13 +9129,13 @@
 following text:
 
 > The following list respects the numbers specified in the markup:
-> 
+>
 > 3. third item
 > 4. fourth item
 > 5. fifth item
 >
 > The following list does not respect the numbers specified in the markup:
-> 
+>
 > 1. third item
 > 2. fourth item
 > 3. fifth item
@@ -9168,13 +9174,13 @@
 following text:
 
 > The following list respects the numbers specified in the markup:
-> 
+>
 > 3. third item
 > 4. fourth item
 > 5. fifth item
 >
 > The following list does not respect the numbers specified in the markup:
-> 
+>
 > 1. third item
 > 2. fourth item
 > 3. fifth item
@@ -9812,7 +9818,7 @@
 > |   12  |  12  |    12   |    12  |
 > |  123  |  123 |   123   |   123  |
 > |    1  |    1 |     1   |     1  |
-> 
+>
 > : Demonstration of pipe table syntax.
 
 ##### \Hologo{ConTeXt} Example {.unnumbered}
@@ -9850,7 +9856,7 @@
 > |   12  |  12  |    12   |    12  |
 > |  123  |  123 |   123   |   123  |
 > |    1  |    1 |     1   |     1  |
-> 
+>
 > : Demonstration of pipe table syntax.
 
 %</manual-options>
@@ -10234,7 +10240,7 @@
 following text:
 
 > $E=mc^2$
-> 
+>
 > $$\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx$$
 
 ##### \LaTeX{} Example {.unnumbered}
@@ -10262,7 +10268,7 @@
 following text:
 
 > $E=mc^2$
-> 
+>
 > $$\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx$$
 
 ##### \Hologo{ConTeXt} Example {.unnumbered}
@@ -10290,7 +10296,7 @@
 following text:
 
 > $E=mc^2$
-> 
+>
 > $$\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx$$
 
 %</manual-options>
@@ -10445,7 +10451,7 @@
 following text:
 
 > \\(E=mc^2\\)
-> 
+>
 > \\[\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx\\]
 
 ##### \LaTeX{} Example {.unnumbered}
@@ -10473,7 +10479,7 @@
 following text:
 
 > \\(E=mc^2\\)
-> 
+>
 > \\[\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx\\]
 
 ##### \Hologo{ConTeXt} Example {.unnumbered}
@@ -10501,7 +10507,7 @@
 following text:
 
 > \\(E=mc^2\\)
-> 
+>
 > \\[\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx\\]
 
 %</manual-options>
@@ -10656,7 +10662,7 @@
 following text:
 
 > \(E=mc^2\)
-> 
+>
 > \[\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx\]
 
 ##### \LaTeX{} Example {.unnumbered}
@@ -10684,7 +10690,7 @@
 following text:
 
 > \(E=mc^2\)
-> 
+>
 > \[\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx\]
 
 ##### \Hologo{ConTeXt} Example {.unnumbered}
@@ -10712,7 +10718,7 @@
 following text:
 
 > \(E=mc^2\)
-> 
+>
 > \[\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx\]
 
 %</manual-options>
@@ -10815,21 +10821,21 @@
 following text:
 
 > The following list is tight:
-> 
+>
 > - first item
 > - second item
 > - third item
-> 
+>
 > The following list is loose:
-> 
+>
 > - first item
 > - second item that spans
-> 
+>
 >   multiple paragraphs
 > - third item
-> 
+>
 > The following list is now also loose:
-> 
+>
 > - first item
 >
 > - second item
@@ -11298,7 +11304,7 @@
 
 > \$\\sqrt{-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -11373,10 +11379,10 @@
 % ``` tex
 % \input markdown
 % a
-% b \markdownBegin c 
-% d 
+% b \markdownBegin c
+% d
 % e \markdownEnd   f
-% g 
+% g
 % \bye
 % ```````
 %
@@ -12696,7 +12702,7 @@
 \end{markdown}
 ```````
 
-Furthermore, we can also specify the name of the snippet in the current 
+Furthermore, we can also specify the name of the snippet in the current
 namespace, which can be different from the name of the snippet in the
 `jdoe/lists` theme. For example, we can make the snippet
 `jdoe/lists/romanNumerals` available under the name `roman`.
@@ -13532,11 +13538,11 @@
 > This is a tight list (the first item, the second item, and the third item).
 >
 > This is a loose list:
-> 
+>
 > - This is the first item.
-> 
+>
 > - This is the second item.
-> 
+>
 > - This is the third item.
 
 ##### \LaTeX{} Example {.unnumbered}
@@ -13560,7 +13566,7 @@
     },
     ulItemEnd = {},
     ulEndTight = {).},
-  },  
+  },
 ]
 This is a tight list
 
@@ -13579,7 +13585,7 @@
     ulItem = {\item},
     ulItemEnd = {.},
     ulEnd = {\end{itemize}},
-  },  
+  },
 ]
 This is a loose list
 
@@ -13602,11 +13608,11 @@
 > This is a tight list (the first item, the second item, and the third item).
 >
 > This is a loose list:
-> 
+>
 > - This is the first item.
-> 
+>
 > - This is the second item.
-> 
+>
 > - This is the third item.
 
 ##### \Hologo{ConTeXt} Example {.unnumbered}
@@ -13668,11 +13674,11 @@
 > This is a tight list (the first item, the second item, and the third item).
 >
 > This is a loose list:
-> 
+>
 > - This is the first item.
-> 
+>
 > - This is the second item.
-> 
+>
 > - This is the third item.
 
 %</manual-tokens>
@@ -13800,7 +13806,7 @@
 
 The \mdef{markdownRendererTextCite} macro represents a string of one or more
 text citations. This macro will only be produced, when the
-\Opt{citations} option is enabled. The macro receives parameters in the same 
+\Opt{citations} option is enabled. The macro receives parameters in the same
 format as the \mref{markdownRendererCite} macro.
 
 % \end{markdown}
@@ -13926,8 +13932,8 @@
 The \mdef{markdownRendererInputFencedCode} macro represents a fenced code
 block. This macro will only be produced, when the \Opt{fencedCode} option is
 enabled. The macro receives three arguments that correspond to the filename of
-a file contaning the code block contents, the fully escaped code fence infostring 
-that can be directly typeset, and the raw code fence infostring that can be used 
+a file contaning the code block contents, the fully escaped code fence infostring
+that can be directly typeset, and the raw code fence infostring that can be used
 outside typesetting.
 
 % \end{markdown}
@@ -14083,7 +14089,7 @@
 >
 > \$\\sqrt{-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -14124,7 +14130,7 @@
 >
 > \$\\sqrt{-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -14161,7 +14167,7 @@
 >
 > \$\\sqrt{-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -15223,7 +15229,7 @@
 > Hello *world*!
 >
 > *(The end of a block)*
-> 
+>
 > _Foo_ bar!
 
 %</manual-tokens>
@@ -16395,7 +16401,7 @@
 > - Hello *world*!
 >
 > *(The end of a block)*
-> 
+>
 > _Foo_ bar!
 
 ##### \LaTeX{} Example {.unnumbered}
@@ -16432,7 +16438,7 @@
 > - Hello *world*!
 >
 > *(The end of a block)*
-> 
+>
 > _Foo_ bar!
 
 ##### \Hologo{ConTeXt} Example {.unnumbered}
@@ -16464,7 +16470,7 @@
 > - Hello *world*!
 >
 > *(The end of a block)*
-> 
+>
 > _Foo_ bar!
 
 %</manual-tokens>
@@ -18080,11 +18086,11 @@
 > This is a tight list (the first item, the second item, and the third item).
 >
 > This is a loose list:
-> 
+>
 > 1. This is the first item.
-> 
+>
 > 2. This is the second item.
-> 
+>
 > 3. This is the third item.
 
 ##### \LaTeX{} Example {.unnumbered}
@@ -18113,7 +18119,7 @@
     },
     olItemEnd = {},
     olEndTight = {).},
-  },  
+  },
 ]
 This is a tight list
 
@@ -18143,7 +18149,7 @@
     },
     olItemEnd = {.},
     olEnd = {\end{enumerate}},
-  },  
+  },
 ]
 This is a loose list
 
@@ -18166,11 +18172,11 @@
 > This is a tight list (the first item, the second item, and the third item).
 >
 > This is a loose list:
-> 
+>
 > 1. This is the first item.
-> 
+>
 > 2. This is the second item.
-> 
+>
 > 3. This is the third item.
 
 ##### \Hologo{ConTeXt} Example {.unnumbered}
@@ -18253,11 +18259,11 @@
 > This is a tight list (the first item, the second item, and the third item).
 >
 > This is a loose list:
-> 
+>
 > 1. This is the first item.
-> 
+>
 > 2. This is the second item.
-> 
+>
 > 3. This is the third item.
 
 %</manual-tokens>
@@ -19323,7 +19329,7 @@
 following text:
 
 > $E=mc^2\dots$
-> 
+>
 > $$\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx\eqno(1)$$
 
 ##### \LaTeX{} Example {.unnumbered}
@@ -19352,7 +19358,7 @@
 following text:
 
 > $E=mc^2\dots$
-> 
+>
 > $$\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx\quad(1)$$
 
 ##### \Hologo{ConTeXt} Example {.unnumbered}
@@ -19380,7 +19386,7 @@
 following text:
 
 > $E=mc^2\dots$
-> 
+>
 > $$\hat{f} \left ( \xi  \right )= \int_{-\infty}^{\infty} f\left ( x  \right ) e^{-i2\pi \xi x} dx\quad(1)$$
 
 %</manual-tokens>
@@ -21053,7 +21059,7 @@
 
 > \$\\sqrt{-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -21674,7 +21680,7 @@
 
 > \$\\sqrt{-1}\$ *equals* \$i\$.
 >
-> <math><mroot><msqrt><mo>−</mo><mn>1</mn></msqrt></mroot></math>
+> <math><msqrt><mo>−</mo><mn>1</mn></msqrt></math>
 > *equals*
 > <math><mi>i</mi></math>.
 
@@ -22057,19 +22063,6 @@
 % \par
 % \begin{markdown}
 %
-% The \luamdef{util.lookup_files} method looks up files with filename `f`
-% and returns their paths. Further options for the \pkg{Kpathsea} library
-% can be specified in table `options`. [@luatex21, Section 10.7.4]
-%
-% \end{markdown}
-%  \begin{macrocode}
-function util.lookup_files(f, options)
-  return kpse.lookup(f, options)
-end
-%    \end{macrocode}
-% \par
-% \begin{markdown}
-%
 % The \luamdef{util.expand_tabs_in_line} expands tabs in string `s`. If
 % `tabstop` is specified, it is used as the tab stop width. Otherwise,
 % the tab stop width of 4 characters is used. The method is a copy of the tab
@@ -24509,7 +24502,7 @@
   end
   local char_table = {}
     for _, code_point in ipairs(code_points) do
-        table.insert(char_table, unicode.utf8.char(code_point))
+      table.insert(char_table, unicode.utf8.char(code_point))
     end
   return table.concat(char_table)
 end
@@ -24689,9 +24682,10 @@
 %
 % \end{markdown}
 %  \begin{macrocode}
+  self.interblocksep_text = "\\markdownRendererInterblockSeparator\n{}"
   function self.interblocksep()
     if not self.is_writing then return "" end
-    return "\\markdownRendererInterblockSeparator\n{}"
+    return self.interblocksep_text
   end
 %    \end{macrocode}
 % \par
@@ -24704,14 +24698,29 @@
 %
 % \end{markdown}
 %  \begin{macrocode}
+  self.paragraphsep_text = "\\markdownRendererParagraphSeparator\n{}"
   function self.paragraphsep()
     if not self.is_writing then return "" end
-    return "\\markdownRendererParagraphSeparator\n{}"
+    return self.paragraphsep_text
   end
 %    \end{macrocode}
 % \par
 % \begin{markdown}
 %
+% Define \luamdef{writer->undosep} as a function that will remove the output
+% produced by an immediately preceding block element / paragraph separator.
+%
+% \end{markdown}
+%  \begin{macrocode}
+  self.undosep_text = "\\markdownRendererUndoSeparator\n{}"
+  function self.undosep()
+    if not self.is_writing then return "" end
+    return self.undosep_text
+  end
+%    \end{macrocode}
+% \par
+% \begin{markdown}
+%
 % Define \luamdef{writer->soft_line_break} as the output format of a soft
 % line break.
 %
@@ -25378,6 +25387,7 @@
     if attributes["#" .. self.slice_end_identifier] ~= nil and
        self.slice_end_type == "^" then
       if self.is_writing then
+        table.insert(buf, self.undosep())
         table.insert(buf, tear_down_attributes())
       end
       self.is_writing = false
@@ -25429,6 +25439,7 @@
       if attributes["#" .. self.slice_end_identifier] ~= nil
          and self.slice_end_type == "$" then
         if self.is_writing then
+          table.insert(buf, self.undosep())
           table.insert(buf, tear_down_attributes())
         end
         self.is_writing = false
@@ -25448,55 +25459,55 @@
 %
 % \end{markdown}
 %  \begin{macrocode}
-local function create_auto_identifier(s)
-  local buffer = {}
-  local prev_space = false
-  local letter_found = false
+  local function create_auto_identifier(s)
+    local buffer = {}
+    local prev_space = false
+    local letter_found = false
 
-  for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do
-    local char = utf8.char(code)
+    for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do
+      local char = utf8.char(code)
 
-    -- Remove everything up to the first letter.
-    if not letter_found then
-      local is_letter = unicode.utf8.match(char, "%a")
-      if is_letter then
-        letter_found = true
-      else
+      -- Remove everything up to the first letter.
+      if not letter_found then
+        local is_letter = unicode.utf8.match(char, "%a")
+        if is_letter then
+          letter_found = true
+        else
+          goto continue
+        end
+      end
+
+      -- Remove all non-alphanumeric characters, except underscores, hyphens, and periods.
+      if not unicode.utf8.match(char, "[%w_%-%.%s]") then
         goto continue
       end
-    end
 
-    -- Remove all non-alphanumeric characters, except underscores, hyphens, and periods.
-    if not unicode.utf8.match(char, "[%w_%-%.%s]") then
-      goto continue
-    end
-
-    -- Replace all spaces and newlines with hyphens.
-    if unicode.utf8.match(char, "[%s\n]") then
-      char = "-"
-      if prev_space then
-        goto continue
+      -- Replace all spaces and newlines with hyphens.
+      if unicode.utf8.match(char, "[%s\n]") then
+        char = "-"
+        if prev_space then
+          goto continue
+        else
+          prev_space = true
+        end
       else
-        prev_space = true
+        -- Convert all alphabetic characters to lowercase.
+        char = unicode.utf8.lower(char)
+        prev_space = false
       end
-    else
-      -- Convert all alphabetic characters to lowercase.
-      char = unicode.utf8.lower(char)
-      prev_space = false
+
+      table.insert(buffer, char)
+
+      ::continue::
     end
 
-    table.insert(buffer, char)
+    if prev_space then
+      table.remove(buffer)
+    end
 
-    ::continue::
+    local identifier = #buffer == 0 and "section" or table.concat(buffer, "")
+    return identifier
   end
-
-  if prev_space then
-    table.remove(buffer)
-  end
-
-  local identifier = #buffer == 0 and "section" or table.concat(buffer, "")
-  return identifier
-end
 %    \end{macrocode}
 % \begin{markdown}
 %
@@ -25504,56 +25515,56 @@
 %
 % \end{markdown}
 %  \begin{macrocode}
-local function create_gfm_auto_identifier(s)
-  local buffer = {}
-  local prev_space = false
-  local letter_found = false
+  local function create_gfm_auto_identifier(s)
+    local buffer = {}
+    local prev_space = false
+    local letter_found = false
 
-  for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do
-    local char = utf8.char(code)
+    for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do
+      local char = utf8.char(code)
 
-    -- Remove everything up to the first non-space.
-    if not letter_found then
-      local is_letter = unicode.utf8.match(char, "%S")
-      if is_letter then
-        letter_found = true
-      else
+      -- Remove everything up to the first non-space.
+      if not letter_found then
+        local is_letter = unicode.utf8.match(char, "%S")
+        if is_letter then
+          letter_found = true
+        else
+          goto continue
+        end
+      end
+
+      -- Remove all non-alphanumeric characters, except underscores and hyphens.
+      if not unicode.utf8.match(char, "[%w_%-%s]") then
+        prev_space = false
         goto continue
       end
-    end
 
-    -- Remove all non-alphanumeric characters, except underscores and hyphens.
-    if not unicode.utf8.match(char, "[%w_%-%s]") then
-      prev_space = false
-      goto continue
-    end
-
-    -- Replace all spaces and newlines with hyphens.
-    if unicode.utf8.match(char, "[%s\n]") then
-      char = "-"
-      if prev_space then
-        goto continue
+      -- Replace all spaces and newlines with hyphens.
+      if unicode.utf8.match(char, "[%s\n]") then
+        char = "-"
+        if prev_space then
+          goto continue
+        else
+          prev_space = true
+        end
       else
-        prev_space = true
+        -- Convert all alphabetic characters to lowercase.
+        char = unicode.utf8.lower(char)
+        prev_space = false
       end
-    else
-      -- Convert all alphabetic characters to lowercase.
-      char = unicode.utf8.lower(char)
-      prev_space = false
+
+      table.insert(buffer, char)
+
+      ::continue::
     end
 
-    table.insert(buffer, char)
+    if prev_space then
+      table.remove(buffer)
+    end
 
-    ::continue::
+    local identifier = #buffer == 0 and "section" or table.concat(buffer, "")
+    return identifier
   end
-
-  if prev_space then
-    table.remove(buffer)
-  end
-
-  local identifier = #buffer == 0 and "section" or table.concat(buffer, "")
-  return identifier
-end
 %    \end{macrocode}
 % \par
 % \begin{markdown}
@@ -25564,6 +25575,8 @@
 %
 % \end{markdown}
 %  \begin{macrocode}
+  self.secbegin_text = "\\markdownRendererSectionBegin\n"
+  self.secend_text = "\n\\markdownRendererSectionEnd "
   function self.heading(s, level, attributes)
     local buf = {}
     local flat_text, inlines = table.unpack(s)
@@ -25573,8 +25586,8 @@
       table.insert(buf,
                    self.push_attributes("heading",
                                         nil,
-                                        "\\markdownRendererSectionBegin\n",
-                                        "\n\\markdownRendererSectionEnd "))
+                                        self.secbegin_text,
+                                        self.secend_text))
     end
 
     -- pop attributes for sections that have ended
@@ -25595,8 +25608,8 @@
     -- push attributes for the new section
     local start_output = {}
     local end_output = {}
-    table.insert(start_output, "\\markdownRendererSectionBegin\n")
-    table.insert(end_output, "\n\\markdownRendererSectionEnd ")
+    table.insert(start_output, self.secbegin_text)
+    table.insert(end_output, self.secend_text)
 
     table.insert(buf, self.push_attributes("heading",
                                            normalized_attributes,
@@ -25752,7 +25765,6 @@
 parsers.alphanumeric           = R("AZ","az","09")
 parsers.keyword                = parsers.letter
                                * (parsers.alphanumeric + parsers.dash)^0
-parsers.internal_punctuation   = S(":;,.?")
 
 parsers.doubleasterisks        = P("**")
 parsers.doubleunderscores      = P("__")
@@ -25763,7 +25775,50 @@
 parsers.succeed                = P(true)
 parsers.fail                   = P(false)
 
-parsers.escapable              = S("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
+parsers.internal_punctuation   = S(":;,.?")
+parsers.ascii_punctuation      = S("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
+%    \end{macrocode}
+% \par
+% \begin{markdown}
+%
+%### Unicode punctuation
+% This section documents [the Unicode punctuation][unicode-punctuation]
+% recognized by the markdown reader. The punctuation is organized in the
+% \luamdef{parsers.punctuation} table according to the number of bytes occupied
+% after conversion to \acro{utf}8.
+%
+% [unicode-punctuation]: https://spec.commonmark.org/0.31.2/#unicode-punctuation-character
+%                        (CommonMark Spec, Version 0.31.2 (2024-01-28))
+%
+% \end{markdown}
+%  \begin{macrocode}
+parsers.punctuation            = {}
+(function()
+  local pathname = kpse.lookup("UnicodeData.txt")
+  local file = assert(io.open(pathname, "r"),
+    [[Could not open file "UnicodeData.txt"]])
+  for line in file:lines() do
+    local codepoint, major_category = line:match("^(%x+);[^;]*;(%a)")
+    if major_category == "P" or major_category == "S" then
+      local code = unicode.utf8.char(tonumber(codepoint, 16))
+      if parsers.punctuation[#code] == nil then
+        parsers.punctuation[#code] = parsers.fail
+      end
+      local code_parser = parsers.succeed
+      for i = 1, #code do
+        local byte = code:sub(i, i)
+        local byte_parser = S(byte)
+        code_parser = code_parser
+                    * byte_parser
+      end
+      parsers.punctuation[#code] = parsers.punctuation[#code]
+                                 + code_parser
+    end
+  end
+  assert(file:close())
+end)()
+
+parsers.escapable              = parsers.ascii_punctuation
 parsers.anyescaped             = parsers.backslash / "" * parsers.escapable
                                + parsers.any
 
@@ -25812,8 +25867,8 @@
 % \end{markdown}
 %  \begin{macrocode}
 local function has_trail(indent_table)
-  return indent_table ~= nil and 
-    indent_table.trail ~= nil and 
+  return indent_table ~= nil and
+    indent_table.trail ~= nil and
     next(indent_table.trail) ~= nil
 end
 
@@ -25825,8 +25880,8 @@
 % \end{markdown}
 %  \begin{macrocode}
 local function has_indents(indent_table)
-  return indent_table ~= nil and 
-    indent_table.indents ~= nil and 
+  return indent_table ~= nil and
+    indent_table.indents ~= nil and
     next(indent_table.indents) ~= nil
 end
 
@@ -25902,8 +25957,8 @@
 %
 % Process the spacing of a string of spaces and tabs `spacing` with preceding indent width from
 % the start of the line `indent` and strip up to `left_strip_length` spaces. Return the remainder
-% `remainder` and whether there is enough spaces to produce a code `is_code`. Return how many 
-% spaces were stripped, as well as if the minimum was met `is_minimum` and what remainder it 
+% `remainder` and whether there is enough spaces to produce a code `is_code`. Return how many
+% spaces were stripped, as well as if the minimum was met `is_minimum` and what remainder it
 % left `minimum_remainder`.
 %
 % \end{markdown}
@@ -25947,7 +26002,7 @@
         minimum_found = true
         minimum_remainder = minimum_remainder .. string.rep(" ", count - minimum)
       end
-      
+
       if (code_started) then
         code_start = code_start .. character
       elseif (count >= minimum + 4) then
@@ -26119,13 +26174,13 @@
 %
 % Apply the patterns decoded from the indents of the indent table `indent_table`
 % iteratively starting at position `index` of the string `s`. If the `is_optional`
-% mode is selected, match as many patterns as possible, else match all or fail. 
-% With the option `is_blank`, the parsing behaves as optional after the position 
+% mode is selected, match as many patterns as possible, else match all or fail.
+% With the option `is_blank`, the parsing behaves as optional after the position
 % of a blank-only indent has been surpassed.
 %
 % \end{markdown}
 %  \begin{macrocode}
-local function traverse_indent(s, i, indent_table, is_optional, is_blank)
+local function traverse_indent(s, i, indent_table, is_optional, is_blank, current_line_indents)
   local new_index = i
 
   local preceding_indentation = 0
@@ -26133,6 +26188,10 @@
 
   local blank_starter = left_blank_starter(indent_table)
 
+  if current_line_indents == nil then
+    current_line_indents = {}
+  end
+
   for index = 1,#indent_table.indents do
     local value = indent_table.indents[index]
     local pattern = decode_pattern(value.name)
@@ -26142,10 +26201,10 @@
     if new_indent_info == nil then
       local blankline_end = lpeg.match(Ct(parsers.blankline * Cg(Cp(), "pos")), s, new_index)
       if is_optional or not indent_table.ignore_blockquote_blank or not blankline_end then
-        return is_optional, new_index, current_trail
+        return is_optional, new_index, current_trail, current_line_indents
       end
 
-      return traverse_indent(s, tonumber(blankline_end.pos), indent_table, is_optional, is_blank)
+      return traverse_indent(s, tonumber(blankline_end.pos), indent_table, is_optional, is_blank, current_line_indents)
     end
 
     local raw_last_trail = new_indent_info[1]
@@ -26158,13 +26217,13 @@
     -- check previous trail
     if not space_only and next(current_trail) == nil then
       local sp = process_starter_spacing(0, raw_last_trail, 0, 0)
-      current_trail = {is_code=sp.is_code, remainder=sp.remainder, total_length=sp.total_length, 
+      current_trail = {is_code=sp.is_code, remainder=sp.remainder, total_length=sp.total_length,
                        full_remainder=sp.full_remainder}
     end
 
     if next(current_trail) ~= nil then
       if not space_only and current_trail.is_code then
-        return is_optional, new_index, current_trail
+        return is_optional, new_index, current_trail, current_line_indents
       end
       if current_trail.internal_remainder ~= nil then
         raw_last_trail = current_trail.internal_remainder
@@ -26196,7 +26255,7 @@
     local sp = process_starter_spacing(total_indent_level, spacing_to_process, minimum, left_strip_length)
 
     if space_only and not sp.is_minimum then
-      return is_optional or (is_blank and blank_starter <= index), new_index, current_trail
+      return is_optional or (is_blank and blank_starter <= index), new_index, current_trail, current_line_indents
     end
 
     local indent_length = raw_last_trail_length + delimiter_length + sp.left_total_stripped
@@ -26210,10 +26269,12 @@
 
     current_trail = {is_code=sp.is_code, remainder=sp.remainder, internal_remainder=sp.minimum_remainder,
                      total_length=sp.total_length, full_remainder=sp.full_remainder}
+
+    current_line_indents[#current_line_indents + 1] = new_indent_info
     new_index = next_index
   end
 
-  return true, new_index, current_trail
+  return true, new_index, current_trail, current_line_indents
 end
 
 %    \end{macrocode}
@@ -26230,8 +26291,8 @@
 %    \end{macrocode}
 % \begin{markdown}
 %
-% Check if the current trail of the `indent_table` would produce code if it is expected `expect_code` 
-% or it would not if it is not. If there is no trail, process and check the current spacing `spacing`. 
+% Check if the current trail of the `indent_table` would produce code if it is expected `expect_code`
+% or it would not if it is not. If there is no trail, process and check the current spacing `spacing`.
 %
 % \end{markdown}
 %  \begin{macrocode}
@@ -26242,7 +26303,7 @@
   if has_trail(indent_table) then
     local trail = indent_table.trail
     is_code = trail.is_code
-    if is_code then 
+    if is_code then
       remainder = trail.remainder
     else
       remainder = trail.full_remainder
@@ -26250,7 +26311,7 @@
   else
     local sp = process_starter_spacing(0, spacing, 0, 0)
     is_code = sp.is_code
-    if is_code then 
+    if is_code then
       remainder = sp.remainder
     else
       remainder = sp.full_remainder
@@ -26291,7 +26352,7 @@
 %    \end{macrocode}
 % \begin{markdown}
 %
-% Check the indentation of the continuation line, optionally with 
+% Check the indentation of the continuation line, optionally with
 % the mode `is_optional` selected. Check blank line exclusively with `is_blank`.
 %
 % \end{markdown}
@@ -26301,9 +26362,11 @@
     return true
   end
 
-  local passes, new_index, current_trail = traverse_indent(s, i, indent_table, is_optional, is_blank)
+  local passes, new_index, current_trail, current_line_indents =
+    traverse_indent(s, i, indent_table, is_optional, is_blank)
 
   if passes then
+    indent_table.current_line_indents = current_line_indents
     indent_table = add_trail(indent_table, current_trail)
     return new_index, indent_table
   end
@@ -26341,7 +26404,7 @@
 %    \end{macrocode}
 % \begin{markdown}
 %
-% Take the trail `trail` or create a new one from `spacing` and comapre it 
+% Take the trail `trail` or create a new one from `spacing` and compare it
 % with the expected `trail_type`. On success return the index `i` and the
 % remainder of the trail.
 %
@@ -26394,25 +26457,25 @@
 %    \end{macrocode}
 % \begin{markdown}
 %
-% Check the indentation of the continuation line, optionally with 
-% the mode `is_optional` selected. Check blank line specifically with `is_blank`. 
+% Check the indentation of the continuation line, optionally with
+% the mode `is_optional` selected. Check blank line specifically with `is_blank`.
 % Additionally, also directly check the new trail with a type `trail_type`.
 %
 % \end{markdown}
 %  \begin{macrocode}
 local function check_continuation_indentation_and_trail(s, i, indent_table, is_optional, is_blank, trail_type,
-                                                        reset_rem, omit_remainder)  
+                                                        reset_rem, omit_remainder)
   if not has_indents(indent_table) then
     local spacing, new_index = lpeg.match(C(parsers.spacechar^0) * Cp(), s, i)
     local result, remainder = check_trail_type(s, i, indent_table.trail, spacing, trail_type)
     if remainder == nil then
-      if result then 
+      if result then
         return new_index
       end
       return false
     end
-    if result then 
-      return new_index, remainder 
+    if result then
+      return new_index, remainder
     end
     return false
   end
@@ -26431,17 +26494,17 @@
     end
     local result, remainder = check_trail_type(s, new_index, current_trail, spacing, trail_type)
     if remainder == nil or omit_remainder then
-      if result then 
-        return new_index 
+      if result then
+        return new_index
       end
       return false
     end
-    
+
     if is_blank and reset_rem then
       remainder = remove_remainder_if_blank(indent_table, remainder)
     end
-    if result then 
-      return new_index, remainder 
+    if result then
+      return new_index, remainder
     end
     return false
   end
@@ -26516,11 +26579,11 @@
                                                             check_continuation_indentation_and_trail)
 
 parsers.check_minimal_indent_and_any_trail = Cmt( Cb("indent_info")
-                                                * Cc(false) * Cc(false) * Cc("full-any") * Cc(true) * Cc(false), 
+                                                * Cc(false) * Cc(false) * Cc("full-any") * Cc(true) * Cc(false),
                                                 check_continuation_indentation_and_trail)
 
 parsers.check_minimal_blank_indent_and_any_trail = Cmt( Cb("indent_info")
-                                                      * Cc(false) * Cc(true) * Cc("full-any") * Cc(true) * Cc(false), 
+                                                      * Cc(false) * Cc(true) * Cc("full-any") * Cc(true) * Cc(false),
                                                        check_continuation_indentation_and_trail)
 
 parsers.check_minimal_blank_indent_and_any_trail_no_rem = Cmt( Cb("indent_info")
@@ -26528,11 +26591,11 @@
                                                         check_continuation_indentation_and_trail)
 
 parsers.check_optional_indent_and_any_trail = Cmt( Cb("indent_info")
-                                                * Cc(true) * Cc(false) * Cc("full-any") * Cc(true) * Cc(false), 
+                                                * Cc(true) * Cc(false) * Cc("full-any") * Cc(true) * Cc(false),
                                                 check_continuation_indentation_and_trail)
 
 parsers.check_optional_blank_indent_and_any_trail = Cmt( Cb("indent_info")
-                                                      * Cc(true) * Cc(true) * Cc("full-any") * Cc(true) * Cc(false), 
+                                                      * Cc(true) * Cc(true) * Cc("full-any") * Cc(true) * Cc(false),
                                                        check_continuation_indentation_and_trail)
 
 %    \end{macrocode}
@@ -26865,10 +26928,10 @@
 
 -- end conditions
 parsers.html_blankline_end_condition  = parsers.linechar^0
-                                      * ( parsers.newline 
-                                        * (parsers.check_minimal_blank_indent_and_any_trail 
+                                      * ( parsers.newline
+                                        * (parsers.check_minimal_blank_indent_and_any_trail
                                           * #parsers.blankline
-                                          + parsers.check_minimal_indent_and_any_trail) 
+                                          + parsers.check_minimal_indent_and_any_trail)
                                         * parsers.linechar^1)^0
                                       * (parsers.newline^-1 / "")
 
@@ -26877,10 +26940,10 @@
 end
 
 parsers.html_until_end = function(end_marker)
-  return Cs(Cs((parsers.newline 
-          * (parsers.check_minimal_blank_indent_and_any_trail 
+  return Cs(Cs((parsers.newline
+          * (parsers.check_minimal_blank_indent_and_any_trail
             * #parsers.blankline
-            + parsers.check_minimal_indent_and_any_trail)  
+            + parsers.check_minimal_indent_and_any_trail)
           + parsers.linechar - end_marker)^0
           * parsers.linechar^0 * parsers.newline^-1)
          / remove_trailing_blank_lines)
@@ -26896,27 +26959,27 @@
                             * (parsers.alphanumeric + parsers.colon + parsers.underscore
                             + parsers.period + parsers.dash)^0
 
-parsers.html_attribute_value  = parsers.squote 
-                              * (parsers.linechar - parsers.squote)^0 
+parsers.html_attribute_value  = parsers.squote
+                              * (parsers.linechar - parsers.squote)^0
                               * parsers.squote
-                              + parsers.dquote 
-                              * (parsers.linechar - parsers.dquote)^0 
+                              + parsers.dquote
+                              * (parsers.linechar - parsers.dquote)^0
                               * parsers.dquote
                               + ( parsers.any - parsers.spacechar - parsers.newline
                                 - parsers.dquote - parsers.squote - parsers.backtick
                                 - parsers.equal - parsers.less - parsers.more)^1
 
-parsers.html_inline_attribute_value = parsers.squote 
+parsers.html_inline_attribute_value = parsers.squote
                                     * (V("NoSoftLineBreakEndline")
                                       + parsers.any
                                       - parsers.blankline^2
-                                      - parsers.squote)^0 
+                                      - parsers.squote)^0
                                     * parsers.squote
-                                    + parsers.dquote 
+                                    + parsers.dquote
                                     * (V("NoSoftLineBreakEndline")
                                       + parsers.any
                                       - parsers.blankline^2
-                                      - parsers.dquote)^0 
+                                      - parsers.dquote)^0
                                     * parsers.dquote
                                     + (parsers.any - parsers.spacechar - parsers.newline
                                       - parsers.dquote - parsers.squote - parsers.backtick
@@ -26944,7 +27007,7 @@
                                     * parsers.html_attribute_value_specification^-1
 
 parsers.nested_breaking_blank = parsers.newline
-                              * parsers.check_minimal_blank_indent 
+                              * parsers.check_minimal_blank_indent
                               * parsers.blankline
 
 parsers.html_comment_start = P("<!--")
@@ -26953,10 +27016,10 @@
 
 parsers.html_comment = Cs( parsers.html_comment_start
                          * parsers.html_until_end(parsers.html_comment_end))
- 
+
 parsers.html_inline_comment = (parsers.html_comment_start / "")
                             * -P(">") * -P("->")
-                            * Cs((V("NoSoftLineBreakEndline") + parsers.any - P("--") 
+                            * Cs((V("NoSoftLineBreakEndline") + parsers.any
                                 - parsers.nested_breaking_blank - parsers.html_comment_end)^0)
                             * (parsers.html_comment_end / "")
 
@@ -26980,7 +27043,7 @@
                               * parsers.html_until_end(parsers.html_declaration_end))
 
 parsers.html_inline_declaration = parsers.html_declaration_start
-                                * Cs(V("NoSoftLineBreakEndline") + parsers.any 
+                                * Cs(V("NoSoftLineBreakEndline") + parsers.any
                                     - parsers.nested_breaking_blank - parsers.html_declaration_end)^0
                                 * parsers.html_declaration_end
 
@@ -26992,7 +27055,7 @@
                               * parsers.html_until_end(parsers.html_instruction_end))
 
 parsers.html_inline_instruction = parsers.html_instruction_start
-                                * Cs(V("NoSoftLineBreakEndline") + parsers.any 
+                                * Cs(V("NoSoftLineBreakEndline") + parsers.any
                                     - parsers.nested_breaking_blank - parsers.html_instruction_end)^0
                                 * parsers.html_instruction_end
 
@@ -27135,7 +27198,7 @@
 
 parsers.html_inline_comment_full  = parsers.html_comment_start
                                   * -P(">") * -P("->")
-                                  * Cs((V("NoSoftLineBreakEndline") + parsers.any - P("--") 
+                                  * Cs((V("NoSoftLineBreakEndline") + parsers.any - P("--")
                                       - parsers.nested_breaking_blank - parsers.html_comment_end)^0)
                                   * parsers.html_comment_end
 
@@ -27367,7 +27430,7 @@
                       * -parsers.hash / length
 
 -- parse setext header ending and return level
-parsers.heading_level = parsers.nonindentspace * parsers.equal^1 * parsers.optionalspace * #parsers.newline * Cc(1) 
+parsers.heading_level = parsers.nonindentspace * parsers.equal^1 * parsers.optionalspace * #parsers.newline * Cc(1)
                       + parsers.nonindentspace * parsers.dash^1 * parsers.optionalspace * #parsers.newline * Cc(2)
 
 local function strip_atx_end(s)
@@ -27377,10 +27440,10 @@
 parsers.atx_heading = parsers.check_trail_no_rem
                     * Cg(parsers.heading_start, "level")
                     * (C( parsers.optionalspace
-                        * parsers.hash^0 
+                        * parsers.hash^0
                         * parsers.optionalspace
                         * parsers.newline)
-                      + parsers.spacechar^1 
+                      + parsers.spacechar^1
                       * C(parsers.line))
 %    \end{macrocode}
 % \par
@@ -27627,7 +27690,7 @@
   parsers.minimally_indented_par_or_plain = parsers.minimally_indented_paragraph
                                           + parsers.minimally_indented_plain
 
-  parsers.minimally_indented_par_or_plain_no_blank  = parsers.minimally_indented_par_or_plain 
+  parsers.minimally_indented_par_or_plain_no_blank  = parsers.minimally_indented_par_or_plain
                                                     - parsers.minimally_indented_blankline
 
   parsers.minimally_indented_ref = parsers.check_minimal_indent * V("Reference")
@@ -27653,8 +27716,8 @@
 %  \begin{macrocode}
 
   parsers.separator_loop = function(separated_block, paragraph, block_separator, paragraph_separator)
-    return  separated_block 
-          + block_separator 
+    return  separated_block
+          + block_separator
             * paragraph
             * separated_block
           + paragraph_separator
@@ -27681,20 +27744,20 @@
   end
 
   parsers.sep_group_no_output = function(blank)
-    return  blank^0 * parsers.eof 
+    return  blank^0 * parsers.eof
           + blank^0
   end
 
   parsers.content_blank = parsers.minimally_indented_blankline
 
-  parsers.ref_or_block_separated  = parsers.sep_group_no_output(parsers.content_blank) 
-                                  * ( parsers.minimally_indented_ref 
+  parsers.ref_or_block_separated  = parsers.sep_group_no_output(parsers.content_blank)
+                                  * ( parsers.minimally_indented_ref
                                     - parsers.content_blank)
                                   + parsers.block_sep_group(parsers.content_blank)
-                                  * ( parsers.minimally_indented_block 
+                                  * ( parsers.minimally_indented_block
                                     - parsers.content_blank)
 
-  parsers.loop_body_pair  = 
+  parsers.loop_body_pair  =
     parsers.create_loop_body_pair(parsers.ref_or_block_separated,
                                   parsers.minimally_indented_par_or_plain_no_blank,
                                   parsers.block_sep_group(parsers.content_blank),
@@ -27712,7 +27775,7 @@
   parsers.indented_content = function()
     return  Ct( (V("Reference") + (parsers.blankline / ""))
               * parsers.content_blank^0
-              * parsers.check_minimal_indent 
+              * parsers.check_minimal_indent
               * parsers.content_loop
               + (V("Reference") + (parsers.blankline / ""))
               * parsers.content_blank^0
@@ -27758,7 +27821,7 @@
              * allowed_end
   end
 
-  parsers.starter = parsers.bullet(parsers.dash) 
+  parsers.starter = parsers.bullet(parsers.dash)
                   + parsers.bullet(parsers.asterisk)
                   + parsers.bullet(parsers.plus)
                   + parsers.enumerator(parsers.period)
@@ -27842,7 +27905,7 @@
 %    \end{macrocode}
 % \begin{markdown}
 %
-% Render content between the `opening_index` and `closing_index` in the delimiter table `t` 
+% Render content between the `opening_index` and `closing_index` in the delimiter table `t`
 % as emphasis.
 %
 % \end{markdown}
@@ -27856,7 +27919,7 @@
 %    \end{macrocode}
 % \begin{markdown}
 %
-% Render content between the `opening_index` and `closing_index` in the delimiter table `t` 
+% Render content between the `opening_index` and `closing_index` in the delimiter table `t`
 % as strong emphasis.
 %
 % \end{markdown}
@@ -27876,8 +27939,8 @@
 % \end{markdown}
 %  \begin{macrocode}
   local function breaks_three_rule(opening_delimiter, closing_delimiter)
-    return (opening_delimiter.is_closing or closing_delimiter.is_opening) and 
-      ((opening_delimiter.original_count + closing_delimiter.original_count) % 3 == 0) and 
+    return (opening_delimiter.is_closing or closing_delimiter.is_opening) and
+      ((opening_delimiter.original_count + closing_delimiter.original_count) % 3 == 0) and
       (opening_delimiter.original_count % 3 ~= 0 or closing_delimiter.original_count % 3 ~= 0)
   end
 
@@ -27940,11 +28003,11 @@
 
     while current_position <= max_position do
       local value = t[current_position]
-      
-      if value.type ~= "delimiter" or 
-        value.element ~= "emphasis" or 
-        not value.is_active or 
-        not value.is_closing or 
+
+      if value.type ~= "delimiter" or
+        value.element ~= "emphasis" or
+        not value.is_active or
+        not value.is_closing or
         (value.current_count <= 0) then
         current_position = current_position + 1
         goto continue
@@ -27978,7 +28041,7 @@
         t[current_position].current_count = current_closing_count - 1
         fill_emph(t, opener_position, current_position)
       end
-      
+
       ::continue::
     end
   end
@@ -28006,7 +28069,6 @@
       return lpeg.R("\240\244") * cont * cont * cont
     end
   end
-
 %    \end{macrocode}
 % \begin{markdown}
 %
@@ -28024,15 +28086,22 @@
       else
         char_length = pos + 1
       end
-      c = lpeg.match({ C(utf8_by_byte_count(char_length)) },s,i+pos)
-      if (c ~= nil) and (unicode.utf8.match(c, chartype)) then
-        return i
+
+      if (chartype == "punctuation") then
+        if lpeg.match(parsers.punctuation[char_length], s, i+pos) then
+          return i
+        end
+      else
+        c = lpeg.match({ C(utf8_by_byte_count(char_length)) },s,i+pos)
+        if (c ~= nil) and (unicode.utf8.match(c, chartype)) then
+          return i
+        end
       end
     end
   end
 
   local function check_preceding_unicode_punctuation(s, i)
-    return check_unicode_type(s, i, -4, -1, "%p")
+    return check_unicode_type(s, i, -4, -1, "punctuation")
   end
 
   local function check_preceding_unicode_whitespace(s, i)
@@ -28040,7 +28109,7 @@
   end
 
   local function check_following_unicode_punctuation(s, i)
-    return check_unicode_type(s, i, 0, 3, "%p")
+    return check_unicode_type(s, i, 0, 3, "punctuation")
   end
 
   local function check_following_unicode_whitespace(s, i)
@@ -28047,12 +28116,12 @@
     return check_unicode_type(s, i, 0, 3, "%s")
   end
 
-  parsers.unicode_preceding_punctuation = B(parsers.escapable) 
+  parsers.unicode_preceding_punctuation = B(parsers.escapable)
                                         + Cmt(parsers.succeed, check_preceding_unicode_punctuation)
 
   parsers.unicode_preceding_whitespace = Cmt(parsers.succeed, check_preceding_unicode_whitespace)
 
-  parsers.unicode_following_punctuation = #parsers.escapable 
+  parsers.unicode_following_punctuation = #parsers.escapable
                                         + Cmt(parsers.succeed, check_following_unicode_punctuation)
 
   parsers.unicode_following_whitespace = Cmt(parsers.succeed, check_following_unicode_whitespace)
@@ -28064,8 +28133,8 @@
   end
 
   parsers.left_flanking_delimiter_run = function(character)
-    return  (B( parsers.any) 
-              * (parsers.unicode_preceding_punctuation + parsers.unicode_preceding_whitespace) 
+    return  (B( parsers.any)
+              * (parsers.unicode_preceding_punctuation + parsers.unicode_preceding_whitespace)
              + -B(parsers.any))
             * parsers.delimiter_run(character)
             * parsers.unicode_following_punctuation
@@ -28079,7 +28148,7 @@
           * parsers.delimiter_run(character)
           * (parsers.unicode_following_punctuation + parsers.unicode_following_whitespace
             + parsers.eof)
-          + (B(parsers.any) 
+          + (B(parsers.any)
             * -(parsers.unicode_preceding_punctuation + parsers.unicode_preceding_whitespace))
           * parsers.delimiter_run(character)
   end
@@ -28087,13 +28156,13 @@
   if options.underscores then
     parsers.emph_start = parsers.left_flanking_delimiter_run(parsers.asterisk)
                       + (-#parsers.right_flanking_delimiter_run(parsers.underscore)
-                          + (parsers.unicode_preceding_punctuation 
+                          + (parsers.unicode_preceding_punctuation
                             * #parsers.right_flanking_delimiter_run(parsers.underscore)))
                       * parsers.left_flanking_delimiter_run(parsers.underscore)
 
     parsers.emph_end  = parsers.right_flanking_delimiter_run(parsers.asterisk)
                       + (-#parsers.left_flanking_delimiter_run(parsers.underscore)
-                        + #(parsers.left_flanking_delimiter_run(parsers.underscore) 
+                        + #(parsers.left_flanking_delimiter_run(parsers.underscore)
                           * parsers.unicode_following_punctuation))
                       * parsers.right_flanking_delimiter_run(parsers.underscore)
   else
@@ -28115,7 +28184,7 @@
                                   * Cg(Cc(true), "is_opening")
                                   * Cg(Cc(false), "is_closing"))
 
-  parsers.emph_capturing_close = Ct( Cg(Cc("delimiter"), "type")  
+  parsers.emph_capturing_close = Ct( Cg(Cc("delimiter"), "type")
                                    * Cg(Cc("emphasis"), "element")
                                    * Cg(C(parsers.emph_end), "content")
                                    * Cg(Cc(false), "is_opening")
@@ -28281,20 +28350,20 @@
   end
 
   parsers.link_and_emph_endline = parsers.newline
-                                * ((parsers.check_minimal_indent 
-                                  * -V("EndlineExceptions") 
-                                  + parsers.check_optional_indent 
-                                  * -V("EndlineExceptions") 
+                                * ((parsers.check_minimal_indent
+                                  * -V("EndlineExceptions")
+                                  + parsers.check_optional_indent
+                                  * -V("EndlineExceptions")
                                   * -parsers.starter) / "")
                                 * parsers.spacechar^0 / "\n"
 
   parsers.link_and_emph_content = Ct( Cg(Cc("content"), "type")
-                                    * Cg(Cs(( parsers.link_emph_precedence 
+                                    * Cg(Cs(( parsers.link_emph_precedence
                                             + parsers.backslash * parsers.any
                                             + parsers.link_and_emph_endline
                                             + (parsers.linechar
                                               - parsers.blankline^2
-                                              - parsers.link_image_open_or_close 
+                                              - parsers.link_image_open_or_close
                                               - parsers.emph_open_or_close))^0), "content"))
 
   parsers.link_and_emph_table = (parsers.link_image_opening + parsers.emph_open)
@@ -28330,7 +28399,7 @@
       local value = t[i]
       if value.type == "delimiter" and
          value.is_opening and
-         (value.element == "link" or value.element == "image") 
+         (value.element == "link" or value.element == "image")
          and not value.removed then
         if value.is_active then
           return i
@@ -28455,8 +28524,8 @@
 %    \end{macrocode}
 % \begin{markdown}
 %
-% Parse content between two delimiters in the delimiter table `t`. Produce the respective link and image 
-% macros. 
+% Parse content between two delimiters in the delimiter table `t`. Produce the respective link and image
+% macros.
 %
 % \end{markdown}
 %  \begin{macrocode}
@@ -28538,7 +28607,7 @@
 %    \end{macrocode}
 % \begin{markdown}
 %
-% Resolve an inline link [a](b "c") from the delimiters at `opening_index` and `closing_index` 
+% Resolve an inline link [a](b "c") from the delimiters at `opening_index` and `closing_index`
 % within a delimiter table `t`. Here, compared to other types of links, no reference definition is needed.
 %
 % \end{markdown}
@@ -28571,7 +28640,7 @@
 % \begin{markdown}
 %
 % Resolve a full link [a][b] from the delimiters at `opening_index` and `closing_index` within a delimiter table `t`.
-% Continue if a tag `b` is not found in the references. 
+% Continue if a tag `b` is not found in the references.
 %
 % \end{markdown}
 %  \begin{macrocode}
@@ -28581,7 +28650,7 @@
     local r = self.lookup_reference(next_link_content)
 
     if r then
-      local inline_content = resolve_inline_following_content(t, next_link_closing_index, false, 
+      local inline_content = resolve_inline_following_content(t, next_link_closing_index, false,
                                                               t.match_link_attributes)
       r.attributes = join_attributes(r.attributes, inline_content.attributes)
       render_link_or_image(t, opening_index, next_link_closing_index, closing_index, r)
@@ -28591,9 +28660,9 @@
 %    \end{macrocode}
 % \begin{markdown}
 %
-% Resolve a collapsed link [a][] from the delimiters at `opening_index` and `closing_index` 
+% Resolve a collapsed link [a][] from the delimiters at `opening_index` and `closing_index`
 % within a delimiter table `t`.
-% Continue if a tag `a` is not found in the references. 
+% Continue if a tag `a` is not found in the references.
 %
 % \end{markdown}
 %  \begin{macrocode}
@@ -28613,7 +28682,7 @@
 % \begin{markdown}
 %
 % Parse a table of link and emphasis delimiters `t`.
-% First, iterate over the link delimiters and produce either link or image macros. 
+% First, iterate over the link delimiters and produce either link or image macros.
 % Then run `process_emphasis` over the entire delimiter table, resolving emphasis and strong
 % emphasis and parsing any content outside of closed delimiters.
 %
@@ -28625,8 +28694,8 @@
     end
 
     for i,value in ipairs(t) do
-      if not value.is_closing or 
-        value.type ~= "delimiter" or 
+      if not value.is_closing or
+        value.type ~= "delimiter" or
         not (value.element == "link" or value.element == "image") then
         goto continue
       end
@@ -28640,7 +28709,7 @@
       opening_delimiter.removed = true
 
       local link_type = opening_delimiter.link_type
-      
+
       if (link_type == "inline") then
         resolve_inline_link(t, opener_position, i)
       end
@@ -28657,7 +28726,7 @@
       ::continue::
     end
 
-    t[#t].content = t[#t].content:gsub("%s*$","") 
+    t[#t].content = t[#t].content:gsub("%s*$","")
 
     process_emphasis(t, 1, #t)
     local final_result = collect_emphasis_content(t, 1, #t)
@@ -28706,7 +28775,7 @@
     parsers.interrupting_bullets = parsers.fail
     parsers.interrupting_enumerators = parsers.fail
   else
-    parsers.interrupting_bullets  = parsers.bullet(parsers.dash, true) 
+    parsers.interrupting_bullets  = parsers.bullet(parsers.dash, true)
                                   + parsers.bullet(parsers.asterisk, true)
                                   + parsers.bullet(parsers.plus, true)
 
@@ -28756,10 +28825,10 @@
 
   parsers.NoSoftLineBreakEndline
                         = parsers.newline
-                        * (parsers.check_minimal_indent 
-                          * -V("NoSoftLineBreakEndlineExceptions") 
-                          + parsers.check_optional_indent 
-                          * -V("NoSoftLineBreakEndlineExceptions") 
+                        * (parsers.check_minimal_indent
+                          * -V("NoSoftLineBreakEndlineExceptions")
+                          + parsers.check_optional_indent
+                          * -V("NoSoftLineBreakEndlineExceptions")
                           * -parsers.starter)
                         * parsers.spacechar^0
                         / writer.space
@@ -28766,7 +28835,7 @@
 
   parsers.EndlineBreak = parsers.backslash * parsers.Endline
                                            / writer.hard_line_break
-  
+
   parsers.OptionalIndent
                      = parsers.spacechar^1 / writer.space
 
@@ -28790,7 +28859,7 @@
                     / writer.soft_line_break
 
   parsers.NonbreakingSpace
-                  = parsers.spacechar^2 * parsers.Endline 
+                  = parsers.spacechar^2 * parsers.Endline
                                         / writer.hard_line_break
                   + parsers.spacechar^1 * parsers.Endline^-1 * parsers.eof / ""
                   + parsers.spacechar^1 * parsers.Endline
@@ -28864,23 +28933,23 @@
 %
 % \end{markdown}
 %  \begin{macrocode}
-  parsers.DisplayHtml = Cs(parsers.check_trail 
-                          * ( parsers.html_comment    
-                            + parsers.html_special_block 
-                            + parsers.html_block         
-                            + parsers.html_any_block     
-                            + parsers.html_instruction   
-                            + parsers.html_cdatasection  
+  parsers.DisplayHtml = Cs(parsers.check_trail
+                          * ( parsers.html_comment
+                            + parsers.html_special_block
+                            + parsers.html_block
+                            + parsers.html_any_block
+                            + parsers.html_instruction
+                            + parsers.html_cdatasection
                             + parsers.html_declaration))
                         / writer.block_html_element
 
   parsers.indented_non_blank_line = parsers.indentedline - parsers.blankline
 
-  parsers.Verbatim  = Cs( 
-                        parsers.check_code_trail 
+  parsers.Verbatim  = Cs(
+                        parsers.check_code_trail
                       * (parsers.line - parsers.blankline)
                       * ((parsers.check_minimal_blank_indent_and_full_code_trail * parsers.blankline)^0
-                        * ((parsers.check_minimal_indent / "") * parsers.check_code_trail 
+                        * ((parsers.check_minimal_indent / "") * parsers.check_code_trail
                            * (parsers.line - parsers.blankline))^1)^0
                       ) / self.expandtabs / writer.verbatim
 
@@ -28921,23 +28990,23 @@
 
   parsers.list_blank = parsers.conditionally_indented_blankline
 
-  parsers.ref_or_block_list_separated = parsers.sep_group_no_output(parsers.list_blank) 
+  parsers.ref_or_block_list_separated = parsers.sep_group_no_output(parsers.list_blank)
                                       * parsers.minimally_indented_ref
                                       + parsers.block_sep_group(parsers.list_blank)
                                       * parsers.minimally_indented_block
 
   parsers.ref_or_block_non_separated  = parsers.minimally_indented_ref
-                                      + (parsers.succeed / writer.interblocksep) 
+                                      + (parsers.succeed / writer.interblocksep)
                                       * parsers.minimally_indented_block
                                       - parsers.minimally_indented_blankline
 
-  parsers.tight_list_loop_body_pair  = 
+  parsers.tight_list_loop_body_pair  =
     parsers.create_loop_body_pair(parsers.ref_or_block_non_separated,
                                   parsers.minimally_indented_par_or_plain_no_blank,
                                   (parsers.succeed / writer.interblocksep),
                                   (parsers.succeed / writer.paragraphsep))
 
-  parsers.loose_list_loop_body_pair  = 
+  parsers.loose_list_loop_body_pair  =
     parsers.create_loop_body_pair(parsers.ref_or_block_list_separated,
                                   parsers.minimally_indented_par_or_plain,
                                   parsers.block_sep_group(parsers.list_blank),
@@ -28969,7 +29038,7 @@
                                       * #parsers.list_blank
                                       * remove_indent("li")
                                       + ( (V("Reference") + (parsers.blankline / ""))
-                                        * parsers.check_minimal_indent 
+                                        * parsers.check_minimal_indent
                                         * parsers.tight_list_content_loop
                                         + (V("Reference") + (parsers.blankline / ""))
                                         + (parsers.tickbox^-1 / writer.escape)
@@ -28977,11 +29046,11 @@
                                         )
                                       * parsers.list_item_tightness_condition
                                   )
-  
+
   parsers.indented_content_loose  = Ct( (parsers.blankline / "")
                                       * #parsers.list_blank
                                       + ( (V("Reference") + (parsers.blankline / ""))
-                                        * parsers.check_minimal_indent 
+                                        * parsers.check_minimal_indent
                                         * parsers.loose_list_content_loop
                                         + (V("Reference") + (parsers.blankline / ""))
                                         + (parsers.tickbox^-1 / writer.escape)
@@ -29004,7 +29073,7 @@
 
   parsers.BulletListOfType = function(bullet_type)
     local bullet = parsers.bullet(bullet_type)
-    return  ( Ct( parsers.TightListItem(bullet) 
+    return  ( Ct( parsers.TightListItem(bullet)
                 * ( (parsers.check_minimal_indent / "")
                   * parsers.TightListItem(bullet)
                   )^0
@@ -29024,7 +29093,7 @@
             ) / writer.bulletlist
   end
 
-  parsers.BulletList = parsers.BulletListOfType(parsers.dash) 
+  parsers.BulletList = parsers.BulletListOfType(parsers.dash)
                      + parsers.BulletListOfType(parsers.asterisk)
                      + parsers.BulletListOfType(parsers.plus)
 
@@ -29046,11 +29115,11 @@
     return  Cg(enumerator, "listtype")
           * (Ct( parsers.TightListItem(Cb("listtype"))
                * ((parsers.check_minimal_indent / "") * parsers.TightListItem(enumerator))^0)
-          * Cc(true) 
-          * -#((parsers.list_blank^0 / "") 
+          * Cc(true)
+          * -#((parsers.list_blank^0 / "")
               * parsers.check_minimal_indent * enumerator)
           + Ct( parsers.LooseListItem(Cb("listtype"))
-              * ((parsers.list_blank^0 / "") 
+              * ((parsers.list_blank^0 / "")
                 * (parsers.check_minimal_indent / "") * parsers.LooseListItem(enumerator))^0)
           * Cc(false)
           ) * Ct(Cb("listtype")) / ordered_list
@@ -29090,10 +29159,10 @@
   parsers.AtxHeading = parsers.check_trail_no_rem
                      * Cg(parsers.heading_start, "level")
                      * ((C( parsers.optionalspace
-                          * parsers.hash^0 
+                          * parsers.hash^0
                           * parsers.optionalspace
                           * parsers.newline)
-                        + parsers.spacechar^1 
+                        + parsers.spacechar^1
                         * C(parsers.line))
                        / strip_atx_end
                        / parsers.parse_heading_text)
@@ -29108,7 +29177,7 @@
                        * parsers.newline^-1
 
   parsers.SetextHeading = parsers.freeze_trail * parsers.check_trail_no_rem
-                        * #(parsers.heading_text 
+                        * #(parsers.heading_text
                            * parsers.check_minimal_indent * parsers.check_trail * parsers.heading_level)
                         * Cs(parsers.heading_text)
                         / parsers.parse_heading_text
@@ -29637,16 +29706,59 @@
       table.sort(opt_string)
       local salt = table.concat(opt_string, ",") .. "," .. metadata.version
       local output
+      local function convert(input)
+        local document = self.parser_functions.parse_blocks(input)
+        local output = util.rope_to_string(writer.document(document))
 %    \end{macrocode}
 % \begin{markdown}
+% Remove block element / paragraph separators immediately followed by the
+% output of \luamref{writer->undosep}, possibly interleaved by section ends.
+% Then, remove any leftover output of \luamref{writer->undosep}.
+% \end{markdown}
+%  \begin{macrocode}
+        local undosep_start, undosep_end
+        local potential_secend_start, secend_start
+        local potential_sep_start, sep_start
+        while true do
+          -- find a `writer->undosep`
+          undosep_start, undosep_end = output:find(writer.undosep_text, 1, true)
+          if undosep_start == nil then break end
+          -- skip any preceding section ends
+          secend_start = undosep_start
+          while true do
+            potential_secend_start = secend_start - #writer.secend_text
+            if potential_secend_start < 1
+               or output:sub(potential_secend_start, secend_start - 1) ~= writer.secend_text then
+              break
+            end
+            secend_start = potential_secend_start
+          end
+          -- find an immediately preceding block element / paragraph separator
+          sep_start = secend_start
+          potential_sep_start = sep_start - #writer.interblocksep_text
+          if potential_sep_start >= 1
+             and output:sub(potential_sep_start, sep_start - 1) == writer.interblocksep_text then
+            sep_start = potential_sep_start
+          else
+            potential_sep_start = sep_start - #writer.paragraphsep_text
+            if potential_sep_start >= 1
+               and output:sub(potential_sep_start, sep_start - 1) == writer.paragraphsep_text then
+              sep_start = potential_sep_start
+            end
+          end
+          -- remove `writer->undosep` and immediately preceding block element / paragraph separator
+          output = output:sub(1, sep_start - 1)
+                .. output:sub(secend_start, undosep_start - 1)
+                .. output:sub(undosep_end + 1)
+        end
+        return output
+      end
+%    \end{macrocode}
+% \begin{markdown}
 % If we cache markdown documents, produce the cache file and transform its
 % filename to plain \TeX{} output via the \luamref{writer->pack} method.
 % \end{markdown}
 %  \begin{macrocode}
-      local function convert(input)
-        local document = self.parser_functions.parse_blocks(input)
-        return util.rope_to_string(writer.document(document))
-      end
       if options.eagerCache or options.finalizeCache then
         local name = util.cache(options.cacheDir, input, salt, convert,
                                 ".md" .. writer.suffix)
@@ -29840,11 +29952,11 @@
                          + parsers.bracketed
                          + parsers.inticks
                          + parsers.autolink
-                        + V("InlineHtml")
+                         + V("InlineHtml")
                          + V("Space") + V("Endline")
                          + (parsers.anyescaped
                            - (parsers.newline + parsers.rbracket + parsers.blankline^2))
-                         - (parsers.spnl * parsers.dash^-1 * parsers.at))^0)
+                         - (parsers.spnl * parsers.dash^-1 * parsers.at))^1)
 
       local citation_body_postnote
                     = Cs((parsers.alphanumeric^1
@@ -29856,18 +29968,28 @@
                          + (parsers.anyescaped
                            - (parsers.newline + parsers.rbracket + parsers.semicolon
                              + parsers.blankline^2))
-                         - (parsers.spnl * parsers.rbracket))^0)
+                         - (parsers.spnl * parsers.rbracket))^1)
 
       local citation_body_chunk
-                    = citation_body_prenote
-                    * parsers.spnlc * citation_name
+                    = ( citation_body_prenote
+                      * parsers.spnlc_sep
+                      + Cc("")
+                      * parsers.spnlc
+                    )
+                    * citation_name
                     * (parsers.internal_punctuation - parsers.semicolon)^-1
-                    * parsers.spnlc * citation_body_postnote
+                    * ( parsers.spnlc
+                      * citation_body_postnote
+                      + Cc("")
+                      * parsers.spnlc
+                    )
 
       local citation_body
                     = citation_body_chunk
-                    * (parsers.semicolon * parsers.spnlc
-                      * citation_body_chunk)^0
+                    * ( parsers.semicolon
+                      * parsers.spnlc
+                      * citation_body_chunk
+                    )^0
 
       local citation_headless_body_postnote
                     = Cs((parsers.alphanumeric^1
@@ -29883,8 +30005,10 @@
 
       local citation_headless_body
                     = citation_headless_body_postnote
-                    * (parsers.sp * parsers.semicolon * parsers.spnlc
-                      * citation_body_chunk)^0
+                    * ( parsers.semicolon
+                      * parsers.spnlc
+                      * citation_body_chunk
+                    )^0
 
       local citations
                     = function(text_cites, raw_cites)
@@ -29969,7 +30093,7 @@
 %  \begin{macrocode}
   local languages_json = (function()
     local base, prev, curr
-    for _, pathname in ipairs{util.lookup_files(language_map, { all=true })} do
+    for _, pathname in ipairs{kpse.lookup(language_map, { all=true })} do
       local file = io.open(pathname, "r")
       if not file then goto continue end
       local input = assert(file:read("*a"))
@@ -30180,7 +30304,7 @@
 
       local indented_blocks = function(bl)
         return Cs( bl
-              * (blank^1 * (parsers.check_minimal_indent / "") 
+              * (blank^1 * (parsers.check_minimal_indent / "")
                 * parsers.check_code_trail * -parsers.blankline * bl)^0
               * (blank^1 + parsers.eof))
       end
@@ -30348,7 +30472,7 @@
 
       local digit_marker = parsers.dig * parsers.dig^-8
 
-      local markers = { 
+      local markers = {
         {lowercase_opening_roman_marker, lowercase_roman_marker},
         {uppercase_opening_roman_marker, uppercase_roman_marker},
         lowercase_letter_marker,
@@ -30358,9 +30482,9 @@
         digit_marker
       }
 
-      local delims = { 
-        parsers.period, 
-        parsers.rparent 
+      local delims = {
+        parsers.period,
+        parsers.rparent
       }
 
       local markers_table = combine_markers_and_delims(markers, delims)
@@ -30466,11 +30590,11 @@
         return Cg(enumerator_start, "listtype")
              * (Ct( TightListItem(Cb("listtype"))
                  * ((parsers.check_minimal_indent / "") * TightListItem(enumerator_cont))^0)
-             * Cc(true) 
-             * -#((parsers.conditionally_indented_blankline^0 / "") 
+             * Cc(true)
+             * -#((parsers.conditionally_indented_blankline^0 / "")
                  * parsers.check_minimal_indent * enumerator_cont)
              + Ct( LooseListItem(Cb("listtype"))
-                 * ((parsers.conditionally_indented_blankline^0 / "") 
+                 * ((parsers.conditionally_indented_blankline^0 / "")
                    * (parsers.check_minimal_indent / "") * LooseListItem(enumerator_cont))^0)
              * Cc(false)
              ) * Ct(Cb("listtype")) / fancylist
@@ -30479,10 +30603,10 @@
       local FancyList = join_table_with_func(FancyListOfType, markers_table)
 
       local Endline   = parsers.newline
-                      * (parsers.check_minimal_indent 
-                        * -parsers.EndlineExceptions 
-                        + parsers.check_optional_indent 
-                        * -parsers.EndlineExceptions 
+                      * (parsers.check_minimal_indent
+                        * -parsers.EndlineExceptions
+                        + parsers.check_optional_indent
+                        * -parsers.EndlineExceptions
                         * -starter)
                       * parsers.spacechar^0
                       / writer.soft_line_break
@@ -30574,12 +30698,12 @@
         return str:gsub("^%s*(.-)%s*$", "%1")
       end
 
-      local tilde_infostring = Cs(Cs((V("HtmlEntity") 
-                                     + parsers.anyescaped 
+      local tilde_infostring = Cs(Cs((V("HtmlEntity")
+                                     + parsers.anyescaped
                                      - parsers.newline)^0)
                                  / strip_enclosing_whitespaces)
 
-      local backtick_infostring = Cs(Cs((V("HtmlEntity") 
+      local backtick_infostring = Cs(Cs((V("HtmlEntity")
                                         + (-#(parsers.backslash * parsers.backtick) * parsers.anyescaped)
                                          - parsers.newline
                                          - parsers.backtick)^0)
@@ -30588,14 +30712,14 @@
       local fenceindent
 
       local function has_trail(indent_table)
-        return indent_table ~= nil and 
-          indent_table.trail ~= nil and 
+        return indent_table ~= nil and
+          indent_table.trail ~= nil and
           next(indent_table.trail) ~= nil
       end
 
       local function has_indents(indent_table)
-        return indent_table ~= nil and 
-          indent_table.indents ~= nil and 
+        return indent_table ~= nil and
+          indent_table.indents ~= nil and
           next(indent_table.indents) ~= nil
       end
 
@@ -30808,24 +30932,57 @@
 % \par
 % \begin{markdown}
 %
-% Initialize a named group named `div_level` for tracking how deep we are
-% nested in divs.
+% Initialize a named group named `fenced_div_level` for tracking how deep
+% we are nested in divs and the named group `fenced_div_num_opening_indents`
+% for tracking the indent of the starting div fence. The former named group
+% is immutable and should roll back properly when we fail to match a fenced
+% div. The latter is mutable and may contain items from unsuccessful matches
+% on top. However, we always know how many items at the head of the latter we
+% can trust by consulting the former.
 %
 % \end{markdown}
 %  \begin{macrocode}
-      self.initialize_named_group("div_level", "0")
+      self.initialize_named_group("fenced_div_level", "0")
+      self.initialize_named_group("fenced_div_num_opening_indents")
 
-      local function increment_div_level(increment)
-        local function update_div_level(s, i, current_level) -- luacheck: ignore s i
-          current_level = tonumber(current_level)
-          local next_level = tostring(current_level + increment)
-          return true, next_level
+      local function increment_div_level()
+        local function push_indent_table(s, i, indent_table, -- luacheck: ignore s i
+                                         fenced_div_num_opening_indents, fenced_div_level)
+          fenced_div_level = tonumber(fenced_div_level) + 1
+          local num_opening_indents = 0
+          if indent_table.indents ~= nil then
+            num_opening_indents = #indent_table.indents
+          end
+          fenced_div_num_opening_indents[fenced_div_level] = num_opening_indents
+          return true, fenced_div_num_opening_indents
         end
 
-        return Cg( Cmt(Cb("div_level"), update_div_level)
-                 , "div_level")
+        local function increment_level(s, i, fenced_div_level) -- luacheck: ignore s i
+          fenced_div_level = tonumber(fenced_div_level) + 1
+          return true, tostring(fenced_div_level)
+        end
+
+        return Cg( Cmt( Cb("indent_info")
+                      * Cb("fenced_div_num_opening_indents")
+                      * Cb("fenced_div_level"), push_indent_table)
+                 , "fenced_div_num_opening_indents")
+             * Cg( Cmt( Cb("fenced_div_level"), increment_level)
+                 , "fenced_div_level")
       end
-      
+
+      local function decrement_div_level()
+        local function pop_indent_table(s, i, fenced_div_indent_table, fenced_div_level) -- luacheck: ignore s i
+          fenced_div_level = tonumber(fenced_div_level)
+          fenced_div_indent_table[fenced_div_level] = nil
+          return true, tostring(fenced_div_level - 1)
+        end
+
+        return Cg( Cmt( Cb("fenced_div_num_opening_indents")
+                      * Cb("fenced_div_level"), pop_indent_table)
+                 , "fenced_div_level")
+      end
+
+
       local non_fenced_div_block  = parsers.check_minimal_indent * V("Block")
                                   - parsers.check_minimal_indent_and_trail * fenced_div_end
 
@@ -30860,11 +31017,12 @@
                           return attr
                         end
                       / writer.div_begin
-                      * increment_div_level(1)
+                      * increment_div_level()
                       * parsers.skipblanklines
                       * Ct(content_loop)
                       * parsers.minimally_indented_blank^0
-                      * parsers.check_minimal_indent_and_trail * fenced_div_end * increment_div_level(-1)
+                      * parsers.check_minimal_indent_and_trail * fenced_div_end
+                      * decrement_div_level()
                       * (Cc("") / writer.div_end)
 
       self.insert_pattern("Block after Verbatim",
@@ -30878,18 +31036,39 @@
 %
 % If the `blank_before_div_fence` parameter is `false`, we will have the
 % closing div at the beginning of a line break the current paragraph if
-% we are currently nested in a div.
+% we are currently nested in a div and the indentation matches the opening
+% div fence.
 %
 % \end{markdown}
 %  \begin{macrocode}
-      local function check_div_level(s, i, current_level) -- luacheck: ignore s i
-        current_level = tonumber(current_level)
-        return current_level > 0
+      local function is_inside_div()
+        local function check_div_level(s, i, fenced_div_level) -- luacheck: ignore s i
+          fenced_div_level = tonumber(fenced_div_level)
+          return fenced_div_level > 0
+        end
+
+        return Cmt(Cb("fenced_div_level"), check_div_level)
       end
 
-      local is_inside_div = Cmt(Cb("div_level"), check_div_level)
-      local fencestart = is_inside_div * fenced_div_end
+      local function check_indent()
+        local function compare_indent(s, i, indent_table, -- luacheck: ignore s i
+                                      fenced_div_num_opening_indents, fenced_div_level)
+          fenced_div_level = tonumber(fenced_div_level)
+          local num_current_indents = (indent_table.current_line_indents ~= nil and
+                                      #indent_table.current_line_indents) or 0
+          local num_opening_indents = fenced_div_num_opening_indents[fenced_div_level]
+          return num_current_indents == num_opening_indents
+        end
 
+        return Cmt( Cb("indent_info")
+                  * Cb("fenced_div_num_opening_indents")
+                  * Cb("fenced_div_level"), compare_indent)
+      end
+
+      local fencestart = is_inside_div()
+                       * fenced_div_end
+                       * check_indent()
+
       if not blank_before_div_fence then
         self.update_rule("EndlineExceptions", function(previous_pattern)
           if previous_pattern == nil then
@@ -30945,10 +31124,10 @@
         return s:gsub("%s*$","")
       end
 
-      local heading_line  = (parsers.linechar 
+      local heading_line  = (parsers.linechar
                             - (parsers.attributes
                               * parsers.optionalspace
-                              * parsers.newline))^1 
+                              * parsers.newline))^1
                           - parsers.thematic_break_lines
 
       local heading_text  = heading_line
@@ -30956,7 +31135,7 @@
                           * parsers.newline^-1
 
       local SetextHeading  = parsers.freeze_trail * parsers.check_trail_no_rem
-                           * #(heading_text 
+                           * #(heading_text
                               * (parsers.attributes
                                 * parsers.optionalspace
                                 * parsers.newline)^-1
@@ -31131,7 +31310,7 @@
 %  \begin{macrocode}
       local define_reference_parser = (parsers.check_trail / "") * parsers.link_label * parsers.colon
                                     * parsers.spnlc * parsers.url
-                                    * ( parsers.spnlc_sep * parsers.title * (parsers.spnlc * Ct(parsers.attributes)) 
+                                    * ( parsers.spnlc_sep * parsers.title * (parsers.spnlc * Ct(parsers.attributes))
                                       * parsers.only_blank
                                       + parsers.spnlc_sep * parsers.title * parsers.only_blank
                                       + Cc("") * (parsers.spnlc * Ct(parsers.attributes)) * parsers.only_blank
@@ -31284,7 +31463,7 @@
 
         local indented_blocks = function(bl)
           return Cs( bl
-                * (blank^1 * (parsers.check_optional_indent / "") 
+                * (blank^1 * (parsers.check_optional_indent / "")
                   * parsers.check_code_trail * -parsers.blankline * bl)^0)
         end
 
@@ -31447,7 +31626,7 @@
                                         , table_hline_separator
                                         , table_hline_column)
 
-      local table_caption_beginning = (parsers.check_minimal_blank_indent_and_any_trail_no_rem 
+      local table_caption_beginning = (parsers.check_minimal_blank_indent_and_any_trail_no_rem
                                       * parsers.optionalspace * parsers.newline)^0
                                     * parsers.check_minimal_indent_and_trail
                                     * (P("Table")^-1 * parsers.colon)
@@ -32246,7 +32425,7 @@
 %
 % \end{markdown}
 %  \begin{macrocode}
-      local pathname = util.lookup_files(filename)
+      local pathname = kpse.lookup(filename)
       local input_file = assert(io.open(pathname, "r"),
         [[Could not open user-defined syntax extension "]]
         .. pathname .. [[" for reading]])

Modified: trunk/Master/texmf-dist/tex/generic/markdown/markdown.tex
===================================================================
--- trunk/Master/texmf-dist/tex/generic/markdown/markdown.tex	2024-04-03 23:45:59 UTC (rev 70855)
+++ trunk/Master/texmf-dist/tex/generic/markdown/markdown.tex	2024-04-04 20:36:41 UTC (rev 70856)
@@ -572,8 +572,8 @@
   { boolean }
   { true }
 \ExplSyntaxOff
-\def\markdownLastModified{2024-03-09}%
-\def\markdownVersion{3.4.2-0-ga45cf0ed}%
+\def\markdownLastModified{2024-04-04}%
+\def\markdownVersion{3.4.3-0-ge2c6be1a}%
 \let\markdownBegin\relax
 \let\markdownEnd\relax
 \let\markdownInput\relax

Modified: trunk/Master/texmf-dist/tex/luatex/markdown/markdown.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/markdown/markdown.lua	2024-04-03 23:45:59 UTC (rev 70855)
+++ trunk/Master/texmf-dist/tex/luatex/markdown/markdown.lua	2024-04-04 20:36:41 UTC (rev 70856)
@@ -58,7 +58,7 @@
 -- those in the standard .ins files.
 --
 local metadata = {
-    version   = "3.4.2-0-ga45cf0ed",
+    version   = "3.4.3-0-ge2c6be1a",
     comment   = "A module for the conversion from markdown to plain TeX",
     author    = "John MacFarlane, Hans Hagen, Vít Starý Novotný",
     copyright = {"2009-2016 John MacFarlane, Hans Hagen",
@@ -222,9 +222,6 @@
   s = s:gsub([["]], [[\"]])
   return [["]] .. s .. [["]]
 end
-function util.lookup_files(f, options)
-  return kpse.lookup(f, options)
-end
 function util.expand_tabs_in_line(s, tabstop)
   local tab = tabstop or 4
   local corr = 0
@@ -2488,7 +2485,7 @@
   end
   local char_table = {}
     for _, code_point in ipairs(code_points) do
-        table.insert(char_table, unicode.utf8.char(code_point))
+      table.insert(char_table, unicode.utf8.char(code_point))
     end
   return table.concat(char_table)
 end
@@ -2540,14 +2537,21 @@
   function self.pack(name)
     return [[\input{]] .. name .. [[}\relax]]
   end
+  self.interblocksep_text = "\\markdownRendererInterblockSeparator\n{}"
   function self.interblocksep()
     if not self.is_writing then return "" end
-    return "\\markdownRendererInterblockSeparator\n{}"
+    return self.interblocksep_text
   end
+  self.paragraphsep_text = "\\markdownRendererParagraphSeparator\n{}"
   function self.paragraphsep()
     if not self.is_writing then return "" end
-    return "\\markdownRendererParagraphSeparator\n{}"
+    return self.paragraphsep_text
   end
+  self.undosep_text = "\\markdownRendererUndoSeparator\n{}"
+  function self.undosep()
+    if not self.is_writing then return "" end
+    return self.undosep_text
+  end
   self.soft_line_break = function()
     if self.flatten_inlines then return "\n" end
     return "\\markdownRendererSoftLineBreak\n{}"
@@ -2911,6 +2915,7 @@
     if attributes["#" .. self.slice_end_identifier] ~= nil and
        self.slice_end_type == "^" then
       if self.is_writing then
+        table.insert(buf, self.undosep())
         table.insert(buf, tear_down_attributes())
       end
       self.is_writing = false
@@ -2949,6 +2954,7 @@
       if attributes["#" .. self.slice_end_identifier] ~= nil
          and self.slice_end_type == "$" then
         if self.is_writing then
+          table.insert(buf, self.undosep())
           table.insert(buf, tear_down_attributes())
         end
         self.is_writing = false
@@ -2961,105 +2967,107 @@
     end
     return buf
   end
-local function create_auto_identifier(s)
-  local buffer = {}
-  local prev_space = false
-  local letter_found = false
+  local function create_auto_identifier(s)
+    local buffer = {}
+    local prev_space = false
+    local letter_found = false
 
-  for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do
-    local char = utf8.char(code)
+    for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do
+      local char = utf8.char(code)
 
-    -- Remove everything up to the first letter.
-    if not letter_found then
-      local is_letter = unicode.utf8.match(char, "%a")
-      if is_letter then
-        letter_found = true
-      else
+      -- Remove everything up to the first letter.
+      if not letter_found then
+        local is_letter = unicode.utf8.match(char, "%a")
+        if is_letter then
+          letter_found = true
+        else
+          goto continue
+        end
+      end
+
+      -- Remove all non-alphanumeric characters, except underscores, hyphens, and periods.
+      if not unicode.utf8.match(char, "[%w_%-%.%s]") then
         goto continue
       end
-    end
 
-    -- Remove all non-alphanumeric characters, except underscores, hyphens, and periods.
-    if not unicode.utf8.match(char, "[%w_%-%.%s]") then
-      goto continue
-    end
-
-    -- Replace all spaces and newlines with hyphens.
-    if unicode.utf8.match(char, "[%s\n]") then
-      char = "-"
-      if prev_space then
-        goto continue
+      -- Replace all spaces and newlines with hyphens.
+      if unicode.utf8.match(char, "[%s\n]") then
+        char = "-"
+        if prev_space then
+          goto continue
+        else
+          prev_space = true
+        end
       else
-        prev_space = true
+        -- Convert all alphabetic characters to lowercase.
+        char = unicode.utf8.lower(char)
+        prev_space = false
       end
-    else
-      -- Convert all alphabetic characters to lowercase.
-      char = unicode.utf8.lower(char)
-      prev_space = false
+
+      table.insert(buffer, char)
+
+      ::continue::
     end
 
-    table.insert(buffer, char)
+    if prev_space then
+      table.remove(buffer)
+    end
 
-    ::continue::
+    local identifier = #buffer == 0 and "section" or table.concat(buffer, "")
+    return identifier
   end
+  local function create_gfm_auto_identifier(s)
+    local buffer = {}
+    local prev_space = false
+    local letter_found = false
 
-  if prev_space then
-    table.remove(buffer)
-  end
+    for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do
+      local char = utf8.char(code)
 
-  local identifier = #buffer == 0 and "section" or table.concat(buffer, "")
-  return identifier
-end
-local function create_gfm_auto_identifier(s)
-  local buffer = {}
-  local prev_space = false
-  local letter_found = false
+      -- Remove everything up to the first non-space.
+      if not letter_found then
+        local is_letter = unicode.utf8.match(char, "%S")
+        if is_letter then
+          letter_found = true
+        else
+          goto continue
+        end
+      end
 
-  for _, code in utf8.codes(uni_algos.normalize.NFC(s)) do
-    local char = utf8.char(code)
+      -- Remove all non-alphanumeric characters, except underscores and hyphens.
+      if not unicode.utf8.match(char, "[%w_%-%s]") then
+        prev_space = false
+        goto continue
+      end
 
-    -- Remove everything up to the first non-space.
-    if not letter_found then
-      local is_letter = unicode.utf8.match(char, "%S")
-      if is_letter then
-        letter_found = true
+      -- Replace all spaces and newlines with hyphens.
+      if unicode.utf8.match(char, "[%s\n]") then
+        char = "-"
+        if prev_space then
+          goto continue
+        else
+          prev_space = true
+        end
       else
-        goto continue
+        -- Convert all alphabetic characters to lowercase.
+        char = unicode.utf8.lower(char)
+        prev_space = false
       end
-    end
 
-    -- Remove all non-alphanumeric characters, except underscores and hyphens.
-    if not unicode.utf8.match(char, "[%w_%-%s]") then
-      prev_space = false
-      goto continue
+      table.insert(buffer, char)
+
+      ::continue::
     end
 
-    -- Replace all spaces and newlines with hyphens.
-    if unicode.utf8.match(char, "[%s\n]") then
-      char = "-"
-      if prev_space then
-        goto continue
-      else
-        prev_space = true
-      end
-    else
-      -- Convert all alphabetic characters to lowercase.
-      char = unicode.utf8.lower(char)
-      prev_space = false
+    if prev_space then
+      table.remove(buffer)
     end
 
-    table.insert(buffer, char)
-
-    ::continue::
+    local identifier = #buffer == 0 and "section" or table.concat(buffer, "")
+    return identifier
   end
-
-  if prev_space then
-    table.remove(buffer)
-  end
-
-  local identifier = #buffer == 0 and "section" or table.concat(buffer, "")
-  return identifier
-end
+  self.secbegin_text = "\\markdownRendererSectionBegin\n"
+  self.secend_text = "\n\\markdownRendererSectionEnd "
   function self.heading(s, level, attributes)
     local buf = {}
     local flat_text, inlines = table.unpack(s)
@@ -3069,8 +3077,8 @@
       table.insert(buf,
                    self.push_attributes("heading",
                                         nil,
-                                        "\\markdownRendererSectionBegin\n",
-                                        "\n\\markdownRendererSectionEnd "))
+                                        self.secbegin_text,
+                                        self.secend_text))
     end
 
     -- pop attributes for sections that have ended
@@ -3091,8 +3099,8 @@
     -- push attributes for the new section
     local start_output = {}
     local end_output = {}
-    table.insert(start_output, "\\markdownRendererSectionBegin\n")
-    table.insert(end_output, "\n\\markdownRendererSectionEnd ")
+    table.insert(start_output, self.secbegin_text)
+    table.insert(end_output, self.secend_text)
 
     table.insert(buf, self.push_attributes("heading",
                                            normalized_attributes,
@@ -3201,7 +3209,6 @@
 parsers.alphanumeric           = R("AZ","az","09")
 parsers.keyword                = parsers.letter
                                * (parsers.alphanumeric + parsers.dash)^0
-parsers.internal_punctuation   = S(":;,.?")
 
 parsers.doubleasterisks        = P("**")
 parsers.doubleunderscores      = P("__")
@@ -3212,7 +3219,35 @@
 parsers.succeed                = P(true)
 parsers.fail                   = P(false)
 
-parsers.escapable              = S("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
+parsers.internal_punctuation   = S(":;,.?")
+parsers.ascii_punctuation      = S("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
+parsers.punctuation            = {}
+(function()
+  local pathname = kpse.lookup("UnicodeData.txt")
+  local file = assert(io.open(pathname, "r"),
+    [[Could not open file "UnicodeData.txt"]])
+  for line in file:lines() do
+    local codepoint, major_category = line:match("^(%x+);[^;]*;(%a)")
+    if major_category == "P" or major_category == "S" then
+      local code = unicode.utf8.char(tonumber(codepoint, 16))
+      if parsers.punctuation[#code] == nil then
+        parsers.punctuation[#code] = parsers.fail
+      end
+      local code_parser = parsers.succeed
+      for i = 1, #code do
+        local byte = code:sub(i, i)
+        local byte_parser = S(byte)
+        code_parser = code_parser
+                    * byte_parser
+      end
+      parsers.punctuation[#code] = parsers.punctuation[#code]
+                                 + code_parser
+    end
+  end
+  assert(file:close())
+end)()
+
+parsers.escapable              = parsers.ascii_punctuation
 parsers.anyescaped             = parsers.backslash / "" * parsers.escapable
                                + parsers.any
 
@@ -3463,7 +3498,7 @@
   return blank_starter_index
 end
 
-local function traverse_indent(s, i, indent_table, is_optional, is_blank)
+local function traverse_indent(s, i, indent_table, is_optional, is_blank, current_line_indents)
   local new_index = i
 
   local preceding_indentation = 0
@@ -3471,6 +3506,10 @@
 
   local blank_starter = left_blank_starter(indent_table)
 
+  if current_line_indents == nil then
+    current_line_indents = {}
+  end
+
   for index = 1,#indent_table.indents do
     local value = indent_table.indents[index]
     local pattern = decode_pattern(value.name)
@@ -3480,10 +3519,10 @@
     if new_indent_info == nil then
       local blankline_end = lpeg.match(Ct(parsers.blankline * Cg(Cp(), "pos")), s, new_index)
       if is_optional or not indent_table.ignore_blockquote_blank or not blankline_end then
-        return is_optional, new_index, current_trail
+        return is_optional, new_index, current_trail, current_line_indents
       end
 
-      return traverse_indent(s, tonumber(blankline_end.pos), indent_table, is_optional, is_blank)
+      return traverse_indent(s, tonumber(blankline_end.pos), indent_table, is_optional, is_blank, current_line_indents)
     end
 
     local raw_last_trail = new_indent_info[1]
@@ -3502,7 +3541,7 @@
 
     if next(current_trail) ~= nil then
       if not space_only and current_trail.is_code then
-        return is_optional, new_index, current_trail
+        return is_optional, new_index, current_trail, current_line_indents
       end
       if current_trail.internal_remainder ~= nil then
         raw_last_trail = current_trail.internal_remainder
@@ -3534,7 +3573,7 @@
     local sp = process_starter_spacing(total_indent_level, spacing_to_process, minimum, left_strip_length)
 
     if space_only and not sp.is_minimum then
-      return is_optional or (is_blank and blank_starter <= index), new_index, current_trail
+      return is_optional or (is_blank and blank_starter <= index), new_index, current_trail, current_line_indents
     end
 
     local indent_length = raw_last_trail_length + delimiter_length + sp.left_total_stripped
@@ -3548,10 +3587,12 @@
 
     current_trail = {is_code=sp.is_code, remainder=sp.remainder, internal_remainder=sp.minimum_remainder,
                      total_length=sp.total_length, full_remainder=sp.full_remainder}
+
+    current_line_indents[#current_line_indents + 1] = new_indent_info
     new_index = next_index
   end
 
-  return true, new_index, current_trail
+  return true, new_index, current_trail, current_line_indents
 end
 
 local function check_trail(expect_code, is_code)
@@ -3609,9 +3650,11 @@
     return true
   end
 
-  local passes, new_index, current_trail = traverse_indent(s, i, indent_table, is_optional, is_blank)
+  local passes, new_index, current_trail, current_line_indents =
+    traverse_indent(s, i, indent_table, is_optional, is_blank)
 
   if passes then
+    indent_table.current_line_indents = current_line_indents
     indent_table = add_trail(indent_table, current_trail)
     return new_index, indent_table
   end
@@ -4113,7 +4156,7 @@
 
 parsers.html_inline_comment = (parsers.html_comment_start / "")
                             * -P(">") * -P("->")
-                            * Cs((V("NoSoftLineBreakEndline") + parsers.any - P("--")
+                            * Cs((V("NoSoftLineBreakEndline") + parsers.any
                                 - parsers.nested_breaking_blank - parsers.html_comment_end)^0)
                             * (parsers.html_comment_end / "")
 
@@ -4907,7 +4950,6 @@
       return lpeg.R("\240\244") * cont * cont * cont
     end
   end
-
   local function check_unicode_type(s, i, start_pos, end_pos, chartype)
     local c
     local char_length
@@ -4917,15 +4959,22 @@
       else
         char_length = pos + 1
       end
-      c = lpeg.match({ C(utf8_by_byte_count(char_length)) },s,i+pos)
-      if (c ~= nil) and (unicode.utf8.match(c, chartype)) then
-        return i
+
+      if (chartype == "punctuation") then
+        if lpeg.match(parsers.punctuation[char_length], s, i+pos) then
+          return i
+        end
+      else
+        c = lpeg.match({ C(utf8_by_byte_count(char_length)) },s,i+pos)
+        if (c ~= nil) and (unicode.utf8.match(c, chartype)) then
+          return i
+        end
       end
     end
   end
 
   local function check_preceding_unicode_punctuation(s, i)
-    return check_unicode_type(s, i, -4, -1, "%p")
+    return check_unicode_type(s, i, -4, -1, "punctuation")
   end
 
   local function check_preceding_unicode_whitespace(s, i)
@@ -4933,7 +4982,7 @@
   end
 
   local function check_following_unicode_punctuation(s, i)
-    return check_unicode_type(s, i, 0, 3, "%p")
+    return check_unicode_type(s, i, 0, 3, "punctuation")
   end
 
   local function check_following_unicode_whitespace(s, i)
@@ -6136,7 +6185,43 @@
       local output
       local function convert(input)
         local document = self.parser_functions.parse_blocks(input)
-        return util.rope_to_string(writer.document(document))
+        local output = util.rope_to_string(writer.document(document))
+        local undosep_start, undosep_end
+        local potential_secend_start, secend_start
+        local potential_sep_start, sep_start
+        while true do
+          -- find a `writer->undosep`
+          undosep_start, undosep_end = output:find(writer.undosep_text, 1, true)
+          if undosep_start == nil then break end
+          -- skip any preceding section ends
+          secend_start = undosep_start
+          while true do
+            potential_secend_start = secend_start - #writer.secend_text
+            if potential_secend_start < 1
+               or output:sub(potential_secend_start, secend_start - 1) ~= writer.secend_text then
+              break
+            end
+            secend_start = potential_secend_start
+          end
+          -- find an immediately preceding block element / paragraph separator
+          sep_start = secend_start
+          potential_sep_start = sep_start - #writer.interblocksep_text
+          if potential_sep_start >= 1
+             and output:sub(potential_sep_start, sep_start - 1) == writer.interblocksep_text then
+            sep_start = potential_sep_start
+          else
+            potential_sep_start = sep_start - #writer.paragraphsep_text
+            if potential_sep_start >= 1
+               and output:sub(potential_sep_start, sep_start - 1) == writer.paragraphsep_text then
+              sep_start = potential_sep_start
+            end
+          end
+          -- remove `writer->undosep` and immediately preceding block element / paragraph separator
+          output = output:sub(1, sep_start - 1)
+                .. output:sub(secend_start, undosep_start - 1)
+                .. output:sub(undosep_end + 1)
+        end
+        return output
       end
       if options.eagerCache or options.finalizeCache then
         local name = util.cache(options.cacheDir, input, salt, convert,
@@ -6254,11 +6339,11 @@
                          + parsers.bracketed
                          + parsers.inticks
                          + parsers.autolink
-                        + V("InlineHtml")
+                         + V("InlineHtml")
                          + V("Space") + V("Endline")
                          + (parsers.anyescaped
                            - (parsers.newline + parsers.rbracket + parsers.blankline^2))
-                         - (parsers.spnl * parsers.dash^-1 * parsers.at))^0)
+                         - (parsers.spnl * parsers.dash^-1 * parsers.at))^1)
 
       local citation_body_postnote
                     = Cs((parsers.alphanumeric^1
@@ -6270,18 +6355,28 @@
                          + (parsers.anyescaped
                            - (parsers.newline + parsers.rbracket + parsers.semicolon
                              + parsers.blankline^2))
-                         - (parsers.spnl * parsers.rbracket))^0)
+                         - (parsers.spnl * parsers.rbracket))^1)
 
       local citation_body_chunk
-                    = citation_body_prenote
-                    * parsers.spnlc * citation_name
+                    = ( citation_body_prenote
+                      * parsers.spnlc_sep
+                      + Cc("")
+                      * parsers.spnlc
+                    )
+                    * citation_name
                     * (parsers.internal_punctuation - parsers.semicolon)^-1
-                    * parsers.spnlc * citation_body_postnote
+                    * ( parsers.spnlc
+                      * citation_body_postnote
+                      + Cc("")
+                      * parsers.spnlc
+                    )
 
       local citation_body
                     = citation_body_chunk
-                    * (parsers.semicolon * parsers.spnlc
-                      * citation_body_chunk)^0
+                    * ( parsers.semicolon
+                      * parsers.spnlc
+                      * citation_body_chunk
+                    )^0
 
       local citation_headless_body_postnote
                     = Cs((parsers.alphanumeric^1
@@ -6297,8 +6392,10 @@
 
       local citation_headless_body
                     = citation_headless_body_postnote
-                    * (parsers.sp * parsers.semicolon * parsers.spnlc
-                      * citation_body_chunk)^0
+                    * ( parsers.semicolon
+                      * parsers.spnlc
+                      * citation_body_chunk
+                    )^0
 
       local citations
                     = function(text_cites, raw_cites)
@@ -6359,7 +6456,7 @@
 M.extensions.content_blocks = function(language_map)
   local languages_json = (function()
     local base, prev, curr
-    for _, pathname in ipairs{util.lookup_files(language_map, { all=true })} do
+    for _, pathname in ipairs{kpse.lookup(language_map, { all=true })} do
       local file = io.open(pathname, "r")
       if not file then goto continue end
       local input = assert(file:read("*a"))
@@ -7040,19 +7137,46 @@
                            * parsers.colon^3
                            * parsers.optionalspace
                            * (parsers.newline + parsers.eof)
-      self.initialize_named_group("div_level", "0")
+      self.initialize_named_group("fenced_div_level", "0")
+      self.initialize_named_group("fenced_div_num_opening_indents")
 
-      local function increment_div_level(increment)
-        local function update_div_level(s, i, current_level) -- luacheck: ignore s i
-          current_level = tonumber(current_level)
-          local next_level = tostring(current_level + increment)
-          return true, next_level
+      local function increment_div_level()
+        local function push_indent_table(s, i, indent_table, -- luacheck: ignore s i
+                                         fenced_div_num_opening_indents, fenced_div_level)
+          fenced_div_level = tonumber(fenced_div_level) + 1
+          local num_opening_indents = 0
+          if indent_table.indents ~= nil then
+            num_opening_indents = #indent_table.indents
+          end
+          fenced_div_num_opening_indents[fenced_div_level] = num_opening_indents
+          return true, fenced_div_num_opening_indents
         end
 
-        return Cg( Cmt(Cb("div_level"), update_div_level)
-                 , "div_level")
+        local function increment_level(s, i, fenced_div_level) -- luacheck: ignore s i
+          fenced_div_level = tonumber(fenced_div_level) + 1
+          return true, tostring(fenced_div_level)
+        end
+
+        return Cg( Cmt( Cb("indent_info")
+                      * Cb("fenced_div_num_opening_indents")
+                      * Cb("fenced_div_level"), push_indent_table)
+                 , "fenced_div_num_opening_indents")
+             * Cg( Cmt( Cb("fenced_div_level"), increment_level)
+                 , "fenced_div_level")
       end
 
+      local function decrement_div_level()
+        local function pop_indent_table(s, i, fenced_div_indent_table, fenced_div_level) -- luacheck: ignore s i
+          fenced_div_level = tonumber(fenced_div_level)
+          fenced_div_indent_table[fenced_div_level] = nil
+          return true, tostring(fenced_div_level - 1)
+        end
+
+        return Cg( Cmt( Cb("fenced_div_num_opening_indents")
+                      * Cb("fenced_div_level"), pop_indent_table)
+                 , "fenced_div_level")
+      end
+
       local non_fenced_div_block  = parsers.check_minimal_indent * V("Block")
                                   - parsers.check_minimal_indent_and_trail * fenced_div_end
 
@@ -7087,11 +7211,12 @@
                           return attr
                         end
                       / writer.div_begin
-                      * increment_div_level(1)
+                      * increment_div_level()
                       * parsers.skipblanklines
                       * Ct(content_loop)
                       * parsers.minimally_indented_blank^0
-                      * parsers.check_minimal_indent_and_trail * fenced_div_end * increment_div_level(-1)
+                      * parsers.check_minimal_indent_and_trail * fenced_div_end
+                      * decrement_div_level()
                       * (Cc("") / writer.div_end)
 
       self.insert_pattern("Block after Verbatim",
@@ -7099,14 +7224,34 @@
 
       self.add_special_character(":")
 
-      local function check_div_level(s, i, current_level) -- luacheck: ignore s i
-        current_level = tonumber(current_level)
-        return current_level > 0
+      local function is_inside_div()
+        local function check_div_level(s, i, fenced_div_level) -- luacheck: ignore s i
+          fenced_div_level = tonumber(fenced_div_level)
+          return fenced_div_level > 0
+        end
+
+        return Cmt(Cb("fenced_div_level"), check_div_level)
       end
 
-      local is_inside_div = Cmt(Cb("div_level"), check_div_level)
-      local fencestart = is_inside_div * fenced_div_end
+      local function check_indent()
+        local function compare_indent(s, i, indent_table, -- luacheck: ignore s i
+                                      fenced_div_num_opening_indents, fenced_div_level)
+          fenced_div_level = tonumber(fenced_div_level)
+          local num_current_indents = (indent_table.current_line_indents ~= nil and
+                                      #indent_table.current_line_indents) or 0
+          local num_opening_indents = fenced_div_num_opening_indents[fenced_div_level]
+          return num_current_indents == num_opening_indents
+        end
 
+        return Cmt( Cb("indent_info")
+                  * Cb("fenced_div_num_opening_indents")
+                  * Cb("fenced_div_level"), compare_indent)
+      end
+
+      local fencestart = is_inside_div()
+                       * fenced_div_end
+                       * check_indent()
+
       if not blank_before_div_fence then
         self.update_rule("EndlineExceptions", function(previous_pattern)
           if previous_pattern == nil then
@@ -8116,7 +8261,7 @@
   end
   for _, user_extension_filename in ipairs(options.extensions) do
     local user_extension = (function(filename)
-      local pathname = util.lookup_files(filename)
+      local pathname = kpse.lookup(filename)
       local input_file = assert(io.open(pathname, "r"),
         [[Could not open user-defined syntax extension "]]
         .. pathname .. [[" for reading]])



More information about the tex-live-commits mailing list.