diff --git a/AUTHORS.md b/AUTHORS.md
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -25,6 +25,7 @@
 - Amy de Buitléir
 - Anabra
 - Anders Waldenborg
+- Andonome
 - Andreas Deininger
 - Andreas Lööw
 - Andreas Scherer
@@ -87,6 +88,7 @@
 - Christoffer Sawicki
 - Christophe Dervieux
 - Christopher Kenny
+- Clar Fon
 - Clare Macrae
 - Clint Adams
 - Conal Elliott
@@ -151,6 +153,7 @@
 - Fyodor Sheremetyev
 - Gabor Pali
 - Gabriel Lewertowski
+- Gaurav Vijay Jadhav
 - Gavin Beatty
 - George Stagg
 - Georgi Lyubenov
@@ -366,6 +369,7 @@
 - Recai Oktaş
 - Repetitive
 - Reuben Thomas
+- Robert Szarka
 - Robertas
 - Rowan Rodrik van der Molen
 - Roland Hieber
@@ -378,6 +382,7 @@
 - Salim B
 - Sam S. Almahri
 - Sam May
+- Samuel Huang
 - Samuel Tardieu
 - Saumel Lemmenmeier
 - Santiago Zarate
@@ -467,6 +472,7 @@
 - Yoan Blanc
 - You Jiangbin
 - Yuchen Pei
+- Yusuf Efe
 - Zihang Chen
 - 3w36zj6
 - arcnmx
@@ -549,4 +555,5 @@
 - willj-dev
 - wuffi
 - wzy
+- zenor0
 - λx.x
diff --git a/MANUAL.txt b/MANUAL.txt
--- a/MANUAL.txt
+++ b/MANUAL.txt
@@ -1,7 +1,7 @@
 ---
 title: Pandoc User's Guide
 author: John MacFarlane
-date: 2026-08-28
+date: 2026-09-26
 ---
 
 # Synopsis
@@ -2937,17 +2937,24 @@
     to e.g. `20px`, but it also accepts `pt`
     (12pt = 16px in most browsers).
 
-`fontcolor`
+`fontcolor`, `fontcolordark`
 :   sets the CSS `color` property on the `html` element.
 
-`linkcolor`
+`linkcolor`, `linkcolordark`
 :   sets the CSS `color` property on all links.
 
+`quotebordercolor`, `quotebordercolordark`
+:   sets the CSS `border-color` property for the left-border of
+:   `blockquote` elements.
+
+`quotecolor`, `quotecolordark`
+:   sets the CSS `color` property for `blockquote` elements.
+
 `monofont`
-:   sets the CSS `font-family` property on `code` elements.
+:   sets the CSS `font-family` property on `code` and `pre` elements.
 
-`monobackgroundcolor`
-:   sets the CSS `background-color` property on `code` elements
+`monobackgroundcolor`, `monobackgroundcolordark`
+:   sets the CSS `background-color` property on `code` and `pre` elements
     and adds extra padding.
 
 `linestretch`
@@ -2957,7 +2964,7 @@
 `maxwidth`
 :   sets the CSS `max-width` property (default is 36em).
 
-`backgroundcolor`
+`backgroundcolor`, `backgroundcolordark`
 :   sets the CSS `background-color` property on the `html` element.
 
 `margin-left`, `margin-right`, `margin-top`, `margin-bottom`
@@ -2984,6 +2991,41 @@
     ---
 
 [CSS]: https://developer.mozilla.org/en-US/docs/Learn/CSS
+
+#### Note on `color-scheme`
+
+By default, the browser's chosen `color-scheme` (either `light` or `dark`)
+will affect the color of various unstyled elements like input fields, which are
+generally only applicable if your document is specifically targeting HTML.
+
+By default, Pandoc emits `color-scheme: light dark` to allow the system to
+choose between a default light-mode or dark-mode scheme, but it alters this
+if variables specifically override colors, under the assumption that the user
+wishes for that scheme to always be used.
+
+Currently, this is only per a basic heuristic that checks whether the following
+variables are provided:
+
+* `fontcolor`
+* `fontcolordark`
+* `backgroundcolor`
+* `backgroundcolordark`
+
+And if only one "mode" is provided (light or dark), `color-scheme` is set so
+that this chosen mode is used always, regardless of the system's preference for
+light or dark mode.
+
+Note that there are a few caveats to this, namely that:
+
+* If the browser does not support `light-dark` (older than ~2024), then it will
+  always choose light mode, regardless of this override.
+* When printing, light mode is always chosen, and color variables are ignored.
+* Syntax highlighting is currently unaffected by color scheme.
+
+In general, if you wish to override the theming for a single scheme, you should
+choose to override either the normal `*color` variables (light mode) or the
+`*colordark` variables (dark mode) to ensure that the styles do not conflict
+with other styles added by new versions of Pandoc or the browser itself.
 
 ### Variables for HTML math
 
diff --git a/benchmark/benchmark-pandoc.hs b/benchmark/benchmark-pandoc.hs
--- a/benchmark/benchmark-pandoc.hs
+++ b/benchmark/benchmark-pandoc.hs
@@ -18,6 +18,7 @@
 -}
 import Text.Pandoc
 import Text.Pandoc.MIME
+import Text.Pandoc.Shared (stringify, stringifyInlines)
 import Control.DeepSeq (force)
 import Control.Monad.Except (throwError)
 import qualified Text.Pandoc.UTF8 as UTF8
@@ -89,6 +90,19 @@
                          writerFun def{ writerExtensions = wexts} d)
                     doc
 
+-- | A large inline sequence exercising both the common constructors
+-- and the ones 'stringify' special-cases ('Quoted', 'Note', 'Cite').
+bigInlines :: [Inline]
+bigInlines = concat $ replicate 1000
+  [ Str "Lorem", Space
+  , Emph [Str "ipsum", Space, Strong [Str "dolor"]], Space
+  , Quoted DoubleQuote [Str "sit", Space, Str "amet"], SoftBreak
+  , Link nullAttr [Str "consectetur"] ("https://example.com", "title")
+  , Space, Code nullAttr "adipiscing", Space
+  , Note [Para [Str "footnote", Space, Emph [Str "text"]]]
+  , Cite [] [Str "elit"], LineBreak
+  ]
+
 main :: IO ()
 main = do
   inp <- UTF8.toText <$> B.readFile "test/testsuite.txt"
@@ -102,4 +116,9 @@
     , bgroup "readers" $ mapMaybe (readerBench doc . fst)
                          (sortOn fst
                            readers :: [(T.Text, Reader PandocPure)])
+    , env (pure $ force bigInlines) $ \ils ->
+      bgroup "stringify"
+        [ bench "stringify" $ nf stringify ils
+        , bench "stringifyInlines" $ nf stringifyInlines ils
+        ]
     ]
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,738 @@
 # Revision history for pandoc
 
+## pandoc 3.12 (2026-09-27)
+
+  * Markdown reader:
+
+    + Make alert keywords case-insensitive (#11836, Hendrik Erz).
+      In addition, the class `alert` is now added to the produced
+      Divs.
+    + Fix stream position handling in `base64DataURI`.
+    + Cheaply reject `bareURL` before trying uri/emailAddress.
+    + Reset sourcepos in `parseWithString'` when parsing a block
+      quote or list item. Otherwise it can happen that by the
+      time `parseWithString'` is called, the position has already
+      been set to the next file on the command line. Fixes an
+      odd bug with `rebase_relative_paths` (#11888).
+    + `rebase_relative_paths`: recognize URLs with unknown schemes
+      (#11858).
+    + Use `takeWhile1P` in the hot inline parsers `str`, `code`,
+      `enclosure`, and `mmdShortSubscript`.
+    + Reset sourcepos in `parseWithString'` when parsing a block quote
+      or list item (#11888). This ensures that `rebase_relative_paths`
+      will see the right source file.
+
+  * Typst reader:
+
+    + Map `form: "prose"` citations to AuthorInText (#11846,
+      Samuel Huang).
+    + Make the handler maps monomorphic by wrapping the handlers
+      in newtypes with polymorphic fields (BlockHandler, InlineHandler).
+    + Store document labels in a Set instead of a list.
+    + In `pInline`, only perform the label-target check for
+      `ref` elements, not for every inline element.
+    + Handle `highlight` as a mark span (#11879, Samuel Huang).
+    + Collapse citations around soft break (#11897).
+    + Handle block content in inline element bodies (#11881,
+      Samuel Huang).
+    + Handle `#par` (explicit paragraph element).
+
+  * LaTeX reader:
+
+    + Support LaTeX3 (xparse) document commands (#7540).
+      Support the features described in the LaTeX usrguide.
+    + Fix `\qed` to produce U+00A0 (nbsp) instead of BEL.
+    + Fix `unescapeURL` handling of escaped backslash.
+    + Require that `\newif` names begin with "if".
+    + Fix position off-by-one after `##` in tokenizer.
+    + Fix doubled source positions in `retokenizeComment`.
+    + Make raw token capture O(1) per token.
+    + Make `untokenize` linear instead of quadratic.
+    + Fail `macroDef` fast on non-macro-defining commands.
+    + Peek at next token directly in `peekTok` instead of going
+      through `satisfyTok`.
+    + Skip `doMacros` state update for non-macro tokens.
+    + Build command dispatch maps (`inlineCommands`, etc.) once per parse.
+    + Keep ASCII quotes when ligatures are disabled.
+    + Don't discard state changes made in optional arguments.
+    + Keep nested conditionals balanced in `\iftrue` etc.
+
+  * Docx reader:
+
+    + Read ScreenTips as link titles (#11869, Robert Szarka).
+    + Use `comment-id` instead of `id` in AST for comments.
+
+  * ODT reader:
+
+    + Rewrite in monadic style. Net -994 lines. No changes to test output.
+    + Handle `textProperties` on paragraph styles (#2623).
+      Previously these just got ignored, not applied to the
+      paragraph's text.
+
+  * HTML reader:
+
+    + Don't require a closing tag for checkbox inputs.
+      `<input>` is a void element.
+    + Allow omitted `</tr>` in tables. The `</tr>` closing
+      tag is optional in HTML.
+    + Don't drop cells when there are too few `<col>` elements.
+    + Let the first of duplicate attributes win. HTML specifies that
+      the first occurrence wins.
+    + Respect `raw_html` for inline `<style>` elements.
+    + Use a Map for the note table.
+    + Report the noteref position for unresolved notes. The
+      ReferenceNotFound warning was logged after the
+      whole document had been parsed, so it always pointed at the
+      end of the input. Record the position of the first reference
+      to each note and use it in the warning.
+    + Find list style anywhere in the class attribute.
+      Previously `<ol class="fancy lower-roman">` got DefaultStyle,
+      because the whole class attribute was compared against the
+      known style names. Check each class individually. As a side
+      effect, an unrecognized class no longer prevents falling back
+      to the style attribute.
+    + Limit iframe nesting depth.
+    + `htmlTag`: Don't copy the remaining input on each invocation. A
+      space was appended to the remaining input to guarantee a
+      TagPosition token after the parsed tag; since the input is a
+      strict Text, this copied the entire remaining input every
+      time htmlTag was called (e.g. for every inline HTML tag in a
+      markdown document), giving quadratic behavior in tag-dense
+      documents. Instead, handle the case where the tag is the
+      final token by computing the end-of-input position directly.
+    + Pass the tag name to `pSpanLike`. The inline dispatcher already
+      knows which span-like element it is looking at, so there is
+      no need for pSpanLike to try a parser.
+    + Fix `pre/code` attribute precedence for first-wins dedup.
+    + Implement `pSatisfy` as a single parsec primitive.
+    + Add a fast path to `pTagText`: when the text contains no
+      character that could parse as anything but Str, Space, or
+      SoftBreak under the enabled extensions (and we are not in a
+      pre element), return B.text directly.
+    + Reorder `pTagContents` to try `pStr` and `pSpace` before the
+      math, smart punctuation, and raw TeX parsers. This is safe
+      because pStr cannot consume the special characters that
+      start those parsers, and it avoids most guard checks on the
+      slow path. This makes the html reader benchmark about 33%
+      faster and halves its allocation.
+
+  * Muse reader:
+
+    + Try `str` earlier in inline parser. This makes the reader 2x
+      faster and reduces heap allocation by 60%.
+    + Check raw line before parsing table rows. Table row parsers
+      are speculatively tried at every paragraph line boundary via
+      the terminator continuation. This change gives another 2x
+      speed improvement.
+
+  * Org reader:
+
+    + Fix hard parse failure on bare backslash.
+    + Recognize `.pdf` as an image format (#11859). This matches
+      the behavior of Emacs, which will render `[[file:foo.pdf]]`
+      as an image.
+    + Make `#+OPTIONS: ^:nil` disable all sub-/superscript parsing.
+    + Allow `-` and `_` in inline footnote labels. Org footnote labels
+      may contain word-constituent characters, hyphens and underscores.
+    + Fix swapped arguments in exportSettings parser.
+    + Don't lowercase meta keyword twice.
+    + Avoid quadratic complexity when parsing table rows. Parsing
+      a 32000-row table drops from 7.8s to 0.9s (and scales
+      linearly instead of quadratically); maximum residency also improves.
+    + Use a Set for anchor ids. Reading a document with 64000
+      anchors and as many internal links drops from 23.5s to 5.2s
+      and now scales linearly.
+
+  * RST reader:
+
+    + Use a predicate to test for for special characters.
+    + Avoid double-parsing lines preceding a non-underline.
+    + Fail fast when no link can start at this position.
+    + Use `lookupGE` to find the next anonymous key. Parsing a
+      document with 16000 anonymous links drops from 5.2s to 2.0s.
+    + Replace association list with Map for resolving note references.
+      Parsing a document with 16000 named notes drops from 5.8s
+      to 3.5s, and scaling is now linear.
+    + Fix a bug that led to an empty class for code blocks with
+      no specified language.
+
+  * CommonMark reader:
+
+    + Avoid nested walks in `tex_math_gfm` handling.
+    + Avoid rebuilding the token list with `map id` in
+      `sourceToToks` in the common case where the source starts
+      at line 1.
+
+  * AsciiDoc reader:
+
+    + Resolve footnotes, stem, and icons during conversion.
+      Previously the reader made three separate passes
+      over the parsed AsciiDoc AST (for footnotes, stem math types,
+      and icons) before converting to the pandoc AST. Instead,
+      resolve all three during the toPandoc conversion, which
+      already traverses everything once, threading footnote state
+      and document attributes through a StateT layer. Table
+      headers are now converted before body rows so that footnote
+      resolution follows document order. This makes the reader
+      about twice as fast on typical documents.
+
+  * Djot reader:
+
+    + Fix a bug that led to an empty class for code blocks with
+      no specified language.
+
+  * Vimwiki reader:
+
+    + Split class attribute and put it in the proper slot in
+      pandoc's Attr.
+
+  * Typst writer:
+
+    + Omit blank line at end of block (#11844). This is just a
+      cosmetic change; it is not semantically significant.
+    + Emit label for bare table if present (#11849). Previously a label
+      was only emitted if the table was figurized (and not
+      `.typst:no-figure`).
+
+  * RST writer:
+
+    + Apply `nowrap` to just footnote label, not body.
+      This bug surfaced after `nowrap` was fixed in doclayout.
+
+  * Docx writer:
+
+    + Fix sizing for images (#11838). Setting size to a percent
+      now scales image to percent of page width. Previously a
+      percent width or height would just provide a maximum bound
+      rather than scaling.
+    + Include default style even if paragraph has non-style props
+      (#11867). Previously a paragraph that was, e.g. center-aligned
+      would be missing its default Body Text style. Also ensure that
+      any block sequence (list items, table cells, block quote,
+      div), First Paragraph is set for the first paragraph in the item.
+    + Use `_` to start all bookmark names (#11845). This ensures that
+      they are "hidden" and will not be read by screen readers.
+    + Honor CSL hanging-indent and spacing hints (#11871, Samuel Huang).
+    + Add a fast path to `withDirection`.
+    + Avoid double `withDirection` for Space and SoftBreak.
+    + Map link/image titles to ScreenTips.
+    + Write link titles as ScreenTips (#11869, Robert Szarka).
+    + Properly signal error if reference docx can't be parsed.
+      Previously this led to an incomprehensible error at a later
+      phase.
+    + Make `convertSpace` linear instead of quadratic. On an ad
+      hoc benchmark (4 paragraphs of 80K words each), conversion
+      time drops from 3.2s to 1.1s, and time no longer depends on
+      paragraph length (16 x 20K words previously took 1.8s, now
+      also 1.2s).
+    + Cache token-type styles instead of rebuilding per Code.
+      On an ad hoc benchmark with 100K inline code spans,
+      conversion time drops from 2.4s to 1.5s.
+    + Add FirstParagraph class after display math (#11900).
+      This way the continuation text can be styled flush-left
+      in a style where normal paragraphs are indented.
+    + Treat a "mixed" widths table as all-default (#11899). Previously if
+      some columns were ColWidthDefault and others ColWidth 0.x,
+      we would get columns with a specified width of 0 for the default
+      ones. Instead, treat all columns as default in this case.
+    + Use `comment-id` instead of `id` in AST for comments.
+      Note that the Docx writer will still interpret an `id`
+      attribute for legacy compatibility, so if you use markdown
+      files that specify `id`, they should still work.
+
+  * TEI writer:
+
+    + Use `rend`, not `rendition` attribute, on milestone
+      (#11842, Yusuf Efe) `rendition` takes pointers to rendition
+      descriptions, while `rend` is the free-text attribute,
+      which is what a plain "line" value needs.
+
+  * LaTeX writer:
+
+    + Avoid nested walk in table cell line-break handling.
+    + Don't use `footnotehyper` for notes in longtable.
+      Instead, generate them manually as we do for floating tables.
+      This removes our dependency on footnotehyper, and resolves a
+      compatibility problem with `endfloat` (#11857).
+    + Remove unused `stInternalLinks` state field. The
+      field has not been read since the writer switched to always
+      adding hypertargets, but we were still doing a full-document
+      query to populate it.
+    + Avoid needless conversions in `sectionHeader`.
+      The note-free and link-free variants of the heading text
+      were rendered for every heading, even though the former is
+      only needed when the heading contains a note, image, or
+      identified span, and the latter only for unnumbered, listed
+      headings.
+    + Fuse preprocessing passes in `inlineListToLaTeX`.
+      The strut-insertion and quote-kerning fixups were separate
+      list traversals, allocating an intermediate list each, and
+      this function is called at every level of inline nesting.
+      Combine them into a single pass.
+    + Avoid String round-trip for highlighted inline code.
+    + Don't unpack code string to choose `lstinline` delimiter.
+    + Only compute PDF trailer ID when `SOURCE_DATE_EPOCH` is set.
+    + Use `showHex` instead of (slower) `printf` in `toLabel`.
+
+  * RST writer:
+
+    + Don't re-transform stored labels and alt text.
+      Reference labels, image substitution labels, and alt text are
+      stored after the document-wide walk in inlineListToRST has
+      already applied transformInlines (flattening, backslash-space
+      insertion, etc.). Rendering them with inlineListToRST applied
+      these non-idempotent transformations a second time, inserting
+      duplicate `\ ` markers. With `--reference-links` this could
+      make the inline reference and its definition render
+      differently, producing a broken RST reference.
+
+  * Native writer:
+
+    + Render directly instead of using pretty-show.
+      Previously writeNative used pretty-show's `ppDoc`, which shows
+      the document, tokenizes and re-parses the result into a
+      generic Value, and lays that out via Text.PrettyPrint.HughesPJ.
+      The layout step dominated the cost of the writer (and of
+      any pipeline producing native output). We now build a
+      width-cached layout tree directly from the AST and render
+      it with a small renderer that reproduces HughesPJ's layout
+      algorithm exactly (including the ribbon computation with
+      ribbonsPerLine = 1.2 and the treatment of glued closing
+      delimiters), so the output is byte-for-byte identical to
+      before. Verified against the old binary on all golden
+      .native files and the markdown test corpus at many column
+      widths, in both standalone and plain modes. The writer is
+      about 4x faster. Also remove the pretty and pretty-show
+      dependencies.
+
+  * XML writer:
+
+    + Respect `--standalone`. When standalone is selected,
+      we get a full Pandoc element with xml header and metadata. When
+      not, we get a fragment -- just the blocks.
+    + Use the pretty-printer instead of manual newlines.
+      Render the document with `ppcElement`, using a
+      configuration that treats elements with inline content as
+      inline tags, so that no significant whitespace is added
+      inside them. Consecutive text nodes are merged, SoftBreak
+      is written as a literal newline, and whitespace runs that
+      would not survive a roundtrip (e.g. `" \n"` or `"\n\n"`)
+      are encoded as Space and SoftBreak elements.
+    + Encode attribute names that are not valid XML names.
+      Pandoc attribute names may contain characters that are not
+      allowed in XML attribute names, such as colons
+      (`typst:property`). Use the common convention of encoding
+      such characters as `_xHHHH_`, where `HHHH` is the hexadecimal
+      code of the character: the writer encodes attribute names
+      (`foo:bar` becomes `foo_x003A_bar`) and the reader decodes
+      them.
+    + Don't drop attributes with empty values. The writer dropped
+      any attribute with an empty value, so user key-value
+      attributes like `("k","")` disappeared and did not round
+      trip.
+    + Improve performance: the XML writer is now on par with the
+      JSON writer.
+
+  * EPUB writer:
+
+    + Render TOC item titles in the host monad. Previously each TOC
+      item title in the nav entry was rendered with a separate
+      `runPure (writeHtmlStringForEPUB ...)`. Since every one of
+      these invocations starts with a fresh CommonState, the
+      translations YAML file was re-read and re-parsed for every
+      TOC item, which accounted for a significant part of the
+      EPUB writer's run time on documents with many sections.
+    + Speed up MathML/SVG detection for the manifest.
+
+  * Powerpoint writer:
+
+    + Add archive entries in a single pass.
+    + Cache the parsed slide master in WriterEnv.
+    + Skip speaker-notes walk when there are no notes.
+
+  * ANSI writer:
+
+    + Fix missing bar on blockquote's first line (#11804,
+      Gaurav Vijay Jadhav D.).
+
+  * HTML writer:
+
+    + Fix typo in `intrinsicEventsHTML4`. The list had
+      `onmouseout` twice and was missing `onmousemove`.
+    + Don't emit `<p></p>` for paragraphs with no rendered content.
+    + Improve email obfuscation. Preserve formatting (e.g. emphasis)
+      in the link text of obfuscated mailto links. Preserve link
+      attributes in reference- and JavaScript-obfuscated links.
+      Avoid double-escaping the already-rendered link text in the
+      fallback branch for unparseable mailto URLs.
+    + Respect `--id-prefix` in EPUB3 footnote section id.
+    + Respect incremental/nonincremental classes inside columns.
+    + Make KaTeX CSS URL handling consistent with the JS URL.
+    + Use truncate consistently for table width percentages.
+      Previously the table width could exceed the sum of column widths.
+    + Avoid walking section contents when not producing slides.
+    + Hoist attribute set unions to top level.
+    + Make `strToHtml` more efficient. Replace the
+      `T.groupBy`-based implementation, which allocated a list of
+      Text fragments and round-tripped through String, with a
+      simple `T.break` scanner.
+
+  * Org writer:
+
+    + Don't render table body rows twice. Table rows
+      were converted once to compute column widths and then again
+      to produce the output. Since blockListToOrg is stateful, any
+      footnote in a table cell was registered twice, yielding
+      duplicate footnote definitions and skewed numbering; it also
+      doubled the rendering work. Reuse the first conversion.
+    + Use `#+begin_export html` for raw HTML blocks. `#+begin_html`
+      was removed in Org 9.0 (2016).
+    + Don't treat bare punctuation as a list marker.
+      The check that keeps ordered list markers from ending up at
+      the beginning of a line matched `Str "."` and `Str ")"`,
+      since `T.all isDigit ""` is True. Require at least one digit.
+    + Don't emit `<<>>` for spans with no id.
+    + Avoid `=` delimiter for inline code containing `=`.
+      Org has no escape mechanism inside verbatim text, so `=code
+      with ==` did not parse as verbatim. Fall back to the
+      equivalent `~...~` delimiter when the content contains `=`
+      (and no `~`).
+    + Escape square brackets in links. Link targets
+      containing square brackets broke the bracket link syntax.
+      Escape targets the way Emacs' `org-link-escape` does:
+      backslash-escape brackets and double backslash runs occurring
+      before a bracket or at the end of the target. Link
+      descriptions cannot contain escapes; instead, like
+      `org-link-make-string`, insert a zero-width space between
+      consecutive closing brackets and before a closing bracket at
+      the end of the description.
+    + Build escaped strings from chunks, not characters.
+      `escapeString` allocated one Doc node per character for any
+      string containing a non-alphanumeric character. Split on the
+      (rare) special characters instead and emit intervening text
+      as single literals. No change in output.
+    + Emit definitions for footnotes nested in footnotes.
+    + Use a counter for footnote numbers. Computing the
+      reference number as `length stNotes + 1` walked the
+      accumulated note list for every footnote, making note
+      numbering quadratic in the number of notes.
+
+  * Org reader and writer:
+
+    + Make code line comma-escaping match Emacs' behavior.
+      Org's escaping rule for code in src/example blocks adds a
+      comma to lines matching `^[ \t]*,*(\*|#\+)`, i.e. lines
+      already starting with commas before `*` or `#+` get an
+      additional comma; unescaping removes one comma from such
+      lines. The writer previously left a literal `,#+foo` line
+      unescaped, and the reader then stripped its comma when
+      reading the result back, corrupting the code on round trips.
+      Writer and reader now both handle runs of commas, matching Emacs.
+
+  * Text.Pandoc.ImageSize:
+
+    + ImageType now derives Eq [API change].
+    + Fix typo in bare JPEG signature detection.
+    + Fix size detection for lossless (VP8L) WebP.
+    + Make EMF parsing more robust.
+    + Don't let zlib errors escape as exceptions in `pdfSize`.
+      Treat malformed streams as a parse scanner (so we keep
+      scanning the rest for a `/MediaBox`).
+    + Fix AVIF detection and parsing.
+    + Take bounding box origin into account for EPS. The
+      size was computed from the upper corner alone, giving wrong
+      dimensions for EPS files whose bounding box has a non-zero origin.
+    + Handle commas and fractional numbers in SVG `viewBox`.
+    + Add tests for image type and size detection (Tests.ImageSize).
+    + Determine JPEG size without decoding the image (which can
+      allocate huge amounts of memory). If the header scan fails,
+      we still fall back to the full decoder.
+    + Scan PDF object streams in chunks, not byte by byte.
+    + Speed up `findSvgTag` by using a single pass. Up to 60X
+      faster on files with few `<` characters.
+    + Determine PNG size without decoding the image.
+      If the header scan fails, we still fall back to the full decoder.
+    + Use `writerDpi` for AVIF images instead of hardcoding 72.
+      With the default options this changes the assumed resolution from
+      72 to 96 dpi.
+    + Allow whitespace between number and unit in `numUnit`.
+      So e.g. `width="3 cm"` is now recognized.
+    + Handle largesize and size-0 boxes in AVIF parser.
+    + Make checkDpi default to 72 for negative dpi values, not
+      0; a negative dpi would produce negative dimensions in
+      `sizeInPoints`.
+
+  * Text.Pandoc.SelfContained:
+
+    + Fix inverted charset condition in `makeDataURI`.
+    + Keep semicolon in `@import` fallback output.
+    + Make `</script` check case-insensitive.
+    + Prefix all `url(#...)` occurrences in SVG attributes.
+      Previously only the first got prefixed rewritten.
+    + Handle gzip decompression errors gracefully.
+    + Remove unused `isHtml5` field from ConvertState.
+    + Cache fetched resources. Previously every occurrence of a
+      resource was fetched, decompressed, and CSS-rewritten
+      independently, so a document referencing the same image
+      N times triggered N network requests. Failed fetches are
+      cached too, so each missing resource is now reported only once.
+    + Only add `role` and `aria-label` when inlining SVGs.
+      Do not add them to other elements with `src` attributes.
+    + Escape only what is needed in textual data URIs.
+
+  * Text.Pandoc.UTF8:
+
+    + Avoid copying input when it contains no CRs. `toText` and
+      `toTextLazy `unconditionally ran a CR-removing
+      filter over the input, allocating a full copy of the document
+      even in the common case where no CRs are present. Check for a
+      CR first (B.elem, a fast memchr) and reuse the input buffer
+      unchanged if none is found; for the lazy variant, do this
+      chunk-wise to preserve laziness. On a 10 MB LF-only input
+      this makes `toText` over 4x faster; when CRs are present the
+      extra scan is not measurable.
+    + Make `readFile` exception-safe. Use `withFile`
+      instead of `openFile` so the handle is closed even if reading
+      throws.
+
+  * Text.Pandoc.Class:
+
+    + Avoid copying input in `toTextM`. Skip the CR-filtering
+      copy when the input contains no CRs, as already done in
+      Text.Pandoc.UTF8.toText.
+    + Make `runSilently` error-safe. Previously, if the action
+      passed to runSilently threw an error that was later caught,
+      the verbosity remained pinned at ERROR and all previously
+      accumulated log messages were lost. Now the original log
+      and verbosity are restored even when the action fails.
+    + Fix `isRelativeToParentDir`. Compare the first path
+      component rather than just looking at a prefix, to
+      correctly handle paths like `..foo/bar.yaml`.
+    + Fix base64 detection in `extractURIData`. The base64 indicator
+      in a data URI is the final parameter of the media type and may
+      follow other parameters, e.g. `charset`. Previously, the
+      code expected `;base64` to be the only parameter.
+    + Fix percent-decoding in `extractURIData`.
+    + Report accurate offset in `toTextM` errors. Scan for the first
+      invalid UTF-8 sequence and report its actual position and byte.
+    + Reset HTTP manager in `setNoCheckCertificate`. The HTTP manager
+      is created lazily with TLS settings based on `stNoCheckCertificate`
+      and then cached in CommonState, so changing the option after the
+      first request had no effect. Discard the cached manager when the
+      option's value changes.
+    + Don't follow symlink cycles in `addToFileTree`.
+    + Finish factoring `openURL` into Text.Pandoc.Class.IO.HTTP.
+      Commit 455bea907 added the new module but did not register it
+      in pandoc.cabal or remove the original definitions.
+    + `logOutput`: avoid multiple `hPutStrLn`, which can cause confusing
+      interleaving.
+
+  * Text.Pandoc.MediaBag:
+
+    + Use hashlazy to avoid copying media contents.
+    + Treat `data:` and `file:` URI schemes case-insensitively.
+    + Make MediaItem mime type and path strict. `mediaContents`
+      is left lazy so contents need not be forced at insert time.
+    + Use `Text.Pandoc.URI.isURI` in `canonicalize`. `Network.URI.isURI`
+      treats Windows drive-letter paths like `c:/foo.png` as URIs.
+    + Only reject `..` as a path component. The insertMedia check used
+      `isInfixOf`, so a harmless name like foo..bar.png was silently
+      renamed to its content hash.
+    + Collapse `.` and `..` components in `canonicalize`. `normalise`
+      does not remove redundant path components, so `img/../a.png`
+      and `a.png` were distinct keys. Use `makeCanonical` (as
+      PandocPure's FileTree already does for its path-indexed map),
+      which also handles duplicate and trailing slashes, replacing
+      backslashes with slashes first.
+    + Prevent `mediaPath` collisions between keys. The friendly
+      mediaPath was derived by percent-unescaping the key, so
+      distinct keys like `a%20b.png` and `a b.png` produced the same
+      mediaPath ("a b.png") and silently clobbered each other on
+      extraction (and inside docx/epub archives). Now the original name
+      is only kept if the key contains no percent sign, so `mediaPath`
+      equals the key and distinct keys yield distinct paths; anything
+      percent-encoded gets a content-hash name. Hashed names can
+      only coincide for identical contents, which is harmless.
+
+  * Text.Pandoc.XML.Light:
+
+    + Fix escaping of repeated `]]>` in `CDATA`.
+    + Make `escStr` more efficient.
+    + Avoid round-trips in `ppCDataS` prettify path.
+      This makes `showCData` and `ppcCData` unused, so they are
+      removed.
+    + Parse XML fragments from the event stream instead of using
+      xml-conduit's document parser, which requires a single root
+      element. Our earlier woraround with a wrapper element was
+      fragile. Behavior changes:
+
+      - Content fragments with an XML declaration or DOCTYPE
+        followed by multiple root elements, and text-only or empty
+        input, now parse instead of erroring.
+      - Attributes now preserve document order instead of being sorted
+        alphabetically (the document parser stored them in a Map).
+      - Errors for unresolved entities and mismatched tags now
+        report source positions.
+
+   + Use `text-builder` package for rendering, instead of text's
+     lazy Text builder. On a large document this cuts docx conversion
+     time by about 13%; output is byte-for-byte identical.
+   + Expose `ppcTopElement` from Output.
+   + ConfigPP now has a field `inlineTag` that checks for inline
+     tags. Inline tags are printed on one line and not indented,
+     by default. Export `useInlineTags`, `prettyConfigPP`.
+
+  * Text.Pandoc.Sources:
+
+    + Add bulk `takeWhileP`/`takeWhile1P` combinators [API change].
+      Character streams over Sources previously had to be
+      consumed one character at a time via `satisfy`, at a cost of
+      several allocations and monadic binds per character. The new
+      combinators scan a whole run of matching characters with a
+      single parser invocation using `T.span`, while replicating the
+      exact semantics of `T.pack <$> many/many1 (satisfy f)`,
+      including empty-chunk handling, position updates at chunk
+      boundaries, and parsec's error messages.
+    + Use the new bulk `takeWhileP`/`takeWhile1P` combinators across
+      readers.
+
+  * Text.Pandoc.Translations:
+
+    + `setTranslations` now keeps an already-loaded translation
+       table when the language is unchanged, instead of
+       unconditionally clearing the cache and forcing a re-read and
+       re-parse of the translations YAML file on the next `translateTerm`.
+    + Term names in translation files are now parsed with a
+       precomputed Map lookup instead of the derived Read instance,
+       which is very slow for a 22-constructor enum.
+
+  * Text.Pandoc.Chunks:
+
+    + Remove vestigial `nav-path` attribute and `rmNavAttrs` walk.
+      This no longer did anything; output is unaffected.
+    + Use `compactifyTable` for tables produced by all readers.
+      Remove old ad hoc `paraToPlain` at the table cell level.
+      This should ensure that we don't get tables that mix Plain
+      and Para (#11864). Such tables tend to look funny when rendered in
+      docx and other formats.
+
+  * Text.Pandoc.Data:
+
+    + Fix `getDataFileNames` with `-embed_data_files`. Previously
+      it was not looking in the right directory and not
+      recursing.
+
+  * Text.Pandoc.Shared:
+
+    + `taskListItemFromAscii`: Fix incorrect treatment of `[ ]` as
+      checked.
+    + Add `stringifyInlines`, a single-pass `stringify` for
+      inlines [API change]. This is about 6x faster than `stringify`
+      for long inline sequences
+    + Speed up `stringify` by making it accumulate `[Text]`
+      and concatenate once at the end, instead of mappending at every node.
+    + Use `stringifyInlines` instead of `stringify` where possible.
+    + Add new function `compactifyTable`. [API change]
+      This converts cells that consist in a single Para block to a
+      Plain, provided the table contains only such cells (or empty cells).
+    + Drop expired entries in `decrementTrailingRowSpans`.
+      Entries whose RowSpan fell to 0 were left in the map, relying
+      on every consumer to guard against them. Delete them instead.
+    + Recognize empty task list items in `toTaskListItem`.
+    + Make `endsWithPlain` look inside DefinitionList.
+      endsWithPlain recursed into the last item of BulletList and
+      OrderedList but ignored DefinitionList, so list items ending
+      with a compact definition list were treated as loose by the
+      RST, Org, and Haddock writers.
+    + Avoid Text -> String round-trips in `htmlAttrs`.
+      This adds a FromText constraint to `htmlAttrs` and
+      `tagWithAttrs` [API change].
+    + Fuse traversals in `ensureValidXmlIdentifiers`.
+
+  * Text.Pandoc.Writers.Shared:
+
+    + Fix logic bug in `splitSentences`.
+    + `lookupMetaBool`: treat empty block or inline list as False.
+    + `htmlAttrs`: escape id and class attributes, like the others.
+    + `stripLeadingTrailingSpace` - strip multiple Space, if present.
+    + `toSubscript`: handle minus sign.
+    + Fix `ensureValidXmlIdentifiers` for Figure and table
+      sub-elements, resolving a bug that produced broken internal
+      links in the HTML4/XHTML, EPUB, DocBook, TEI, ICML, FB2, and
+      ODT writers.
+
+  * Text.Pandoc.Parsing:
+
+    + Improve performance of `uriScheme` by using a trie. Up to 15%
+      faster in URL-heavy documents with `autolink_bare_uris`.
+    + Minor code cleanup.
+    + Remove `$` checks in math when delim is `\(` or `\\(`.
+    + Make `anyOrderedListMarker` more efficient.
+
+  * HTML template:
+
+    + Dark mode support (#11831, Clar Fon).
+
+  * reveal.js template:
+
+    + Fix plugin paths (#11907).
+
+  * flake.nix: parse allow-newer and allow-newer-deps in
+    stack.yaml.
+
+  * Make `embed_data_files` flag default to True. Remove flag
+    settings from cabal.project. This makes it possible to
+    override it on the command line.
+
+  * Depend on commonmark 0.3.1, commonmark-extensions 0.2.7.3,
+    commonmark-pandoc 0.3.0.2 (major performance improvements).
+
+  * Depend on released asciidoc 0.1.1 (major performance
+    improvements).
+
+  * Use released texmath 0.13.3 (major performance improvements).
+
+  * Depend on released djot 0.1.4.3 (major performance
+    improvements).
+
+  * Use released citeproc 0.14 (major performance improvements).
+
+  * Use released doclayout 0.6.
+
+  * Use released zip-archive 0.5 (major performance
+    improvements).
+
+  * Use released skylighting-0.15 (major performance
+    improvements).
+
+  * Depend on released doctemplates 0.11.1.
+
+  * Depend on released typst 0.12.
+
+  * Require text >= 2.0.
+
+  * Bump upper bound for unicode-data.
+
+  * Allow crypton 2.0.x.
+
+  * Allow Diff 2.0.
+
+  * Add `tools/diff-golden-tests.sh`.
+
+  * Fix `tools/diff-zip.sh` on non-Darwin.
+
+  * Add `tools/benchplot.js`. This creates a nice graph comparing two
+    benchmarks.
+
+  * `typst-properties.md`: fix fill syntax in Typst property
+    examples (#11855, zenor0).
+
+  * Remove tested-with from cabal file. We tend not to keep it up to date.
+
+  * Fix typo in Lua filter example (#11875, Andonome).
+
+  * Fix a bug in jats-reader.xml (duplicate attribute)
+
 ## pandoc 3.11 (2026-08-28)
 
   * Add `--math-method` option. This replaces (now deprecated but
@@ -60,6 +793,11 @@
     + Fix treatment of DefaultHighlighting (#11829).
 
   * Docx writer:
+
+    + Honor CSL bibliography formatting hints emitted by citeproc (#11871).
+      Bibliographies from styles with `hanging-indent="true"` (e.g. APA)
+      now get a hanging indent, and CSL `line-spacing`/`entry-spacing`
+      are mapped to paragraph spacing properties.
 
     + Initialize envLang from `lang` metadata (#11301).
       This ensures that setting `lang` will affect the whole document.
diff --git a/data/templates/common.latex b/data/templates/common.latex
--- a/data/templates/common.latex
+++ b/data/templates/common.latex
@@ -88,9 +88,6 @@
 \makeatletter
 \patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
 \makeatother
-% Allow footnotes in longtable head/foot
-\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}}
-\makesavenoteenv{longtable}
 $endif$
 $endif$
 $--
diff --git a/data/templates/default.revealjs b/data/templates/default.revealjs
--- a/data/templates/default.revealjs
+++ b/data/templates/default.revealjs
@@ -30,7 +30,7 @@
   <link rel="stylesheet" href="$revealjs-url$/dist/theme/black.css" id="theme">
 $endif$
 $if(highlight-js)$
-  <link rel="stylesheet" href="$revealjs-url$/plugin/highlight/$highlightjs-theme$.css">
+  <link rel="stylesheet" href="$revealjs-url$/dist/plugin/highlight/$highlightjs-theme$.css">
 $endif$
 $for(css)$
   <link rel="stylesheet" href="$css$"/>
@@ -84,14 +84,14 @@
   <script src="$revealjs-url$/dist/reveal.js"></script>
 
   <!-- reveal.js plugins -->
-  <script src="$revealjs-url$/plugin/notes/notes.js"></script>
-  <script src="$revealjs-url$/plugin/search/search.js"></script>
-  <script src="$revealjs-url$/plugin/zoom/zoom.js"></script>
+  <script src="$revealjs-url$/dist/plugin/notes.js"></script>
+  <script src="$revealjs-url$/dist/plugin/search.js"></script>
+  <script src="$revealjs-url$/dist/plugin/zoom.js"></script>
 $if(mathjax)$
-  <script src="$revealjs-url$/plugin/math/math.js"></script>
+  <script src="$revealjs-url$/dist/plugin/math.js"></script>
 $endif$
 $if(highlight-js)$
-  <script src="$revealjs-url$/plugin/highlight/highlight.js"></script>
+  <script src="$revealjs-url$/dist/plugin/highlight.js"></script>
 $endif$
 
   <script>
diff --git a/data/templates/styles.html b/data/templates/styles.html
--- a/data/templates/styles.html
+++ b/data/templates/styles.html
@@ -13,7 +13,10 @@
   line-height: $linestretch$;
 $endif$
   color: $if(fontcolor)$$fontcolor$$else$#1a1a1a$endif$;
+  color: light-dark($if(fontcolor)$$fontcolor$$else$#1a1a1a$endif$, $if(fontcolordark)$$fontcolordark$$else$#fdfdfd$endif$);
   background-color: $if(backgroundcolor)$$backgroundcolor$$else$#fdfdfd$endif$;
+  background-color: light-dark($if(backgroundcolor)$$backgroundcolor$$else$#fdfdfd$endif$, $if(backgroundcolordark)$$backgroundcolordark$$else$#1a1a1a$endif$);
+  color-scheme:$if(fontcolor)$ light$elseif(backgroundcolor)$ light$elseif(fontcolordark)$$elseif(backgroundcolordark)$$else$ light$endif$$if(fontcolordark)$ dark$elseif(backgroundcolordark)$ dark$elseif(fontcolor)$$elseif(backgroundcolor)$$else$ dark$endif$;
 }
 body {
   margin: 0 auto;
@@ -38,12 +41,17 @@
 }
 @media print {
   html {
-    background-color: $if(backgroundcolor)$$backgroundcolor$$else$white$endif$;
+    color-scheme: light only;
   }
-  body {
+  html, body, pre, code {
     background-color: transparent;
+  }
+  body, a, a:visited, blockquote {
     color: black;
   }
+  pre, code {
+    padding: 0;
+  }
   p, h2, h3 {
     orphans: 3;
     widows: 3;
@@ -55,11 +63,13 @@
 p {
   margin: 1em 0;
 }
-a {
-  color: $if(linkcolor)$$linkcolor$$else$#1a1a1a$endif$;
-}
-a:visited {
-  color: $if(linkcolor)$$linkcolor$$else$#1a1a1a$endif$;
+a, a:visited {
+  color: $if(linkcolor)$$linkcolor$$else$inherit$endif$;
+$if(linkcolor)$
+  color: light-dark($linkcolor$, $if(linkcolordark)$$linkcolordark$$else$inherit$endif$);
+$elseif(linkcolordark)$
+  color: light-dark(inherit, $linkcolordark$);
+$endif$
 }
 img {
   max-width: 100%;
@@ -88,8 +98,11 @@
 blockquote {
   margin: 1em 0 1em 1.7em;
   padding-left: 1em;
-  border-left: 2px solid #e6e6e6;
-  color: #606060;
+  border-left: 2px solid;
+  border-color: $if(quotebordercolor)$$quotebordercolor$$else$#e6e6e6$endif$;
+  border-color: light-dark($if(quotebordercolor)$$quotebordercolor$$else$#e6e6e6$endif$, $if(quotebordercolordark)$$quotebordercolordark$$else$#4c4c4c$endif$);
+  color: $if(quotecolor)$$quotecolor$$else$#606060$endif$;
+  color: light-dark($if(quotecolor)$$quotecolor$$else$#606060$endif$, $if(quotecolordark)$$quotecolordark$$else$#b6b6b6$endif$);
 }
 $if(abstract)$
 div.abstract {
@@ -109,7 +122,12 @@
   font-family: $if(monofont)$$monofont$$else$Menlo, Monaco, Consolas, 'Lucida Console', monospace$endif$;
 $if(monobackgroundcolor)$
   background-color: $monobackgroundcolor$;
+  background-color: light-dark($monobackgroundcolor$, $if(monobackgroundcolordark)$$monobackgroundcolordark$$else$inherit$endif$);
   padding: .2em .4em;
+$elseif(monobackgroundcolordark)$
+  background-color: inherit;
+  background-color: light-dark(inherit, $monobackgroundcolordark$);
+  padding: .2em .4em;
 $endif$
   font-size: 85%;
   margin: 0;
@@ -119,7 +137,12 @@
   margin: 1em 0;
 $if(monobackgroundcolor)$
   background-color: $monobackgroundcolor$;
+  background-color: light-dark($monobackgroundcolor$, $if(monobackgroundcolordark)$$monobackgroundcolordark$$else$inherit$endif$);
   padding: 1em;
+$elseif(monobackgroundcolordark)$
+  background-color: inherit;
+  background-color: light-dark(inherit, $monobackgroundcolordark$);
+  padding: 1em;
 $endif$
   overflow: auto;
 }
@@ -134,7 +157,7 @@
 }
 hr {
   border: none;
-  border-top: 1px solid #1a1a1a;
+  border-top: 1px solid currentColor;
   height: 1px;
   margin: 1em 0;
 }
@@ -156,11 +179,11 @@
 }
 tbody {
   margin-top: 0.5em;
-  border-top: 1px solid $if(fontcolor)$$fontcolor$$else$#1a1a1a$endif$;
-  border-bottom: 1px solid $if(fontcolor)$$fontcolor$$else$#1a1a1a$endif$;
+  border-top: 1px solid currentColor;
+  border-bottom: 1px solid currentColor;
 }
 th {
-  border-top: 1px solid $if(fontcolor)$$fontcolor$$else$#1a1a1a$endif$;
+  border-top: 1px solid currentColor;
   padding: 0.25em 0.5em 0.25em 0.5em;
 }
 td {
diff --git a/pandoc.cabal b/pandoc.cabal
--- a/pandoc.cabal
+++ b/pandoc.cabal
@@ -1,6 +1,6 @@
 cabal-version:   2.4
 name:            pandoc
-version:         3.11
+version:         3.12
 build-type:      Simple
 license:         GPL-2.0-or-later
 license-file:    COPYING.md
@@ -11,7 +11,6 @@
 stability:       alpha
 homepage:        https://pandoc.org
 category:        Text
-tested-with:     GHC == 9.6.7, GHC == 9.8.4, GHC == 9.10.3, GHC == 9.12.2
 synopsis:        Conversion between markup formats
 description:     Pandoc is a Haskell library for converting from one markup
                  format to another.  The formats it can handle include
@@ -219,6 +218,7 @@
                  test/command/*.md
                  test/command/*.csl
                  test/command/*.svg
+                 test/command/2623.odt
                  test/command/7691.docx
                  test/command/9391.docx
                  test/command/9358.docx
@@ -281,6 +281,9 @@
                  test/command/11486/scroll.revealjs
                  test/command/11498.png
                  test/command/11301-styles.opendocument
+                 test/command/11888/section1/lab.md
+                 test/command/11888/section2/lab.md
+                 test/command/11888/section3/lab.md
                  test/asciidoc-reader.adoc
                  test/asciidoc-reader.native
                  test/asciidoc-reader-include.rb
@@ -463,7 +466,7 @@
 
 flag embed_data_files
   Description:   Embed data files in binary for relocatable executable.
-  Default:       False
+  Default:       True
 
 flag http
   Description:   Support for fetching resources using HTTP.
@@ -498,11 +501,13 @@
 
 library xml-light
   import:        common-options
-  build-depends: xml                   >= 1.3.12   && < 1.4,
+  build-depends: conduit               >= 1.3      && < 1.4,
+                 xml                   >= 1.3.12   && < 1.4,
                  xml-conduit           >= 1.9.1.1  && < 1.11,
                  xml-types             >= 0.3      && < 0.4,
                  containers            >= 0.6.0.1  && < 0.9,
-                 text                  >= 1.1.1.0  && < 2.2
+                 text                  >= 2.0      && < 2.2,
+                 text-builder          >= 1.0      && < 1.1
 
   hs-source-dirs:  xml-light
   exposed-modules: Text.Pandoc.XML.Light,
@@ -525,17 +530,17 @@
                  blaze-markup          >= 0.8      && < 0.9,
                  bytestring            >= 0.9      && < 0.13,
                  case-insensitive      >= 1.2      && < 1.3,
-                 citeproc              >= 0.13.0.1 && < 0.14,
-                 commonmark            >= 0.3      && < 0.4,
-                 commonmark-extensions >= 0.2.7.1  && < 0.3,
-                 commonmark-pandoc     >= 0.3      && < 0.4,
+                 citeproc              >= 0.14     && < 0.15,
+                 commonmark            >= 0.3.1    && < 0.4,
+                 commonmark-extensions >= 0.2.7.3  && < 0.3,
+                 commonmark-pandoc     >= 0.3.0.2  && < 0.4,
                  containers            >= 0.6.0.1  && < 0.9,
-                 crypton               >= 0.30     && < 1.2,
+                 crypton               >= 0.30     && < 2.1,
                  data-default          >= 0.4      && < 0.9,
                  deepseq               >= 1.3      && < 1.6,
-                 directory             >= 1.2.3    && < 1.4,
-                 doclayout             >= 0.5.0.3  && < 0.6,
-                 doctemplates          >= 0.11     && < 0.12,
+                 directory             >= 1.2.5    && < 1.4,
+                 doclayout             >= 0.6      && < 0.7,
+                 doctemplates          >= 0.11.1   && < 0.12,
                  emojis                >= 0.1.5    && < 0.2,
                  exceptions            >= 0.8      && < 0.11,
                  file-embed            >= 0.0      && < 0.1,
@@ -550,34 +555,32 @@
                  network-uri           >= 2.6      && < 2.8,
                  pandoc-types          >= 1.23.1.2 && < 1.24,
                  parsec                >= 3.1      && < 3.2,
-                 pretty                >= 1.1      && < 1.2,
-                 pretty-show           >= 1.10     && < 1.11,
                  process               >= 1.2.3    && < 1.7,
                  random                >= 1.2      && < 1.4,
                  safe                  >= 0.3.18   && < 0.4,
                  scientific            >= 0.3      && < 0.4,
-                 skylighting           >= 0.14.7   && < 0.15,
-                 skylighting-core      >= 0.14.7   && < 0.15,
+                 skylighting           >= 0.15     && < 0.16,
+                 skylighting-core      >= 0.15     && < 0.16,
                  split                 >= 0.2      && < 0.3,
                  syb                   >= 0.1      && < 0.8,
                  tagsoup               >= 0.14.6   && < 0.15,
                  temporary             >= 1.1      && < 1.4,
-                 texmath               >= 0.13.2.2 && < 0.14,
-                 text                  >= 1.1.1.0  && < 2.2,
+                 texmath               >= 0.13.3   && < 0.14,
+                 text                  >= 2.0      && < 2.2,
                  text-conversions      >= 0.3      && < 0.4,
                  time                  >= 1.5      && < 1.17,
                  unicode-collation     >= 0.1.1    && < 0.2,
-                 unicode-data          >= 0.6      && < 0.9,
+                 unicode-data          >= 0.6      && < 0.10,
                  unicode-transforms    >= 0.3      && < 0.5,
                  yaml                  >= 0.11     && < 0.12,
                  libyaml               >= 0.1.4    && < 0.2,
-                 zip-archive           >= 0.4.3.1  && < 0.5,
-                 zlib                  >= 0.5      && < 0.8,
+                 zip-archive           >= 0.5      && < 0.6,
+                 zlib                  >= 0.6      && < 0.8,
                  xml                   >= 1.3.12   && < 1.4,
-                 typst                 >= 0.11.0.1 && < 0.12,
+                 typst                 >= 0.12     && < 0.13,
                  vector                >= 0.12     && < 0.14,
-                 djot                  >= 0.1.4.2  && < 0.2,
-                 asciidoc              >= 0.1.0.5  && < 0.2
+                 djot                  >= 0.1.4.3  && < 0.2,
+                 asciidoc              >= 0.1.1    && < 0.2
 
   if !os(windows)
     build-depends:  unix >= 2.4 && < 2.9
@@ -724,6 +727,7 @@
                    Text.Pandoc.App.Opt,
                    Text.Pandoc.App.OutputSettings,
                    Text.Pandoc.Class.CommonState,
+                   Text.Pandoc.Class.IO.HTTP,
                    Text.Pandoc.Class.PandocMonad,
                    Text.Pandoc.Class.PandocIO,
                    Text.Pandoc.Class.PandocPure,
@@ -771,7 +775,6 @@
                    Text.Pandoc.Readers.Mdoc.Standards,
                    Text.Pandoc.Readers.Typst.Parsing,
                    Text.Pandoc.Readers.Typst.Math,
-                   Text.Pandoc.Readers.ODT.Base,
                    Text.Pandoc.Readers.ODT.Namespaces,
                    Text.Pandoc.Readers.ODT.StyleReader,
                    Text.Pandoc.Readers.ODT.ContentReader,
@@ -780,8 +783,6 @@
                    Text.Pandoc.Readers.ODT.Generic.Utils,
                    Text.Pandoc.Readers.ODT.Generic.Namespaces,
                    Text.Pandoc.Readers.ODT.Generic.XMLConverter,
-                   Text.Pandoc.Readers.ODT.Arrows.State,
-                   Text.Pandoc.Readers.ODT.Arrows.Utils,
                    Text.Pandoc.Readers.Org.BlockStarts,
                    Text.Pandoc.Readers.Org.Blocks,
                    Text.Pandoc.Readers.Org.DocumentTree,
@@ -843,12 +844,12 @@
   main-is:        test-pandoc.hs
   hs-source-dirs: test
   build-depends:  pandoc,
-                  Diff              >= 0.2     && < 1.1,
+                  Diff              >= 0.2     && < 2.1,
                   Glob              >= 0.7     && < 0.11,
                   bytestring        >= 0.9     && < 0.13,
                   containers        >= 0.4.2.1 && < 0.9,
-                  directory         >= 1.2.3   && < 1.4,
-                  doctemplates      >= 0.11    && < 0.12,
+                  directory         >= 1.2.5   && < 1.4,
+                  doctemplates      >= 0.11.1  && < 0.12,
                   filepath          >= 1.1     && < 1.6,
                   mtl               >= 2.2     && < 2.4,
                   pandoc-types      >= 1.23.1  && < 1.24,
@@ -857,17 +858,18 @@
                   tasty-golden      >= 2.3     && < 2.4,
                   tasty-hunit       >= 0.9     && < 0.11,
                   tasty-quickcheck  >= 0.8     && < 0.12,
-                  text              >= 1.1.1.0 && < 2.2,
+                  text              >= 2.0     && < 2.2,
                   temporary         >= 1.1     && < 1.4,
                   time              >= 1.5     && < 1.17,
                   xml               >= 1.3.12  && < 1.4,
-                  zip-archive       >= 0.4.3   && < 0.5
+                  zip-archive       >= 0.5     && < 0.6
   other-modules:  Tests.Old
                   Tests.Command
                   Tests.Helpers
                   Tests.Shared
                   Tests.MediaBag
                   Tests.XML
+                  Tests.ImageSize
                   Tests.Readers.LaTeX
                   Tests.Readers.HTML
                   Tests.Readers.JATS
@@ -934,7 +936,7 @@
   build-depends:   bytestring,
                    tasty-bench >= 0.4     && <= 0.5,
                    mtl         >= 2.2     && < 2.4,
-                   text        >= 1.1.1.0 && < 2.2,
+                   text        >= 2.0     && < 2.2,
                    deepseq
   -- we increase heap size to avoid benchmarking garbage collection:
   ghc-options:     -rtsopts -with-rtsopts=-A8m -threaded
diff --git a/src/Text/Pandoc/App/Input.hs b/src/Text/Pandoc/App/Input.hs
--- a/src/Text/Pandoc/App/Input.hs
+++ b/src/Text/Pandoc/App/Input.hs
@@ -16,6 +16,7 @@
 
 import Control.Monad ((>=>), when)
 import Control.Monad.Except (throwError, catchError)
+import Data.Char (toLower)
 import Data.Text (Text)
 import Network.URI (URI (..), parseURI)
 import Text.Pandoc.Transforms (adjustLinksAndIds)
@@ -89,8 +90,9 @@
 readSource "-" = (,Nothing) <$> readStdinStrict
 readSource src =
   case parseURI src of
-    Just u | uriScheme u `elem` ["http:","https:"] -> openURL (T.pack src)
-           | uriScheme u == "file:" ->
+    Just u | map toLower (uriScheme u) `elem` ["http:","https:"] ->
+               openURL (T.pack src)
+           | map toLower (uriScheme u) == "file:" ->
                (,Nothing) <$>
                  readFileStrict (uriPathToPath $ T.pack $ uriPath u)
     _       -> (,Nothing) <$> readFileStrict src
diff --git a/src/Text/Pandoc/Chunks.hs b/src/Text/Pandoc/Chunks.hs
--- a/src/Text/Pandoc/Chunks.hs
+++ b/src/Text/Pandoc/Chunks.hs
@@ -28,8 +28,8 @@
   ) where
 
 import Text.Pandoc.Definition
-import Text.Pandoc.Shared (makeSections, stringify, inlineListToIdentifier,
-                           tshow, uniqueIdent)
+import Text.Pandoc.Shared (makeSections, stringifyInlines,
+                           inlineListToIdentifier, tshow, uniqueIdent)
 import Text.Pandoc.Walk (Walkable(..), query)
 import Data.Aeson (FromJSON, ToJSON)
 import Data.Text (Text)
@@ -57,8 +57,7 @@
 splitIntoChunks pathTemplate numberSections mbBaseLevel
                 chunklev (Pandoc meta blocks) =
    addNav .
-   fixInternalReferences .
-   walk rmNavAttrs $
+   fixInternalReferences $
    ChunkedDoc{ chunkedMeta = meta
              , chunkedChunks = chunks
              , chunkedTOC = tocTree }
@@ -214,12 +213,11 @@
       , chunkPrev = Nothing
       , chunkUnlisted = "unlisted" `elem` classes
       , chunkContents =
-         [Div (divid,"section":classes,kvs') (h : bs)]
+         [Div (divid,"section":classes,kvs) (h : bs)]
       }
-     where kvs' = kvs ++ [("nav-path", T.pack chunkpath)]
-           secnum = lookup "number" kvs
+     where secnum = lookup "number" kvs
            chunkpath = resolvePathTemplate pathTemplate chunknum
-                        (stringify ils)
+                        (stringifyInlines ils)
                         divid
                         (fromMaybe "" secnum)
   toChunk chunknum (Div ("",["preamble"],[]) bs) =
@@ -238,7 +236,7 @@
       }
     where
       chunkpath = resolvePathTemplate pathTemplate chunknum
-                        (stringify (docTitle meta))
+                        (stringifyInlines (docTitle meta))
                         chunkid
                         "0"
       chunkid = inlineListToIdentifier mempty (docTitle meta) <>
@@ -246,14 +244,6 @@
   toChunk _ b = error $ "toChunk called on inappropriate block " <> show b
   -- should not happen
 
-
--- Remove some attributes we added just to construct chunkNext etc.
-rmNavAttrs :: Block -> Block
-rmNavAttrs (Div (ident,classes,kvs) bs) =
-  Div (ident,classes,filter (not . isNavAttr) kvs) bs
- where
-  isNavAttr (k,_) = "nav-" `T.isPrefixOf` k
-rmNavAttrs b = b
 
 resolvePathTemplate :: PathTemplate
                     -> Int -- ^ Chunk number
diff --git a/src/Text/Pandoc/Citeproc.hs b/src/Text/Pandoc/Citeproc.hs
--- a/src/Text/Pandoc/Citeproc.hs
+++ b/src/Text/Pandoc/Citeproc.hs
@@ -30,7 +30,7 @@
 import Text.Pandoc.Extensions (pandocExtensions)
 import Text.Pandoc.Logging (LogMessage(..))
 import Text.Pandoc.Options (ReaderOptions(..))
-import Text.Pandoc.Shared (stringify, tshow, makeSections)
+import Text.Pandoc.Shared (stringifyInlines, tshow, makeSections)
 import Data.Containers.ListUtils (nubOrd)
 import Text.Pandoc.Walk (query, walk, walkM)
 import Control.Applicative ((<|>))
@@ -422,7 +422,7 @@
 mvPunct moveNotes locale (q : s : x@(Cite _ [il]) : ys)
   | isSpacy s
   , isNote il
-  = let spunct = T.takeWhile isPunct $ stringify ys
+  = let spunct = T.takeWhile isPunct $ stringifyInlines ys
     in  if moveNotes
            then if T.null spunct
                    then q : x : mvPunct moveNotes locale ys
@@ -436,7 +436,7 @@
    | isNote (last ils)
    , startWithPunct ys
    , moveNotes
-   = let s = stringify ys
+   = let s = stringifyInlines ys
          spunct = T.takeWhile isPunct s
      in  Cite cs (movePunctInsideQuotes locale $
                     init ils
@@ -451,7 +451,7 @@
 mvPunct moveNotes locale (s : x@(Cite _ (Superscript _ : _)) : ys)
   | isSpacy s = x : mvPunct moveNotes locale ys
 mvPunct moveNotes locale (Cite cs ils : Str "." : ys)
-  | "." `T.isSuffixOf` (stringify ils)
+  | "." `T.isSuffixOf` (stringifyInlines ils)
   = Cite cs ils : mvPunct moveNotes locale ys
 mvPunct moveNotes locale (x:xs) = x : mvPunct moveNotes locale xs
 mvPunct _ _ [] = []
@@ -464,7 +464,7 @@
 endWithPunct :: Bool -> [Inline] -> Bool
 endWithPunct _ [] = False
 endWithPunct onlyFinal xs@(_:_) =
-  case reverse (T.unpack $ stringify xs) of
+  case reverse (T.unpack $ stringifyInlines xs) of
        []                       -> True
        -- covers .), .", etc.:
        (d:c:_) | isPunct d
@@ -478,15 +478,15 @@
 
 startWithPunct :: [Inline] -> Bool
 startWithPunct ils =
-  case T.uncons (stringify ils) of
+  case T.uncons (stringifyInlines ils) of
     Just (c,_) -> c `elem` (".,;:!?" :: [Char])
     Nothing -> False
 
 truish :: MetaValue -> Bool
 truish (MetaBool t) = t
 truish (MetaString s) = isYesValue (T.toLower s)
-truish (MetaInlines ils) = isYesValue (T.toLower (stringify ils))
-truish (MetaBlocks [Plain ils]) = isYesValue (T.toLower (stringify ils))
+truish (MetaInlines ils) = isYesValue (T.toLower (stringifyInlines ils))
+truish (MetaBlocks [Plain ils]) = isYesValue (T.toLower (stringifyInlines ils))
 truish _ = False
 
 isYesValue :: Text -> Bool
diff --git a/src/Text/Pandoc/Citeproc/BibTeX.hs b/src/Text/Pandoc/Citeproc/BibTeX.hs
--- a/src/Text/Pandoc/Citeproc/BibTeX.hs
+++ b/src/Text/Pandoc/Citeproc/BibTeX.hs
@@ -29,7 +29,7 @@
 import Text.Pandoc.Extensions (Extension(..), extensionsFromList)
 import Text.Pandoc.Options (ReaderOptions(..), WriterOptions)
 import Text.Pandoc.Error (PandocError)
-import Text.Pandoc.Shared (stringify)
+import Text.Pandoc.Shared (stringify, stringifyInlines)
 import Text.Pandoc.Writers.LaTeX (writeLaTeX)
 import Text.Pandoc.Class (runPure)
 import qualified Text.Pandoc.Walk       as Walk
@@ -262,7 +262,7 @@
 
   getVariable v = lookupVariable (toVariable v) ref
 
-  getVariableAsText v = (stringify . valToInlines) <$> getVariable v
+  getVariableAsText v = (stringifyInlines . valToInlines) <$> getVariable v
 
   getYear val =
     case val of
@@ -340,7 +340,7 @@
 
   getContentsFor x = getVariable x >>=
     if isURL x
-       then Just . literal . stringify . valToInlines
+       then Just . literal . stringifyInlines . valToInlines
        else toLaTeX .
             (if x == "title"
                 then titlecase
@@ -381,7 +381,7 @@
     -- hyphenation:
     let getLangId = do
              langid <- T.strip . T.toLower <$> getRawField "langid"
-             idopts <- T.strip . T.toLower . stringify <$>
+             idopts <- T.strip . T.toLower . stringifyInlines <$>
                            getField "langidopts" <|> return ""
              case (langid, idopts) of
                   ("english","variant=british")    -> return "british"
@@ -842,14 +842,11 @@
   updateState (\(l,m) -> (l, Map.insert k v m))
   return ()
 
-take1WhileP :: Monad m => (Char -> Bool) -> ParsecT Sources u m Text
-take1WhileP f = T.pack <$> many1 (satisfy f)
-
 inBraces :: BibParser Text
 inBraces = do
   char '{'
   res <- manyTill
-         (  take1WhileP (\c -> c /= '{' && c /= '}' && c /= '\\')
+         (  takeWhile1P (\c -> c /= '{' && c /= '}' && c /= '\\')
         <|> (char '\\' >> T.cons '\\' . T.singleton <$> anyChar)
         <|> (braced <$> inBraces)
          ) (char '}')
@@ -862,14 +859,14 @@
 inQuotes = do
   char '"'
   T.concat <$> manyTill
-             ( take1WhileP (\c -> c /= '{' && c /= '"' && c /= '\\')
+             ( takeWhile1P (\c -> c /= '{' && c /= '"' && c /= '\\')
                <|> (char '\\' >> T.cons '\\' . T.singleton <$> anyChar)
                <|> braced <$> inBraces
             ) (char '"')
 
 fieldName :: BibParser Text
 fieldName = resolveAlias . T.toLower
-  <$> take1WhileP (\c ->
+  <$> takeWhile1P (\c ->
          isAlphaNum c || c == '-' || c == '_' || c == ':' || c == '+')
 
 isBibtexKeyChar :: Char -> Bool
@@ -883,11 +880,11 @@
 bibItem = do
   char '@'
   pos <- getPosition
-  enttype <- T.toLower <$> take1WhileP isLetter
+  enttype <- T.toLower <$> takeWhile1P isLetter
   spaces'
   char '{'
   spaces'
-  entid <- take1WhileP isBibtexKeyChar
+  entid <- takeWhile1P isBibtexKeyChar
   spaces'
   char ','
   spaces'
@@ -913,7 +910,7 @@
 resolveAlias s = s
 
 rawWord :: BibParser Text
-rawWord = take1WhileP isAlphaNum
+rawWord = takeWhile1P isAlphaNum
 
 expandString :: BibParser Text
 expandString = do
@@ -1039,17 +1036,17 @@
 
 getOldDate :: Text -> Bib Date
 getOldDate prefix = do
-  year' <- (readMay . T.unpack . fixLeadingDash . stringify
+  year' <- (readMay . T.unpack . fixLeadingDash . stringifyInlines
               <$> getField (prefix <> "year")) <|> return Nothing
   month' <- (parseMonth <$> getRawField (prefix <> "month"))
             <|> return Nothing
   day' <- (readMay . T.unpack <$> getRawField (prefix <> "day"))
           <|> return Nothing
-  endyear' <- (readMay . T.unpack . fixLeadingDash . stringify
+  endyear' <- (readMay . T.unpack . fixLeadingDash . stringifyInlines
               <$> getField (prefix <> "endyear")) <|> return Nothing
-  endmonth' <- (parseMonth . stringify
+  endmonth' <- (parseMonth . stringifyInlines
                  <$> getField (prefix <> "endmonth")) <|> return Nothing
-  endday' <- (readMay . T.unpack . stringify <$>
+  endday' <- (readMay . T.unpack . stringifyInlines <$>
                  getField (prefix <> "endday")) <|> return Nothing
   let toDateParts (y', m', d') =
               DateParts $
diff --git a/src/Text/Pandoc/Citeproc/Locator.hs b/src/Text/Pandoc/Citeproc/Locator.hs
--- a/src/Text/Pandoc/Citeproc/Locator.hs
+++ b/src/Text/Pandoc/Citeproc/Locator.hs
@@ -14,7 +14,7 @@
 import qualified Data.List as L
 import Text.Pandoc.Definition
 import Text.Pandoc.Parsing
-import Text.Pandoc.Shared (stringify)
+import Text.Pandoc.Shared (stringify, stringifyInlines)
 import Control.Monad (mzero)
 import qualified Data.Map as M
 import Data.Char (isSpace, isPunctuation, isDigit)
@@ -152,7 +152,7 @@
           -- the pathological case is "p.3"
           t <- anyToken
           ts <- manyTill anyToken (try $ lookAhead lim)
-          let s = acc <> stringify (t:ts)
+          let s = acc <> stringifyInlines (t:ts)
           case M.lookup (T.toCaseFold $ T.strip s) (unLocatorMap locMap) of
             -- try to find a longer one, or return this one
             Just l -> go s <|> return (s, l, False)
@@ -239,7 +239,7 @@
                        notFollowedBy pLocatorPunct >>
                        notFollowedBy pMath >>
                        anyToken)
-          let s = stringify ts
+          let s = stringifyInlines ts
           -- otherwise look for actual digits or -s
           return (T.any isDigit s, s)
 
diff --git a/src/Text/Pandoc/Citeproc/MetaValue.hs b/src/Text/Pandoc/Citeproc/MetaValue.hs
--- a/src/Text/Pandoc/Citeproc/MetaValue.hs
+++ b/src/Text/Pandoc/Citeproc/MetaValue.hs
@@ -9,7 +9,7 @@
 import Citeproc.Types
 import Text.Pandoc.Definition
 import Text.Pandoc.Builder as B
-import Text.Pandoc.Shared (stringify, blocksToInlines')
+import Text.Pandoc.Shared (stringify, stringifyInlines, blocksToInlines')
 import Data.Maybe
 import Safe
 import qualified Data.Set as Set
@@ -21,7 +21,7 @@
 
 metaValueToText :: MetaValue -> Maybe Text
 metaValueToText (MetaString t) = Just t
-metaValueToText (MetaInlines ils) = Just $ stringify ils
+metaValueToText (MetaInlines ils) = Just $ stringifyInlines ils
 metaValueToText (MetaBlocks bls) = Just $ stringify bls
 metaValueToText (MetaList xs) = T.unwords <$> mapM metaValueToText xs
 metaValueToText _ = Nothing
@@ -31,7 +31,7 @@
 metaValueToBool (MetaString "true") = Just True
 metaValueToBool (MetaString "false") = Just False
 metaValueToBool (MetaInlines ils) =
-  metaValueToBool (MetaString (stringify ils))
+  metaValueToBool (MetaString (stringifyInlines ils))
 metaValueToBool _ = Nothing
 
 referenceToMetaValue :: Reference Inlines -> MetaValue
diff --git a/src/Text/Pandoc/Citeproc/Name.hs b/src/Text/Pandoc/Citeproc/Name.hs
--- a/src/Text/Pandoc/Citeproc/Name.hs
+++ b/src/Text/Pandoc/Citeproc/Name.hs
@@ -23,7 +23,7 @@
     where
 
 import Text.Pandoc.Definition
-import Text.Pandoc.Shared (stringify)
+import Text.Pandoc.Shared (stringifyInlines)
 import Citeproc.Types
 import Citeproc.Pandoc ()
 import Text.Pandoc.Citeproc.Util (splitStrWhen)
@@ -62,7 +62,7 @@
 toName _ [Str "others"] =
   return emptyName{ nameLiteral = Just "others" }
 toName _ [Span ("",[],[]) ils] = -- corporate author
-  return emptyName{ nameLiteral = Just $ stringify ils }
+  return emptyName{ nameLiteral = Just $ stringifyInlines ils }
 -- extended BibLaTeX name format - see #266
 toName _ ils@(Str ys:_) | T.any (== '=') ys = do
   let commaParts = splitWhen (== Str ",")
@@ -70,17 +70,17 @@
                    $ ils
   let addPart ag (Str "given" : Str "=" : xs) =
         ag{ nameGiven = case nameGiven ag of
-                          Nothing -> Just $ stringify xs
-                          Just t  -> Just $ t <> " " <> stringify xs }
+                          Nothing -> Just $ stringifyInlines xs
+                          Just t  -> Just $ t <> " " <> stringifyInlines xs }
       addPart ag (Str "family" : Str "=" : xs) =
-        ag{ nameFamily = Just $ stringify xs }
+        ag{ nameFamily = Just $ stringifyInlines xs }
       addPart ag (Str "prefix" : Str "=" : xs) =
-        ag{ nameDroppingParticle =  Just $ stringify xs }
+        ag{ nameDroppingParticle =  Just $ stringifyInlines xs }
       addPart ag (Str "useprefix" : Str "=" : Str "true" : _) =
         ag{ nameNonDroppingParticle = nameDroppingParticle ag
           , nameDroppingParticle    = Nothing }
       addPart ag (Str "suffix" : Str "=" : xs) =
-        ag{ nameSuffix = Just $ stringify xs }
+        ag{ nameSuffix = Just $ stringifyInlines xs }
       addPart ag (Space : xs) = addPart ag xs
       addPart ag _ = ag
   return $ L.foldl' addPart emptyName commaParts
@@ -117,10 +117,10 @@
                  case break isCapitalized vonlast of
                         (vs@(_:_), []) -> (init vs, [last vs])
                         (vs, ws)       -> (vs, ws)
-  let prefix = T.unwords $ map stringify von
-  let family = T.unwords $ map stringify lastname
-  let suffix = T.unwords $ map stringify jr
-  let given = T.unwords $ map stringify first
+  let prefix = T.unwords $ map stringifyInlines von
+  let family = T.unwords $ map stringifyInlines lastname
+  let suffix = T.unwords $ map stringifyInlines jr
+  let given = T.unwords $ map stringifyInlines first
   return
     Name {  nameFamily              = if T.null family
                                          then Nothing
diff --git a/src/Text/Pandoc/Class/IO.hs b/src/Text/Pandoc/Class/IO.hs
--- a/src/Text/Pandoc/Class/IO.hs
+++ b/src/Text/Pandoc/Class/IO.hs
@@ -41,35 +41,16 @@
 import Data.Text (Text, pack, unpack)
 import Data.Time (TimeZone, UTCTime)
 import Data.Unique (hashUnique)
-#ifdef PANDOC_HTTP_SUPPORT
-import Data.ByteString.Lazy (toChunks)
-import System.Environment (getEnv)
-import Data.Default (def)
-import Network.Connection (TLSSettings(..))
-import qualified Network.TLS as TLS
-import qualified Network.TLS.Extra as TLS
-import System.X509 (getSystemCertificateStore)
-import Network.HTTP.Client
-       (httpLbs, Manager, responseBody, responseHeaders,
-        Request(port, host, requestHeaders), parseUrlThrow, newManager, HttpException)
-import Network.HTTP.Client.Internal (addProxy)
-import Network.HTTP.Client.TLS (mkManagerSettings)
-import Network.HTTP.Types.Header ( hContentType )
-import Network.Socket (withSocketsDo)
-import Text.Pandoc.Class.CommonState (CommonState (..))
-import Text.Pandoc.Class.PandocMonad ( getsCommonState, modifyCommonState )
-import qualified Data.CaseInsensitive as CI
-#endif
-import Network.URI (URI(..), parseURI, unEscapeString)
+import Network.URI (unEscapeString)
 import System.Directory (createDirectoryIfMissing)
 import System.FilePath ((</>), takeDirectory, takeFileName, normalise, takeExtension)
 import qualified System.FilePath.Posix as Posix
 import System.IO (stderr)
 import System.IO.Error
 import System.Random (StdGen)
+import Text.Pandoc.Class.IO.HTTP (openURL)
 import Text.Pandoc.Class.PandocMonad
-       (PandocMonad,
-        getMediaBag, report, extractURIData)
+       (PandocMonad, getMediaBag, report)
 import Text.Pandoc.Definition (Pandoc, Inline (Image))
 import Text.Pandoc.Error (PandocError (..))
 import Text.Pandoc.Logging (LogMessage (..), messageVerbosity, showLogMessage)
@@ -129,70 +110,6 @@
 newUniqueHash :: MonadIO m => m Int
 newUniqueHash = hashUnique <$> liftIO Data.Unique.newUnique
 
-#ifdef PANDOC_HTTP_SUPPORT
-getManager :: (PandocMonad m, MonadIO m) => m Manager
-getManager = do
-  mbManager <- getsCommonState stManager
-  disableCertificateValidation <- getsCommonState stNoCheckCertificate
-  case mbManager of
-    Just manager -> pure manager
-    Nothing -> do
-      manager <- liftIO $ do
-        certificateStore <- getSystemCertificateStore
-        let tlsSettings = TLSSettings $
-               (TLS.defaultParamsClient "localhost.localdomain" "80")
-                  { TLS.clientSupported = def{ TLS.supportedCiphers =
-                                               TLS.ciphersuite_default
-                                             , TLS.supportedExtendedMainSecret =
-                                                TLS.AllowEMS }
-                  , TLS.clientShared = def
-                      { TLS.sharedCAStore = certificateStore
-                      , TLS.sharedValidationCache =
-                          if disableCertificateValidation
-                             then TLS.ValidationCache
-                                   (\_ _ _ -> return TLS.ValidationCachePass)
-                                   (\_ _ _ -> return ())
-                             else def
-                      }
-                  }
-        let tlsManagerSettings = mkManagerSettings tlsSettings  Nothing
-        newManager tlsManagerSettings
-      modifyCommonState $ \st -> st{ stManager = Just manager }
-      pure manager
-#endif
-
-openURL :: (PandocMonad m, MonadIO m) => Text -> m (B.ByteString, Maybe MimeType)
-openURL u
- | Just (URI{ uriScheme = "data:",
-              uriPath = upath }) <- parseURI (T.unpack u)
-     = pure $ extractURIData upath
-#ifdef PANDOC_HTTP_SUPPORT
- | otherwise = do
-     let toReqHeader (n, v) = (CI.mk (UTF8.fromText n), UTF8.fromText v)
-     customHeaders <- map toReqHeader <$> getsCommonState stRequestHeaders
-     report $ Fetching u
-     manager <- getManager
-     res <- liftIO $ E.try $ withSocketsDo $ do
-       proxy <- tryIOError (getEnv "http_proxy")
-       let addProxy' x = case proxy of
-                            Left _ -> return x
-                            Right pr -> parseUrlThrow pr >>= \r ->
-                                return (addProxy (host r) (port r) x)
-       req <- parseUrlThrow (unpack u) >>= addProxy'
-       let req' = req{requestHeaders = customHeaders ++ requestHeaders req}
-       resp <- httpLbs req' manager
-       return (B.concat $ toChunks $ responseBody resp,
-               UTF8.toText `fmap` lookup hContentType (responseHeaders resp))
-
-     case res of
-          Right r -> return r
-          Left (e :: HttpException)
-                  -> throwError $ PandocHttpError u (T.pack (show e))
-#else
- | otherwise =
-     throwError $ PandocHttpError u "pandoc was compiled without HTTP support"
-#endif
-
 -- | Read the lazy ByteString contents from a file path, raising an error on
 -- failure.
 readFileLazy :: (PandocMonad m, MonadIO m) => FilePath -> m BL.ByteString
@@ -232,19 +149,9 @@
 -- | Output a log message.
 logOutput :: (PandocMonad m, MonadIO m) => LogMessage -> m ()
 logOutput msg = liftIO $ do
-  UTF8.hPutStr stderr $
-      "[" <> T.pack (show (messageVerbosity msg)) <> "] "
-  alertIndent $ T.lines $ showLogMessage msg
-
--- | Prints the list of lines to @stderr@, indenting every but the first
--- line by two spaces.
-alertIndent :: [Text] -> IO ()
-alertIndent [] = return ()
-alertIndent (l:ls) = do
-  UTF8.hPutStrLn stderr l
-  mapM_ go ls
-  where go l' = do UTF8.hPutStr stderr "  "
-                   UTF8.hPutStrLn stderr l'
+  UTF8.hPutStrLn stderr $
+      "[" <> T.pack (show (messageVerbosity msg)) <> "] " <>
+      T.intercalate ("\n  ") (T.lines (showLogMessage msg))
 
 -- | Extract media from the mediabag into a directory (or a zip archive if the
 -- path supplied ends in @.zip@.
diff --git a/src/Text/Pandoc/Class/IO/HTTP.hs b/src/Text/Pandoc/Class/IO/HTTP.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Pandoc/Class/IO/HTTP.hs
@@ -0,0 +1,115 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE OverloadedStrings #-}
+{- |
+Module      : Text.Pandoc.Class.IO.HTTP
+Copyright   : Copyright (C) 2025 John MacFarlane
+License     : GNU GPL, version 2 or above
+
+Maintainer  : John MacFarlane <jgm@berkeley.edu>
+Stability   : alpha
+Portability : portable
+
+HTTP fetching functionality for pandoc.
+-}
+module Text.Pandoc.Class.IO.HTTP
+  ( openURL
+  ) where
+
+import Network.URI (URI(..), parseURI)
+import Data.Text (Text)
+import Control.Monad.IO.Class (MonadIO)
+import Text.Pandoc.Class.PandocMonad (PandocMonad, extractURIData)
+import Text.Pandoc.Error (PandocError (..))
+import Text.Pandoc.MIME (MimeType)
+import qualified Data.ByteString as B
+import qualified Data.Text as T
+import Control.Monad.Except (throwError)
+#ifdef PANDOC_HTTP_SUPPORT
+import Data.ByteString.Lazy (toChunks)
+import Control.Monad.IO.Class (liftIO)
+import System.Environment (getEnv)
+import Data.Default (def)
+import Network.Connection (TLSSettings(..))
+import qualified Network.TLS as TLS
+import qualified Network.TLS.Extra as TLS
+import System.X509 (getSystemCertificateStore)
+import Network.HTTP.Client
+       (httpLbs, Manager, responseBody, responseHeaders,
+        Request(port, host, requestHeaders), parseUrlThrow, newManager, HttpException)
+import Network.HTTP.Client.Internal (addProxy)
+import Network.HTTP.Client.TLS (mkManagerSettings)
+import Network.HTTP.Types.Header ( hContentType )
+import Network.Socket (withSocketsDo)
+import Text.Pandoc.Class.CommonState (CommonState (..))
+import Text.Pandoc.Class.PandocMonad ( getsCommonState, modifyCommonState, report )
+import qualified Data.CaseInsensitive as CI
+import System.IO.Error
+import Text.Pandoc.Logging (LogMessage (..))
+import qualified Control.Exception as E
+import qualified Text.Pandoc.UTF8 as UTF8
+#endif
+
+#ifdef PANDOC_HTTP_SUPPORT
+getManager :: (PandocMonad m, MonadIO m) => m Manager
+getManager = do
+  mbManager <- getsCommonState stManager
+  disableCertificateValidation <- getsCommonState stNoCheckCertificate
+  case mbManager of
+    Just manager -> pure manager
+    Nothing -> do
+      manager <- liftIO $ do
+        certificateStore <- getSystemCertificateStore
+        let tlsSettings = TLSSettings $
+               (TLS.defaultParamsClient "localhost.localdomain" "80")
+                  { TLS.clientSupported = def{ TLS.supportedCiphers =
+                                               TLS.ciphersuite_default
+                                             , TLS.supportedExtendedMainSecret =
+                                                TLS.AllowEMS }
+                  , TLS.clientShared = def
+                      { TLS.sharedCAStore = certificateStore
+                      , TLS.sharedValidationCache =
+                          if disableCertificateValidation
+                             then TLS.ValidationCache
+                                   (\_ _ _ -> return TLS.ValidationCachePass)
+                                   (\_ _ _ -> return ())
+                             else def
+                      }
+                  }
+        let tlsManagerSettings = mkManagerSettings tlsSettings  Nothing
+        newManager tlsManagerSettings
+      modifyCommonState $ \st -> st{ stManager = Just manager }
+      pure manager
+#endif
+
+openURL :: (PandocMonad m, MonadIO m) => Text -> m (B.ByteString, Maybe MimeType)
+openURL u
+ | Just (URI{ uriScheme = "data:",
+              uriPath = upath }) <- parseURI (T.unpack u)
+     = pure $ extractURIData upath
+#ifdef PANDOC_HTTP_SUPPORT
+ | otherwise = do
+     let toReqHeader (n, v) = (CI.mk (UTF8.fromText n), UTF8.fromText v)
+     customHeaders <- map toReqHeader <$> getsCommonState stRequestHeaders
+     report $ Fetching u
+     manager <- getManager
+     res <- liftIO $ E.try $ withSocketsDo $ do
+       proxy <- tryIOError (getEnv "http_proxy")
+       let addProxy' x = case proxy of
+                            Left _ -> return x
+                            Right pr -> parseUrlThrow pr >>= \r ->
+                                return (addProxy (host r) (port r) x)
+       req <- parseUrlThrow (T.unpack u) >>= addProxy'
+       let req' = req{requestHeaders = customHeaders ++ requestHeaders req}
+       resp <- httpLbs req' manager
+       return (B.concat $ toChunks $ responseBody resp,
+               UTF8.toText `fmap` lookup hContentType (responseHeaders resp))
+
+     case res of
+          Right r -> return r
+          Left (e :: HttpException)
+                  -> throwError $ PandocHttpError u (T.pack (show e))
+#else
+ | otherwise =
+     throwError $ PandocHttpError u "pandoc was compiled without HTTP support"
+#endif
diff --git a/src/Text/Pandoc/Class/PandocMonad.hs b/src/Text/Pandoc/Class/PandocMonad.hs
--- a/src/Text/Pandoc/Class/PandocMonad.hs
+++ b/src/Text/Pandoc/Class/PandocMonad.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE TupleSections #-}
 {-# LANGUAGE FlexibleContexts #-}
@@ -64,6 +65,9 @@
 import Control.Monad.Except (MonadError (catchError, throwError))
 import Control.Monad.Trans (MonadTrans, lift)
 import Control.Monad (when)
+import Data.Char (chr, digitToInt, isHexDigit, toLower)
+import Data.List (intercalate)
+import Data.Word (Word8)
 import Data.Time (UTCTime)
 import Data.Time.Clock.POSIX (POSIXTime, utcTimeToPOSIXSeconds,
                              posixSecondsToUTCTime)
@@ -72,7 +76,7 @@
                      unEscapeString, parseURIReference, isAllowedInURI,
                      parseURI, URI(..) )
 import System.FilePath ((</>), takeExtension, dropExtension,
-                        isRelative, makeRelative)
+                        isRelative, makeRelative, splitDirectories)
 import System.Random (StdGen)
 import Text.Collate.Lang (Lang(..), parseLang)
 import Text.Pandoc.Class.CommonState (CommonState (..))
@@ -85,10 +89,10 @@
 import Text.Pandoc.URI (uriPathToPath, pBase64DataURI)
 import qualified Data.Attoparsec.Text as A
 import Text.Pandoc.Walk (walkM)
-import qualified Text.Pandoc.UTF8 as UTF8
 import Data.ByteString.Base64 (decodeLenient)
 import Text.Parsec (ParsecT, getPosition, sourceLine, sourceName)
 import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as B8
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.Text as T
 import qualified Debug.Trace
@@ -140,7 +144,6 @@
   getCommonState :: m CommonState
   -- | Set the value of the 'CommonState' used by all instances
   -- of 'PandocMonad'.
-  -- | Get the value of a specific field of 'CommonState'.
   putCommonState :: CommonState -> m ()
   -- | Get the value of a specific field of 'CommonState'.
   getsCommonState :: (CommonState -> a) -> m a
@@ -201,13 +204,15 @@
   -- get current settings
   origLog <- getsCommonState stLog
   origVerbosity <- getVerbosity
+  let restore = modifyCommonState
+        (\st -> st { stVerbosity = origVerbosity, stLog = origLog })
   -- reset log level and set verbosity to the minimum
   modifyCommonState (\st -> st { stVerbosity = ERROR, stLog = []})
-  result <- action
+  -- restore the original log and verbosity even if the action fails
+  result <- action `catchError` (\e -> restore *> throwError e)
   -- get log messages reported while running `action`
   newLog <- getsCommonState stLog
-  modifyCommonState (\st -> st { stVerbosity = origVerbosity, stLog = origLog})
-
+  restore
   return (result, newLog)
 
 -- | Set request header to use in HTTP requests.
@@ -221,7 +226,16 @@
 
 -- | Determine whether certificate validation is disabled
 setNoCheckCertificate :: PandocMonad m => Bool -> m ()
-setNoCheckCertificate noCheckCertificate = modifyCommonState $ \st -> st{stNoCheckCertificate = noCheckCertificate}
+setNoCheckCertificate noCheckCertificate = modifyCommonState $ \st ->
+  st{ stNoCheckCertificate = noCheckCertificate
+#ifdef PANDOC_HTTP_SUPPORT
+    -- discard any cached HTTP manager, since it was created with
+    -- TLS settings based on the previous value of this option
+    , stManager = if stNoCheckCertificate st == noCheckCertificate
+                     then stManager st
+                     else Nothing
+#endif
+    }
 
 -- | Initialize the media bag.
 setMediaBag :: PandocMonad m => MediaBag -> m ()
@@ -281,7 +295,7 @@
 setRequestHeaders :: PandocMonad m => [(T.Text, T.Text)] -> m ()
 setRequestHeaders hs = modifyCommonState $ \st -> st{ stRequestHeaders = hs }
 
--- | Get the absolute UL or directory of first source file.
+-- | Get the absolute URL or directory of first source file.
 getSourceURL :: PandocMonad m => m (Maybe T.Text)
 getSourceURL = getsCommonState stSourceURL
 
@@ -372,7 +386,7 @@
                => T.Text
                -> m (B.ByteString, Maybe MimeType)
 downloadOrRead s
- | "data:" `T.isPrefixOf` s,
+ | T.toLower (T.take 5 s) == "data:",
    Right (bs, mt) <- A.parseOnly (pBase64DataURI <* A.endOfInput) s
    = pure (bs, Just mt)
  | otherwise = do
@@ -389,9 +403,10 @@
             Nothing -> openURL s' -- will throw error
     (Nothing, s') ->
        case parseURI (T.unpack s') of  -- requires absolute URI
-            Just URI{ uriScheme = "file:", uriPath = upath}
+            Just URI{ uriScheme = sch, uriPath = upath}
+              | map toLower sch == "file:"
               -> readLocalFile $ uriPathToPath (T.pack upath)
-            Just URI{ uriScheme = "data:", uriPath = upath}
+              | map toLower sch == "data:"
               -> pure $ extractURIData upath
             -- We don't want to treat C:/ as a scheme:
             Just u' | length (uriScheme u') > 2 -> openURL (T.pack $ show u')
@@ -420,18 +435,40 @@
 -- Extract data from a data URI's path component.
 extractURIData :: String -> (B.ByteString, Maybe MimeType)
 extractURIData upath =
-  case break (== ';') (filter (/= ' ') mimespec) of
-     (mime', ";base64") -> (decodeLenient contents, Just (T.pack mime'))
-     (mime', _) -> (contents, Just (T.pack mime'))
+  if isBase64
+     then (decodeLenient contents, Just mime)
+     else (contents, Just mime)
   where
-    (mimespec, rest) = break (== ',') $ unEscapeString upath
-    contents = UTF8.fromString $ drop 1 rest
+    (mimespec, rest) = break (== ',') upath
+    -- The base64 indicator is the final parameter of the media type
+    -- and may follow other parameters, e.g.
+    -- data:text/plain;charset=utf-8;base64,...
+    metaParts = splitParts (filter (/= ' ') (unEscapeString mimespec))
+    splitParts s = case break (== ';') s of
+                     (x, [])    -> [x]
+                     (x, _:s')  -> x : splitParts s'
+    isBase64 = length metaParts > 1 && last metaParts == "base64"
+    mime = T.pack $ intercalate ";" $
+             if isBase64 then init metaParts else metaParts
+    -- Percent-escapes in a data URI represent raw octets (RFC 2397),
+    -- so we decode them to bytes directly.  (unEscapeString cannot be
+    -- used here: it UTF-8-decodes consecutive escapes, which corrupts
+    -- binary data when the result is re-encoded.)
+    contents = unEscapeBytes $ drop 1 rest
+    unEscapeBytes = B8.pack . go
+      where
+        go ('%':x:y:cs)
+          | isHexDigit x, isHexDigit y
+            = chr (digitToInt x * 16 + digitToInt y) : go cs
+        go (c:cs) = c : go cs
+        go [] = []
 
 -- | Checks if the file path is relative to a parent directory.
 isRelativeToParentDir :: FilePath -> Bool
 isRelativeToParentDir fname =
-  let canonical = makeCanonical fname
-   in length canonical >= 2 && take 2 canonical == ".."
+  case splitDirectories (makeCanonical fname) of
+    "..":_ -> True
+    _      -> False
 
 -- | Returns possible user data directory if the file path refers to a file or
 -- subdirectory within it.
@@ -468,9 +505,9 @@
 toTextM fp bs =
   case TSE.decodeUtf8' . filterCRs . dropBOM $ bs of
     Left (TSE.DecodeError _ (Just w)) ->
-      case B.elemIndex w bs of
-        Just offset ->
-          throwError $ PandocUTF8DecodingError (T.pack fp) offset w
+      case findDecodingError bs of
+        Just (offset, w') ->
+          throwError $ PandocUTF8DecodingError (T.pack fp) offset w'
         Nothing -> throwError $ PandocUTF8DecodingError (T.pack fp) 0 w
     Left e -> throwError $ PandocAppError (tshow e)
     Right t -> return t
@@ -479,7 +516,45 @@
      if "\xEF\xBB\xBF" `B.isPrefixOf` bs'
         then B.drop 3 bs'
         else bs'
-   filterCRs = B.filter (/=13)
+   -- Only allocate a filtered copy if a CR is actually present;
+   -- B.elem compiles to a fast memchr.
+   filterCRs bs' = if 13 `B.elem` bs'
+                      then B.filter (/=13) bs'
+                      else bs'
+
+-- Find the offset and value of the first byte at which UTF-8 decoding
+-- fails (RFC 3629).  Used to give an accurate position in decoding
+-- error messages.  (The BOM and CR bytes stripped before decoding are
+-- themselves valid UTF-8, so scanning the unstripped input finds the
+-- same error, at its offset in the original file.)
+findDecodingError :: B.ByteString -> Maybe (Int, Word8)
+findDecodingError = go 0
+ where
+  go !i bs = case B.uncons bs of
+    Nothing -> Nothing
+    Just (w, rest)
+      | w < 0x80  -> go (i + 1) rest
+      | w < 0xC2  -> Just (i, w)  -- continuation byte or overlong lead
+      | w == 0xE0 -> cont i w rest [(0xA0,0xBF),(0x80,0xBF)]
+      | w == 0xED -> cont i w rest [(0x80,0x9F),(0x80,0xBF)]  -- no surrogates
+      | w < 0xE0  -> cont i w rest [(0x80,0xBF)]
+      | w <  0xF0 -> cont i w rest [(0x80,0xBF),(0x80,0xBF)]
+      | w == 0xF0 -> cont i w rest [(0x90,0xBF),(0x80,0xBF),(0x80,0xBF)]
+      | w == 0xF4 -> cont i w rest [(0x80,0x8F),(0x80,0xBF),(0x80,0xBF)]
+      | w <  0xF4 -> cont i w rest [(0x80,0xBF),(0x80,0xBF),(0x80,0xBF)]
+      | otherwise -> Just (i, w)  -- above U+10FFFF
+  -- check that the bytes following the lead byte w at offset i fall
+  -- into the given ranges; report the first byte that does not
+  cont i w = go' (i + 1)
+   where
+    go' !j rest [] = go j rest
+    go' !j rest ((lo,hi):ranges) =
+      case B.uncons rest of
+        Just (b, rest')
+          | b >= lo && b <= hi -> go' (j + 1) rest' ranges
+          | otherwise          -> Just (j, b)
+        -- input ends in the middle of a sequence: report the lead byte
+        Nothing -> Just (i, w)
 
 -- | Returns @fp@ if the file exists in the current directory; otherwise
 -- searches for the data file relative to @/subdir/@. Returns @Nothing@
diff --git a/src/Text/Pandoc/Class/PandocPure.hs b/src/Text/Pandoc/Class/PandocPure.hs
--- a/src/Text/Pandoc/Class/PandocPure.hs
+++ b/src/Text/Pandoc/Class/PandocPure.hs
@@ -47,7 +47,8 @@
 import Data.Time.Clock.POSIX ( posixSecondsToUTCTime )
 import Data.Time.LocalTime (TimeZone, utc)
 import Data.Word (Word8)
-import System.Directory (doesDirectoryExist, getDirectoryContents)
+import System.Directory (canonicalizePath, doesDirectoryExist,
+                         getDirectoryContents)
 import System.FilePath ((</>))
 import System.FilePath.Glob (match, compile)
 import System.Random (StdGen, mkStdGen)
@@ -57,6 +58,7 @@
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.Map as M
+import qualified Data.Set as Set
 import qualified Data.Text as T
 import qualified System.Directory as Directory (getModificationTime)
 
@@ -143,20 +145,27 @@
 -- | Add the specified file to the FileTree. If file
 -- is a directory, add its contents recursively.
 addToFileTree :: FileTree -> FilePath -> IO FileTree
-addToFileTree tree fp = do
-  isdir <- doesDirectoryExist fp
-  if isdir
-     then do -- recursively add contents of directories
-       let isSpecial ".." = True
-           isSpecial "."  = True
-           isSpecial _    = False
-       fs <- map (fp </>) . filter (not . isSpecial) <$> getDirectoryContents fp
-       foldM addToFileTree tree fs
-     else do
-       contents <- B.readFile fp
-       mtime <- Directory.getModificationTime fp
-       return $ insertInFileTree fp FileInfo{ infoFileMTime = mtime
-                                            , infoFileContents = contents } tree
+addToFileTree = go Set.empty
+ where
+  go ancestors tree fp = do
+    isdir <- doesDirectoryExist fp
+    if isdir
+       then do -- recursively add contents of directories
+         canonical <- canonicalizePath fp
+         if canonical `Set.member` ancestors
+            then return tree  -- don't follow symlink cycles
+            else do
+              let isSpecial ".." = True
+                  isSpecial "."  = True
+                  isSpecial _    = False
+              fs <- map (fp </>) . filter (not . isSpecial) <$>
+                      getDirectoryContents fp
+              foldM (go (Set.insert canonical ancestors)) tree fs
+       else do
+         contents <- B.readFile fp
+         mtime <- Directory.getModificationTime fp
+         return $ insertInFileTree fp FileInfo{ infoFileMTime = mtime
+                                              , infoFileContents = contents } tree
 
 -- | Insert an ersatz file into the 'FileTree'.
 insertInFileTree :: FilePath -> FileInfo -> FileTree -> FileTree
diff --git a/src/Text/Pandoc/Data.hs b/src/Text/Pandoc/Data.hs
--- a/src/Text/Pandoc/Data.hs
+++ b/src/Text/Pandoc/Data.hs
@@ -25,6 +25,7 @@
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.ByteString as B
 import Codec.Archive.Zip
+import Data.List (sort)
 import qualified Data.Text as T
 import Control.Monad.Except (throwError)
 import Text.Pandoc.Error (PandocError(..))
@@ -220,10 +221,20 @@
 #ifdef EMBED_DATA_FILES
   let allDataFiles = map fst dataFiles
 #else
-  allDataFiles <- filter (\x -> x /= "." && x /= "..") <$>
-                      (getDataDir >>= getDirectoryContents)
+  let listDirectoryRecursive d = do
+        xs <- listDirectory d
+        concat <$>
+           mapM (\f -> do
+                      isdir <- doesDirectoryExist (d </> f)
+                      if isdir
+                         then map (f </>) <$> listDirectoryRecursive (d </> f)
+                         else pure [f]) xs
+  ddir <- getDataDir
+  allDataFiles <- ("MANUAL.txt" :) <$>
+                     listDirectoryRecursive (ddir </> "data")
 #endif
-  return $ "reference.docx" : "reference.odt" : "reference.pptx" : allDataFiles
+  return $ sort $
+    "reference.docx" : "reference.odt" : "reference.pptx" : allDataFiles
 
 -- | Return appropriate user data directory for platform.  We use
 -- XDG_DATA_HOME (or its default value), but for backwards compatibility,
diff --git a/src/Text/Pandoc/ImageSize.hs b/src/Text/Pandoc/ImageSize.hs
--- a/src/Text/Pandoc/ImageSize.hs
+++ b/src/Text/Pandoc/ImageSize.hs
@@ -38,8 +38,8 @@
 import qualified Data.ByteString.Lazy as BL
 import Data.Binary.Get
 import Data.Bits ((.&.), shiftR, shiftL)
-import Data.Word (bitReverse32, Word32)
-import Data.Maybe (isJust, fromJust)
+import Data.Word (Word32)
+import Data.Maybe (fromMaybe)
 import Data.Char (isDigit)
 import Control.Monad
 import Text.Pandoc.Shared (safeRead)
@@ -57,13 +57,13 @@
 import qualified Data.Attoparsec.ByteString.Char8 as A
 import qualified Codec.Picture.Metadata as Metadata
 import Codec.Picture (decodeImageWithMetadata)
-import Codec.Compression.Zlib (decompress)
+import qualified Codec.Compression.Zlib.Internal as Zlib
 -- import Debug.Trace
 
 -- quick and dirty functions to get image sizes
 
 data ImageType = Png | Gif | Jpeg | Svg | Pdf | Eps | Emf | Tiff | Webp | Avif
-                 deriving Show
+                 deriving (Show, Eq)
 data Direction = Width | Height
 instance Show Direction where
   show Width  = "width"
@@ -118,7 +118,7 @@
                      "\x47\x49\x46\x38" -> return Gif
                      "\x49\x49\x2a\x00" -> return Tiff
                      "\x4D\x4D\x00\x2a" -> return Tiff
-                     "\xff\xd8\xff\xbd" -> return Jpeg  -- JPEG without application segment -- see p.32 in https://www.w3.org/Graphics/JPEG/itu-t81.pdf (and https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5?permalink_comment_id=3863054#gistcomment-3863054)
+                     "\xff\xd8\xff\xdb" -> return Jpeg  -- JPEG without application segment -- see p.32 in https://www.w3.org/Graphics/JPEG/itu-t81.pdf (and https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5?permalink_comment_id=3863054#gistcomment-3863054)
                      _ | B.take 3 img == "\xff\xd8\xff"
                           && (let byte4 = B.take 1 (B.drop 3 img)
                               in byte4 >= "\xe0" && byte4 <= "\xef")  -- JPEG with application segment
@@ -139,18 +139,39 @@
                      "RIFF"
                        | B.take 4 (B.drop 8 img) == "WEBP"
                                         -> return Webp
-                     _ | B.take 4 (B.drop 4 img) == "ftyp" -> return Avif
+                     _ | B.take 4 (B.drop 4 img) == "ftyp"
+                          -- require the AVIF brand, so that other
+                          -- ISO media (mp4, mov, heic...) is excluded:
+                          && (B.take 4 (B.drop 8 img) == "avif" ||
+                              B.take 4 (B.drop 8 img) == "avis")
+                                        -> return Avif
                      _ -> mzero
 
+-- | Check for the presence of an @<svg@ (or @<SVG@) tag, in a
+-- single pass over the file.
 findSvgTag :: ByteString -> Bool
-findSvgTag img = "<svg" `B.isInfixOf` img || "<SVG" `B.isInfixOf` img
+findSvgTag img = case B.elemIndex '<' img of
+  Nothing -> False
+  Just i ->
+    case B.drop (i + 1) img of
+      rest | "svg" `B.isPrefixOf` rest -> True
+           | "SVG" `B.isPrefixOf` rest -> True
+           | otherwise -> findSvgTag rest
 
 imageSize :: WriterOptions -> ByteString -> Either T.Text ImageSize
 imageSize opts img = checkDpi <$>
   case imageType img of
-       Just Png  -> getSize img
+       Just Png  -> case pngSize img of
+                      Just sz -> Right sz
+                      -- fall back to the full decoder if the header
+                      -- scan fails:
+                      Nothing -> getSize img
        Just Gif  -> getSize img
-       Just Jpeg -> getSize img
+       Just Jpeg -> case jpegSize img of
+                      Just sz -> Right sz
+                      -- fall back to the full decoder if the header
+                      -- scan fails:
+                      Nothing -> getSize img
        Just Tiff -> getSize img
        Just Svg  -> mbToEither "could not determine SVG size" $ svgSize opts img
        Just Eps  -> mbToEither "could not determine EPS size" $ epsSize img
@@ -162,10 +183,10 @@
   where mbToEither msg Nothing  = Left msg
         mbToEither _   (Just x) = Right x
         -- see #6880, some defective JPEGs may encode dpi 0, so default to 72
-        -- if that value is 0
+        -- if that value is 0 or negative
         checkDpi size =
-          size{ dpiX = if dpiX size == 0 then 72 else dpiX size
-              , dpiY = if dpiY size == 0 then 72 else dpiY size }
+          size{ dpiX = if dpiX size <= 0 then 72 else dpiX size
+              , dpiY = if dpiY size <= 0 then 72 else dpiY size }
 
 
 sizeInPixels :: ImageSize -> (Integer, Integer)
@@ -236,11 +257,12 @@
 showInPixel _ (Percent _) = ""
 showInPixel opts dim = T.pack $ show $ inPixel opts dim
 
--- | Maybe split a string into a leading number and trailing unit, e.g. "3cm" to Just (3.0, "cm")
+-- | Maybe split a string into a leading number and trailing unit, e.g. "3cm" to Just (3.0, "cm").
+-- Whitespace between the number and the unit is ignored.
 numUnit :: T.Text -> Maybe (Double, T.Text)
 numUnit s =
   let (nums, unit) = T.span (\c -> isDigit c || ('.'==c)) s
-  in (\n -> (n, unit)) <$> safeRead nums
+  in (\n -> (n, T.stripStart unit)) <$> safeRead nums
 
 -- | Scale a dimension by a factor.
 scaleDimension :: Double -> Dimension -> Dimension
@@ -287,12 +309,14 @@
   case ls' of
        []    -> mzero
        (x:_) -> case B.words x of
-                     [_, _, _, ux, uy] -> do
-                        ux' <- safeRead $ TE.decodeUtf8 ux
-                        uy' <- safeRead $ TE.decodeUtf8 uy
+                     [_, llx, lly, urx, ury] -> do
+                        llx' <- safeRead $ TE.decodeUtf8Lenient llx
+                        lly' <- safeRead $ TE.decodeUtf8Lenient lly
+                        urx' <- safeRead $ TE.decodeUtf8Lenient urx
+                        ury' <- safeRead $ TE.decodeUtf8Lenient ury
                         return ImageSize{
-                            pxX  = ux'
-                          , pxY  = uy'
+                            pxX  = urx' - llx'
+                          , pxY  = ury' - lly'
                           , dpiX = 72
                           , dpiY = 72 }
                      _ -> mzero
@@ -330,13 +354,10 @@
       A.skipSpace
       A.string "/ObjStm"
       _ <- A.manyTill pLine (A.string "stream" *> pEol)
-      stream <- BL.pack <$> A.manyTill
-                        (AW.satisfy (const True))
-                        (pEol *> A.string "endstream" *> pEol)
-      let contents = BL.toStrict (decompress stream)
-      case A.parseOnly pPdfSize contents of
-        Left _ -> pPdfSize
-        Right is -> pure is)
+      stream <- pTakeUntil "endstream"
+      case A.parseOnly pPdfSize <$> safeDecompress stream of
+        Just (Right is) -> pure is
+        _ -> pPdfSize)
   <|>
   (A.char '/' *> pPdfSize)
  where
@@ -346,6 +367,192 @@
    pEol = A.satisfy iseol *> A.skipMany (A.satisfy iseol)
    pLine = A.takeWhile (not . iseol) <* pEol
 
+-- | Consume input up to and including the first occurrence of the
+-- (non-empty) terminator, returning what precedes it.  Unlike
+-- @manyTill anyWord8@, this consumes chunks at a time.
+pTakeUntil :: ByteString -> A.Parser ByteString
+pTakeUntil terminator = B.concat . reverse <$> go []
+ where
+  go acc = do
+    chunk <- A.takeWhile (/= B.head terminator)
+    let acc' = chunk : acc
+    (A.string terminator *> pure acc')
+      <|> (do c <- A.take 1
+              go (c : acc'))
+
+-- | Decompress a zlib stream, returning Nothing on malformed input.
+-- ('Codec.Compression.Zlib.decompress' would instead throw an
+-- exception from pure code when the corrupt part of its lazy result
+-- is forced.)
+safeDecompress :: ByteString -> Maybe ByteString
+safeDecompress bs = fmap B.concat $
+  Zlib.foldDecompressStreamWithInput
+    (\chunk rest -> (chunk :) <$> rest)
+    (const (Just []))
+    (const Nothing)
+    (Zlib.decompressST Zlib.zlibFormat Zlib.defaultDecompressParams)
+    (BL.fromStrict bs)
+
+-- | Extract PNG size from the IHDR and pHYs chunks, without
+-- decoding any image data.  (For paletted PNGs, JuicyPixels decodes
+-- the whole image before returning metadata.)
+pngSize :: ByteString -> Maybe ImageSize
+pngSize img =
+  case runGetOrFail pPngSize (BL.fromStrict img) of
+    Left _ -> Nothing
+    Right (_, _, sz) -> Just sz
+ where
+  pPngSize = do
+    skip 8  -- signature
+    -- the IHDR chunk always comes first:
+    ihdrLen <- getWord32be
+    ihdr <- getByteString 4
+    when (ihdr /= "IHDR" || ihdrLen < 13) $ fail "IHDR chunk not found"
+    w <- getWord32be
+    h <- getWord32be
+    skip (fromIntegral ihdrLen - 8 + 4)  -- rest of chunk and CRC
+    (dx, dy) <- findPhys
+    return ImageSize{ pxX = toInteger w, pxY = toInteger h
+                    , dpiX = dx, dpiY = dy }
+  -- scan the following chunks for pHYs:
+  findPhys = do
+    done <- isEmpty
+    if done
+       then return (72, 72)
+       else do
+         len <- getWord32be
+         typ <- getByteString 4
+         case typ of
+           "pHYs" -> do
+             ppuX <- getWord32be
+             ppuY <- getWord32be
+             unit <- getWord8
+             return $ if unit == 1  -- pixels per meter
+                         then (dpmToDpi (toInteger ppuX),
+                               dpmToDpi (toInteger ppuY))
+                         else (72, 72)
+           "IDAT" -> return (72, 72)  -- pHYs must precede image data
+           "IEND" -> return (72, 72)
+           _ -> skip (fromIntegral len + 4) *> findPhys
+
+-- | Convert dots per meter to dots per inch, using the same integer
+-- arithmetic as JuicyPixels for consistency.
+dpmToDpi :: Integer -> Integer
+dpmToDpi z = z * 254 `div` 10000
+
+-- | Extract JPEG size from the header, without decoding any image
+-- data.  Scans the marker segments preceding the entropy-coded data
+-- for a start-of-frame marker (which gives the dimensions in pixels)
+-- and JFIF APP0 and Exif APP1 segments (which give the resolution).
+jpegSize :: ByteString -> Maybe ImageSize
+jpegSize img =
+  case runGetOrFail (skip 2 *> scanSegments Nothing Nothing)
+         (BL.fromStrict img) of
+    Left _ -> Nothing
+    Right (_, _, sz) -> Just sz
+ where
+  scanSegments jfifDpi exifDpi = do
+    ff <- getWord8
+    when (ff /= 0xff) $ fail "malformed JPEG segment"
+    marker <- skipFill
+    scanSegment marker jfifDpi exifDpi
+
+  -- extra 0xff bytes before a marker are padding:
+  skipFill = do
+    b <- getWord8
+    if b == 0xff then skipFill else return b
+
+  scanSegment marker jfifDpi exifDpi
+    -- start of frame (baseline, progressive, etc.); C4, C8, and CC
+    -- in this range are entropy-coding markers, not SOF:
+    | marker >= 0xc0 && marker <= 0xcf
+      && marker `notElem` [0xc4, 0xc8, 0xcc] = do
+        skip 3  -- segment length and sample precision
+        h <- getWord16be
+        w <- getWord16be
+        -- as in JuicyPixels, Exif resolution overrides JFIF:
+        let (dx, dy) = fromMaybe (fromMaybe (72, 72) jfifDpi) exifDpi
+        return ImageSize{ pxX = toInteger w, pxY = toInteger h
+                        , dpiX = dx, dpiY = dy }
+    | marker == 0xd9 || marker == 0xda =
+        fail "no SOF marker before image data"  -- EOI or SOS
+    | (marker >= 0xd0 && marker <= 0xd7) || marker == 0x01 =
+        scanSegments jfifDpi exifDpi  -- markers without a payload
+    | otherwise = do
+        len <- getWord16be
+        when (len < 2) $ fail "invalid segment length"
+        let n = fromIntegral len - 2
+        case marker of
+          0xe0 -> do  -- APP0 (JFIF)
+            body <- getByteString n
+            scanSegments (jfifDensity body <|> jfifDpi) exifDpi
+          0xe1 -> do  -- APP1 (Exif)
+            body <- getByteString n
+            scanSegments jfifDpi (exifDensity body <|> exifDpi)
+          _ -> skip n *> scanSegments jfifDpi exifDpi
+
+-- | Pixel density from the body of a JFIF APP0 segment.
+jfifDensity :: ByteString -> Maybe (Integer, Integer)
+jfifDensity body = do
+  guard $ "JFIF\0" `B.isPrefixOf` body
+  -- after the identifier and 2-byte version: density units,
+  -- horizontal density, vertical density
+  (units, x, y) <- getAt 7 ((,,) <$> getWord8 <*> getWord16be <*> getWord16be)
+                     body
+  case units of
+    1 -> Just (toInteger x, toInteger y)  -- dots per inch
+    2 -> Just (dpcmToDpi (toInteger x), dpcmToDpi (toInteger y))  -- per cm
+    _ -> Nothing
+
+-- | Resolution from the TIFF structure in the body of an Exif APP1
+-- segment.
+exifDensity :: ByteString -> Maybe (Integer, Integer)
+exifDensity body = do
+  guard $ "Exif\0\0" `B.isPrefixOf` body
+  let tiff = B.drop 6 body  -- offsets are relative to the TIFF header
+  (w16, w32) <- case B.take 2 tiff of
+    "II" -> Just (getWord16le, getWord32le)
+    "MM" -> Just (getWord16be, getWord32be)
+    _    -> Nothing
+  ifd <- fromIntegral <$> getAt 4 w32 tiff
+  n <- fromIntegral <$> getAt ifd w16 tiff
+  entries <- mapM (\i -> do let off = ifd + 2 + 12 * i
+                            tag <- getAt off w16 tiff
+                            return (tag, off))
+                  [0 .. n - 1 :: Int]
+  -- resolution unit (SHORT, stored inline in the value field):
+  -- 2 = inches, 3 = centimeters
+  unit <- lookup 0x0128 entries >>= \off -> getAt (off + 8) w16 tiff
+  toDpi <- case unit of
+             2 -> Just id
+             3 -> Just dpcmToDpi
+             _ -> Nothing
+  let resolution tag = do
+        off <- lookup tag entries
+        -- the value field holds the offset of the RATIONAL value:
+        valOff <- fromIntegral <$> getAt (off + 8) w32 tiff
+        num <- getAt valOff w32 tiff
+        den <- getAt (valOff + 4) w32 tiff
+        guard $ den /= 0
+        return $ toDpi (toInteger num `div` toInteger den)
+  x <- resolution 0x011a  -- XResolution
+  y <- resolution 0x011b  -- YResolution
+  return (x, y)
+
+-- | Convert dots per centimeter to dots per inch, using the same
+-- integer arithmetic as JuicyPixels for consistency.
+dpcmToDpi :: Integer -> Integer
+dpcmToDpi z = z * 254 `div` 100
+
+-- | Run a 'Get' parser at the given offset in a strict ByteString,
+-- returning Nothing if it fails (e.g. by running out of input).
+getAt :: Int -> Get a -> ByteString -> Maybe a
+getAt off g bs
+  | off < 0 = Nothing
+  | otherwise = case runGetOrFail (skip off *> g) (BL.fromStrict bs) of
+      Left _ -> Nothing
+      Right (_, _, x) -> Just x
+
 getSize :: ByteString -> Either T.Text ImageSize
 getSize img =
   case decodeImageWithMetadata img of
@@ -369,8 +576,11 @@
                                      $ TL.fromStrict $ UTF8.toText $ dropBOM img
   let viewboxSize = do
         vb <- findAttrBy (== QName "viewBox" Nothing Nothing) doc
-        [_,_,w,h] <- mapM safeRead (T.words vb)
-        return (w,h)
+        -- per the SVG spec, the numbers are separated by whitespace
+        -- and/or a comma, and may be fractional:
+        [_,_,w,h] <- mapM safeRead $ T.words $
+                       T.map (\c -> if c == ',' then ' ' else c) vb
+        return (floor (w :: Double), floor (h :: Double))
   let dpi = fromIntegral $ writerDpi opts
   let dirToInt dir = do
         dim <- findAttrBy (== QName dir Nothing Nothing) doc >>= lengthToDim
@@ -391,26 +601,34 @@
   let
     parseheader = runGetOrFail $ do
       skip 0x18             -- 0x00
-      frameL <- getWord32le -- 0x18  measured in 1/100 of a millimetre
-      frameT <- getWord32le -- 0x1C
-      frameR <- getWord32le -- 0x20
-      frameB <- getWord32le -- 0x24
+      -- the frame bounds are signed, measured in 1/100 of a millimetre:
+      frameL <- getInt32le  -- 0x18
+      frameT <- getInt32le  -- 0x1C
+      frameR <- getInt32le  -- 0x20
+      frameB <- getInt32le  -- 0x24
       skip 0x20             -- 0x28
       deviceX <- getWord32le  -- 0x48 pixels of reference device
       deviceY <- getWord32le  -- 0x4C
       mmX <- getWord32le      -- 0x50 real mm of reference device (always 320*240?)
-      mmY <- getWord32le      -- 0x58
+      mmY <- getWord32le      -- 0x54
       -- end of header
+      -- guard against division by zero below; since the ImageSize
+      -- fields are lazy, the exception would escape runGetOrFail
+      when (mmX == 0 || mmY == 0) $
+        fail "EMF header has zero-size reference device"
+      -- compute with Integers to avoid Word32 overflow:
       let
-        w = (deviceX * (frameR - frameL)) `quot` (mmX * 100)
-        h = (deviceY * (frameB - frameT)) `quot` (mmY * 100)
-        dpiW = (deviceX * 254) `quot` (mmX * 10)
-        dpiH = (deviceY * 254) `quot` (mmY * 10)
+        w = (toInteger deviceX * (toInteger frameR - toInteger frameL))
+              `quot` (toInteger mmX * 100)
+        h = (toInteger deviceY * (toInteger frameB - toInteger frameT))
+              `quot` (toInteger mmY * 100)
+        dpiW = (toInteger deviceX * 254) `quot` (toInteger mmX * 10)
+        dpiH = (toInteger deviceY * 254) `quot` (toInteger mmY * 10)
       return $ ImageSize
-        { pxX = fromIntegral w
-        , pxY = fromIntegral h
-        , dpiX = fromIntegral dpiW
-        , dpiY = fromIntegral dpiH
+        { pxX = w
+        , pxY = h
+        , dpiX = dpiW
+        , dpiY = dpiH
         }
   in
     case parseheader . BL.fromStrict $ img of
@@ -446,24 +664,23 @@
       AW.word8 0x2a
       width16 <- AW.take 2
       height16 <- AW.take 2
-      let w = toInteger <$> decode lossySize width16
-          h = toInteger <$> decode lossySize height16
-      guard $ isJust w && isJust h
-      return (fromJust w, fromJust h)
-    losslessSizes = runGetOrFail $ do
-      bitReverse32 <$> getWord32le
+      case (decode lossySize width16, decode lossySize height16) of
+        (Just w, Just h) -> return (toInteger w, toInteger h)
+        _ -> empty
+    -- The VP8L bitstream is read starting from the least significant
+    -- bit of each byte, so after reading the 4 bytes as a little-endian
+    -- word, width - 1 is in bits 0-13 and height - 1 in bits 14-27.
+    losslessSizes = runGetOrFail getWord32le
     losslessSize word = 1 + (word .&. 0x3FFF)
     lossless = do
       AW.string "VP8L"
       AW.take 4 -- length in bytes of VP8 Lossless chunk size
       AW.word8 0x2f  -- webp lossless stream magic
       sizes <- AW.take 4
-      let mbword = decode losslessSizes sizes
-      guard $ isJust mbword
-      let word = fromJust mbword
-      let w = toInteger $ losslessSize word
-          h = toInteger $ losslessSize (word `shiftR` 14)
-      return (w, h)
+      case decode losslessSizes sizes of
+        Just word -> return ( toInteger $ losslessSize word
+                            , toInteger $ losslessSize (word `shiftR` 14) )
+        Nothing -> empty
     extendedSize = runGetOrFail $ do
       low <- toInteger <$> getWord16le
       high <- toInteger <$> getWord8
@@ -473,10 +690,9 @@
       AW.take 8  -- VP8X chunk length, flags and reserved area
       width24 <- AW.take 3
       height24 <- AW.take 3
-      let w = decode extendedSize width24
-          h = decode extendedSize height24
-      guard $ isJust w && isJust h
-      return (fromJust w, fromJust h)
+      case (decode extendedSize width24, decode extendedSize height24) of
+        (Just w, Just h) -> return (w, h)
+        _ -> empty
 
 webpSize :: WriterOptions -> ByteString -> Maybe ImageSize
 webpSize opts img =
@@ -485,32 +701,49 @@
     Right sz -> Just sz { dpiX = fromIntegral $ writerDpi opts, dpiY = fromIntegral $ writerDpi opts}
 
 avifSize :: WriterOptions -> ByteString -> Maybe ImageSize
-avifSize _opts img =
+avifSize opts img =
   case runGetOrFail (verifyFtyp >> findAvifDimensions) (BL.fromStrict img) of
     Left (_, _, _err) -> Nothing
     Right (_, _, (width, height)) ->
       Just $ ImageSize { pxX = fromIntegral width
                        , pxY = fromIntegral height
-                       , dpiX = 72
-                       , dpiY = 72 }
+                       , dpiX = fromIntegral $ writerDpi opts
+                       , dpiY = fromIntegral $ writerDpi opts }
 
 ---- AVIF parsing:
 
+-- | Read an ISO BMFF box header, returning the box type, the size of
+-- the box contents, and the size of the header itself.  Handles the
+-- 64-bit "largesize" field (size == 1) and boxes that extend to the
+-- end of the file (size == 0).
+getBoxHeader :: Get (B.ByteString, Int, Int)
+getBoxHeader = do
+  boxSize <- getWord32be
+  boxType <- getByteString 4
+  case boxSize of
+    0 -> do  -- the box extends to the end of the file
+      rest <- lookAhead getRemainingLazyByteString
+      return (boxType, fromIntegral (BL.length rest), 8)
+    1 -> do  -- a 64-bit size follows the box type
+      largeSize <- getWord64be
+      when (largeSize < 16 || largeSize > 0x7fffffff) $
+        fail "invalid box largesize"
+      return (boxType, fromIntegral largeSize - 16, 16)
+    _ -> do
+      when (boxSize < 8) $ fail "invalid box size"
+      return (boxType, fromIntegral boxSize - 8, 8)
+
 verifyFtyp :: Get ()
 verifyFtyp = do
-  ftypSize <- getWord32be
-  when (ftypSize < 16) $ fail "Invalid ftyp size"
-
-  ftyp <- getByteString 4
-  unless (ftyp == "ftyp") $ fail "ftyp signature not found"
+  (boxType, contentSize, _) <- getBoxHeader
+  unless (boxType == "ftyp") $ fail "ftyp signature not found"
+  when (contentSize < 8) $ fail "Invalid ftyp size"
 
   brand <- getByteString 4
   unless (brand == "avif" || brand == "avis") $ fail "Not an AVIF file"
 
   -- Skip minor version and compatible brands
-  -- (we've read 12 bytes: size+type+brand)
-  let remaining_ftyp = fromIntegral ftypSize - 12
-  when (remaining_ftyp > 0) $ skip remaining_ftyp
+  skip (contentSize - 4)
 
 findAvifDimensions :: Get (Word32, Word32)
 findAvifDimensions = searchAvifBoxes []
@@ -521,10 +754,7 @@
   if isempty
     then fail $ "No dimensions found. Searched: " ++ show (reverse path)
     else do
-      boxSize <- getWord32be
-      boxType <- getByteString 4
-
-      let contentSize = fromIntegral boxSize - 8
+      (boxType, contentSize, _) <- getBoxHeader
       let newPath = boxType : path
 
       -- If it's a container box, search inside it
@@ -535,10 +765,8 @@
           result <- tryParseDimensions boxType contentSize
           case result of
             Just dims -> return dims
-            Nothing -> do
-              -- Skip this box and continue
-              when (contentSize > 0 && contentSize < 10000000) $
-                skip contentSize
+            Nothing ->
+              -- Don't skip here - tryParseDimensions already handled it
               searchAvifBoxes path
 
 tryParseDimensions :: B.ByteString -> Int -> Get (Maybe (Word32, Word32))
@@ -575,8 +803,12 @@
   version <- getWord8
   skip 3  -- flags
 
-  -- Skip to width/height based on version
-  let skipBytes = if version == 1 then 76 else 64
+  -- Skip to width/height based on version:
+  -- creation/modification times, track ID, reserved, duration
+  -- (20 bytes with 32-bit times, 32 bytes with 64-bit times),
+  -- then 8 reserved, layer, alternate_group, volume, reserved
+  -- (2 bytes each), and the 36-byte transformation matrix
+  let skipBytes = if version == 1 then 84 else 72
   skip skipBytes
 
   width <- getWord32be
@@ -631,13 +863,11 @@
 searchAvifBoxesInRange remaining' path
   | remaining' < 8 = searchAvifBoxes path
   | otherwise = do
-      boxSize <- getWord32be
-      boxType <- getByteString 4
+      (boxType, contentSize, headerSize) <- getBoxHeader
 
-      let contentSize = fromIntegral boxSize - 8
       let newPath = boxType : path
 
-      when (contentSize < 0 || fromIntegral boxSize > remaining') $ do
+      when (contentSize + headerSize > remaining') $
         fail $ "Malformed box at path: " ++ show (reverse newPath)
 
       if isContainerBox boxType
@@ -648,7 +878,8 @@
             Just dims -> return dims
             Nothing -> do
               -- Don't skip here - tryParseDimensions already handled it
-              searchAvifBoxesInRange (remaining' - fromIntegral boxSize) path
+              searchAvifBoxesInRange
+                (remaining' - contentSize - headerSize) path
 
 isContainerBox :: B.ByteString -> Bool
 isContainerBox boxType = boxType `elem`
diff --git a/src/Text/Pandoc/MediaBag.hs b/src/Text/Pandoc/MediaBag.hs
--- a/src/Text/Pandoc/MediaBag.hs
+++ b/src/Text/Pandoc/MediaBag.hs
@@ -23,26 +23,29 @@
                      mediaDirectory,
                      mediaItems
                      ) where
-import Crypto.Hash (hashWith, SHA1(SHA1))
+import Crypto.Hash (hashlazy, Digest, SHA1)
 import qualified Data.ByteString.Lazy as BL
+import Data.Char (toLower)
 import Data.Data (Data)
-import qualified Data.Map as M
+import qualified Data.Map.Strict as M
 import Data.Maybe (fromMaybe, isNothing)
 import Data.Typeable (Typeable)
 import System.FilePath
 import qualified System.FilePath.Posix as Posix
 import qualified System.FilePath.Windows as Windows
 import Text.Pandoc.MIME (MimeType, getMimeTypeDef, extensionFromMimeType)
+import Text.Pandoc.Shared (makeCanonical)
 import Data.Text (Text)
 import qualified Data.Text as T
-import Network.URI (URI (..), isURI, parseURI, unEscapeString)
-import Data.List (isInfixOf)
+import Network.URI (URI (..), parseURI, unEscapeString)
+import Text.Pandoc.URI (isURI)
 
 data MediaItem =
   MediaItem
-  { mediaMimeType :: MimeType
-  , mediaPath :: FilePath
+  { mediaMimeType :: !MimeType
+  , mediaPath :: !FilePath
   , mediaContents :: BL.ByteString
+    -- ^ Left lazy so that contents need not be forced at insert time.
   } deriving (Eq, Ord, Show, Data, Typeable)
 
 -- | A container for a collection of binary resources, with names and
@@ -55,14 +58,22 @@
 instance Show MediaBag where
   show bag = "MediaBag " ++ show (mediaDirectory bag)
 
--- | We represent paths with /, in normalized form.  Percent-encoding
--- is not resolved.
+-- | Check for the (case-insensitive) @data:@ URI scheme.
+isDataURI :: FilePath -> Bool
+isDataURI = (== "data:") . map toLower . take 5
+
+-- | We represent paths with /, in canonical form (redundant @.@ and
+-- @..@ components removed).  Percent-encoding is not resolved.
 canonicalize :: FilePath -> Text
--- avoid an expensive call to isURI for data URIs:
-canonicalize fp@('d':'a':'t':'a':':':_) = T.pack fp
 canonicalize fp
-  | isURI fp = T.pack fp
-  | otherwise = T.replace "\\" "/" . T.pack . normalise $ fp
+  -- avoid an expensive call to isURI for data URIs:
+  | isDataURI fp = fp'
+  | isURI fp' = fp'
+  | otherwise = T.pack . makeCanonical . map slashify $ fp
+ where
+  fp' = T.pack fp
+  slashify '\\' = '/'
+  slashify c    = c
 
 -- | Delete a media item from a 'MediaBag', or do nothing if no item corresponds
 -- to the given path.
@@ -80,7 +91,7 @@
             -> MediaBag
             -> MediaBag
 insertMedia fp mbMime contents (MediaBag mediamap)
- | 'd':'a':'t':'a':':':_ <- fp
+ | isDataURI fp
  , Just mt' <- mbMime
    = MediaBag (M.insert fp'
                MediaItem{ mediaPath = hashpath
@@ -94,14 +105,24 @@
   fp' = canonicalize fp
   fp'' = unEscapeString $ T.unpack fp'
   uri = parseURI fp
-  hashpath = show (hashWith SHA1 (BL.toStrict contents)) <> ext
+  hashpath = show (hashlazy contents :: Digest SHA1) <> ext
+  -- We only keep the original name if the key contains no
+  -- percent-encoding, i.e., unescaping is the identity; otherwise
+  -- distinct keys (e.g. "a%20b.png" and "a b.png") could unescape
+  -- to the same mediaPath and clobber each other on extraction.
   newpath = if Posix.isRelative fp''
                  && Windows.isRelative fp''
                  && isNothing uri
-                 && not (".." `isInfixOf` fp'')
-                 && '%' `notElem` fp''
+                 && not containsParentRef
+                 && not (T.any (== '%') fp')
                then fp''
                else hashpath
+  -- Check for a ".." path component (treating both / and \ as
+  -- separators, since the unescaped path may contain backslashes
+  -- from percent-encoding); a mere ".." substring (as in
+  -- "foo..bar.png") is harmless.
+  containsParentRef = ".." `elem`
+    Posix.splitDirectories (map (\c -> if c == '\\' then '/' else c) fp'')
   fallback = case takeExtension fp'' of
                   ".gz" -> getMimeTypeDef $ dropExtension fp''
                   _     -> getMimeTypeDef fp''
diff --git a/src/Text/Pandoc/PDF.hs b/src/Text/Pandoc/PDF.hs
--- a/src/Text/Pandoc/PDF.hs
+++ b/src/Text/Pandoc/PDF.hs
@@ -49,7 +49,7 @@
 import Text.Pandoc.Extensions (disableExtension, Extension(Ext_smart))
 import Text.Pandoc.Process (pipeProcess)
 import System.Process (readProcessWithExitCode)
-import Text.Pandoc.Shared (inDirectory, stringify, tshow)
+import Text.Pandoc.Shared (inDirectory, stringify, stringifyInlines, tshow)
 import qualified Text.Pandoc.UTF8 as UTF8
 import Text.Pandoc.Walk (walkM)
 import Text.Pandoc.Writers.Shared (getField, metaToContext)
@@ -196,7 +196,7 @@
                       _ -> []
   meta' <- metaToContext opts
              (return . literal . stringify)
-             (return . literal . stringify)
+             (return . literal . stringifyInlines)
              meta
   let toArgs (f, mbd) = maybe [] (\d -> ["--" <> f, T.unpack d]) mbd
   let args   = mathArgs ++ concatMap toArgs
diff --git a/src/Text/Pandoc/Parsing.hs b/src/Text/Pandoc/Parsing.hs
--- a/src/Text/Pandoc/Parsing.hs
+++ b/src/Text/Pandoc/Parsing.hs
@@ -188,7 +188,6 @@
       ParsecT,
       SourcePos,
       SourceName,
-      setSourceName,
       Column,
       Line,
       incSourceLine,
diff --git a/src/Text/Pandoc/Parsing/Capabilities.hs b/src/Text/Pandoc/Parsing/Capabilities.hs
--- a/src/Text/Pandoc/Parsing/Capabilities.hs
+++ b/src/Text/Pandoc/Parsing/Capabilities.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE MultiParamTypeClasses      #-}
+{-# LANGUAGE BangPatterns               #-}
 {- |
    Module      : Text.Pandoc.Parsing
    Copyright   : © 2006-2024 John MacFarlane
@@ -121,7 +122,9 @@
 -- | Update the position on which the last string ended.
 updateLastStrPos :: (Stream s m a, HasLastStrPosition st)
                  => ParsecT s st m ()
-updateLastStrPos = getPosition >>= updateState . setLastStrPos . Just
+updateLastStrPos = do
+  !pos <- getPosition
+  updateState $ setLastStrPos $ Just pos
 
 -- | Whether we are right after the end of a string.
 notAfterString :: (Stream s m a, HasLastStrPosition st) => ParsecT s st m Bool
diff --git a/src/Text/Pandoc/Parsing/Future.hs b/src/Text/Pandoc/Parsing/Future.hs
--- a/src/Text/Pandoc/Parsing/Future.hs
+++ b/src/Text/Pandoc/Parsing/Future.hs
@@ -17,10 +17,8 @@
   )
 where
 
-import Prelude hiding (Applicative(..))
-import Control.Applicative (Applicative(..))
 import Control.Monad.Reader
-  ( asks, runReader, MonadReader(ask), Reader, ReaderT(ReaderT) )
+  ( asks, runReader, MonadReader(ask), Reader )
 
 -- | Reader monad wrapping the parser state. This is used to possibly
 -- delay evaluation until all relevant information has been parsed and
@@ -31,9 +29,8 @@
 instance Semigroup a => Semigroup (Future s a) where
   (<>) = liftA2 (<>)
 
-instance (Semigroup a, Monoid a) => Monoid (Future s a) where
+instance Monoid a => Monoid (Future s a) where
   mempty = return mempty
-  mappend = (<>)
 
 -- | Run a delayed action with the given state.
 runF :: Future s a -> s -> a
diff --git a/src/Text/Pandoc/Parsing/General.hs b/src/Text/Pandoc/Parsing/General.hs
--- a/src/Text/Pandoc/Parsing/General.hs
+++ b/src/Text/Pandoc/Parsing/General.hs
@@ -64,7 +64,6 @@
 
 import Control.Monad
   ( join
-  , liftM
   , unless
   , void
   , when
@@ -129,8 +128,6 @@
   , setInput
   , setPosition
   , skipMany
-  , sourceColumn
-  , sourceName
   , tokenPrim
   , try
   , unexpected
@@ -142,6 +139,7 @@
 import Text.Pandoc.Parsing.Capabilities
 import Text.Pandoc.Parsing.State
 import Text.Pandoc.Parsing.Future (Future (..))
+import qualified Data.Map as M
 import qualified Data.Set as Set
 import qualified Data.Text as T
 import qualified Text.Pandoc.Builder as B
@@ -151,7 +149,7 @@
 -- | Remove whitespace from start and end; just like @'trimInlines'@,
 -- but lifted into the 'Future' type.
 trimInlinesF :: Future s Inlines -> Future s Inlines
-trimInlinesF = liftM trimInlines
+trimInlinesF = fmap trimInlines
 
 -- | Like @count@, but packs its result
 countChar :: (Stream s m Char, UpdateSourcePos s Char, Monad m)
@@ -311,13 +309,15 @@
                => [Text] -> ParsecT s st m Text
 oneOfStringsCI = oneOfStrings' ciMatch
   where ciMatch x y = toLower' x == toLower' y
-        -- this optimizes toLower by checking common ASCII case
-        -- first, before calling the expensive unicode-aware
-        -- function:
-        toLower' c | isAsciiUpper c = chr (ord c + 32)
-                   | isAscii c = c
-                   | otherwise = toLower c
 
+-- | Optimized 'toLower': checks the common ASCII case
+-- first, before calling the expensive unicode-aware
+-- function.
+toLower' :: Char -> Char
+toLower' c | isAsciiUpper c = chr (ord c + 32)
+           | isAscii c = c
+           | otherwise = toLower c
+
 -- | Parses a space or tab.
 spaceChar :: (Stream s m Char, UpdateSourcePos s Char)
           => ParsecT s st m Char
@@ -356,7 +356,7 @@
              => Int -> ParsecT Sources st m ()
 gobbleSpaces 0 = return ()
 gobbleSpaces n
-  | n < 0     = error "gobbleSpaces called with negative number"
+  | n < 0     = Prelude.fail "gobbleSpaces called with negative number"
   | otherwise = try $ do
       char ' ' <|> eatOneSpaceOfTab
       gobbleSpaces (n - 1)
@@ -369,12 +369,11 @@
   -- replace the tab on the input stream with spaces
   let numSpaces = tabstop - ((sourceColumn pos - 1) `mod` tabstop)
   inp <- getInput
-  setInput $
-    case inp of
-      Sources [] -> error "eatOneSpaceOfTab - empty Sources list"
-      Sources ((fp,t):rest) ->
-        -- drop the tab and add spaces
-        Sources ((fp, T.replicate numSpaces " " <> T.drop 1 t):rest)
+  case inp of
+    Sources [] -> Prelude.fail "eatOneSpaceOfTab - empty Sources list"
+    Sources ((fp,t):rest) -> setInput $
+      -- drop the tab and add spaces
+      Sources ((fp, T.replicate numSpaces " " <> T.drop 1 t):rest)
   char ' '
 
 -- | Gobble up to n spaces; if tabs are encountered, expand them
@@ -383,7 +382,7 @@
                    => Int -> ParsecT Sources st m Int
 gobbleAtMostSpaces 0 = return 0
 gobbleAtMostSpaces n
-  | n < 0     = error "gobbleAtMostSpaces called with negative number"
+  | n < 0     = Prelude.fail "gobbleAtMostSpaces called with negative number"
   | otherwise = option 0 $ do
       char ' ' <|> eatOneSpaceOfTab
       (+ 1) <$> gobbleAtMostSpaces (n - 1)
@@ -487,8 +486,36 @@
 emailPunctChars :: Set.Set Char
 emailPunctChars = Set.fromList "!\"#$%&'*+-/=?^_{|}~;"
 
+-- | Trie over 'Char', used for efficient matching against the
+-- (large, static) set of known URI schemes.
+data CharTrie = CharTrie !Bool !(M.Map Char CharTrie)
+
+trieInsert :: Text -> CharTrie -> CharTrie
+trieInsert t (CharTrie terminal m) =
+  case T.uncons t of
+    Nothing -> CharTrie True m
+    Just (c, rest) -> CharTrie terminal $
+      M.alter (Just . trieInsert rest . fromMaybe (CharTrie False mempty)) c m
+
+-- | Trie of known URI schemes; keys are case-folded with 'toLower''.
+schemeTrie :: CharTrie
+schemeTrie = foldr trieInsert (CharTrie False mempty)
+                   (map (T.map toLower') (Set.toList schemes))
+
+-- | Parses a known URI scheme, case-insensitively, preferring the
+-- longest matching scheme.  Returns the scheme as written in the input.
 uriScheme :: (Stream s m Char, UpdateSourcePos s Char) => ParsecT s st m Text
-uriScheme = oneOfStringsCI (Set.toList schemes)
+uriScheme = TL.toStrict . TB.toLazyText <$> try (go mempty schemeTrie)
+ where
+  go acc (CharTrie _ m) = do
+    c <- anyChar
+    case M.lookup (toLower' c) m of
+      Nothing -> Prelude.fail "not a URI scheme"
+      Just subtrie@(CharTrie terminal _) ->
+        let !acc' = acc <> TB.singleton c
+        in if terminal
+              then option acc' (try (go acc' subtrie))
+              else go acc' subtrie
 
 -- | Parses a URI. Returns pair of original and URI-escaped version.
 uri :: (Stream s m Char, UpdateSourcePos s Char) => ParsecT s st m (Text, Text)
@@ -661,8 +688,7 @@
        let id'' = if Ext_ascii_identifiers `extensionEnabled` exts
                      then toAsciiText id'
                      else id'
-       updateState $ updateIdentifierList $ Set.insert id'
-       updateState $ updateIdentifierList $ Set.insert id''
+       updateState $ updateIdentifierList (Set.insert id' . Set.insert id'')
        return (id'',classes,kvs)
      else do
         unless (T.null ident) $ do
diff --git a/src/Text/Pandoc/Parsing/GridTable.hs b/src/Text/Pandoc/Parsing/GridTable.hs
--- a/src/Text/Pandoc/Parsing/GridTable.hs
+++ b/src/Text/Pandoc/Parsing/GridTable.hs
@@ -39,7 +39,6 @@
 import Text.Pandoc.Parsing.General
 import Text.Pandoc.Sources
 import Text.Parsec (Stream (..), ParsecT, optional, sepEndBy1, try)
-
 import Data.Maybe (mapMaybe)
 import qualified Data.Text as T
 import qualified Text.GridTable as GT
@@ -145,7 +144,7 @@
                tbl
   let rows = GT.rows blkTbl
   let toPandocCell (GT.Cell c (GT.RowSpan rs) (GT.ColSpan cs)) =
-        fmap (B.cell AlignDefault (B.RowSpan rs) (B.ColSpan cs) . plainify) <$> c
+        fmap (B.cell AlignDefault (B.RowSpan rs) (B.ColSpan cs)) <$> c
   rows' <- mapM (mapM toPandocCell) rows
   columns <- getOption readerColumns
   let colspecs = zipWith (\cs w -> (convAlign $ fst cs, B.ColWidth w))
@@ -186,11 +185,6 @@
    where startsWithSpace t = case T.uncons t of
            Nothing     -> True
            Just (c, _) -> c == ' '
-
-plainify :: B.Blocks -> B.Blocks
-plainify blks = case B.toList blks of
-  [Para x] -> B.fromList [Plain x]
-  _        -> blks
 
 convAlign :: GT.Alignment -> B.Alignment
 convAlign GT.AlignLeft    = B.AlignLeft
diff --git a/src/Text/Pandoc/Parsing/Lists.hs b/src/Text/Pandoc/Parsing/Lists.hs
--- a/src/Text/Pandoc/Parsing/Lists.hs
+++ b/src/Text/Pandoc/Parsing/Lists.hs
@@ -24,9 +24,11 @@
 import Data.Char
   ( isAsciiUpper
   , isAsciiLower
+  , isDigit
   , ord
   , toLower
   )
+import Control.Monad (mzero)
 import Data.Maybe (fromMaybe)
 import Text.Pandoc.Definition
   ( ListNumberDelim(..)
@@ -160,10 +162,29 @@
 
 -- | Parses an ordered list marker and returns list attributes.
 anyOrderedListMarker :: (Stream s m Char, UpdateSourcePos s Char) => ParsecT s ParserState m ListAttributes
-anyOrderedListMarker = choice
-  [delimParser numParser | delimParser <- [inPeriod, inOneParen, inTwoParens],
-                           numParser <- [decimal, exampleNum, defaultNum, romanOne,
-                           lowerAlpha, lowerRoman, upperAlpha, upperRoman]]
+anyOrderedListMarker = try $ do
+  openParen <- option False $ True <$ char '('
+  c <- lookAhead anyChar
+  let withDelim f = try $ do
+        (style, start) <- f
+        delim <-
+          ((if style == DefaultStyle
+               then DefaultDelim
+               else Period) <$ char '.')
+           <|>
+          ((if openParen
+               then TwoParens
+               else OneParen) <$ char ')')
+        return (start, style, delim)
+  case c of
+     '@' -> withDelim exampleNum
+     '#' -> withDelim defaultNum
+     _ | isDigit c -> withDelim exampleNum <|> withDelim decimal
+       | isAsciiLower c ->
+           withDelim romanOne <|> withDelim lowerAlpha <|> withDelim lowerRoman
+       | isAsciiUpper c ->
+           withDelim romanOne <|> withDelim upperAlpha <|> withDelim upperRoman
+     _ -> mzero
 
 -- | Parses a list number (num) followed by a period, returns list attributes.
 inPeriod :: (Stream s m Char, UpdateSourcePos s Char)
diff --git a/src/Text/Pandoc/Parsing/Math.hs b/src/Text/Pandoc/Parsing/Math.hs
--- a/src/Text/Pandoc/Parsing/Math.hs
+++ b/src/Text/Pandoc/Parsing/Math.hs
@@ -15,7 +15,7 @@
   )
 where
 
-import Control.Monad (mzero, when)
+import Control.Monad (mzero, when, guard)
 import Data.Text (Text)
 import Text.Parsec ((<|>), ParsecT, Stream(..), notFollowedBy, many1, try)
 import Text.Pandoc.Options
@@ -44,8 +44,10 @@
                            (try (string "text" >>
                                  (("\\text" <>) <$> inBalancedBraces 0 ""))
                             <|>  (\c -> T.pack ['\\',c]) <$> anyChar))
-                   <|> ("\n" <$ blankline <* notFollowedBy' blankline <* notFollowedBy (char '$'))
-                   <|> (T.pack <$> many1 spaceChar <* notFollowedBy (char '$'))
+                   <|> ("\n" <$ blankline <* notFollowedBy' blankline <*
+                          (guard (op /= "$") <|> notFollowedBy (char '$')))
+                   <|> (T.pack <$> many1 spaceChar <*
+                          (guard (op /= "$") <|> notFollowedBy (char '$')))
                     ) (try $ textStr cl)
   notFollowedBy digit  -- to prevent capture of $5
   return $ trimMath $ T.concat words'
diff --git a/src/Text/Pandoc/Readers/AsciiDoc.hs b/src/Text/Pandoc/Readers/AsciiDoc.hs
--- a/src/Text/Pandoc/Readers/AsciiDoc.hs
+++ b/src/Text/Pandoc/Readers/AsciiDoc.hs
@@ -25,7 +25,7 @@
 import Text.Pandoc.Definition
 import Text.Pandoc.Walk
 import Text.Pandoc.Shared (addPandocAttributes, blocksToInlines, safeRead,
-                           tshow)
+                           tshow, compactifyTable)
 import qualified Text.Pandoc.UTF8 as UTF8
 import qualified AsciiDoc as A
 import Text.Pandoc.Error
@@ -53,9 +53,6 @@
    (\(sourcepos, t) ->
      A.parseDocument getIncludeFile raiseError (sourceName sourcepos) t)
     sources)
-   >>= resolveFootnotes
-   >>= resolveStem
-   >>= resolveIcons
    >>= toPandoc
  where
   getIncludeFile fp = UTF8.toText <$> readFileStrict fp
@@ -63,64 +60,56 @@
                             $ msg <> " at " <> show fp <>
                               " char " <> show pos
 
-toPandoc :: PandocMonad m => A.Document -> m Pandoc
-toPandoc doc =
-  Pandoc <$> doMeta (A.docMeta doc)
-         <*> (B.toList <$> doBlocks (A.docBlocks doc))
+-- Context used when converting the AsciiDoc AST: resolved footnote
+-- contents, plus document attributes governing stem (math) and icon
+-- interpretation.  These are used to resolve footnote references,
+-- math types, and icons during conversion; doing this in the course
+-- of the conversion is much cheaper than making separate passes over
+-- the AST with mapInlines/mapBlocks.
+data ADContext = ADContext
+  { adFootnotes :: M.Map T.Text B.Inlines
+  , adMathType  :: A.MathType
+  , adIconFont  :: Bool
+  , adIconsDir  :: T.Text
+  , adIconType  :: T.Text
+  }
 
-resolveFootnotes :: Monad m => A.Document -> m A.Document
-resolveFootnotes doc = do
-  evalStateT (A.mapInlines go doc) (mempty :: M.Map T.Text [A.Inline])
+toPandoc :: PandocMonad m => A.Document -> m Pandoc
+toPandoc doc = evalStateT
+  (Pandoc <$> doMeta (A.docMeta doc)
+          <*> (B.toList <$> doBlocks (A.docBlocks doc)))
+  ADContext
+    { adFootnotes = mempty
+    , adMathType = case M.lookup "stem" docattrs of
+                     Just "asciimath" -> A.AsciiMath
+                     _ -> A.LaTeXMath
+    , adIconFont = case M.lookup "icons" docattrs of
+                     Just "font" -> True
+                     _ -> False
+    , adIconsDir = fromMaybe "./images/icons" $ M.lookup "iconsdir" docattrs
+    , adIconType = fromMaybe "png" $ M.lookup "icontype" docattrs
+    }
  where
-   go (A.Inline attr (A.Footnote (Just (A.FootnoteId fnid)) ils)) = do
-     fnmap <- get
-     case M.lookup fnid fnmap of
-       Just ils' ->
-         pure $ A.Inline attr (A.Footnote (Just (A.FootnoteId fnid)) ils')
-       Nothing -> do
-         put $ M.insert fnid ils fnmap
-         pure $ A.Inline attr (A.Footnote (Just (A.FootnoteId fnid)) ils)
-   go x = pure x
-
-resolveStem :: Monad m => A.Document -> m A.Document
-resolveStem doc = do
-  let defaultType = case M.lookup "stem" (A.docAttributes (A.docMeta doc)) of
-                      Just "asciimath" -> A.AsciiMath
-                      _ -> A.LaTeXMath
-  let doInlineStem (A.Inline attr (A.Math Nothing t)) =
-        pure $ A.Inline attr (A.Math (Just defaultType) t)
-      doInlineStem x = pure x
-  let doBlockStem (A.Block attr mbtit (A.MathBlock Nothing t)) =
-        pure $ A.Block attr mbtit (A.MathBlock (Just defaultType) t)
-      doBlockStem x = A.mapInlines doInlineStem x
-  A.mapBlocks doBlockStem doc
+  docattrs = A.docAttributes (A.docMeta doc)
 
 -- resolve icons as either characters in an icon font or images
-resolveIcons :: Monad m => A.Document -> m A.Document
-resolveIcons doc = A.mapInlines fromIcon doc
+resolveIcon :: ADContext -> A.Inline -> A.Inline
+resolveIcon ctx (A.Inline attr (A.Icon name)) =
+  if adIconFont ctx
+     then A.Inline (addClasses ["fa", "fa-" <> name] attr) (A.Span [])
+     else -- default is to use an image
+          A.Inline (addClasses ["icon"] attr)
+             (A.InlineImage
+               (A.Target
+                  (adIconsDir ctx <> "/" <> name <> "." <> adIconType ctx))
+                  Nothing Nothing Nothing)
  where
-   docattrs = A.docAttributes (A.docMeta doc)
-   iconFont = case M.lookup "icons" docattrs of
-                Just "font" -> True
-                _ -> False
-   iconsdir = fromMaybe "./images/icons" $ M.lookup "iconsdir" docattrs
-   icontype = fromMaybe "png" $ M.lookup "icontype" docattrs
    addClasses cls (A.Attr ps kvs) =
      A.Attr ps $
       case M.lookup "role" kvs of
        Just r -> M.insert "role" (T.unwords (r : cls)) kvs
        Nothing -> M.insert "role" (T.unwords cls) kvs
-   fromIcon (A.Inline attr (A.Icon name)) =
-     if iconFont
-        then pure $
-              A.Inline (addClasses ["fa", "fa-" <> name] attr) (A.Span [])
-        else pure $ -- default is to use an image
-              A.Inline (addClasses ["icon"] attr)
-                 (A.InlineImage
-                   (A.Target
-                      (iconsdir <> "/" <> name <> "." <> icontype))
-                      Nothing Nothing Nothing)
-   fromIcon x = pure x
+resolveIcon _ x = x
 
 addAttribution :: Maybe A.Attribution -> B.Blocks -> B.Blocks
 addAttribution Nothing bs = bs
@@ -132,7 +121,7 @@
  where
    attrBlock = Para (B.toList $ B.text $ "\x2014 " <> t)
 
-doMeta :: PandocMonad m => A.Meta -> m B.Meta
+doMeta :: (PandocMonad m, MonadState ADContext m) => A.Meta -> m B.Meta
 doMeta meta = do
   tit' <- doInlines (A.docTitle meta)
   pure $
@@ -164,7 +153,7 @@
     " (" <> B.link ("mailto:" <> email) "" (B.str email) <> ")")
     (A.authorEmail au)
 
-doBlocks :: PandocMonad m => [A.Block] -> m B.Blocks
+doBlocks :: (PandocMonad m, MonadState ADContext m) => [A.Block] -> m B.Blocks
 doBlocks = fmap mconcat . mapM doBlock
 
 addBlockAttr :: A.Attr -> B.Blocks -> B.Blocks
@@ -184,7 +173,8 @@
   let tit = B.toList tit'
   in case B.toList bs of
     [B.Table attr _ colspecs thead tbody tfoot] ->
-      B.singleton $ B.Table attr (B.Caption Nothing [B.Plain tit])
+      B.singleton $ compactifyTable
+                  $ B.Table attr (B.Caption Nothing [B.Plain tit])
                      colspecs thead tbody tfoot
     [B.Figure attr _ bs'] ->
       B.singleton $ B.Figure attr (B.Caption Nothing [B.Plain tit]) bs'
@@ -195,7 +185,7 @@
       B.singleton $ B.Div attr (B.Div ("",["title"],[]) [B.Para tit] : bs')
     _ -> B.divWith B.nullAttr (B.divWith ("",["title"],[]) (B.para tit') <> bs)
 
-doBlock :: PandocMonad m => A.Block -> m B.Blocks
+doBlock :: (PandocMonad m, MonadState ADContext m) => A.Block -> m B.Blocks
 doBlock (A.Block attr@(A.Attr ps kvs) mbtitle bt) = do
   mbtitle' <- case mbtitle of
                 Nothing -> pure Nothing
@@ -231,11 +221,12 @@
       addAttribution mbattrib . B.blockQuote <$> doBlocks bs
     A.Verse mbattrib bs ->
       addAttribution mbattrib . B.blockQuote <$> doBlocks bs
-    -- TODO when texmath's asciimath parser works, convert:
-    A.MathBlock (Just A.AsciiMath) t -> pure $ B.para $ B.displayMath t
-    A.MathBlock (Just A.LaTeXMath) t -> pure $ B.para $ B.displayMath t
-    A.MathBlock Nothing _ ->
-      throwError $ PandocParseError "Encountered math type Nothing"
+    A.MathBlock mbMathType t -> do
+      mathType <- maybe (gets adMathType) pure mbMathType
+      case mathType of
+        -- TODO when texmath's asciimath parser works, convert:
+        A.AsciiMath -> pure $ B.para $ B.displayMath t
+        A.LaTeXMath -> pure $ B.para $ B.displayMath t
     A.List (A.BulletList _) items ->
       B.bulletList <$> mapM doItem items
     A.List A.CheckList items ->
@@ -271,8 +262,10 @@
                         (B.RowSpan rowspan) (B.ColSpan colspan) . B.toList
                    <$> doBlocks bs
       let fromRow (A.TableRow cs) = B.Row B.nullAttr <$> mapM fromCell cs
-      tbody <- B.TableBody B.nullAttr (B.RowHeadColumns 0) [] <$> mapM fromRow rows
+      -- note: conversion is stateful (footnotes), so we convert in
+      -- document order: header, body, footer
       thead <- B.TableHead B.nullAttr <$> maybe (pure []) (mapM fromRow) mbHeader
+      tbody <- B.TableBody B.nullAttr (B.RowHeadColumns 0) [] <$> mapM fromRow rows
       tfoot <- B.TableFoot B.nullAttr <$> maybe (pure []) (mapM fromRow) mbFooter
       let totalWidth = sum $ map (fromMaybe 1 . A.colWidth) specs
       let toColSpec spec = (maybe B.AlignDefault toAlign (A.colHorizAlign spec),
@@ -281,7 +274,8 @@
                                    fromIntegral x / fromIntegral totalWidth))
                                (A.colWidth spec))
       let colspecs = map toColSpec specs
-      pure $ B.table (B.Caption Nothing mempty) -- added by addBlockTitle
+      pure $ compactifyTable
+           $ B.table (B.Caption Nothing mempty) -- added by addBlockTitle
                 colspecs thead [tbody] tfoot
     A.BlockImage target mbalt mbw mbh -> do
       img' <- doInline (A.Inline mempty (A.InlineImage target mbalt mbw mbh))
@@ -315,7 +309,7 @@
         Left _ -> pure $ B.rawBlock "html" t
         Right (Pandoc _ bs) -> pure $ B.fromList bs
 
-doItem :: PandocMonad m => A.ListItem -> m B.Blocks
+doItem :: (PandocMonad m, MonadState ADContext m) => A.ListItem -> m B.Blocks
 doItem (A.ListItem Nothing bs) = doBlocks bs
 doItem (A.ListItem (Just checkstate) bs) = do
   bs' <- doBlocks bs
@@ -328,18 +322,24 @@
            (B.Plain ils : rest) -> B.Plain (check : B.Space : ils) : rest
            rest -> B.Para [check] : rest
 
-doDefListItem :: PandocMonad m
+doDefListItem :: (PandocMonad m, MonadState ADContext m)
               => ([A.Inline], [A.Block]) -> m (B.Inlines , [B.Blocks])
 doDefListItem (lab, bs) = do
   lab' <- doInlines lab
   bs' <- doBlocks bs
   pure (lab', [bs'])
 
-doInlines :: PandocMonad m => [A.Inline] -> m B.Inlines
+doInlines :: (PandocMonad m, MonadState ADContext m) => [A.Inline] -> m B.Inlines
 doInlines = fmap mconcat . mapM doInline
 
-doInline :: PandocMonad m => A.Inline -> m B.Inlines
-doInline (A.Inline (A.Attr _ps kvs') it) = do
+doInline :: (PandocMonad m, MonadState ADContext m) => A.Inline -> m B.Inlines
+doInline il@(A.Inline _ A.Icon{}) = do
+  ctx <- get
+  doInline' (resolveIcon ctx il)
+doInline il = doInline' il
+
+doInline' :: (PandocMonad m, MonadState ADContext m) => A.Inline -> m B.Inlines
+doInline' (A.Inline (A.Attr _ps kvs') it) = do
   let kvs = M.mapKeys (\k -> if k == "role" then "class" else k) kvs'
   addPandocAttributes (M.toList kvs) <$>
    case it of
@@ -354,12 +354,14 @@
     A.Strikethrough ils -> B.strikeout <$> doInlines ils
     A.DoubleQuoted ils -> B.doubleQuoted <$> doInlines ils
     A.SingleQuoted ils -> B.singleQuoted <$> doInlines ils
-    -- TODO when texmath's asciimath parser works, convert:
-    A.Math (Just A.AsciiMath) t -> pure $ B.math t
-    A.Math (Just A.LaTeXMath) t -> pure $ B.math t
-    A.Math Nothing _ ->
-      throwError $ PandocParseError "Encountered math type Nothing"
-    A.Icon t -> pure $ B.spanWith ("",["icon"],[("name",t)])
+    A.Math mbMathType t -> do
+      mathType <- maybe (gets adMathType) pure mbMathType
+      case mathType of
+        -- TODO when texmath's asciimath parser works, convert:
+        A.AsciiMath -> pure $ B.math t
+        A.LaTeXMath -> pure $ B.math t
+    A.Icon t -> -- can't happen (rewritten by resolveIcon in doInline)
+      pure $ B.spanWith ("",["icon"],[("name",t)])
                          (B.str ("[" <> t <> "]"))
     A.Button t -> pure $ B.spanWith ("",["button"],[])
                          (B.strong $ B.str ("[" <> t <> "]"))
@@ -380,7 +382,19 @@
                   Just (A.Height n) -> [("height", T.pack $ show n <> "px")]
                   Nothing -> []
       pure $ B.imageWith ("",[], width ++ height) url "" alt
-    A.Footnote _ ils -> B.note . B.para <$> doInlines ils
+    A.Footnote (Just (A.FootnoteId fnid)) ils -> do
+      -- repeated references to the same id get the contents of the
+      -- first footnote with that id
+      contents <- doInlines ils
+      fnmap <- gets adFootnotes
+      contents' <- case M.lookup fnid fnmap of
+                     Just stored -> pure stored
+                     Nothing -> do
+                       modify $ \ctx ->
+                         ctx{ adFootnotes = M.insert fnid contents fnmap }
+                       pure contents
+      pure $ B.note $ B.para contents'
+    A.Footnote Nothing ils -> B.note . B.para <$> doInlines ils
     A.InlineAnchor t _ -> pure $ B.spanWith (t, [], []) mempty
     A.BibliographyAnchor t _ -> pure $ B.spanWith (t, [], []) mempty
     A.CrossReference t Nothing ->
diff --git a/src/Text/Pandoc/Readers/CommonMark.hs b/src/Text/Pandoc/Readers/CommonMark.hs
--- a/src/Text/Pandoc/Readers/CommonMark.hs
+++ b/src/Text/Pandoc/Readers/CommonMark.hs
@@ -79,12 +79,12 @@
 makeFigures b = b
 
 sourceToToks :: (SourcePos, Text) -> [Tok]
-sourceToToks (pos, s) = map adjust $ tokenize (sourceName pos) s
+sourceToToks (pos, s) =
+  case sourceLine pos of
+    1 -> toks
+    n -> map (\tok -> tok{ tokPos = incSourceLine (tokPos tok) (n - 1) }) toks
  where
-   adjust = case sourceLine pos of
-              1 -> id
-              n -> \tok -> tok{ tokPos =
-                                  incSourceLine (tokPos tok) (n - 1) }
+   toks = tokenize (sourceName pos) s
 
 
 metaValueParser :: Monad m
@@ -102,7 +102,7 @@
       then walk makeFigures
       else id) .
   (if isEnabled Ext_tex_math_gfm opts
-      then walk handleGfmMath
+      then walk handleGfmMathBlock . walk handleGfmMathInline
       else id) .
   (if readerStripComments opts
       then walk stripBlockComments . walk stripInlineComments
@@ -115,9 +115,9 @@
             Left err -> throwError $ fromParsecError s err
             Right (Cm bls :: Cm () Blocks) -> return $ B.doc bls
 
-handleGfmMath :: Block -> Block
-handleGfmMath (CodeBlock ("",["math"],[]) raw) = Para [Math DisplayMath raw]
-handleGfmMath x = walk handleGfmMathInline x
+handleGfmMathBlock :: Block -> Block
+handleGfmMathBlock (CodeBlock ("",["math"],[]) raw) = Para [Math DisplayMath raw]
+handleGfmMathBlock x = x
 
 handleGfmMathInline :: Inline -> Inline
 handleGfmMathInline (Math InlineMath math'') =
diff --git a/src/Text/Pandoc/Readers/Creole.hs b/src/Text/Pandoc/Readers/Creole.hs
--- a/src/Text/Pandoc/Readers/Creole.hs
+++ b/src/Text/Pandoc/Readers/Creole.hs
@@ -91,7 +91,7 @@
   where
     content = brackets <|> line
     brackets = try $ option "" (T.singleton <$> newline)
-               <+> (char ' ' >> (manyChar (char ' ') <+> textStr "}}}") <* eol)
+               <+> (char ' ' >> (takeWhileP (== ' ') <+> textStr "}}}") <* eol)
     line = option "" (T.singleton <$> newline) <+> manyTillChar anyChar eol
     eol = lookAhead $ try $ nowikiEnd <|> newline
     nowikiStart = optional newline >> string "{{{" >> skipMany spaceChar >> newline
@@ -215,8 +215,8 @@
   (orig, src) <- wikiImg
   return $ B.image src "" (B.str orig)
   where
-    linkSrc = manyChar $ noneOf "|}\n\r\t"
-    linkDsc = char '|' >> manyChar (noneOf "}\n\r\t")
+    linkSrc = takeWhileP (`notElem` ("|}\n\r\t" :: [Char]))
+    linkDsc = char '|' >> takeWhileP (`notElem` ("}\n\r\t" :: [Char]))
     wikiImg = try $ do
       string "{{"
       src <- linkSrc
@@ -229,11 +229,11 @@
   (orig, src) <- uriLink <|> wikiLink
   return $ B.link src "" orig
   where
-    linkSrc = manyChar $ noneOf "|]\n\r\t"
+    linkSrc = takeWhileP (`notElem` ("|]\n\r\t" :: [Char]))
     linkDsc :: PandocMonad m => Text -> CRLParser m B.Inlines
     linkDsc otxt = B.str
                    <$> try (option otxt
-                         (char '|' >> manyChar (noneOf "]\n\r\t")))
+                         (char '|' >> takeWhileP (`notElem` ("]\n\r\t" :: [Char]))))
     linkImg = try $ char '|' >> image
     wikiLink = try $ do
       string "[["
diff --git a/src/Text/Pandoc/Readers/Djot.hs b/src/Text/Pandoc/Readers/Djot.hs
--- a/src/Text/Pandoc/Readers/Djot.hs
+++ b/src/Text/Pandoc/Readers/Djot.hs
@@ -25,7 +25,7 @@
 import Text.Parsec.Pos (newPos)
 import Text.Pandoc.Options
 import Text.Pandoc.Definition
-import Text.Pandoc.Shared (addPandocAttributes, tshow)
+import Text.Pandoc.Shared (addPandocAttributes, tshow, compactifyTable)
 import qualified Text.Pandoc.UTF8 as UTF8
 import Djot (ParseOptions(..), SourcePosOption(..), parseDoc, Pos(..))
 import qualified Djot.AST as D
@@ -72,8 +72,11 @@
     D.Section bls -> divWith ("",["section"],[]) <$> convertBlocks bls
     D.Heading lev ils -> header lev <$> convertInlines ils
     D.BlockQuote bls -> blockQuote <$> convertBlocks bls
-    D.CodeBlock lang bs -> pure $
-      codeBlockWith ("", [UTF8.toText lang], []) $ UTF8.toText bs
+    D.CodeBlock lang bs ->
+      let classes = case UTF8.toText lang of
+                      "" -> []
+                      l -> [l]
+      in pure $ codeBlockWith ("", classes, []) $ UTF8.toText bs
     D.Div bls -> divWith nullAttr <$> convertBlocks bls
     D.OrderedList olattr listSpacing items ->
       orderedListWith olattr' .
@@ -151,7 +154,8 @@
               mapM toRow hs <*> mapM toRow rs
       tbodies <- mapM toTableBody bodies
       let tfoot = TableFoot mempty []
-      pure $ singleton $ Table mempty capt colspecs thead tbodies tfoot
+      pure $ singleton $ compactifyTable
+           $ Table mempty capt colspecs thead tbodies tfoot
     D.RawBlock (D.Format fmt) bs -> pure $
       rawBlock (UTF8.toText fmt) (UTF8.toText bs)
 
diff --git a/src/Text/Pandoc/Readers/Docx.hs b/src/Text/Pandoc/Readers/Docx.hs
--- a/src/Text/Pandoc/Readers/Docx.hs
+++ b/src/Text/Pandoc/Readers/Docx.hs
@@ -80,7 +80,6 @@
 import qualified Data.Map as M
 import qualified Data.Text as T
 import Data.Maybe (isJust, fromMaybe, mapMaybe)
-import Data.Sequence (ViewL (..), viewl)
 import qualified Data.Sequence as Seq
 import qualified Data.Set as Set
 import Citeproc (ItemId(..), Val(TextVal,FancyVal), Reference(..), CitationItem(..))
@@ -267,8 +266,10 @@
 
 parPartToText :: ParPart -> T.Text
 parPartToText (PlainRun run)             = runToText run
-parPartToText (InternalHyperLink _ children) = T.concat $ map parPartToText children
-parPartToText (ExternalHyperLink _ children) = T.concat $ map parPartToText children
+parPartToText (InternalHyperLink _ _ children) =
+  T.concat $ map parPartToText children
+parPartToText (ExternalHyperLink _ _ children) =
+  T.concat $ map parPartToText children
 parPartToText _                          = ""
 
 blacklistedCharStyles :: [CharStyleName]
@@ -411,14 +412,15 @@
     AllChanges -> do
       blks <- smushBlocks <$> mapM bodyPartToBlocks bodyParts
       ils <- blocksToInlinesWarn cmtId blks
-      let attr = ("", ["comment-start"], ("id", cmtId) : addAuthorAndDate author date)
+      let attr = ("", ["comment-start"], ("comment-id", cmtId) :
+                    addAuthorAndDate author date)
       return $ spanWith attr ils
     _ -> return mempty
 parPartToInlines' (CommentEnd cmtId) = do
   opts <- asks docxOptions
   case readerTrackChanges opts of
     AllChanges -> do
-      let attr = ("", ["comment-end"], [("id", cmtId)])
+      let attr = ("", ["comment-end"], [("comment-id", cmtId)])
       return $ spanWith attr mempty
     _ -> return mempty
 parPartToInlines' (BookMark _ anchor) | anchor `elem` dummyAnchors =
@@ -461,19 +463,20 @@
   return $ spanWith ("", ["chart"], []) $ text "[CHART]"
 parPartToInlines' Diagram =
   return $ spanWith ("", ["diagram"], []) $ text "[DIAGRAM]"
-parPartToInlines' (InternalHyperLink anchor children) = do
+parPartToInlines' (InternalHyperLink anchor tooltip children) = do
   ils <- smushInlines <$> mapM parPartToInlines' children
-  return $ link ("#" <> anchor) "" ils
-parPartToInlines' (ExternalHyperLink target children) = do
+  return $ link ("#" <> anchor) tooltip ils
+parPartToInlines' (ExternalHyperLink target tooltip children) = do
   ils <- smushInlines <$> mapM parPartToInlines' children
-  return $ link target "" ils
+  return $ link target tooltip ils
 parPartToInlines' (PlainOMath exps) =
   return $ math $ writeTeX exps
 parPartToInlines' (OMathPara exps) =
   return $ displayMath $ writeTeX exps
 parPartToInlines' (Field info children) =
   case info of
-    HyperlinkField url -> parPartToInlines' $ ExternalHyperLink url children
+    HyperlinkField url ->
+      parPartToInlines' $ ExternalHyperLink url "" children
     IndexrefField ie ->
       pure $ spanWith ("",["indexref"],
                            (("entry", entryTitle ie) :
@@ -481,8 +484,10 @@
                           ++ maybe [] (\x -> [("yomi",x)]) (entryYomi ie)
                           ++ [("bold","") | entryBold ie]
                           ++ [("italic","") | entryItalic ie])) mempty
-    PagerefField fieldAnchor True -> parPartToInlines' $ InternalHyperLink fieldAnchor children
-    CrossrefField fieldAnchor True -> parPartToInlines' $ InternalHyperLink fieldAnchor children
+    PagerefField fieldAnchor True ->
+      parPartToInlines' $ InternalHyperLink fieldAnchor "" children
+    CrossrefField fieldAnchor True ->
+      parPartToInlines' $ InternalHyperLink fieldAnchor "" children
     EndNoteCite t -> do
       formattedCite <- smushInlines <$> mapM parPartToInlines' children
       opts <- asks docxOptions
@@ -605,18 +610,10 @@
     return $ Header n (newIdent, classes, kvs) ils
 makeHeaderAnchor' blk = return blk
 
--- Rewrite a standalone paragraph block as a plain
-singleParaToPlain :: Blocks -> Blocks
-singleParaToPlain blks
-  | (Para ils :< seeq) <- viewl $ unMany blks
-  , Seq.null seeq =
-      singleton $ Plain ils
-singleParaToPlain blks = blks
-
 cellToCell :: PandocMonad m => RowSpan -> Docx.Cell -> DocxContext m Pandoc.Cell
 cellToCell rowSpan (Docx.Cell align gridSpan _ bps) = do
   blks <- smushBlocks <$> mapM bodyPartToBlocks bps
-  let blks' = singleParaToPlain $ fromList $ blocksToDefinitions $ blocksToBullets $ toList blks
+  let blks' = fromList $ blocksToDefinitions $ blocksToBullets $ toList blks
   return (cell (convertAlign align)
           rowSpan (ColSpan (fromIntegral gridSpan)) blks')
 
@@ -860,7 +857,8 @@
   let attr = case mbsty of
                 Just sty | extStylesEnabled -> ("", [], [("custom-style", sty)])
                 _ -> nullAttr
-  return $ tableWith attr cap'
+  return $ compactifyTable
+         $ tableWith attr cap'
                  (zip alignments widths)
                  (TableHead nullAttr headerCells)
                  [TableBody nullAttr 0 [] bodyCells]
diff --git a/src/Text/Pandoc/Readers/Docx/Parse.hs b/src/Text/Pandoc/Readers/Docx/Parse.hs
--- a/src/Text/Pandoc/Readers/Docx/Parse.hs
+++ b/src/Text/Pandoc/Readers/Docx/Parse.hs
@@ -398,8 +398,8 @@
              | CommentStart CommentId Author (Maybe CommentDate) [BodyPart]
              | CommentEnd CommentId
              | BookMark BookMarkId Anchor
-             | InternalHyperLink Anchor [ParPart]
-             | ExternalHyperLink URL [ParPart]
+             | InternalHyperLink Anchor T.Text [ParPart]          -- tooltip
+             | ExternalHyperLink URL T.Text [ParPart]             -- tooltip
              | Drawing FilePath T.Text T.Text B.ByteString Extent -- title, alt
              | Chart                                              -- placeholder for now
              | Diagram                                            -- placeholder for now
@@ -1217,18 +1217,20 @@
     location <- asks envLocation
     children <- mconcat <$> mapD (elemToParPart ns) (elChildren element)
     rels <- asks envRelationships
+    let tooltip = fromMaybe "" $ findAttrByName ns "w" "tooltip" element
     case lookupRelationship location relId rels of
       Just target ->
          case findAttrByName ns "w" "anchor" element of
              Just anchor -> return
-               [ExternalHyperLink (target <> "#" <> anchor) children]
-             Nothing -> return [ExternalHyperLink target children]
-      Nothing     -> return [ExternalHyperLink "" children]
+               [ExternalHyperLink (target <> "#" <> anchor) tooltip children]
+             Nothing -> return [ExternalHyperLink target tooltip children]
+      Nothing     -> return [ExternalHyperLink "" tooltip children]
 elemToParPart' ns element
   | isElem ns "w" "hyperlink" element
   , Just anchor <- findAttrByName ns "w" "anchor" element = do
     children <- mconcat <$> mapD (elemToParPart ns) (elChildren element)
-    return [InternalHyperLink anchor children]
+    let tooltip = fromMaybe "" $ findAttrByName ns "w" "tooltip" element
+    return [InternalHyperLink anchor tooltip children]
 elemToParPart' ns element
   | isElem ns "w" "commentRangeStart" element
   , Just cmtId <- findAttrByName ns "w" "id" element = do
diff --git a/src/Text/Pandoc/Readers/DokuWiki.hs b/src/Text/Pandoc/Readers/DokuWiki.hs
--- a/src/Text/Pandoc/Readers/DokuWiki.hs
+++ b/src/Text/Pandoc/Readers/DokuWiki.hs
@@ -28,7 +28,7 @@
 import Text.Pandoc.Definition
 import Text.Pandoc.Options
 import Text.Pandoc.Parsing hiding (enclosed)
-import Text.Pandoc.Shared (trim, stringify, tshow)
+import Text.Pandoc.Shared (trim, stringifyInlines, tshow, compactifyTable)
 import Data.List (isPrefixOf, isSuffixOf, groupBy)
 import qualified Safe
 
@@ -174,10 +174,11 @@
              => DWParser m a -> DWParser m Text
 nestedText end = innerSpace <|> countChar 1 nonspaceChar
   where
-    innerSpace = try $ many1Char spaceChar <* notFollowedBy end
+    innerSpace = try $ takeWhile1P (\c -> c == ' ' || c == '\t')
+                        <* notFollowedBy end
 
 monospaced :: PandocMonad m => DWParser m B.Inlines
-monospaced = try $ B.code . (T.concat . map stringify . B.toList) <$> enclosed (string "''") nestedInlines
+monospaced = try $ B.code . stringifyInlines <$> enclosed (string "''") nestedInlines
 
 subscript :: PandocMonad m => DWParser m B.Inlines
 subscript = try $ B.subscript <$> between (string "<sub>") (try $ string "</sub>") nestedInlines
@@ -238,7 +239,7 @@
 nocache = try $ mempty <$ string "~~NOCACHE~~"
 
 str :: PandocMonad m => DWParser m B.Inlines
-str = B.str <$> (many1Char alphaNum <|> characterReference)
+str = B.str <$> (takeWhile1P isAlphaNum <|> characterReference)
 
 symbol :: PandocMonad m => DWParser m B.Inlines
 symbol = B.str <$> (notFollowedBy' blockCode *> countChar 1 nonspaceChar)
@@ -517,7 +518,8 @@
   let attrs =  map (\(a, _) -> (a, ColWidthDefault)) firstRow
   let toRow = Row nullAttr . map B.simpleCell
       toHeaderRow l = [toRow l | not (null l)]
-  pure $ B.table B.emptyCaption
+  pure $ compactifyTable
+       $ B.table B.emptyCaption
                  attrs
                  (TableHead nullAttr $ toHeaderRow (map snd headerRow))
                  [TableBody nullAttr 0 [] $ map (toRow . (map snd)) body]
diff --git a/src/Text/Pandoc/Readers/HTML.hs b/src/Text/Pandoc/Readers/HTML.hs
--- a/src/Text/Pandoc/Readers/HTML.hs
+++ b/src/Text/Pandoc/Readers/HTML.hs
@@ -58,7 +58,9 @@
 import Text.Pandoc.Logging
 import Text.Pandoc.Options (
     Extension (Ext_epub_html_exts, Ext_empty_paragraphs, Ext_native_divs,
-               Ext_native_spans, Ext_raw_html, Ext_line_blocks, Ext_raw_tex),
+               Ext_native_spans, Ext_raw_html, Ext_line_blocks, Ext_raw_tex,
+               Ext_smart, Ext_tex_math_dollars,
+               Ext_tex_math_single_backslash, Ext_tex_math_double_backslash),
     ReaderOptions (readerExtensions, readerStripComments),
     extensionEnabled)
 import Text.Pandoc.Parsing hiding ((<|>))
@@ -76,7 +78,17 @@
          => ReaderOptions -- ^ Reader options
          -> a             -- ^ Input to parse
          -> m Pandoc
-readHtml opts inp = do
+readHtml = readHtmlWithDepth 0
+
+-- Like 'readHtml', but starting at the given iframe nesting depth.
+-- Used to limit recursion when the contents of iframes are fetched
+-- and parsed (see pIframe).
+readHtmlWithDepth :: (PandocMonad m, ToSources a)
+                  => Int
+                  -> ReaderOptions
+                  -> a
+                  -> m Pandoc
+readHtmlWithDepth depth opts inp = do
   let tags = stripPrefixes $ canonicalizeTags $
              parseTagsOptions parseOptions{ optTagPosition = True }
              (sourcesToText $ toSources inp)
@@ -92,7 +104,7 @@
   result <- flip runReaderT def $
        runParserT parseDoc
        (HTMLState def{ stateOptions = opts }
-         [] Nothing Set.empty [] M.empty opts False False)
+         M.empty M.empty Nothing Set.empty [] M.empty opts False False depth)
        "source" tags
   case result of
     Right doc -> return doc
@@ -127,12 +139,14 @@
   walkM (replaceNotes' notes) bs
 
 replaceNotes' :: PandocMonad m
-              => [(Text, Blocks)] -> Inline -> TagParser m Inline
+              => M.Map Text Blocks -> Inline -> TagParser m Inline
 replaceNotes' noteTbl (RawInline (Format "noteref") ref) =
-  maybe warnNotFound (pure . Note . B.toList) $ lookup ref noteTbl
+  maybe warnNotFound (pure . Note . B.toList) $ M.lookup ref noteTbl
  where
   warnNotFound = do
-    pos <- getPosition
+    -- use the position of the noteref, if we recorded one; the
+    -- current position is at the end of the document by now:
+    pos <- M.lookup ref . noteRefPos <$> getState >>= maybe getPosition pure
     logMessage $ ReferenceNotFound ref pos
     pure (Note [])
 replaceNotes' _ x = pure x
@@ -298,7 +312,7 @@
   let ident = fromMaybe "" (lookup "id" attr)
   content <- pInTags tag block
   updateState $ \s ->
-    s {noteTable = (ident, content) : noteTable s}
+    s {noteTable = M.insert ident content (noteTable s)}
 
 eFootnotes :: PandocMonad m => TagParser m Blocks
 eFootnotes = try $ do
@@ -331,6 +345,9 @@
   ident <- case lookup "href" attr >>= T.uncons of
              Just ('#', rest) -> return rest
              _ -> mzero
+  pos <- getPosition
+  updateState $ \s ->
+    s{ noteRefPos = M.insertWith (\_new old -> old) ident pos (noteRefPos s) }
   _ <- manyTill pAny (pSatisfy (\case
                                    TagClose t -> t == tag
                                    _          -> False))
@@ -377,8 +394,9 @@
 
 pCheckbox :: PandocMonad m => TagParser m Inlines
 pCheckbox = do
-  TagOpen _ attr' <- pSatisfy $ matchTagOpen "input" [("type","checkbox")]
-  TagClose _ <- pSatisfy (matchTagClose "input")
+  -- <input> is a void element, so the closing tag is optional
+  TagOpen _ attr' <- pSelfClosing (=="input")
+                       (\as -> lookup "type" as == Just "checkbox")
   let attr = toStringAttr attr'
   let isChecked = isJust $ lookup "checked" attr
   let escapeSequence = B.str $ if isChecked then "\9746" else "\9744"
@@ -409,10 +427,14 @@
   let start = fromMaybe 1 $ lookup "start" attribs >>= safeRead
   let style = fromMaybe DefaultStyle
          $  (parseTypeAttr      <$> lookup "type" attribs)
-        <|> (parseListStyleType <$> lookup "class" attribs)
+        <|> (lookup "class" attribs >>= pickClassStyle)
         <|> (parseListStyleType <$> (lookup "style" attribs >>= pickListStyle))
         where
           pickListStyle = pickStyleAttrProps ["list-style-type", "list-style"]
+          -- the list style may be one of several words in the class
+          -- attribute:
+          pickClassStyle = L.find (/= DefaultStyle)
+                             . map parseListStyleType . T.words
 
   -- note: if they have an <ol> or <ul> not in scope of a <li>,
   -- treat it as a list item, though it's not valid xhtml...
@@ -520,7 +542,10 @@
   skipMany pBlank
   pCloses "iframe" <|> eof
   url <- canonicalizeUrl $ fromAttrib "src" tag
-  if T.null url
+  depth <- iframeDepth <$> getState
+  -- limit nesting depth, since iframes that (indirectly) embed
+  -- themselves would otherwise cause infinite recursion:
+  if T.null url || depth >= maxIframeDepth
      then ignore $ renderTags' [tag, TagClose "iframe"]
      else catchError
        (do (bs, mbMime) <- openURL url
@@ -529,7 +554,7 @@
                | "text/html" `T.isPrefixOf` mt -> do
                     let inp = UTF8.toText bs
                     opts <- readerOpts <$> getState
-                    Pandoc _ contents <- readHtml opts inp
+                    Pandoc _ contents <- readHtmlWithDepth (depth + 1) opts inp
                     return $ B.divWith ("",["iframe"],[]) $ B.fromList contents
                | "image/" `T.isPrefixOf` mt -> do
                     return $ B.divWith ("",["iframe"],[]) $
@@ -539,6 +564,9 @@
          logMessage $ CouldNotFetchResource url (renderError e)
          ignore $ renderTags' [tag, TagClose "iframe"])
 
+maxIframeDepth :: Int
+maxIframeDepth = 5
+
 pRawHtmlBlock :: PandocMonad m => TagParser m Blocks
 pRawHtmlBlock = do
   raw <- pHtmlBlock "script" <|> pHtmlBlock "style" <|> pHtmlBlock "textarea"
@@ -664,7 +692,9 @@
   let modifyClasses f ("class",v) =
         ("class", T.unwords . map f . T.words $ v)
       modifyClasses _ (k,v) = (k,v)
-  let attr = toAttr $ map (modifyClasses stripLanguagePrefix) $ codeAttr <> attr'
+  -- pre's attributes take precedence (toAttr keeps the first of
+  -- duplicate attributes):
+  let attr = toAttr $ map (modifyClasses stripLanguagePrefix) $ attr' <> codeAttr
   contents <- manyTill pAny (pCloses "pre" <|> eof)
   let rawText = T.concat $ map tagToText contents
   -- drop trailing newline if any
@@ -720,11 +750,14 @@
         "input"
           | lookup "type" attr == Just "checkbox"
           -> asks inListItem >>= guard >> pCheckbox
-        "style" -> B.rawInline "html" <$> pHtmlBlock "style"
+        "style"
+          | extensionEnabled Ext_raw_html exts
+          -> B.rawInline "html" <$> pHtmlBlock "style"
+          | otherwise -> pHtmlBlock "style" >>= ignore
         "script"
           | Just x <- lookup "type" attr
           , "math/tex" `T.isPrefixOf` x -> pScriptMath
-        _ | name `elem` htmlSpanLikeElements -> pSpanLike
+        _ | name `Set.member` htmlSpanLikeElements -> pSpanLike name
         _ -> pRawHtmlInline
     TagText _ -> pTagText
     _ -> pRawHtmlInline
@@ -770,18 +803,12 @@
 pSubscript :: PandocMonad m => TagParser m Inlines
 pSubscript = pInlinesInTags "sub" B.subscript
 
-pSpanLike :: PandocMonad m => TagParser m Inlines
-pSpanLike =
-  Set.foldr
-    (\tagName acc -> acc <|> parseTag tagName)
-    mzero
-    htmlSpanLikeElements
-  where
-    parseTag tagName = do
-      TagOpen _ attrs <- pSatisfy $ tagOpenLit tagName (const True)
-      let (ids, cs, kvs) = toAttr attrs
-      content <- mconcat <$> manyTill inline (pCloses tagName <|> eof)
-      return $ B.spanWith (ids, tagName : cs, kvs) content
+pSpanLike :: PandocMonad m => Text -> TagParser m Inlines
+pSpanLike tagName = do
+  TagOpen _ attrs <- pSatisfy $ tagOpenLit tagName (const True)
+  let (ids, cs, kvs) = toAttr attrs
+  content <- mconcat <$> manyTill inline (pCloses tagName <|> eof)
+  return $ B.spanWith (ids, tagName : cs, kvs) content
 
 pSmall :: PandocMonad m => TagParser m Inlines
 pSmall = pInlinesInTags "small" (B.spanWith ("",["small"],[]))
@@ -973,23 +1000,43 @@
   pos <- getPosition
   (TagText str) <- pSatisfy isTagText
   st <- getState
-  qu <- ask
-  parsed <- lift $ lift $
-            flip runReaderT qu $ runParserT (many pTagContents) st "text"
-               (Sources [(pos, str)])
-  case parsed of
-       Left _        -> throwError $ PandocParseError $
-                        "Could not parse `" <> str <> "'"
-       Right result  -> return $ mconcat result
+  exts <- getOption readerExtensions
+  let smart = extensionEnabled Ext_smart exts
+  let mathDollars = extensionEnabled Ext_tex_math_dollars exts
+  let backslashSpecial = extensionEnabled Ext_raw_tex exts ||
+                         extensionEnabled Ext_tex_math_single_backslash exts ||
+                         extensionEnabled Ext_tex_math_double_backslash exts
+  -- Chars that could make pTagContents yield something different from
+  -- a plain B.text decomposition into Str, Space, and SoftBreak:
+  let needsParsing c =
+        isBad c ||                    -- gets remapped (see pBad)
+        (c == '$' && mathDollars) ||  -- may start tex math
+        (c == '\\' && backslashSpecial) ||  -- raw tex or tex math
+        (smart && (c == '\'' || c == '"' || c == '.' || c == '-'))
+  if not (inPre st) && not (T.any needsParsing str)
+     -- fast path: the result is a plain decomposition of the text into
+     -- Str, Space, and SoftBreak, which is exactly what B.text gives us
+     -- (adjacent Strs produced by pTagContents are merged by the
+     -- Inlines Semigroup instance, so the results coincide):
+     then return $ B.text str
+     else do
+       qu <- ask
+       parsed <- lift $ lift $
+                 flip runReaderT qu $ runParserT (many pTagContents) st "text"
+                    (Sources [(pos, str)])
+       case parsed of
+            Left _        -> throwError $ PandocParseError $
+                             "Could not parse `" <> str <> "'"
+            Right result  -> return $ mconcat result
 
 type InlinesParser m = HTMLParser m Sources
 
 pTagContents :: PandocMonad m => InlinesParser m Inlines
 pTagContents =
-      B.displayMath <$> mathDisplay
+      pStr    -- can't consume the special chars that start the other
+  <|> pSpace  -- parsers, so it is safe to try these two first
+  <|> B.displayMath <$> mathDisplay
   <|> B.math        <$> mathInline
-  <|> pStr
-  <|> pSpace
   <|> smartPunctuation pTagContents
   <|> pRawTeX
   <|> pSymbol
@@ -1184,12 +1231,19 @@
   let ts = canonicalizeTags $ parseTagsOptions
                                parseOptions{ optTagWarning = False
                                            , optTagPosition = True }
-                               (inp <> " ")
-                               -- add space to ensure that
-                               -- we get a TagPosition after the tag
+                               inp
+  -- if the tag is the last token, there is no TagPosition after it;
+  -- in that case its end is the end of the input (positions are 1-based).
+  -- (Note: only computed when needed, to avoid a scan of the input.)
+  let endOfInput = (T.count "\n" inp + 1,
+                    T.length (T.takeWhileEnd (/= '\n') inp) + 1)
   (next, ln, col) <- case ts of
-                      (TagPosition{} : next : TagPosition ln col : _)
-                        | f next -> return (next, ln, col)
+                      (TagPosition{} : next : rest)
+                        | f next ->
+                           case dropWhile (not . isTagPosition) rest of
+                             TagPosition ln col : _ -> return (next, ln, col)
+                             _ -> let (ln, col) = endOfInput
+                                  in return (next, ln, col)
                       _ -> mzero
 
   -- <www.boe.es/buscar/act.php?id=BOE-A-1996-8930#a66>
diff --git a/src/Text/Pandoc/Readers/HTML/Parsing.hs b/src/Text/Pandoc/Readers/HTML/Parsing.hs
--- a/src/Text/Pandoc/Readers/HTML/Parsing.hs
+++ b/src/Text/Pandoc/Readers/HTML/Parsing.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE BangPatterns          #-}
 {-# LANGUAGE LambdaCase            #-}
 {-# LANGUAGE OverloadedStrings     #-}
 {- |
@@ -34,12 +35,15 @@
 import Data.Maybe (fromMaybe)
 import Data.Text (Text)
 import Text.HTML.TagSoup
-  ( Attribute, Tag (..), isTagPosition, isTagOpen, isTagClose, (~==) )
+  ( Attribute, Tag (..), isTagOpen, isTagClose, (~==) )
 import Text.Pandoc.Class.PandocMonad (PandocMonad (..))
 import Text.Pandoc.Definition (Attr)
 import Text.Pandoc.Parsing
-  ( (<|>), eof, getPosition, lookAhead, manyTill, newPos, option, optional
-  , skipMany, setPosition, token, try)
+  ( (<|>), eof, lookAhead, manyTill, newPos, option, optional
+  , skipMany, try)
+import Text.Parsec.Prim (mkPT, Consumed (..), Reply (..), State (..))
+import Text.Parsec.Error (Message (SysUnExpect), newErrorMessage,
+                          newErrorUnknown)
 import Text.Pandoc.Readers.HTML.TagCategories
 import Text.Pandoc.Readers.HTML.Types
 import Text.Pandoc.Shared (tshow)
@@ -126,18 +130,24 @@
   isBlank (TagComment _) = True
   isBlank _ = False
 
-pLocation :: PandocMonad m => TagParser m ()
-pLocation = do
-  (TagPosition r c) <- pSat isTagPosition
-  setPosition $ newPos "input" r c
-
-pSat :: PandocMonad m => (Tag Text -> Bool) -> TagParser m (Tag Text)
-pSat f = do
-  pos <- getPosition
-  token tshow (const pos) (\x -> if f x then Just x else Nothing)
-
+-- | Skip any 'TagPosition' tokens (using them to update the source
+-- position), then consume the next tag if it satisfies the predicate.
+-- Fails without consuming input otherwise.  Implemented as a single
+-- parsec primitive, since this is the hottest spot of the HTML reader.
 pSatisfy :: PandocMonad m => (Tag Text -> Bool) -> TagParser m (Tag Text)
-pSatisfy f = try $ optional pLocation >> pSat f
+pSatisfy f = mkPT $ \(State inp pos u) ->
+  let go !pos' toks =
+        case toks of
+          TagPosition r c : rest -> go (newPos "input" r c) rest
+          t : rest
+            | f t -> Consumed (return
+                       (Ok t (State rest pos' u) (newErrorUnknown pos')))
+          _ -> Empty (return (Error
+                 (newErrorMessage (SysUnExpect (descr toks)) pos')))
+  in return (go pos inp)
+ where
+  descr []    = ""
+  descr (t:_) = T.unpack (tshow t)
 
 matchTagClose :: Text -> (Tag Text -> Bool)
 matchTagClose t = (~== TagClose t)
@@ -199,20 +209,23 @@
 _ `closes` _ = False
 
 toStringAttr :: [(Text, Text)] -> [(Text, Text)]
-toStringAttr = foldr go []
+toStringAttr = go mempty
   where
-   go :: (Text, Text) -> [(Text, Text)] -> [(Text, Text)]
+   go :: Set.Set Text -> [(Text, Text)] -> [(Text, Text)]
+   go _ [] = []
+   go seen ((x,y):rest)
+     -- prevent duplicate attributes; the first one wins:
+     | x' `Set.member` seen = go seen rest
+     | otherwise = (x', y) : go (Set.insert x' seen) rest
+    where x' = normalizeName x
    -- treat xml:lang as lang
-   go ("xml:lang",y) ats = go ("lang",y) ats
-   -- prevent duplicate attributes
-   go (x,y) ats
-     | any (\(x',_) -> x == x') ats = ats
-     | otherwise      =
-        case T.stripPrefix "data-" x of
-          Just x' | x' `Set.notMember` (html5Attributes <>
-                                        html4Attributes <> rdfaAttributes)
-            -> go (x',y) ats
-          _ -> (x,y):ats
+   normalizeName "xml:lang" = "lang"
+   -- strip data- prefix unless the bare name is a standard attribute
+   normalizeName x =
+     case T.stripPrefix "data-" x of
+       Just x' | x' `Set.notMember` standardAttrs -> x'
+       _ -> x
+   standardAttrs = html5Attributes <> html4Attributes <> rdfaAttributes
 
 -- Unlike fromAttrib from tagsoup, this distinguishes
 -- between a missing attribute and an attribute with empty content.
diff --git a/src/Text/Pandoc/Readers/HTML/Table.hs b/src/Text/Pandoc/Readers/HTML/Table.hs
--- a/src/Text/Pandoc/Readers/HTML/Table.hs
+++ b/src/Text/Pandoc/Readers/HTML/Table.hs
@@ -28,7 +28,7 @@
 import Text.Pandoc.Definition
 import Text.Pandoc.Class.PandocMonad (PandocMonad (..))
 import Text.Pandoc.Parsing
-  ( eof, lookAhead, many, many1, manyTill, option, optional
+  ( eof, lookAhead, many, many1, manyTill, notFollowedBy, option, optional
   , optionMaybe, skipMany, try )
 import Text.Pandoc.Readers.HTML.Parsing
 import Text.Pandoc.Readers.HTML.Types (TagParser)
@@ -134,7 +134,8 @@
   skipMany pBlank
   TagOpen _ attribs <- pSatisfy (matchTagOpen "tr" []) <* skipMany pBlank
   cells <- many (pCell block BodyCell <|> pCell block HeaderCell)
-  TagClose _ <- pSatisfy (matchTagClose "tr")
+  -- the closing tag may be omitted (it is optional in HTML):
+  optional $ pSatisfy (matchTagClose "tr")
   let numheadcells = length $ takeWhile (\(ct,_) -> ct == HeaderCell) cells
   return (numheadcells, Row (toAttr attribs) $ map snd cells)
 
@@ -145,9 +146,15 @@
            -> TagParser m B.Row
 pHeaderRow block = try $ do
   skipMany pBlank
-  let pThs = many (snd <$> pCell block HeaderCell)
-  let mkRow (attribs, cells) = Row (toAttr attribs) cells
-  mkRow <$> pInTagWithAttribs TagsRequired "tr" pThs
+  TagOpen _ attribs <- pSatisfy (matchTagOpen "tr" [])
+  cells <- many (snd <$> pCell block HeaderCell)
+  skipMany pBlank
+  -- a header row may contain only <th> cells; a following <td>
+  -- means this is a body row, so we backtrack and let pRow parse it:
+  notFollowedBy $ pSatisfy (matchTagOpen "td" [])
+  -- the closing tag may be omitted (it is optional in HTML):
+  optional $ pSatisfy (matchTagClose "tr")
+  return $ Row (toAttr attribs) cells
 
 -- | Parses a table head. If there is no @thead@ element, this looks for
 -- a row of @<th>@-only elements as the first line of the table.
@@ -278,7 +285,9 @@
   [] -> case tblType of
           SimpleTable -> replicate ncols ColWidthDefault
           NormalTable -> replicate ncols (ColWidth $ 1 / fromIntegral ncols)
-  widths -> widths
+  -- pad if fewer <col> elements than columns, so that cells in the
+  -- extra columns aren't lost (the colspecs are zipped with alignments):
+  widths -> widths ++ replicate (ncols - length widths) ColWidthDefault
 
 calculateAlignments :: Int -> [TableBody] -> [Alignment]
 calculateAlignments cols tbodies =
diff --git a/src/Text/Pandoc/Readers/HTML/Types.hs b/src/Text/Pandoc/Readers/HTML/Types.hs
--- a/src/Text/Pandoc/Readers/HTML/Types.hs
+++ b/src/Text/Pandoc/Readers/HTML/Types.hs
@@ -33,7 +33,7 @@
 import Text.Pandoc.Parsing
   ( HasIdentifierList (..), HasLastStrPosition (..), HasLogMessages (..)
   , HasMacros (..), HasQuoteContext (..), HasReaderOptions (..)
-  , ParsecT, ParserState, QuoteContext (NoQuote)
+  , ParsecT, ParserState, QuoteContext (NoQuote), SourcePos
   )
 import Text.Pandoc.TeX (Macro)
 
@@ -46,7 +46,8 @@
 -- | Global HTML parser state
 data HTMLState = HTMLState
   { parserState :: ParserState
-  , noteTable   :: [(Text, Blocks)]
+  , noteTable   :: Map Text Blocks
+  , noteRefPos  :: Map Text SourcePos -- ^ position of first ref to each note
   , baseHref    :: Maybe URI
   , identifiers :: Set Text
   , logMessages :: [LogMessage]
@@ -54,6 +55,7 @@
   , readerOpts  :: ReaderOptions
   , inFootnotes :: Bool
   , inPre       :: Bool
+  , iframeDepth :: Int -- ^ how many iframes deep we are (recursion limit)
   }
 
 -- | Local HTML parser state
diff --git a/src/Text/Pandoc/Readers/Jira.hs b/src/Text/Pandoc/Readers/Jira.hs
--- a/src/Text/Pandoc/Readers/Jira.hs
+++ b/src/Text/Pandoc/Readers/Jira.hs
@@ -20,7 +20,7 @@
 import Text.Pandoc.Builder hiding (cell)
 import Text.Pandoc.Error (PandocError (PandocParseError))
 import Text.Pandoc.Options (ReaderOptions)
-import Text.Pandoc.Shared (stringify)
+import Text.Pandoc.Shared (stringifyInlines)
 import Text.Pandoc.Sources (ToSources(..), sourcesToText)
 import qualified Text.Jira.Markup as Jira
 
@@ -140,7 +140,7 @@
                             in imageWith attr (Jira.fromURL url) title mempty
   Jira.Link lt alias url -> jiraLinkToPandoc lt alias url
   Jira.Linebreak         -> linebreak
-  Jira.Monospaced inlns  -> code . stringify . toList . fromInlines $ inlns
+  Jira.Monospaced inlns  -> code . stringifyInlines . toList . fromInlines $ inlns
   Jira.Space             -> space
   Jira.SpecialChar c     -> str (Data.Text.singleton c)
   Jira.Str t             -> str t
diff --git a/src/Text/Pandoc/Readers/LaTeX.hs b/src/Text/Pandoc/Readers/LaTeX.hs
--- a/src/Text/Pandoc/Readers/LaTeX.hs
+++ b/src/Text/Pandoc/Readers/LaTeX.hs
@@ -24,6 +24,7 @@
 import Control.Applicative (many, optional, (<|>))
 import Control.Monad
 import Control.Monad.Except (throwError)
+import Control.Monad.Reader (runReaderT)
 import Data.Containers.ListUtils (nubOrd)
 import Data.Char (isDigit, isLetter, isAlphaNum, toUpper, chr)
 import Data.Default
@@ -87,12 +88,22 @@
           -> m Pandoc
 readLaTeX opts ltx = do
   let sources = toSources ltx
-  parsed <- runParserT parseLaTeX def{ sOptions = opts } "source"
+  parsed <- flip runReaderT latexEnv $
+               runParserT parseLaTeX def{ sOptions = opts } "source"
                (TokStream False (tokenizeSources sources))
   case parsed of
     Right result -> return result
     Left e       -> throwError $ fromParsecError sources e
 
+-- | The command dispatch tables, built once per parse and shared
+-- through the reader environment (see 'LaTeXEnv').
+latexEnv :: PandocMonad m => LaTeXEnv m
+latexEnv = LaTeXEnv
+  { envInlineCommands = inlineCommands
+  , envBlockCommands  = blockCommands
+  , envEnvironments   = environments
+  }
+
 parseLaTeX :: PandocMonad m => LP m Pandoc
 parseLaTeX = do
   bs <- blocks
@@ -156,7 +167,7 @@
   lookAhead (try (char '\\' >> letter))
   toks <- getInputTokens
   snd <$> (
-          rawLaTeXParser toks
+          rawLaTeXParser latexEnv toks
              (makeAtLetterSection <|>
               macroDef (const mempty) <|>
               do choice (map controlSeq
@@ -164,7 +175,7 @@
                  skipMany opt
                  braced
                  return mempty) blocks
-      <|> rawLaTeXParser toks
+      <|> rawLaTeXParser latexEnv toks
            (void (environment <|> blockCommand))
            (mconcat <$> many (block <|> beginOrEndCommand)))
 
@@ -199,10 +210,10 @@
   lookAhead (try (char '\\' >> letter))
   toks <- getInputTokens
   raw <- snd <$>
-          (   rawLaTeXParser toks
+          (   rawLaTeXParser latexEnv toks
               (mempty <$ (controlSeq "input" >> skipMany rawopt >> braced))
               inlines
-          <|> rawLaTeXParser toks (void inline) inlines
+          <|> rawLaTeXParser latexEnv toks (void inline) inlines
           )
   finalbraces <- mconcat <$> many (try (string "{}")) -- see #5439
   return $ raw <> T.pack finalbraces
@@ -211,7 +222,8 @@
 inlineCommand = do
   lookAhead (try (char '\\' >> letter))
   toks <- getInputTokens
-  fst <$> rawLaTeXParser toks (void (inlineEnvironment <|> inlineCommand'))
+  fst <$> rawLaTeXParser latexEnv toks
+          (void (inlineEnvironment <|> inlineCommand'))
           inlines
 
 -- inline elements:
@@ -337,21 +349,23 @@
        rawcommand <- getRawCommand name (cmd <> star)
        (guardEnabled Ext_raw_tex >> return (rawInline "latex" rawcommand))
          <|> ignore rawcommand
-  lookupListDefault raw names inlineCommands
+  commandMap <- envInlineCommands <$> askEnv
+  lookupListDefault raw names commandMap
 
 tok :: PandocMonad m => LP m Inlines
 tok = tokWith inline
 
 unescapeURL :: Text -> Text
-unescapeURL = T.concat . go . T.splitOn "\\"
-  where
-    isEscapable c = T.any (== c) "#$%&~_^\\{}"
-    go (x:xs) = x : map unescapeInterior xs
-    go []     = []
-    unescapeInterior t
-      | Just (c, _) <- T.uncons t
-      , isEscapable c = t
-      | otherwise = "\\" <> t
+unescapeURL t =
+  let (xs, ys) = T.break (== '\\') t
+  in case T.uncons ys of
+       Nothing -> xs
+       Just (_, rest) ->
+         case T.uncons rest of
+           Just (c, rest')
+             | isEscapable c -> xs <> T.cons c (unescapeURL rest')
+           _ -> xs <> "\\" <> unescapeURL rest
+  where isEscapable c = T.any (== c) "#$%&~_^\\{}"
 
 inlineCommands :: PandocMonad m => M.Map Text (LP m Inlines)
 inlineCommands = M.unions
@@ -407,22 +421,30 @@
     , ("sl", extractSpaces emph <$> inlines)
     , ("bf", extractSpaces strong <$> inlines)
     , ("tt", formatCode nullAttr <$> inlines)
+    , ("ttfamily", extractSpaces (formatCode nullAttr) <$> inlines)
     , ("rm", inlines)
     , ("itshape", extractSpaces emph <$> inlines)
     , ("slshape", extractSpaces emph <$> inlines)
     , ("scshape", extractSpaces smallcaps <$> inlines)
     , ("bfseries", extractSpaces strong <$> inlines)
-    , ("MakeUppercase", makeUppercase <$> tok)
-    , ("MakeTextUppercase", makeUppercase <$> tok) -- textcase
-    , ("uppercase", makeUppercase <$> tok)
-    , ("MakeLowercase", makeLowercase <$> tok)
-    , ("MakeTextLowercase", makeLowercase <$> tok)
-    , ("lowercase", makeLowercase <$> tok)
+    , ("MakeUppercase", caseTransform "upper" T.toUpper)
+    , ("MakeTextUppercase", caseTransform "upper" T.toUpper) -- textcase
+    , ("uppercase", caseTransform "upper" T.toUpper)
+    , ("MakeLowercase", caseTransform "lower" T.toLower)
+    , ("MakeTextLowercase", caseTransform "lower" T.toLower)
+    , ("lowercase", caseTransform "lower" T.toLower)
+    , ("MakeTitlecase", makeTitlecaseCommand)
+    , ("NoCaseChange", spanWith ("",["nocasechange"],[]) <$> tok)
+    , ("CaseSwitch", tok <* tok <* tok <* tok)
     , ("thanks", skipopts >> note <$> grouped block)
     , ("footnote", skipopts >> footnote)
     , ("footnotemark", footnotemark)
     , ("footnotetext", footnotetext)
     , ("newline", pure B.linebreak)
+    -- xparse argument markers, in case they leak into the document:
+    , ("NoValue", pure (B.str "-NoValue-"))
+    , ("BooleanTrue", pure mempty)
+    , ("BooleanFalse", pure mempty)
     , ("passthrough", fixPassthroughEscapes <$> tok)
     -- \passthrough macro used by latex writer
                            -- for listings
@@ -457,6 +479,7 @@
     , ("iftoggle", try $ ifToggle >> inline)
     -- include
     , ("input", rawInlineOr "input" $ include "input")
+    , ("expandableinput", rawInlineOr "expandableinput" $ include "input")
     -- soul package
     , ("st", extractSpaces strikeout <$> tok)
     , ("ul", underline <$> tok)
@@ -471,6 +494,19 @@
     -- this is used internally by pandoc but the definition is too complicated
     -- for pandoc to handle (see #11140):
     , ("pandocbounded", tok)
+    -- LaTeX3 constants
+    , ("c_ampersand_str", pure (str "&"))
+    , ("c_atsign_str", pure (str "@"))
+    , ("c_backslash_str", pure (str "\\"))
+    , ("c_left_brace_str", pure (str "{"))
+    , ("c_right_brace_str", pure (str "}"))
+    , ("c_circumflex_str", pure (str "^"))
+    , ("c_colon_str", pure (str ":"))
+    , ("c_dollar_str", pure (str "$"))
+    , ("c_hash_str", pure (str "#"))
+    , ("c_percent_str", pure (str "%"))
+    , ("c_tilde_str", pure (str "~"))
+    , ("c_underscore_str", pure (str "_"))
     ]
 
 bracedFilename :: PandocMonad m => LP m Text
@@ -547,16 +583,98 @@
   contents <- manyTill anyTok (controlSeq "fi")
   return $ rawInline "latex" $ "\\ifdim" <> untokenize contents <> "\\fi"
 
-makeUppercase :: Inlines -> Inlines
-makeUppercase = fromList . walk (alterStr T.toUpper) . toList
+-- | Parse the argument of a case-changing command (\MakeUppercase,
+-- \MakeLowercase) and apply the case transformation, honoring
+-- exclusions declared with \Declare*caseExclusions.
+caseTransform :: PandocMonad m => Text -> (Text -> Text) -> LP m Inlines
+caseTransform kind f = do
+  void $ option [] keyvals -- locale options, ignored
+  excl <- M.findWithDefault Set.empty kind . sCaseExclusions <$> getState
+  caseTransformWith excl f <$> tok
 
-makeLowercase :: Inlines -> Inlines
-makeLowercase = fromList . walk (alterStr T.toLower) . toList
+-- | Apply a case transformation to text, leaving math, code and
+-- citations untouched, unwrapping (and skipping) \NoCaseChange
+-- content, and skipping excluded words.
+caseTransformWith :: Set.Set Text -> (Text -> Text) -> Inlines -> Inlines
+caseTransformWith excl f = fromList . go . toList
+  where
+    go = concatMap goInline
+    goInline (Span ("",["nocasechange"],[]) ils) = ils
+    goInline (Str t)
+      | t `Set.member` excl = [Str t]
+      | otherwise = [Str (f t)]
+    goInline (Emph ils) = [Emph (go ils)]
+    goInline (Strong ils) = [Strong (go ils)]
+    goInline (Underline ils) = [Underline (go ils)]
+    goInline (Strikeout ils) = [Strikeout (go ils)]
+    goInline (Superscript ils) = [Superscript (go ils)]
+    goInline (Subscript ils) = [Subscript (go ils)]
+    goInline (SmallCaps ils) = [SmallCaps (go ils)]
+    goInline (Quoted qt ils) = [Quoted qt (go ils)]
+    goInline (Span attr ils) = [Span attr (go ils)]
+    goInline (Link attr ils target) = [Link attr (go ils) target]
+    goInline x = [x] -- Math, Code, Cite, Space, etc.
 
-alterStr :: (Text -> Text) -> Inline -> Inline
-alterStr f (Str xs) = Str (f xs)
-alterStr _ x = x
+-- | Parse the arguments of \MakeTitlecase, supporting the
+-- @words=all@ option and title-case exclusions.
+makeTitlecaseCommand :: PandocMonad m => LP m Inlines
+makeTitlecaseCommand = do
+  options <- option [] keyvals
+  excl <- M.findWithDefault Set.empty "title" . sCaseExclusions <$> getState
+  (if lookup "words" options == Just "all"
+      then makeTitlecaseAll excl
+      else makeTitlecase) <$> tok
 
+-- | Titlecase the first letter of each word (\MakeTitlecase with
+-- @words=all@), skipping excluded words.
+makeTitlecaseAll :: Set.Set Text -> Inlines -> Inlines
+makeTitlecaseAll excl = fromList . go . toList
+  where
+    go [] = []
+    go xs =
+      let (w, rest) = break isSep xs
+          (seps, rest') = span isSep rest
+       in tcWord w ++ seps ++ go rest'
+    isSep Space = True
+    isSep SoftBreak = True
+    isSep LineBreak = True
+    isSep _ = False
+    tcWord w
+      | stringify w `Set.member` excl = w
+      | otherwise = toList (makeTitlecase (fromList w))
+
+-- | Handle \DeclareUppercaseExclusions and friends: store a
+-- comma-separated list of words excluded from case changing.
+declareCaseExclusions :: PandocMonad m => Text -> LP m Blocks
+declareCaseExclusions kind = do
+  ws <- map T.strip . T.splitOn "," . untokenize <$> braced
+  updateState $ \st -> st{ sCaseExclusions =
+      M.insertWith Set.union kind (Set.fromList ws) (sCaseExclusions st) }
+  return mempty
+
+-- | Uppercase the first character of the first string (LaTeX3
+-- \MakeTitlecase, which title-cases only the first word by default).
+makeTitlecase :: Inlines -> Inlines
+makeTitlecase = fromList . snd . go . toList
+  where
+    go :: [Inline] -> (Bool, [Inline])
+    go (x : xs) =
+      case goInline x of
+        (True, x')  -> (True, x' : xs)
+        (False, x') -> (x' :) <$> go xs
+    go [] = (False, [])
+    goInline (Str t) | not (T.null t) =
+      (True, Str (T.toTitle (T.take 1 t) <> T.drop 1 t))
+    goInline (Emph ils) = Emph <$> go ils
+    goInline (Strong ils) = Strong <$> go ils
+    goInline (Underline ils) = Underline <$> go ils
+    goInline (SmallCaps ils) = SmallCaps <$> go ils
+    goInline (Span attr ils) = Span attr <$> go ils
+    goInline (Link attr ils target) =
+      (\ils' -> Link attr ils' target) <$> go ils
+    goInline (Quoted qt ils) = Quoted qt <$> go ils
+    goInline x = (False, x)
+
 fixPassthroughEscapes :: Inlines -> Inlines
 fixPassthroughEscapes = walk go
  where
@@ -704,13 +822,16 @@
                 -> eatOneToken *>
                     option (str "-") (symbol '-' *>
                       option (str "–") (str "—" <$ symbol '-'))
-        "'"     -> eatOneToken *>
-                    option (str "’") (str  "”" <$ (guard ligatures *> symbol '\''))
+        "'" | ligatures
+                -> eatOneToken *>
+                    option (str "’") (str "”" <$ symbol '\'')
+            | otherwise
+                -> symbolAsString
         "~"     -> str "\160" <$ eatOneToken
         "`" | ligatures
                 -> doubleQuote <|> singleQuote <|> (str "‘" <$ symbol '`')
             | otherwise
-                -> str "‘" <$ symbol '`'
+                -> symbolAsString
         "\"" | ligatures
                 -> doubleQuote <|> singleQuote <|> symbolAsString
         "“"     -> doubleQuote <|> symbolAsString
@@ -740,13 +861,10 @@
 opt :: PandocMonad m => LP m Inlines
 opt = do
   toks <- try (sp *> bracketedToks <* sp)
-  -- now parse the toks as inlines
-  st <- getState
-  parsed <- runParserT (mconcat <$> many inline) st "bracketed option"
-              (TokStream False toks)
-  case parsed of
-    Right result -> return result
-    Left e       -> throwError $ fromParsecError (toSources toks) e
+  -- now parse the toks as inlines; parseFromToks preserves any
+  -- state changes (e.g. macro definitions), since an optional
+  -- argument is not a TeX group
+  parseFromToks (mconcat <$> many inline) toks
 
 -- block elements:
 
@@ -764,7 +882,7 @@
 rule :: PandocMonad m => LP m Blocks
 rule = do
   skipopts
-  width <- T.takeWhile (\c -> isDigit c || c == '.') . stringify <$> tok
+  width <- T.takeWhile (\c -> isDigit c || c == '.') . stringifyInlines <$> tok
   _thickness <- tok
   -- 0-width rules are used to fix spacing issues:
   case safeRead width of
@@ -945,7 +1063,8 @@
         lookAhead $ blankline <|> startCommand
         return $ curr <> mconcat rest
   let raw = rawDefiniteBlock <|> rawMaybeBlock
-  lookupListDefault raw names blockCommands
+  commandMap <- envBlockCommands <$> askEnv
+  lookupListDefault raw names commandMap
 
 closing :: PandocMonad m => LP m Blocks
 closing = do
@@ -1062,10 +1181,30 @@
    -- include
    , ("include", rawBlockOr "include" $ include "include")
    , ("input", rawBlockOr "input" $ include "input")
+   , ("expandableinput", rawBlockOr "expandableinput" $ include "input")
    , ("subfile", rawBlockOr "subfile" doSubfile)
    , ("usepackage", rawBlockOr "usepackage" usepackage)
    -- preamble
    , ("PackageError", mempty <$ (braced >> braced >> braced))
+   -- LaTeX3 conveniences, parsed and ignored:
+   , ("ExplSyntaxOn", pure mempty)
+   , ("ExplSyntaxOff", pure mempty)
+   , ("ShowCommand", mempty <$ withVerbatimMode (spaces *> anyControlSeq))
+   , ("ShowEnvironment", mempty <$ braced)
+   , ("DeclareKeys", mempty <$ (skipopts *> braced))
+   , ("DeclareUnknownKeyHandler", mempty <$ (skipopts *> braced))
+   , ("ProcessKeyOptions", mempty <$ skipopts)
+   , ("SetKeys", mempty <$ (skipopts *> braced))
+   -- LaTeX3 case changing
+   , ("DeclareUppercaseExclusions", declareCaseExclusions "upper")
+   , ("DeclareLowercaseExclusions", declareCaseExclusions "lower")
+   , ("DeclareTitlecaseExclusions", declareCaseExclusions "title")
+   , ("AddToNoCaseChangeList", mempty <$ braced)
+   , ("DeclareCaseChangeEquivalent", mempty <$
+        (withVerbatimMode (spaces *> anyControlSeq) *> braced))
+   , ("DeclareUppercaseMapping", mempty <$ (skipopts *> braced *> braced))
+   , ("DeclareLowercaseMapping", mempty <$ (skipopts *> braced *> braced))
+   , ("DeclareTitlecaseMapping", mempty <$ (skipopts *> braced *> braced))
    -- epigraph package
    , ("epigraph", epigraph)
    -- alignment
@@ -1154,7 +1293,8 @@
 environment = try $ do
   controlSeq "begin"
   name <- untokenize <$> braced
-  M.findWithDefault mzero name environments <|>
+  envMap <- envEnvironments <$> askEnv
+  M.findWithDefault mzero name envMap <|>
     langEnvironment name <|>
     theoremEnvironment blocks inlines opt name <|>
     if M.member name (inlineEnvironments
diff --git a/src/Text/Pandoc/Readers/LaTeX/Citation.hs b/src/Text/Pandoc/Readers/LaTeX/Citation.hs
--- a/src/Text/Pandoc/Readers/LaTeX/Citation.hs
+++ b/src/Text/Pandoc/Readers/LaTeX/Citation.hs
@@ -13,8 +13,6 @@
 import Data.Text (Text)
 import Control.Applicative ((<|>), optional, many)
 import Control.Monad (mzero)
-import Control.Monad.Trans (lift)
-import Control.Monad.Except (throwError)
 import Text.Pandoc.Parsing hiding (blankline, many, mathDisplay, mathInline,
                             optional, space, spaces, withRaw, (<|>))
 
@@ -113,14 +111,10 @@
   opt :: PandocMonad m => LP m Inlines
   opt = do
     toks <- try (sp *> bracketedToks <* sp)
-    -- now parse the toks as inlines
-    st <- getState
-    parsed <- lift $
-      runParserT (mconcat <$> many inline) st "bracketed option"
-       (TokStream False toks)
-    case parsed of
-      Right result -> return result
-      Left e       -> throwError $ fromParsecError (toSources toks) e
+    -- now parse the toks as inlines; parseFromToks preserves any
+    -- state changes (e.g. macro definitions), since an optional
+    -- argument is not a TeX group
+    parseFromToks (mconcat <$> many inline) toks
 
 
 
diff --git a/src/Text/Pandoc/Readers/LaTeX/Inline.hs b/src/Text/Pandoc/Readers/LaTeX/Inline.hs
--- a/src/Text/Pandoc/Readers/LaTeX/Inline.hs
+++ b/src/Text/Pandoc/Readers/LaTeX/Inline.hs
@@ -36,8 +36,7 @@
 import Text.Pandoc.Readers.LaTeX.Parsing
 import Text.Pandoc.Extensions (extensionEnabled, Extension(..))
 import Text.Pandoc.Parsing (getOption, updateState, getState, notFollowedBy,
-                            manyTill, getInput, setInput, incSourceColumn,
-                            option, many1)
+                            manyTill, option, many1)
 import Data.Char (isDigit)
 import Text.Pandoc.Highlighting (fromListingsLanguage,)
 import Data.Maybe (maybeToList, fromMaybe)
@@ -89,19 +88,6 @@
     code . untokenize <$>
       manyTill (notFollowedBy newlineTok >> verbTok marker) (symbol marker)
 
-verbTok :: PandocMonad m => Char -> LP m Tok
-verbTok stopchar = do
-  t@(Tok pos toktype txt) <- anyTok
-  case T.findIndex (== stopchar) txt of
-       Nothing -> return t
-       Just i  -> do
-         let (t1, t2) = T.splitAt i txt
-         TokStream macrosExpanded inp <- getInput
-         setInput $ TokStream macrosExpanded
-                  $ Tok (incSourceColumn pos i) Symbol (T.singleton stopchar)
-                  : tokenize (incSourceColumn pos (i + 1)) (T.drop 1 t2) ++ inp
-         return $ Tok pos toktype t1
-
 listingsLanguage :: [(Text, Text)] -> Maybe Text
 listingsLanguage opts =
   case lookup "language" opts of
@@ -282,7 +268,7 @@
   , ("{", lit "{")
   , ("}", lit "}")
   , ("-", lit "\x00ad") -- soft hyphen
-  , ("qed", lit "\a0\x25FB")
+  , ("qed", lit "\xa0\x25FB")
   , ("lq", return (str "‘"))
   , ("rq", return (str "’"))
   , ("textquoteleft", return (str "‘"))
diff --git a/src/Text/Pandoc/Readers/LaTeX/Macro.hs b/src/Text/Pandoc/Readers/LaTeX/Macro.hs
--- a/src/Text/Pandoc/Readers/LaTeX/Macro.hs
+++ b/src/Text/Pandoc/Readers/LaTeX/Macro.hs
@@ -12,7 +12,10 @@
 import Text.Pandoc.Parsing hiding (blankline, mathDisplay, mathInline,
                             optional, space, spaces, withRaw, (<|>))
 import Control.Applicative ((<|>), optional)
+import Control.Monad (guard)
+import Data.Char (chr, isLetter, ord)
 import qualified Data.Map as M
+import qualified Data.Set as Set
 import Data.Text (Text)
 import qualified Data.Text as T
 import qualified Data.List.NonEmpty as NonEmpty
@@ -20,12 +23,17 @@
 
 macroDef :: (PandocMonad m, Monoid a) => (Text -> a) -> LP m a
 macroDef constructor = do
+    Tok _ (CtrlSeq name) _ <- peekTok
+    -- fail quickly, before trying each alternative in turn, unless
+    -- the next token can begin a macro definition:
+    guard $ name `Set.member` macroDefCommands
     (_, s) <- withRaw (commandDef <|> environmentDef)
     (constructor (untokenize s) <$
       guardDisabled Ext_latex_macros)
      <|> return mempty
   where commandDef = do
-          nameMacroPairs <- newcommand <|>
+          nameMacroPairs <- newcommand <|> newDocumentCommand <|>
+            newDocumentEnvironment <|> commandCopy <|> environmentCopy <|>
             checkGlobal (letmacro <|> edefmacro <|> defmacro <|> newif)
           guardDisabled Ext_latex_macros <|>
             mapM_ insertMacro nameMacroPairs
@@ -42,6 +50,26 @@
         -- @\newcommand{\envname}[n-args][default]{begin}@
         -- @\newcommand{\endenvname}@
 
+-- | Control sequences that can begin a macro definition.  This
+-- must include every control sequence that one of the parsers
+-- used in 'macroDef' can start with.
+macroDefCommands :: Set.Set Text
+macroDefCommands = Set.fromList
+  [ "global"  -- see checkGlobal
+  , "let", "edef", "xdef", "def", "gdef", "newif"
+  , "newcommand", "renewcommand", "providecommand"
+  , "DeclareMathOperator", "DeclareRobustCommand"
+  , "NewDocumentCommand", "RenewDocumentCommand"
+  , "ProvideDocumentCommand", "DeclareDocumentCommand"
+  , "NewExpandableDocumentCommand", "RenewExpandableDocumentCommand"
+  , "ProvideExpandableDocumentCommand", "DeclareExpandableDocumentCommand"
+  , "NewDocumentEnvironment", "RenewDocumentEnvironment"
+  , "ProvideDocumentEnvironment", "DeclareDocumentEnvironment"
+  , "NewCommandCopy", "RenewCommandCopy", "DeclareCommandCopy"
+  , "NewEnvironmentCopy", "RenewEnvironmentCopy", "DeclareEnvironmentCopy"
+  , "newenvironment", "renewenvironment", "provideenvironment"
+  ]
+
 insertMacro :: PandocMonad m => (Text, Macro) -> LP m ()
 insertMacro (name, macro'@(Macro GlobalScope _ _ _ _)) =
   updateState $ \s ->
@@ -116,10 +144,11 @@
 -- \footrue to be a command that defines \iffoo to be \iftrue
 -- \foofalse to be a command that defines \iffoo to be \iffalse
 newif :: PandocMonad m => LP m [(Text, Macro)]
-newif = do
+newif = try $ do
   controlSeq "newif"
   withVerbatimMode $ do
     Tok pos (CtrlSeq name) _ <- anyControlSeq
+    guard $ "if" `T.isPrefixOf` name
     -- \def\iffoo\iffalse
     -- \def\footrue{\def\iffoo\iftrue}
     -- \def\foofalse{\def\iffoo\iffalse}
@@ -191,6 +220,241 @@
           "renewcommand" -> return [(name, macro)]
           _ -> [] <$ report (MacroAlreadyDefined txt pos))
       <|> pure [(name, macro)]
+
+-- | Parses a definition of the form
+-- @\NewDocumentCommand\cmd{argspec}{body}@ (and the Renew, Provide,
+-- Declare, and Expandable variants), with xparse (LaTeX3) argument
+-- specifiers.
+newDocumentCommand :: PandocMonad m => LP m [(Text, Macro)]
+newDocumentCommand = try $ do
+  Tok pos (CtrlSeq mtype) _ <-
+        controlSeq "NewDocumentCommand"
+    <|> controlSeq "RenewDocumentCommand"
+    <|> controlSeq "ProvideDocumentCommand"
+    <|> controlSeq "DeclareDocumentCommand"
+    <|> controlSeq "NewExpandableDocumentCommand"
+    <|> controlSeq "RenewExpandableDocumentCommand"
+    <|> controlSeq "ProvideExpandableDocumentCommand"
+    <|> controlSeq "DeclareExpandableDocumentCommand"
+  withVerbatimMode $ do
+    Tok _ (CtrlSeq name) txt <- do
+      spaces
+      anyControlSeq <|>
+        (symbol '{' *> spaces *> anyControlSeq <* spaces <* symbol '}')
+    spaces
+    (argspecs, _) <- xparseArgSpecs False
+    spaces
+    contents <- bracedOrToken
+    let macro = Macro GroupScope ExpandWhenUsed argspecs Nothing contents
+    (do lookupMacro name
+        if "Provide" `T.isPrefixOf` mtype
+           then return []
+           else if "New" `T.isPrefixOf` mtype
+                   then [] <$ report (MacroAlreadyDefined txt pos)
+                   else return [(name, macro)]) -- Renew or Declare
+      <|> pure [(name, macro)]
+
+-- | Parses a definition of the form
+-- @\NewDocumentEnvironment{name}{argspec}{begin-code}{end-code}@
+-- (and the Renew, Provide, and Declare variants), with xparse
+-- (LaTeX3) argument specifiers.  Unlike with @\newenvironment@,
+-- the arguments are also available in the end-code; to support
+-- this we bind a group-scoped helper macro, at the point where
+-- @\begin{name}@ is expanded, whose body is the end-code with the
+-- arguments already substituted; @\end{name}@ just expands the
+-- helper.  (Group scoping makes this work for nested environments.)
+newDocumentEnvironment :: PandocMonad m => LP m [(Text, Macro)]
+newDocumentEnvironment = try $ do
+  Tok pos (CtrlSeq mtype) _ <-
+        controlSeq "NewDocumentEnvironment"
+    <|> controlSeq "RenewDocumentEnvironment"
+    <|> controlSeq "ProvideDocumentEnvironment"
+    <|> controlSeq "DeclareDocumentEnvironment"
+  withVerbatimMode $ do
+    spaces
+    name <- T.strip . untokenize <$> braced
+    spaces
+    (argspecs, usesBody) <- xparseArgSpecs True
+    startcontents <- spaces >> bracedOrToken
+    endcontents <- spaces >> bracedOrToken
+    -- we need the environment to be in a group so macros defined
+    -- inside behave correctly:
+    let bg = Tok pos (CtrlSeq "bgroup") "\\bgroup "
+    let eg = Tok pos (CtrlSeq "egroup") "\\egroup "
+    let helperName = "pandocxparseenvend" <> letterize name
+    let helper = Tok pos (CtrlSeq helperName) ("\\" <> helperName <> " ")
+    let defHelper = Tok pos (CtrlSeq "def") "\\def"
+                  : helper
+                  : Tok pos Symbol "{"
+                  : endcontents ++ [Tok pos Symbol "}"]
+    let result
+          | usesBody =
+            -- a 'b' argspec grabs everything up to \end{name} as the
+            -- last argument (the argspecs end with its ArgNum, and we
+            -- add a Pattern that consumes the \end{name}), so begin-
+            -- and end-code run together:
+            [ (name,
+               Macro GroupScope ExpandWhenUsed
+                 (argspecs ++ [Pattern (tokenize pos ("\\end{" <> name <> "}"))])
+                 Nothing
+                 (bg : defHelper ++ startcontents ++ [helper, eg])) ]
+          | otherwise =
+            [ (name,
+               Macro GroupScope ExpandWhenUsed argspecs Nothing
+                 (bg : defHelper ++ startcontents))
+            , ("end" <> name,
+               Macro GroupScope ExpandWhenUsed [] Nothing [helper, eg]) ]
+    (do lookupMacro name
+        if "Provide" `T.isPrefixOf` mtype
+           then return []
+           else if "New" `T.isPrefixOf` mtype
+                   then [] <$ report (MacroAlreadyDefined name pos)
+                   else return result) -- Renew or Declare
+      <|> pure result
+
+-- | Parses @\NewCommandCopy\new\old@ (and the Renew and Declare
+-- variants): like @\let@, restricted to control sequences.
+commandCopy :: PandocMonad m => LP m [(Text, Macro)]
+commandCopy = try $ do
+  Tok pos (CtrlSeq mtype) _ <-
+        controlSeq "NewCommandCopy"
+    <|> controlSeq "RenewCommandCopy"
+    <|> controlSeq "DeclareCommandCopy"
+  withVerbatimMode $ do
+    Tok _ (CtrlSeq name) txt <- do
+      spaces
+      anyControlSeq <|>
+        (symbol '{' *> spaces *> anyControlSeq <* spaces <* symbol '}')
+    spaces
+    target@(Tok _ (CtrlSeq targetName) _) <- anyControlSeq
+    result <- (do m <- lookupMacro targetName
+                  pure [(name, m)])
+          <|> pure [(name, Macro GroupScope ExpandWhenDefined [] Nothing
+                             [target])]
+    (do lookupMacro name
+        if "New" `T.isPrefixOf` mtype
+           then [] <$ report (MacroAlreadyDefined txt pos)
+           else return result) -- Renew or Declare
+      <|> pure result
+
+-- | Parses @\NewEnvironmentCopy{new}{old}@ (and the Renew and
+-- Declare variants), copying both the begin and the end macros.
+-- If the target environment is not macro-defined, the copy expands
+-- to @\begin{old}@ / @\end{old}@.
+environmentCopy :: PandocMonad m => LP m [(Text, Macro)]
+environmentCopy = try $ do
+  Tok pos (CtrlSeq mtype) _ <-
+        controlSeq "NewEnvironmentCopy"
+    <|> controlSeq "RenewEnvironmentCopy"
+    <|> controlSeq "DeclareEnvironmentCopy"
+  withVerbatimMode $ do
+    spaces
+    name <- untokenize <$> braced
+    spaces
+    target <- untokenize <$> braced
+    let copyOne to from fallback =
+          (do m <- lookupMacro from
+              pure (to, m))
+          <|> pure (to, Macro GroupScope ExpandWhenUsed [] Nothing
+                          (tokenize pos fallback))
+    beginmacro <- copyOne name target ("\\begin{" <> target <> "}")
+    endmacro <- copyOne ("end" <> name) ("end" <> target)
+                        ("\\end{" <> target <> "}")
+    let result = [beginmacro, endmacro]
+    (do lookupMacro name
+        if "New" `T.isPrefixOf` mtype
+           then [] <$ report (MacroAlreadyDefined name pos)
+           else return result) -- Renew or Declare
+      <|> pure result
+
+-- | Encode a text using letters only (so that the result can be
+-- part of a control sequence name that survives retokenization).
+letterize :: Text -> Text
+letterize = T.concatMap go
+  where go c | isLetter c = T.singleton c
+             | otherwise = "x" <> T.map toLetter (T.pack (show (ord c)))
+        toLetter d = chr (ord d + 49)  -- '0'..'9' -> 'a'..'j'
+
+-- | Parses a braced xparse argument specification, e.g.
+-- @{s O{default} m}@.  The Bool parameter determines whether a
+-- @b@ (environment body) specifier is allowed; the Bool in the
+-- result is True if one was used (it can only come last).
+xparseArgSpecs :: PandocMonad m => Bool -> LP m ([ArgSpec], Bool)
+xparseArgSpecs allowBody = symbol '{' *> go 1
+ where
+  go n = do
+    spaces
+    (([], False) <$ symbol '}') <|>
+      (do (spec, isBody) <- xparseArgSpec allowBody n
+          if isBody
+             then ([spec], True) <$ (spaces <* symbol '}')
+             else do (rest, usesBody) <- go (n + numslots spec)
+                     return (spec : rest, usesBody))
+  -- most specifiers bind one argument; embellishments bind one
+  -- argument per embellishment token
+  numslots (EmbellishArg embs) = length embs
+  numslots (ProcessedArg _ spec) = numslots spec
+  numslots _ = 1
+
+xparseArgSpec :: PandocMonad m => Bool -> Int -> LP m (ArgSpec, Bool)
+xparseArgSpec allowBody n = go True
+ where
+  -- the parameter is False if the ! modifier has been seen; it
+  -- disables space-skipping before optional arguments, and (for the
+  -- b and c body specifiers) trimming of the ends of the body
+  go noBang = do
+   Tok pos _ c <- singleChar
+   let plain spec = pure (spec, False)
+   case c of
+    "+" -> spaces *> go noBang  -- "long": no distinction
+    "!" -> spaces *> go False   -- no space-skipping
+    "=" -> spaces *> braced *> spaces *> go noBang
+           -- key-value interface for the argument: not modeled
+    ">" -> do proc <- spaces *> braced
+              (spec, isBody) <- spaces *> go noBang
+              if isBody
+                 then pure (spec, isBody)
+                      -- processors are not supported on 'b' arguments
+                 else case spec of
+                        ProcessedArg procs inner ->
+                          pure (ProcessedArg (proc : procs) inner, False)
+                        _ -> pure (ProcessedArg [proc] spec, False)
+    "m" -> plain $ ArgNum n
+    "o" -> plain $ DelimArg noBang (lbTok pos) (rbTok pos) Nothing
+    "O" -> do dflt <- spaces *> braced
+              plain $ DelimArg noBang (lbTok pos) (rbTok pos) (Just dflt)
+    "s" -> plain $ BoolArg noBang (Tok pos Symbol "*")
+    "t" -> specTok >>= plain . BoolArg noBang
+    "d" -> do o <- specTok
+              c' <- specTok
+              plain $ DelimArg noBang o c' Nothing
+    "D" -> do o <- specTok
+              c' <- specTok
+              dflt <- spaces *> braced
+              plain $ DelimArg noBang o c' (Just dflt)
+    "r" -> do o <- specTok
+              c' <- specTok
+              plain $ DelimArg noBang o c' Nothing
+    "R" -> do o <- specTok
+              c' <- specTok
+              dflt <- spaces *> braced
+              plain $ DelimArg noBang o c' (Just dflt)
+    "v" -> plain VerbArg
+    "e" -> do embtoks <- spaces *> braced
+              plain $ EmbellishArg [(t, Nothing) | t <- embTokens embtoks]
+    "E" -> do embtoks <- spaces *> braced
+              spaces
+              defaults <- symbol '{' *> many (try (spaces *> braced))
+                            <* spaces <* symbol '}'
+              plain $ EmbellishArg $
+                zip (embTokens embtoks) (map Just defaults ++ repeat Nothing)
+    "b" | allowBody -> pure (BodyArg noBang n, True)
+    "c" | allowBody -> pure (VerbBodyArg noBang n, True)  -- verbatim body
+    _   -> fail "unsupported xparse argument specifier"
+  lbTok pos = Tok pos Symbol "["
+  rbTok pos = Tok pos Symbol "]"
+  specTok = spaces *> singleChar
+  embTokens = filter (not . tokTypeIn [Spaces, Newline, Comment])
 
 newenvironment :: PandocMonad m => LP m (Maybe (Text, Macro, Macro))
 newenvironment = do
diff --git a/src/Text/Pandoc/Readers/LaTeX/Parsing.hs b/src/Text/Pandoc/Readers/LaTeX/Parsing.hs
--- a/src/Text/Pandoc/Readers/LaTeX/Parsing.hs
+++ b/src/Text/Pandoc/Readers/LaTeX/Parsing.hs
@@ -25,6 +25,9 @@
   , LaTeXState(..)
   , defaultLaTeXState
   , LP
+  , LaTeXEnv(..)
+  , emptyLaTeXEnv
+  , askEnv
   , TokStream(..)
   , withVerbatimMode
   , rawLaTeXParser
@@ -73,6 +76,7 @@
   , bracedOrToken
   , bracketed
   , bracketedToks
+  , verbTok
   , parenWrapped
   , dimenarg
   , ignore
@@ -97,11 +101,12 @@
 import Control.Applicative (many, (<|>))
 import Control.Monad
 import Control.Monad.Except (throwError)
+import Control.Monad.Reader (ReaderT, ask, runReaderT)
 import Control.Monad.Trans (lift)
 import Data.Char (chr, isAlphaNum, isDigit, isLetter, ord)
 import Data.Default
-import Data.List (intercalate)
-import qualified Data.IntMap as IntMap
+import Data.List (dropWhileEnd, intercalate, isSuffixOf, unfoldr)
+import Numeric (showEFloat, showFFloat)
 import qualified Data.Map as M
 import qualified Data.Set as Set
 import Data.Text (Text)
@@ -112,7 +117,7 @@
 import Text.Pandoc.Builder
 import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
 import Text.Pandoc.Error
-         (PandocError (PandocMacroLoop,PandocShouldNeverHappenError))
+         (PandocError (PandocMacroLoop))
 import Text.Pandoc.Logging
 import Text.Pandoc.Options
 import Text.Pandoc.Parsing hiding (blankline, many, mathDisplay, mathInline,
@@ -174,8 +179,18 @@
                             , sToggles       :: M.Map Text Bool
                             , sFileContents  :: M.Map Text Text
                             , sEnableWithRaw :: Bool
-                            , sRawTokens     :: IntMap.IntMap [Tok]
+                            , sRawTokens     :: [Tok]
+                              -- ^ reversed list of tokens consumed
+                              -- while at least one withRaw scope is
+                              -- active
+                            , sRawTokenCount :: !Int
+                              -- ^ length of sRawTokens
+                            , sRawScopes     :: !Int
+                              -- ^ number of active withRaw scopes
                             , sLigatures     :: Bool
+                            , sCaseExclusions :: M.Map Text (Set.Set Text)
+                              -- ^ words excluded from case changing
+                              -- (keys: @upper@, @lower@, @title@)
                             }
      deriving Show
 
@@ -205,8 +220,11 @@
                               , sToggles       = M.empty
                               , sFileContents  = M.empty
                               , sEnableWithRaw = True
-                              , sRawTokens     = IntMap.empty
+                              , sRawTokens     = []
+                              , sRawTokenCount = 0
+                              , sRawScopes     = 0
                               , sLigatures     = True
+                              , sCaseExclusions = M.empty
                               }
 
 instance PandocMonad m => HasQuoteContext LaTeXState m where
@@ -267,8 +285,27 @@
   uncons (TokStream _ []) = return Nothing
   uncons (TokStream _ (t:ts)) = return $ Just (t, TokStream False ts)
 
-type LP m = ParsecT TokStream LaTeXState m
+type LP m = ParsecT TokStream LaTeXState (ReaderT (LaTeXEnv m) m)
 
+-- | Environment holding the command dispatch tables.  Because these
+-- tables have types that mention the parser monad, they cannot be
+-- top-level constants; passing them in a reader environment ensures
+-- they are constructed once per parse rather than once per use.
+data LaTeXEnv m = LaTeXEnv
+  { envInlineCommands :: M.Map Text (LP m Inlines)
+  , envBlockCommands  :: M.Map Text (LP m Blocks)
+  , envEnvironments   :: M.Map Text (LP m Blocks)
+  }
+
+-- | An environment with empty dispatch tables, for running parsers
+-- that do not consult them.
+emptyLaTeXEnv :: LaTeXEnv m
+emptyLaTeXEnv = LaTeXEnv mempty mempty mempty
+
+-- | Retrieve the command dispatch tables.
+askEnv :: Monad m => LP m (LaTeXEnv m)
+askEnv = lift ask
+
 withVerbatimMode :: PandocMonad m => LP m a -> LP m a
 withVerbatimMode parser = do
   alreadyVerbatimMode <- sVerbatimMode <$> getState
@@ -281,9 +318,9 @@
        return result
 
 rawLaTeXParser :: (PandocMonad m, HasMacros s, HasReaderOptions s, Show a)
-               => [Tok] -> LP m () -> LP m a
+               => LaTeXEnv m -> [Tok] -> LP m () -> LP m a
                -> ParsecT Sources s m (a, Text)
-rawLaTeXParser toks parser valParser = do
+rawLaTeXParser lenv toks parser valParser = do
   pstate <- getState
   let lstate = def{ sOptions = extractReaderOptions pstate }
   let lstate' = lstate { sMacros = extractMacros pstate :| [] }
@@ -292,12 +329,14 @@
                       _ -> return ()
   let preparser = setStartPos >> parser
   let rawparser = (,) <$> withRaw valParser <*> getState
-  res' <- lift $ runParserT (withRaw (preparser >> getPosition))
+  res' <- lift $ flip runReaderT lenv $
+                 runParserT (withRaw (preparser >> getPosition))
                             lstate "chunk" $ TokStream False toks
   case res' of
        Left _    -> mzero
        Right (endpos, toks') -> do
-         res <- lift $ runParserT rawparser lstate' "chunk"
+         res <- lift $ flip runReaderT lenv $
+                       runParserT rawparser lstate' "chunk"
                      $ TokStream False toks'
          case res of
               Left _    -> mzero
@@ -328,7 +367,8 @@
       pstate <- getState
       let lstate = def{ sOptions = extractReaderOptions pstate
                       , sMacros  = extractMacros pstate :| [] }
-      res <- runParserT retokenize lstate "math" $
+      res <- flip runReaderT emptyLaTeXEnv $
+                 runParserT retokenize lstate "math" $
                  TokStream False (tokenize (initialPos "math") s)
       case res of
            Left e   -> Prelude.fail (show e)
@@ -360,7 +400,7 @@
       Sources ((_,t):rest) -> tokenizeSources $ Sources ((pos,t):rest)
 
 tokenize :: SourcePos -> Text -> [Tok]
-tokenize = totoks False
+tokenize = totoks (TokenizerState False False)
  where
   totoks atIsLetter pos t =
     case T.uncons t of
@@ -389,10 +429,17 @@
                   | isLetter' atIsLetter d ->
                       let (ws, rest'') = T.span (isLetter' atIsLetter) rest
                           (ss, rest''') = T.span isSpaceOrTab rest''
-                          atIsLetter' = case ws of
-                                          "makeatletter" -> True
-                                          "makeatother" -> False
-                                          _ -> atIsLetter
+                          atIsLetter' =
+                            case ws of
+                              "makeatletter" ->
+                                atIsLetter{ tsAtIsLetter = True }
+                              "makeatother" ->
+                                atIsLetter{ tsAtIsLetter = False }
+                              "ExplSyntaxOn" ->
+                                atIsLetter{ tsExplSyntax = True }
+                              "ExplSyntaxOff" ->
+                                atIsLetter{ tsExplSyntax = False }
+                              _ -> atIsLetter
                       in  Tok pos (CtrlSeq ws) ("\\" <> ws <> ss)
                           : totoks atIsLetter' (incSourceColumn pos
                                (1 + T.length ws + T.length ss)) rest'''
@@ -427,7 +474,7 @@
                               (incSourceColumn pos (2 + T.length t1)) t2
                         Nothing -> Tok pos Symbol "#"
                                   : Tok (incSourceColumn pos 1) Symbol "#"
-                                  : totoks atIsLetter (incSourceColumn pos 1) t3
+                                  : totoks atIsLetter (incSourceColumn pos 2) t3
              _ ->
                let (t1, t2) = T.span (\d -> d >= '0' && d <= '9') rest
                in  case safeRead t1 of
@@ -470,9 +517,18 @@
 isSpaceOrTab '\t' = True
 isSpaceOrTab _    = False
 
--- First parameter is True if @ is letter
-isLetter' :: Bool -> Char -> Bool
-isLetter' True '@' = True
+-- | State threaded through the tokenizer: whether @\@@ is a letter
+-- (between @\makeatletter@ and @\makeatother@), and whether @:@ and
+-- @_@ are letters (between @\ExplSyntaxOn@ and @\ExplSyntaxOff@).
+data TokenizerState = TokenizerState
+  { tsAtIsLetter :: Bool
+  , tsExplSyntax :: Bool
+  }
+
+isLetter' :: TokenizerState -> Char -> Bool
+isLetter' st '@' = tsAtIsLetter st
+isLetter' st ':' = tsExplSyntax st
+isLetter' st '_' = tsExplSyntax st
 isLetter' _ c = isLetter c
 
 isLetterOrAt :: Char -> Bool
@@ -483,21 +539,23 @@
 isLowerHex x = x >= '0' && x <= '9' || x >= 'a' && x <= 'f'
 
 untokenize :: [Tok] -> Text
-untokenize = foldr untokenAccum mempty
-
-untokenAccum :: Tok -> Text -> Text
-untokenAccum (Tok _ (CtrlSeq _) t) accum =
-  -- insert space to prevent breaking a control sequence; see #5836
-  case (T.unsnoc t, T.uncons accum) of
-    (Just (_,c), Just (d,_))
-      | isLetter c
-      , isLetter d
-      -> t <> " " <> accum
-    _ -> t <> accum
-untokenAccum (Tok _ _ t) accum = t <> accum
+untokenize = T.concat . go
+  where
+    go [] = []
+    go (Tok _ (CtrlSeq _) t : ts)
+      -- insert space to prevent breaking a control sequence; see #5836
+      | Just (_, c) <- T.unsnoc t
+      , isLetter c
+      , nextStartsWithLetter ts = t : " " : go ts
+    go (Tok _ _ t : ts) = t : go ts
+    nextStartsWithLetter (Tok _ _ t : ts) =
+      case T.uncons t of
+        Just (d, _) -> isLetter d
+        Nothing     -> nextStartsWithLetter ts
+    nextStartsWithLetter [] = False
 
 untoken :: Tok -> Text
-untoken t = untokenAccum t mempty
+untoken (Tok _ _ t) = t
 
 parseFromToks :: PandocMonad m => LP m a -> [Tok] -> LP m a
 parseFromToks parser toks = do
@@ -507,11 +565,15 @@
   case toks of
      Tok pos _ _ : _ -> setPosition pos
      _ -> return ()
-  -- we ignore existing raw tokens maps (see #9517)
-  oldRawTokens <- sRawTokens <$> getState
-  updateState $ \st -> st{ sRawTokens = mempty }
+  -- we ignore existing raw token accumulation (see #9517)
+  oldst <- getState
+  updateState $ \st -> st{ sRawTokens = []
+                         , sRawTokenCount = 0
+                         , sRawScopes = 0 }
   result <- parser
-  updateState $ \st -> st{ sRawTokens = oldRawTokens }
+  updateState $ \st -> st{ sRawTokens = sRawTokens oldst
+                         , sRawTokenCount = sRawTokenCount oldst
+                         , sRawScopes = sRawScopes oldst }
   setInput oldInput
   setPosition oldpos
   return result
@@ -529,10 +591,9 @@
     doMacros -- apply macros on remaining input stream
     res <- tokenPrim (T.unpack . untoken) updatePos matcher
     updateState $ \st ->
-      if sEnableWithRaw st
-         then
-           let !newraws = IntMap.map (res:) $! sRawTokens st
-            in  st{ sRawTokens = newraws }
+      if sRawScopes st > 0 && sEnableWithRaw st
+         then st{ sRawTokens = res : sRawTokens st
+                , sRawTokenCount = sRawTokenCount st + 1 }
          else st
     return $! res
   where matcher t | f t       = Just t
@@ -544,15 +605,24 @@
 peekTok :: PandocMonad m => LP m Tok
 peekTok = do
   doMacros
-  lookAhead (satisfyTok (const True))
+  TokStream _ toks <- getInput
+  case toks of
+    t : _ -> return t
+    []    -> mzero
 
 doMacros :: PandocMonad m => LP m ()
 doMacros = do
   TokStream macrosExpanded toks <- getInput
-  unless macrosExpanded $ do
-    st <- getState
-    unless (sVerbatimMode st) $
-      doMacros' 1 toks >>= setInput . TokStream True
+  unless macrosExpanded $
+    case toks of
+      -- only a control sequence at the head of the stream can
+      -- trigger macro expansion; in other cases we skip the
+      -- state update:
+      Tok _ (CtrlSeq _) _ : _ -> do
+        st <- getState
+        unless (sVerbatimMode st) $
+          doMacros' 1 toks >>= setInput . TokStream True
+      _ -> return ()
 
 doMacros' :: PandocMonad m => Int -> [Tok] -> LP m [Tok]
 doMacros' n inp =
@@ -583,18 +653,81 @@
 
     matchPattern toks = try $ mapM_ matchTok toks
 
-    getargs argmap [] = return argmap
-    getargs argmap (Pattern toks : rest) = try $ do
+    -- the first parameter is the number of the next argument
+    -- to be bound (needed for the argspecs that don't carry an
+    -- argument number themselves)
+    getargs _ argmap [] = return argmap
+    getargs num argmap (Pattern toks : rest) = try $ do
        matchPattern toks
-       getargs argmap rest
-    getargs argmap (ArgNum i : Pattern toks : rest) =
+       getargs num argmap rest
+    getargs _ argmap (ArgNum i : Pattern toks : rest) =
       try $ do
         x <- mconcat <$> manyTill (braced <|> ((:[]) <$> anyTok))
                   (matchPattern toks)
-        getargs (M.insert i x argmap) rest
-    getargs argmap (ArgNum i : rest) = do
+        getargs (i + 1) (M.insert i x argmap) rest
+    getargs _ argmap (ArgNum i : rest) = do
       x <- try $ spaces >> bracedOrToken
-      getargs (M.insert i x argmap) rest
+      getargs (i + 1) (M.insert i x argmap) rest
+    getargs num argmap (BoolArg skipSp t@(Tok pos _ _) : rest) = do
+      -- the ! modifier (skipSp False) disables space-skipping:
+      let sp' = when skipSp sp
+      x <- option [Tok pos (CtrlSeq "BooleanFalse") "\\BooleanFalse "]
+             ([Tok pos (CtrlSeq "BooleanTrue") "\\BooleanTrue "]
+               <$ try (sp' *> matchTok t))
+      getargs (num + 1) (M.insert num x argmap) rest
+    getargs num argmap (DelimArg skipSp open@(Tok pos _ _) close mbdef
+                         : rest) = do
+      let sp' = when skipSp sp
+      let missing = fromMaybe [Tok pos (CtrlSeq "NoValue") "\\NoValue "] mbdef
+      x <- option missing (try (sp' *> delimitedToks open close))
+      getargs (num + 1) (M.insert num x argmap) rest
+    getargs num argmap (VerbArg : rest) = do
+      x <- verbatimArg
+      getargs (num + 1) (M.insert num x argmap) rest
+    -- xparse 'b' specifier: environment body, grabbed up to the
+    -- \end{name} pattern; spaces at the ends are trimmed unless
+    -- the ! modifier was used:
+    getargs _ argmap (BodyArg doTrim i : Pattern toks : rest) = try $ do
+      x <- mconcat <$> manyTill ((snd <$> withRaw (try braced))
+                                  <|> ((:[]) <$> anyTok))
+                (matchPattern toks)
+      let x' = if doTrim then trimSpaceToks x else x
+      getargs (i + 1) (M.insert i x' argmap) rest
+    getargs num argmap (BodyArg _ i : rest) =
+      getargs num argmap (ArgNum i : rest)
+    -- xparse 'c' specifier: environment body, grabbed verbatim up
+    -- to the \end{name} pattern; blank lines at the ends are
+    -- trimmed unless the ! modifier was used:
+    getargs _ argmap (VerbBodyArg doTrim i : Pattern toks : rest) = try $ do
+      x <- mconcat <$> manyTill ((snd <$> withRaw (try braced))
+                                  <|> ((:[]) <$> anyTok))
+                (matchPattern toks)
+      getargs (i + 1) (M.insert i (verbatimBodyToks doTrim x) argmap) rest
+    getargs num argmap (VerbBodyArg _ i : rest) =
+      getargs num argmap (ArgNum i : rest)
+    getargs num argmap (EmbellishArg embs : rest) = do
+      let grab seen
+            | length seen == length embs = pure seen
+            | otherwise = option seen $ try $ do
+                sp
+                i <- choice [ i <$ matchTok t
+                            | (i, (t, _)) <- zip [(0 :: Int)..] embs
+                            , i `notElem` map fst seen ]
+                x <- braced <|> count 1 anyTok
+                grab ((i, x) : seen)
+      seen <- grab []
+      let getval i (Tok pos _ _, mbdef) =
+            case lookup i seen of
+              Just x  -> x
+              Nothing ->
+                fromMaybe [Tok pos (CtrlSeq "NoValue") "\\NoValue "] mbdef
+      let argmap' = foldr (\(i, e) m -> M.insert (num + i) (getval i e) m)
+                          argmap (zip [0..] embs)
+      getargs (num + length embs) argmap' rest
+    getargs num argmap (ProcessedArg procs spec : rest) = do
+      newargs <- getargs num M.empty [spec]
+      newargs' <- traverse (applyArgProcessors procs) newargs
+      getargs (num + M.size newargs) (M.union newargs' argmap) rest
 
     addTok False _args spos (Tok _ (DeferredArg i) txt) acc =
       Tok spos (Arg i) txt : acc
@@ -615,7 +748,9 @@
         $ throwError $ PandocMacroLoop name
       (macros :| _ ) <- sMacros <$> getState
       case M.lookup name macros of
-           Nothing -> trySpecialMacro name ts
+           -- the result of a special macro may itself begin with a
+           -- macro call, so we continue expanding:
+           Nothing -> trySpecialMacro name ts >>= doMacros' (n' + 1)
            Just (Macro _scope expansionPoint argspecs optarg newtoks) -> do
              let getargs' = do
                    args <-
@@ -623,10 +758,10 @@
                         ExpandWhenUsed    -> withVerbatimMode
                         ExpandWhenDefined -> id)
                      $ case optarg of
-                             Nothing -> getargs M.empty argspecs
+                             Nothing -> getargs 1 M.empty argspecs
                              Just o  -> do
                                 x <- option o bracketedToks
-                                getargs (M.singleton 1 x) $ drop 1 argspecs
+                                getargs 2 (M.singleton 1 x) $ drop 1 argspecs
                    TokStream _ rest <- getInput
                    return (args, rest)
              lstate <- getState
@@ -634,11 +769,34 @@
              case res of
                Left _ -> Prelude.fail $ "Could not parse arguments for " ++
                                 T.unpack name
-               Right (args, rest) -> do
+               Right (args', rest) -> do
+                 -- An argument default may refer to other arguments
+                 -- (e.g. O{#2}); resolve such references (the visited
+                 -- list guards against reference cycles):
+                 let resolveTok visited t@(Tok _ (Arg j) _)
+                       | j `notElem` visited
+                       , Just ys <- M.lookup j args'
+                       = concatMap (resolveTok (j : visited)) ys
+                       | otherwise = [t]
+                     resolveTok _ t = [t]
+                 let args = M.mapWithKey
+                              (\i -> concatMap (resolveTok [i])) args'
+                 -- In TeX, a newline in a macro body behaves like a
+                 -- space; a single newline at the end of the body,
+                 -- followed by a newline in the source, must not be
+                 -- mistaken for a blank line (paragraph break):
+                 let newtoks' =
+                       case reverse newtoks of
+                         Tok p Newline _ : rts
+                           | not (any isNewlineTok (take 1 rts))
+                           , (Tok _ Newline _ : _) <-
+                               dropWhile (tokTypeIn [Spaces, Comment]) rest
+                           -> reverse (Tok p Spaces " " : rts)
+                         _ -> newtoks
                  -- first boolean param is true if we're tokenizing
                  -- an argument (in which case we don't want to
                  -- expand #1 etc.)
-                 let result = foldr (addTok False args spos) rest newtoks
+                 let result = foldr (addTok False args spos) rest newtoks'
                  case expansionPoint of
                    ExpandWhenUsed    -> doMacros' (n' + 1) result
                    ExpandWhenDefined -> return result
@@ -652,15 +810,133 @@
     Tok pos Word t : _
       | startsWithAlphaNum t -> return $ Tok pos Spaces " " : ts'
     _ -> return ts'
-trySpecialMacro "iftrue" ts = handleIf (ifParser True) ts
-trySpecialMacro "iffalse" ts = handleIf (ifParser False) ts
+trySpecialMacro "iftrue" ts = doIf True ts
+trySpecialMacro "iffalse" ts = doIf False ts
 trySpecialMacro "ifmmode" ts = do
   mathMode <- sMathMode <$> getState
-  handleIf (ifParser mathMode) ts
+  doIf mathMode ts
 trySpecialMacro "ifstrequal" ts = do
   handleIf ifStrequalParser ts
+-- xparse (LaTeX3) argument conditionals:
+trySpecialMacro "IfNoValueTF" ts = handleIf (xparseIf isNoValueArg True True) ts
+trySpecialMacro "IfNoValueT" ts = handleIf (xparseIf isNoValueArg True False) ts
+trySpecialMacro "IfNoValueF" ts = handleIf (xparseIf isNoValueArg False True) ts
+trySpecialMacro "IfValueTF" ts =
+  handleIf (xparseIf (not . isNoValueArg) True True) ts
+trySpecialMacro "IfValueT" ts =
+  handleIf (xparseIf (not . isNoValueArg) True False) ts
+trySpecialMacro "IfValueF" ts =
+  handleIf (xparseIf (not . isNoValueArg) False True) ts
+trySpecialMacro "IfBooleanTF" ts =
+  handleIf (xparseIf isBooleanTrueArg True True) ts
+trySpecialMacro "IfBooleanT" ts =
+  handleIf (xparseIf isBooleanTrueArg True False) ts
+trySpecialMacro "IfBooleanF" ts =
+  handleIf (xparseIf isBooleanTrueArg False True) ts
+trySpecialMacro "IfBlankTF" ts = handleIf (xparseIf isBlankArg True True) ts
+trySpecialMacro "IfBlankT" ts = handleIf (xparseIf isBlankArg True False) ts
+trySpecialMacro "IfBlankF" ts = handleIf (xparseIf isBlankArg False True) ts
+-- \ProcessList{list}{tokens}: apply tokens to every item of list:
+trySpecialMacro "ProcessList" ts = handleIf processListParser ts
+-- \UseName{string}: turn string into a csname and execute it:
+trySpecialMacro "UseName" ts = handleIf useNameParser ts
+-- \ExpandArgs{spec}\cmd{arg1}...: pre-expand the command's
+-- arguments as described by the spec:
+trySpecialMacro "ExpandArgs" ts = handleIf expandArgsParser ts
+-- LaTeX3 expandable evaluators:
+trySpecialMacro "inteval" ts = handleEval evalInteval ts
+trySpecialMacro "fpeval" ts = handleEval evalFpeval ts
+-- dimension expressions are not evaluated; we substitute the
+-- expression itself:
+trySpecialMacro "dimeval" ts = handleEval (Just . T.strip) ts
+trySpecialMacro "skipeval" ts = handleEval (Just . T.strip) ts
 trySpecialMacro _ _ = mzero
 
+-- | Parse a conditional of the kind used with xparse commands
+-- (@\\IfNoValueTF@ etc.): test the first argument and select the
+-- true or false branch.  The Bool parameters indicate whether a
+-- true and a false branch, respectively, are to be parsed.
+xparseIf :: PandocMonad m => ([Tok] -> Bool) -> Bool -> Bool -> LP m [Tok]
+xparseIf test hasTrueBranch hasFalseBranch = do
+  -- spaces and comments may intervene between the arguments:
+  let grabArg = withVerbatimMode (spaces *> (braced <|> count 1 anyTok))
+  let getBranch cond = if cond
+                          then grabArg
+                          else pure []
+  arg <- grabArg
+  trueToks <- getBranch hasTrueBranch
+  falseToks <- getBranch hasFalseBranch
+  TokStream _ rest <- getInput
+  return $ (if test arg then trueToks else falseToks) ++ rest
+
+isNoValueArg :: [Tok] -> Bool
+isNoValueArg toks =
+  case filter (not . tokTypeIn [Spaces, Newline, Comment]) toks of
+    [Tok _ (CtrlSeq "NoValue") _] -> True
+    [Tok _ Symbol "-", Tok _ Word "NoValue", Tok _ Symbol "-"] -> True
+    _ -> False
+
+isBooleanTrueArg :: [Tok] -> Bool
+isBooleanTrueArg toks =
+  case filter (not . tokTypeIn [Spaces, Newline, Comment]) toks of
+    [Tok _ (CtrlSeq "BooleanTrue") _] -> True
+    _ -> False
+
+-- | An argument is \"blank\" (in the sense of @\\IfBlankTF@) if it
+-- is empty or consists only of blanks.
+isBlankArg :: [Tok] -> Bool
+isBlankArg = all (tokTypeIn [Spaces, Newline, Comment])
+
+-- | Parser for the arguments of @\\ProcessList{list}{tokens}@:
+-- apply the tokens to every item (braced group or single token) of
+-- the list.
+processListParser :: PandocMonad m => LP m [Tok]
+processListParser = withVerbatimMode $ do
+  pos <- getPosition
+  spaces
+  list <- braced <|> count 1 anyTok
+  spaces
+  fn <- braced <|> count 1 anyTok
+  TokStream _ rest <- getInput
+  let items = unfoldr tokGroup list
+  let braceIt item = Tok pos Symbol "{" : item ++ [Tok pos Symbol "}"]
+  return $ concatMap (\item -> fn ++ braceIt item) items ++ rest
+
+-- | Parser for the argument of @\\UseName{string}@: turn the
+-- string into a control sequence.
+useNameParser :: PandocMonad m => LP m [Tok]
+useNameParser = do
+  pos <- getPosition
+  name <- untokenize <$> withVerbatimMode (spaces *> braced)
+  TokStream _ rest <- getInput
+  return $ Tok pos (CtrlSeq name) ("\\" <> name <> " ") : rest
+
+-- | Parser for the arguments of @\\ExpandArgs{spec}\\cmd{arg1}...@:
+-- transform each argument as described by the corresponding letter
+-- of the spec (@c@ = turn a string into a control sequence, @n@ =
+-- leave a braced argument unchanged, @N@ = leave a single token
+-- unchanged), then put the command before the transformed
+-- arguments.
+expandArgsParser :: PandocMonad m => LP m [Tok]
+expandArgsParser = withVerbatimMode $ do
+  spec <- T.unpack . untokenize <$> (spaces *> braced)
+  spaces
+  cmd <- anyTok
+  args <- concat <$> mapM transformArg spec
+  TokStream _ rest <- getInput
+  return $ cmd : args ++ rest
+ where
+  transformArg 'c' = do
+    pos <- getPosition
+    name <- untokenize <$> (spaces *> braced)
+    return [Tok pos (CtrlSeq name) ("\\" <> name <> " ")]
+  transformArg 'n' = do
+    pos <- getPosition
+    toks <- spaces *> braced
+    return $ Tok pos Symbol "{" : toks ++ [Tok pos Symbol "}"]
+  transformArg 'N' = spaces *> count 1 anyTok
+  transformArg _ = mzero
+
 ifStrequalParser :: PandocMonad m => LP m [Tok]
 ifStrequalParser = do
   str1 <- braced <|> count 1 anyTok
@@ -681,15 +957,292 @@
     Left _ -> Prelude.fail "Could not parse conditional"
     Right ts' -> return ts'
 
-ifParser :: PandocMonad m => Bool -> LP m [Tok]
-ifParser b = do
-  ifToks <- many (notFollowedBy (controlSeq "else" <|> controlSeq "fi")
-                    *> anyTok)
-  elseToks <- (controlSeq "else" >> manyTill anyTok (controlSeq "fi"))
-                 <|> ([] <$ controlSeq "fi")
-  TokStream _ rest <- getInput
-  return $ (if b then ifToks else elseToks) ++ rest
+-- | Names of TeX's primitive conditionals.  While scanning for the
+-- @\\else@ or @\\fi@ that ends a conditional branch, we need to
+-- know which control sequences begin a conditional, so that the
+-- @\\else@ and @\\fi@ belonging to nested conditionals are not
+-- mistaken for the end of the current one.
+primitiveConditionalNames :: Set.Set Text
+primitiveConditionalNames = Set.fromList
+  [ "if", "ifcase", "ifcat", "ifcsname", "ifdefined", "ifdim"
+  , "ifeof", "iffalse", "iffontchar", "ifhbox", "ifhmode"
+  , "ifincsname", "ifinner", "ifmmode", "ifnum", "ifodd", "iftrue"
+  , "ifvbox", "ifvmode", "ifvoid", "ifx" ]
 
+-- | Handle a conditional like @\\iftrue@: select the branch before
+-- @\\else@ (or @\\fi@) if the Bool is True, the else branch
+-- otherwise.  The unselected branch is skipped without expansion
+-- (as TeX does), keeping nested conditionals balanced.
+doIf :: PandocMonad m => Bool -> [Tok] -> LP m [Tok]
+doIf b ts = do
+  macros <- sMacros <$> getState
+  -- Conditionals defined with \newif (or \let from a primitive
+  -- conditional) expand to a single conditional token; TeX
+  -- recognizes these too when skipping conditional text.
+  let isConditionalMacro (Macro _ _ [] Nothing [Tok _ (CtrlSeq n) _]) =
+        n `Set.member` primitiveConditionalNames
+      isConditionalMacro _ = False
+  let conditionals = foldr
+        (\m s -> M.foldrWithKey
+           (\k v s' -> if isConditionalMacro v
+                          then Set.insert k s'
+                          else s')
+           s m)
+        primitiveConditionalNames macros
+  case splitConditional conditionals ts of
+    Just (ifToks, elseToks, rest) ->
+      return $ (if b then ifToks else elseToks) ++ rest
+    Nothing -> Prelude.fail "Could not parse conditional"
+
+-- | Split the tokens following a conditional into the tokens
+-- before @\\else@ (or @\\fi@), the tokens of the else branch (if
+-- any), and the tokens after the matching @\\fi@.  Returns Nothing
+-- if there is no matching @\\fi@.
+splitConditional :: Set.Set Text -> [Tok] -> Maybe ([Tok], [Tok], [Tok])
+splitConditional conditionals = goIf (0 :: Int) id
+ where
+  goIf _ _ [] = Nothing
+  goIf depth acc (t@(Tok _ (CtrlSeq name) _) : rest)
+    | name == "fi"
+    , depth == 0 = Just (acc [], [], rest)
+    | name == "fi" = goIf (depth - 1) (acc . (t:)) rest
+    | name == "else"
+    , depth == 0 = goElse (acc []) (0 :: Int) id rest
+    | name `Set.member` conditionals = goIf (depth + 1) (acc . (t:)) rest
+  goIf depth acc (t : rest) = goIf depth (acc . (t:)) rest
+  goElse _ _ _ [] = Nothing
+  goElse ifToks depth acc (t@(Tok _ (CtrlSeq name) _) : rest)
+    | name == "fi"
+    , depth == 0 = Just (ifToks, acc [], rest)
+    | name == "fi" = goElse ifToks (depth - 1) (acc . (t:)) rest
+    | name `Set.member` conditionals =
+        goElse ifToks (depth + 1) (acc . (t:)) rest
+  goElse ifToks depth acc (t : rest) = goElse ifToks depth (acc . (t:)) rest
+
+-- | Handle a LaTeX3 expandable evaluator (@\inteval@, @\fpeval@,
+-- ...): grab the braced argument (macros in it are expanded as it
+-- is consumed), evaluate it, and substitute the result.  If the
+-- expression cannot be evaluated, substitute the expression text
+-- itself and report it.
+handleEval :: PandocMonad m => (Text -> Maybe Text) -> [Tok] -> LP m [Tok]
+handleEval evaluator ts = do
+  lstate <- getState
+  res <- lift $ runParserT evalParser lstate "eval" $ TokStream False ts
+  case res of
+    Left _ -> Prelude.fail "Could not parse evaluator argument"
+    Right ts' -> return ts'
+ where
+  evalParser = do
+    pos <- getPosition
+    arg <- untokenize <$> braced
+    TokStream _ rest <- getInput
+    case evaluator arg of
+      Just result -> return $ tokenize pos result ++ rest
+      Nothing -> do
+        report $ SkippedContent ("evaluation of " <> arg) pos
+        return $ tokenize pos arg ++ rest
+
+-- | Evaluate an integer expression (@\inteval@): @+ - * / ( )@,
+-- with division rounding to the nearest integer (ties away from
+-- zero, as in eTeX's @\numexpr@).
+evalInteval :: Text -> Maybe Text
+evalInteval t = do
+  (n, rest) <- pIntExpr t
+  guard $ T.null (skipWs rest)
+  pure $ T.pack (show n)
+ where
+  pIntExpr s0 = pIntTerm s0 >>= addLoop
+  addLoop (acc, s) =
+    case T.uncons (skipWs s) of
+      Just ('+', s') -> do (y, s'') <- pIntTerm s'
+                           addLoop (acc + y, s'')
+      Just ('-', s') -> do (y, s'') <- pIntTerm s'
+                           addLoop (acc - y, s'')
+      _ -> Just (acc, s)
+  pIntTerm s0 = pIntFactor s0 >>= mulLoop
+  mulLoop (acc, s) =
+    case T.uncons (skipWs s) of
+      Just ('*', s') -> do (y, s'') <- pIntFactor s'
+                           mulLoop (acc * y, s'')
+      Just ('/', s') -> do (y, s'') <- pIntFactor s'
+                           guard $ y /= 0
+                           mulLoop (divRound acc y, s'')
+      _ -> Just (acc, s)
+  pIntFactor s0 =
+    case T.uncons (skipWs s0) of
+      Just ('-', s) -> do (x, s') <- pIntFactor s
+                          pure (negate x, s')
+      Just ('+', s) -> pIntFactor s
+      Just ('(', s) -> do
+        (x, s') <- pIntExpr s
+        case T.uncons (skipWs s') of
+          Just (')', s'') -> pure (x, s'')
+          _ -> Nothing
+      Just (c, _) | isDigit c ->
+        let (ds, s) = T.span isDigit (skipWs s0)
+        in do n <- safeRead ds
+              pure (n :: Integer, s)
+      _ -> Nothing
+  divRound a b =
+    let (q, r) = a `quotRem` b
+    in if 2 * abs r >= abs b
+          then q + signum a * signum b
+          else q
+
+-- | Evaluate a floating point expression (a practical subset of
+-- @\fpeval@): @+ - * / ^ ( )@ (also @**@ for @^@) and decimal
+-- literals.
+evalFpeval :: Text -> Maybe Text
+evalFpeval t0 = do
+  let t = T.replace "**" "^" t0
+  (x, rest) <- pFpExpr t
+  guard $ T.null (skipWs rest)
+  guard $ not (isNaN x || isInfinite x)
+  pure $ formatFp x
+ where
+  pFpExpr s0 = pFpTerm s0 >>= addLoop
+  addLoop (acc, s) =
+    case T.uncons (skipWs s) of
+      Just ('+', s') -> do (y, s'') <- pFpTerm s'
+                           addLoop (acc + y, s'')
+      Just ('-', s') -> do (y, s'') <- pFpTerm s'
+                           addLoop (acc - y, s'')
+      _ -> Just (acc, s)
+  pFpTerm s0 = pFpPow s0 >>= mulLoop
+  mulLoop (acc, s) =
+    case T.uncons (skipWs s) of
+      Just ('*', s') -> do (y, s'') <- pFpPow s'
+                           mulLoop (acc * y, s'')
+      Just ('/', s') -> do (y, s'') <- pFpPow s'
+                           mulLoop (acc / y, s'')
+      _ -> Just (acc, s)
+  pFpPow s0 = do
+    (x, s) <- pFpFactor s0
+    case T.uncons (skipWs s) of
+      Just ('^', s') -> do (y, s'') <- pFpPow s'  -- right-associative
+                           pure (x ** y, s'')
+      _ -> pure (x, s)
+  pFpFactor s0 =
+    case T.uncons (skipWs s0) of
+      Just ('-', s) -> do (x, s') <- pFpFactor s
+                          pure (negate x, s')
+      Just ('+', s) -> pFpFactor s
+      Just ('(', s) -> do
+        (x, s') <- pFpExpr s
+        case T.uncons (skipWs s') of
+          Just (')', s'') -> pure (x, s'')
+          _ -> Nothing
+      Just (c, _) | isLetter c ->
+        let (name, s1) = T.span isLetter (skipWs s0)
+        in case name of
+             "pi"  -> pure (pi, s1)
+             "deg" -> pure (pi / 180, s1)  -- one degree in radians
+             _ ->
+               case T.uncons (skipWs s1) of
+                 Just ('(', s2) -> do
+                   (args, s3) <- pFpArgs s2
+                   x <- applyFn name args
+                   pure (x, s3)
+                 _ -> do
+                   -- prefix application without parentheses,
+                   -- e.g. "sqrt 2":
+                   (y, s2) <- pFpFactor s1
+                   x <- applyFn name [y]
+                   pure (x, s2)
+      Just (c, _) | isDigit c || c == '.' ->
+        let s = skipWs s0
+            (ds, s') = T.span (\d -> isDigit d || d == '.') s
+            (expt, s'') = case T.uncons s' of
+                            Just (e, r) | e == 'e' || e == 'E' ->
+                              let (sign, r') =
+                                    case T.uncons r of
+                                      Just (sg, rr) | sg == '+' || sg == '-'
+                                        -> (T.singleton sg, rr)
+                                      _ -> ("", r)
+                                  (eds, r'') = T.span isDigit r'
+                              in if T.null eds
+                                    then ("", s')
+                                    else ("e" <> sign <> eds, r'')
+                            _ -> ("", s')
+        in do x <- safeRead (fixup ds <> expt)
+              pure (x :: Double, s'')
+      _ -> Nothing
+  fixup ds -- make the literal readable for Haskell's 'read'
+    | "." `T.isPrefixOf` ds = "0" <> fixup' ds
+    | otherwise = fixup' ds
+  fixup' ds
+    | "." `T.isSuffixOf` ds = ds <> "0"
+    | otherwise = ds
+  -- comma-separated function arguments, ending with ')':
+  pFpArgs s0 = do
+    (x, s) <- pFpExpr s0
+    case T.uncons (skipWs s) of
+      Just (',', s') -> do (xs, s'') <- pFpArgs s'
+                           pure (x : xs, s'')
+      Just (')', s') -> pure ([x], s')
+      _ -> Nothing
+  applyFn name args =
+    case (lookup name unaryFns, args) of
+      (Just f, [x]) -> Just (f x)
+      _ ->
+        case (name, args) of
+          ("max", _:_) -> Just (maximum args)
+          ("min", _:_) -> Just (minimum args)
+          ("atan", [x, y]) -> Just (atan2 x y)
+          ("atand", [x, y]) -> Just (unrad (atan2 x y))
+          ("round", _) -> rounder (fromInteger . round) args
+          ("floor", _) -> rounder (fromInteger . floor) args
+          ("ceil", _) -> rounder (fromInteger . ceiling) args
+          ("trunc", _) -> rounder (fromInteger . truncate) args
+          _ -> Nothing
+  -- rounding functions take an optional number of decimal places:
+  rounder f [x] = Just (f x)
+  rounder f [x, n] | n == fromInteger (round n) =
+    let m = 10 ^^ (round n :: Integer)
+        -- round to 16 significant digits first (like l3fp), so
+        -- that e.g. round(2.345,2) gives 2.34, not 2.35:
+        y = read (showEFloat (Just 15) (x * m) "") :: Double
+    in Just (f y / m)
+  rounder _ _ = Nothing
+  rad x = x * pi / 180
+  unrad x = x * 180 / pi
+  unaryFns :: [(Text, Double -> Double)]
+  unaryFns =
+    [ ("abs", abs), ("sign", signum), ("sqrt", sqrt)
+    , ("exp", exp), ("ln", log)
+    , ("fact", \x -> if x >= 0 && x == fromInteger (round x) && x < 171
+                        then fromInteger (product [1 .. round x])
+                        else 0 / 0)
+    , ("sin", sin), ("cos", cos), ("tan", tan)
+    , ("cot", recip . tan), ("sec", recip . cos), ("csc", recip . sin)
+    , ("asin", asin), ("acos", acos), ("atan", atan)
+    , ("acot", atan . recip), ("asec", acos . recip), ("acsc", asin . recip)
+    , ("sind", sin . rad), ("cosd", cos . rad), ("tand", tan . rad)
+    , ("cotd", recip . tan . rad), ("secd", recip . cos . rad)
+    , ("cscd", recip . sin . rad)
+    , ("asind", unrad . asin), ("acosd", unrad . acos)
+    , ("atand", unrad . atan), ("acotd", unrad . atan . recip)
+    , ("asecd", unrad . acos . recip), ("acscd", unrad . asin . recip)
+    ]
+
+formatFp :: Double -> Text
+formatFp x0
+  | x == fromInteger r && abs x < 1e16 = T.pack (show r)
+  | otherwise = T.pack (trimZeros (showFFloat Nothing x ""))
+ where
+  -- l3fp computes with 16 significant decimal digits; round to
+  -- that precision so that e.g. 0.1 + 0.2 yields 0.3.
+  x = read (showEFloat (Just 15) x0 "") :: Double
+  r = round x
+  trimZeros s
+    | '.' `elem` s = case dropWhileEnd (== '0') s of
+                       s' | "." `isSuffixOf` s' -> take (length s' - 1) s'
+                          | otherwise -> s'
+    | otherwise = s
+
+skipWs :: Text -> Text
+skipWs = T.dropWhile (\c -> c == ' ' || c == '\t' || c == '\n' || c == '\r')
+
 startsWithAlphaNum :: Text -> Bool
 startsWithAlphaNum t =
   case T.uncons t of
@@ -876,10 +1429,7 @@
 retokenizeComment :: PandocMonad m => LP m ()
 retokenizeComment = (do
   Tok pos Comment txt <- satisfyTok isCommentTok
-  let updPos (Tok pos' toktype' txt') =
-        Tok (incSourceColumn (incSourceLine pos' (sourceLine pos - 1))
-             (sourceColumn pos)) toktype' txt'
-  let newtoks = map updPos $ tokenize pos $ T.tail txt
+  let newtoks = tokenize (incSourceColumn pos 1) $ T.tail txt
   TokStream macrosExpanded ts <- getInput
   setInput $ TokStream macrosExpanded ((Tok pos Symbol "%" : newtoks) ++ ts))
     <|> return ()
@@ -898,6 +1448,168 @@
   concat <$> manyTill ((snd <$> withRaw (try braced)) <|> count 1 anyTok)
                       (symbol ']')
 
+-- | Tokens between opening and closing delimiter tokens (which are
+-- compared by token type and text, ignoring position).  Nested
+-- delimiter pairs are balanced (when the delimiters differ), and
+-- braced groups are skipped, so delimiters inside braces don't count.
+delimitedToks :: PandocMonad m => Tok -> Tok -> LP m [Tok]
+delimitedToks open close = matchesTok open *> go (1 :: Int)
+ where
+  matchesTok (Tok _ toktype txt) =
+    satisfyTok (\(Tok _ toktype' txt') -> toktype == toktype' && txt == txt')
+  go n = (do ts <- snd <$> withRaw (try braced)
+             (ts ++) <$> go n)
+     <|> (do t <- matchesTok close
+             if n == 1
+                then return []
+                else (t:) <$> go (n - 1))
+     <|> (do t <- matchesTok open
+             (t:) <$> go (n + 1))
+     <|> (do t <- anyTok
+             (t:) <$> go n)
+
+-- | A verbatim argument (xparse @v@ specifier): either a braced
+-- group or tokens between two identical delimiter characters.
+-- The result is a single Word token containing the raw text, so
+-- that its contents are not reinterpreted when substituted.
+verbatimArg :: PandocMonad m => LP m [Tok]
+verbatimArg = try $ do
+  optional sp
+  toks <- braced <|> delimited
+  case toks of
+    [] -> pure []
+    Tok pos _ _ : _ -> pure [Tok pos Word (untokenize toks)]
+ where
+  delimited = do
+    Tok _ Symbol t <- anySymbol
+    marker <- case T.uncons t of
+                Just (c, ts) | T.null ts -> return c
+                _            -> mzero
+    manyTill (notFollowedBy newlineTok >> verbTok marker) (symbol marker)
+
+-- | Convert a verbatim environment body (xparse @c@ specifier) to
+-- tokens for substitution.  The body is typeset verbatim by LaTeX,
+-- with each space rendered as the character in slot 32 of the
+-- current font (the visible space U+2423 in typewriter fonts) and
+-- each source line on its own line; we emulate this by making each
+-- line a single Word token (so contents are not reinterpreted),
+-- with spaces replaced by U+2423 and lines separated by @\\\\@.
+-- Tabs are kept as-is (LaTeX typesets the raw tab character, not a
+-- visible space).  Note that the typewriter font is not automatic:
+-- it comes from a @\\ttfamily@ or similar in the environment
+-- definition.  If the first parameter is True, leading and trailing
+-- blank lines are trimmed.
+verbatimBodyToks :: Bool -> [Tok] -> [Tok]
+verbatimBodyToks _ [] = []
+verbatimBodyToks doTrim toks@(Tok pos _ _ : _) =
+  intercalate [Tok pos (CtrlSeq "\\") "\\\\"] (map lineToks ls)
+ where
+  lineToks l
+    | T.null l = []
+    | otherwise = [Tok pos Word (T.map toVisibleSpace l)]
+  toVisibleSpace c = if c == ' ' then '\x2423' else c
+  ls = (if doTrim
+           then dropWhileEnd isBlankLine . dropWhile isBlankLine
+           else id) $ T.lines (untokenize toks)
+  isBlankLine = T.all (\c -> c == ' ' || c == '\t')
+
+-- | Apply xparse argument processors (from the @>{...}@ modifier)
+-- to a grabbed argument.  Processors are applied from right to left
+-- (i.e., the one nearest the argument specifier first).
+applyArgProcessors :: PandocMonad m => [[Tok]] -> [Tok] -> LP m [Tok]
+applyArgProcessors [] x = pure x
+applyArgProcessors (p:ps) x = applyArgProcessors ps x >>= applyArgProcessor p
+
+applyArgProcessor :: PandocMonad m => [Tok] -> [Tok] -> LP m [Tok]
+applyArgProcessor proc x =
+  case dropWhile spaceLike proc of
+    Tok _ (CtrlSeq "TrimSpaces") _ : _ -> pure $ trimSpaceToks x
+    Tok pos (CtrlSeq "ReverseBoolean") _ : _ ->
+      pure $ case filter (not . spaceLike) x of
+        [Tok _ (CtrlSeq "BooleanTrue") _]  ->
+          [Tok pos (CtrlSeq "BooleanFalse") "\\BooleanFalse "]
+        [Tok _ (CtrlSeq "BooleanFalse") _] ->
+          [Tok pos (CtrlSeq "BooleanTrue") "\\BooleanTrue "]
+        _ -> x
+    Tok pos (CtrlSeq "SplitArgument") _ : ts
+      | Just (numtoks, ts') <- tokGroup ts
+      , Just numparts <- safeRead (untokenize numtoks)
+      , Just (delimtoks, _) <- tokGroup ts'
+      , (d : _) <- dropWhile spaceLike delimtoks ->
+        let missing = [Tok pos (CtrlSeq "NoValue") "\\NoValue "]
+            pieces = take (numparts + 1) $
+                       map trimSpaceToks (splitToksOn d x) ++ repeat missing
+        in pure $ concatMap (braceGroup pos) pieces
+    Tok pos (CtrlSeq "SplitList") _ : ts
+      | Just (delimtoks, _) <- tokGroup ts
+      , (d : _) <- dropWhile spaceLike delimtoks ->
+        pure $ concatMap (braceGroup pos) (map trimSpaceToks (splitToksOn d x))
+    Tok pos _ _ : _ -> do
+      -- unknown (or malformed) processor: keep the argument unprocessed
+      report $ SkippedContent ("processor " <> untokenize proc) pos
+      pure x
+    [] -> pure x
+ where
+  spaceLike = tokTypeIn [Spaces, Newline, Comment]
+  braceGroup pos ts = Tok pos Symbol "{" : ts ++ [Tok pos Symbol "}"]
+
+-- | Remove space tokens at both ends of a token list.
+trimSpaceToks :: [Tok] -> [Tok]
+trimSpaceToks = dropWhile isSp . dropWhileEnd isSp
+  where isSp = tokTypeIn [Spaces, Newline]
+
+-- | Parse a braced group (or a single token) from the beginning of
+-- a token list, skipping leading spaces; return the group's
+-- contents and the remaining tokens.
+tokGroup :: [Tok] -> Maybe ([Tok], [Tok])
+tokGroup ts =
+  case dropWhile (tokTypeIn [Spaces, Newline, Comment]) ts of
+    Tok _ Symbol "{" : rest -> go (1 :: Int) [] rest
+    t : rest -> Just ([t], rest)
+    [] -> Nothing
+ where
+  go _ _ [] = Nothing
+  go depth acc (t : rest)
+    | isSym "{" t = go (depth + 1) (t : acc) rest
+    | isSym "}" t = if depth == 1
+                       then Just (reverse acc, rest)
+                       else go (depth - 1) (t : acc) rest
+    | otherwise = go depth (t : acc) rest
+  isSym s (Tok _ Symbol s') = s == s'
+  isSym _ _ = False
+
+-- | Split a token list at each depth-0 occurrence of the delimiter
+-- token (compared by token type and text).
+splitToksOn :: Tok -> [Tok] -> [[Tok]]
+splitToksOn (Tok _ dtype dtxt) = go (0 :: Int) []
+ where
+  go _ acc [] = [reverse acc]
+  go depth acc (t@(Tok _ toktype txt) : rest)
+    | depth == 0, toktype == dtype, txt == dtxt = reverse acc : go 0 [] rest
+    | otherwise =
+        let depth' = case t of
+                       Tok _ Symbol "{" -> depth + 1
+                       Tok _ Symbol "}" -> max 0 (depth - 1)
+                       _ -> depth
+        in go depth' (t : acc) rest
+
+-- | Any token, but if the token contains @stopchar@, it is split
+-- so that the part before @stopchar@ is returned and @stopchar@
+-- itself (plus what follows) is left in the input.  Used for
+-- parsing verbatim text delimited by @stopchar@.
+verbTok :: PandocMonad m => Char -> LP m Tok
+verbTok stopchar = do
+  t@(Tok pos toktype txt) <- anyTok
+  case T.findIndex (== stopchar) txt of
+       Nothing -> return t
+       Just i  -> do
+         let (t1, t2) = T.splitAt i txt
+         TokStream macrosExpanded inp <- getInput
+         setInput $ TokStream macrosExpanded
+                  $ Tok (incSourceColumn pos i) Symbol (T.singleton stopchar)
+                  : tokenize (incSourceColumn pos (i + 1)) (T.drop 1 t2) ++ inp
+         return $ Tok pos toktype t1
+
 parenWrapped :: PandocMonad m => Monoid a => LP m a -> LP m a
 parenWrapped parser = try $ do
   symbol '('
@@ -936,23 +1648,16 @@
 
 withRaw :: PandocMonad m => LP m a -> LP m (a, [Tok])
 withRaw parser = do
-  rawTokensMap <- sRawTokens <$> getState
-  let key = case IntMap.lookupMax rawTokensMap of
-               Nothing     -> 0
-               Just (n,_)  -> n + 1
-  -- insert empty list at key
-  updateState $ \st -> st{ sRawTokens =
-                             IntMap.insert key [] $ sRawTokens st }
+  startCount <- sRawTokenCount <$> getState
+  updateState $ \st -> st{ sRawScopes = sRawScopes st + 1 }
   result <- parser
-  mbRevToks <- IntMap.lookup key . sRawTokens <$> getState
-  raw <- case mbRevToks of
-           Just revtoks -> do
-             updateState $ \st -> st{ sRawTokens =
-                                        IntMap.delete key $ sRawTokens st}
-             return $ reverse revtoks
-           Nothing      ->
-             throwError $ PandocShouldNeverHappenError $
-                "sRawTokens has nothing at key " <> T.pack (show key)
+  st <- getState
+  let raw = reverse $ take (sRawTokenCount st - startCount) (sRawTokens st)
+  setState $ if sRawScopes st <= 1
+                then st{ sRawScopes = 0
+                       , sRawTokens = []
+                       , sRawTokenCount = 0 }
+                else st{ sRawScopes = sRawScopes st - 1 }
   return (result, raw)
 
 keyval :: PandocMonad m => LP m (Text, Text)
diff --git a/src/Text/Pandoc/Readers/LaTeX/Table.hs b/src/Text/Pandoc/Readers/LaTeX/Table.hs
--- a/src/Text/Pandoc/Readers/LaTeX/Table.hs
+++ b/src/Text/Pandoc/Readers/LaTeX/Table.hs
@@ -15,7 +15,7 @@
 import qualified Data.Text as T
 import Control.Applicative ((<|>), optional, many)
 import Control.Monad (when, void)
-import Text.Pandoc.Shared (safeRead, trim)
+import Text.Pandoc.Shared (safeRead, trim, compactifyTable)
 import Text.Pandoc.Logging (LogMessage(SkippedContent))
 import Text.Pandoc.Walk (walkM)
 import Text.Pandoc.Parsing hiding (blankline, many, mathDisplay, mathInline,
@@ -190,7 +190,7 @@
     -- The parsing of empty cells is important in LaTeX, especially when dealing
     -- with multirow/multicolumn. See #6603.
     parseEmptyCell = spaces $> emptyCell
-    parseSimpleCell = simpleCell <$> (plainify . mconcat <$> many block)
+    parseSimpleCell = simpleCell . mconcat <$> many block
 
 
 cellAlignment :: PandocMonad m => LP m Alignment
@@ -205,11 +205,6 @@
         "*" -> AlignDefault
         _   -> AlignDefault
 
-plainify :: Blocks -> Blocks
-plainify bs = case toList bs of
-                [Para ils] -> plain (fromList ils)
-                _          -> bs
-
 multirowCell :: PandocMonad m => LP m Blocks -> LP m Cell
 multirowCell block = controlSeq "multirow" >> do
   -- Full prototype for \multirow macro is:
@@ -221,7 +216,7 @@
   _ <- optional $ symbol '[' *> manyTill anyTok (symbol ']')  -- bigstrut-related
   _ <- symbol '{' *> manyTill anyTok (symbol '}')             -- Cell width
   _ <- optional $ symbol '[' *> manyTill anyTok (symbol ']')  -- Length used for fine-tuning
-  content <- symbol '{' *> (plainify . mconcat <$> many block) <* symbol '}'
+  content <- symbol '{' *> (mconcat <$> many block) <* symbol '}'
   return $ cell AlignDefault (RowSpan nrows) (ColSpan 1) content
 
 multicolumnCell :: PandocMonad m => LP m Blocks -> LP m Cell
@@ -230,7 +225,7 @@
   alignment <- symbol '{' *> cellAlignment <* symbol '}'
 
   let singleCell = do
-        content <- plainify . mconcat <$> many block
+        content <- mconcat <$> many block
         return $ cell alignment (RowSpan 1) (ColSpan span') content
 
   -- Two possible contents: either a \multirow cell, or content.
@@ -357,7 +352,8 @@
   let th  = fixTableHead $ TableHead nullAttr header'
   let tbs = [fixTableBody $ TableBody nullAttr 0 [] rows]
   let tf  = TableFoot nullAttr []
-  return $ table emptyCaption (zip aligns widths) th tbs tf
+  return $ compactifyTable
+         $ table emptyCaption (zip aligns widths) th tbs tf
 
 addTableCaption :: PandocMonad m => Blocks -> LP m Blocks
 addTableCaption = walkM go
diff --git a/src/Text/Pandoc/Readers/Man.hs b/src/Text/Pandoc/Readers/Man.hs
--- a/src/Text/Pandoc/Readers/Man.hs
+++ b/src/Text/Pandoc/Readers/Man.hs
@@ -33,7 +33,7 @@
 import qualified Text.Pandoc.Parsing as P
 import qualified Data.Foldable as Foldable
 import qualified Data.Set as Set
-import Text.Pandoc.Shared (extractSpaces)
+import Text.Pandoc.Shared (extractSpaces, compactifyTable)
 
 data ManState = ManState { readerOptions   :: ReaderOptions
                          , manLogMessages  :: []LogMessage
@@ -129,7 +129,8 @@
       let widths = if isPlainTable
                       then repeat ColWidthDefault
                       else repeat $ ColWidth (1.0 / fromIntegral (length alignments))
-      return $ B.table B.emptyCaption (zip alignments widths)
+      return $ compactifyTable
+             $ B.table B.emptyCaption (zip alignments widths)
                   (TableHead nullAttr $ toHeaderRow headerRow)
                   [TableBody nullAttr 0 [] $ map toRow bodyRows]
                   (TableFoot nullAttr [])) <|> fallback pos
diff --git a/src/Text/Pandoc/Readers/Markdown.hs b/src/Text/Pandoc/Readers/Markdown.hs
--- a/src/Text/Pandoc/Readers/Markdown.hs
+++ b/src/Text/Pandoc/Readers/Markdown.hs
@@ -23,7 +23,7 @@
 import Control.Monad
 import Control.Monad.Except (throwError)
 import qualified Data.Bifunctor as Bifunctor
-import Data.Char (isAlphaNum, isPunctuation, isSpace)
+import Data.Char (isAlphaNum, isDigit, isLetter, isPunctuation, isSpace)
 import Data.List (transpose, elemIndex, sortOn)
 import qualified Data.List as L
 import qualified Data.Map as M
@@ -53,7 +53,8 @@
 import Text.Pandoc.Readers.HTML.TagCategories (voidTags)
 import Text.Pandoc.Readers.LaTeX (applyMacros, rawLaTeXBlock, rawLaTeXInline)
 import Text.Pandoc.Shared
-import Text.Pandoc.URI (escapeURI, isURI, pBase64DataURI)
+import Text.Pandoc.URI (escapeURI, pBase64DataURI)
+import Network.URI (isURI)
 import Text.Pandoc.XML (fromEntities)
 import Text.Pandoc.Readers.Metadata (yamlBsToMeta, yamlBsToRefs, yamlMetaBlock)
 -- import Debug.Trace (traceShowId)
@@ -412,7 +413,7 @@
   char c
   notFollowedBy spaces
   let pEnder = try $ char c >> notFollowedBy (satisfy isAlphaNum)
-  let regChunk = many1Char (noneOf ['\\','\n','&',c]) <|> litChar
+  let regChunk = takeWhile1P (`notElem` ['\\','\n','&',c]) <|> litChar
   let nestedChunk = (\x -> (c `T.cons` x) `T.snoc` c) <$> quotedTitle c
   T.unwords . T.words . T.concat <$> manyTill (nestedChunk <|> regChunk) pEnder
 
@@ -660,7 +661,7 @@
 identifierAttr :: PandocMonad m => MarkdownParser m (Attr -> Attr)
 identifierAttr = try $ do
   char '#'
-  result <- T.pack <$> many1 (alphaNum <|> oneOf "-_:.") -- see #7920
+  result <- takeWhile1P (\x -> isAlphaNum x || x `elem` ("-_:." :: [Char])) -- see #7920
   return $ \(_,cs,kvs) -> (result,cs,kvs)
 
 classAttr :: PandocMonad m => MarkdownParser m (Attr -> Attr)
@@ -814,12 +815,13 @@
 
 blockQuote :: PandocMonad m => MarkdownParser m (F Blocks)
 blockQuote = do
+  pos <- getPosition
   raw <- emailBlockQuote
   (mbAlert, raw') <-
     (do guardEnabled Ext_alerts
         case raw of
           (t:ts) | "[!" `T.isPrefixOf` t ->
-              case T.strip t of
+              case T.toUpper (T.strip t) of
                 "[!TIP]" -> pure (Just "tip", ts)
                 "[!WARNING]" -> pure (Just "warning", ts)
                 "[!IMPORTANT]" -> pure (Just "important", ts)
@@ -829,12 +831,13 @@
           _ -> pure (Nothing, raw))
       <|> pure (Nothing, raw)
   -- parse the extracted block, which may contain various block elements:
-  contents <- parseFromString' parseBlocks $ T.intercalate "\n" raw' <> "\n\n"
+  contents <- parseFromString' (setPosition pos >> parseBlocks)
+               $ T.intercalate "\n" raw' <> "\n\n"
   return $
     case mbAlert of
       Nothing -> B.blockQuote <$> contents
       Just alert ->
-        (B.divWith ("", [alert], [])
+        (B.divWith ("", ["alert", alert], [])
           . (B.divWith ("", ["title"], []) (B.para (B.str (T.toTitle alert))) <>))
            <$> contents
 
@@ -859,7 +862,7 @@
   skipNonindentSpaces
   notFollowedBy $ string "p." >> spaceChar >> digit  -- page number
   (do guardDisabled Ext_fancy_lists
-      start <- many1Char digit >>= safeRead
+      start <- takeWhile1P isDigit >>= safeRead
       char '.'
       gobbleSpaces 1 <|> () <$ lookAhead newline
       optional $ try (gobbleAtMostSpaces 3 >> notFollowedBy spaceChar)
@@ -971,11 +974,12 @@
   state <- getState
   let oldContext = stateParserContext state
   setState $ state {stateParserContext = ListItemState}
+  pos <- getPosition
   (first, continuationIndent) <- rawListItem fourSpaceRule start
   continuations <- many (listContinuation continuationIndent)
   -- parse the extracted block, which may contain various block elements:
   let raw = T.concat (first:continuations)
-  contents <- parseFromString' parseBlocks raw
+  contents <- parseFromString' (setPosition pos >> parseBlocks) raw
   updateState (\st -> st {stateParserContext = oldContext})
   exts <- getOption readerExtensions
   return $ B.fromList . taskListItemFromAscii exts . B.toList <$> contents
@@ -1016,8 +1020,9 @@
 
 definitionListItem :: PandocMonad m => MarkdownParser m (F (Inlines, [Blocks]))
 definitionListItem = try $ do
+  pos <- getPosition
   rawLine' <- anyLine
-  term <- parseFromString' (trimInlinesF <$> inlines) rawLine'
+  term <- parseFromString' (setPosition pos >> (trimInlinesF <$> inlines)) rawLine'
   isTight <- (False <$ blanklines) <|> pure True
   fourSpaceRule <- (True <$ guardEnabled Ext_four_space_rule) <|> pure False
   contents <- many1 $ listItem fourSpaceRule defListStart
@@ -1223,8 +1228,9 @@
 lineBlock = do
   guardEnabled Ext_line_blocks
   try $ do
+    pos <- getPosition
     lines' <- lineBlockLines >>=
-              mapM (parseFromString' (trimInlinesF <$> inlines))
+              mapM (parseFromString' (setPosition pos >> (trimInlinesF <$> inlines)))
     return $ B.lineBlock <$> sequence lines'
 
 --
@@ -1311,8 +1317,9 @@
 tableLine :: PandocMonad m
           => [Int]
           -> MarkdownParser m (F [Blocks])
-tableLine indices = rawTableLine indices >>=
-  fmap sequence . mapM (parseFromString' (mconcat <$> many plain))
+tableLine indices = do
+  raw <- rawTableLine indices
+  sequence <$> mapM (parseFromString' (mconcat <$> many plain)) raw
 
 -- Parse a multiline table row and return a list of blocks (columns).
 multilineRow :: PandocMonad m
@@ -1397,7 +1404,7 @@
                     then []
                     else map (T.unlines . map trim) rawHeadsList
   heads <- fmap sequence $
-            mapM (parseFromString' (mconcat <$> many plain).trim) rawHeads
+            mapM (parseFromString' (mconcat <$> many plain) . trim) rawHeads
   return (fmap (:[]) heads, aligns, indices')
 
 -- Parse a grid table:  starts with row of '-' on top, then header
@@ -1533,7 +1540,7 @@
   return $ do
     caption' <- caption
     (TableComponents _attr _capt colspecs th tb tf) <- tableComponents
-    return $ B.tableWith attr
+    return $ compactifyTable $ B.tableWith attr
                 (B.simpleCaption $ B.plain caption') colspecs th tb tf
 
 --
@@ -1647,8 +1654,8 @@
   skipSpaces
   result <- trim . T.concat
         <$> manyTill
-              (   many1Char (noneOf "`\n")
-              <|> many1Char (char '`')
+              (   takeWhile1P (\c -> c /= '`' && c /= '\n')
+              <|> takeWhile1P (== '`')
               <|> (char '\n'
                     >> notFollowedBy (inList >> listStart)
                     >> notFollowedBy' blankline
@@ -1683,7 +1690,7 @@
   guardDisabled Ext_intraword_underscores
     <|> guard (c == '*')
     <|> (guard =<< notAfterString)
-  cs <- many1Char (char c)
+  cs <- takeWhile1P (== c)
   (return (B.str cs) <>) <$> whitespace
     <|>
         case T.length cs of
@@ -1783,7 +1790,7 @@
         mmdShortSubscript = try $ do
           guardEnabled Ext_short_subsuperscripts
           char '~'
-          result <- T.pack <$> many1 alphaNum
+          result <- takeWhile1P isAlphaNum
           return $ return $ B.str result
 
 whitespace :: PandocMonad m => MarkdownParser m (F Inlines)
@@ -1798,7 +1805,7 @@
 str :: PandocMonad m => MarkdownParser m (F Inlines)
 str = do
   !result <- mconcat <$> many1
-             ( T.pack <$> (many1 alphaNum)
+             ( takeWhile1P isAlphaNum
               <|> "." <$ try (char '.' <* notFollowedBy (char '.')) )
   updateLastStrPos
   (do guardEnabled Ext_smart
@@ -1862,7 +1869,8 @@
         try parenthesizedChars
           <|> (notFollowedBy (oneOf "\n\r )") >> litChar)
           <|> (lookAhead (oneOf "\n\r") >> notFollowedBy linkTitle' >> litChar)
-          <|> try (many1Char spaceChar <* notFollowedBy (oneOf "\"')"))
+          <|> try (takeWhile1P (\x -> x == ' ' || x == '\t')
+                    <* notFollowedBy (oneOf "\"')"))
   let sourceURL = T.unwords . T.words . T.concat <$> many urlChunk
   src <- try (litBetween '<' '>') <|> try base64DataURI <|> sourceURL
   tit <- option "" linkTitle'
@@ -1872,13 +1880,21 @@
 
 base64DataURI :: PandocMonad m => ParsecT Sources s m Text
 base64DataURI = do
-  Sources ((pos, txt):rest) <- getInput
-  let r = A.parse (fst <$> A.match pBase64DataURI) txt
-  case r of
-    A.Done remaining consumed -> do
-      let pos' = incSourceColumn pos (T.length consumed)
-      setInput $ Sources ((pos', remaining):rest)
-      return consumed
+  inp <- getInput
+  case inp of
+    Sources ((pos, txt):rest) ->
+      -- feed mempty to force a result if attoparsec returns Partial:
+      case A.feed (A.parse (fst <$> A.match pBase64DataURI) txt) mempty of
+        A.Done remaining consumed -> do
+          -- keep the chunk's position unchanged: chunk positions are
+          -- invariant (see uncons in T.P.Sources), and withRaw's
+          -- sourcesDifference relies on this.  Instead, advance parsec's
+          -- own position (a data URI cannot contain newlines):
+          setInput $ Sources ((pos, remaining):rest)
+          curPos <- getPosition
+          setPosition $ incSourceColumn curPos (T.length consumed)
+          return consumed
+        _ -> mzero
     _ -> mzero
 
 linkTitle :: PandocMonad m => MarkdownParser m Text
@@ -2021,6 +2037,20 @@
 bareURL = do
   guardEnabled Ext_autolink_bare_uris
   getState >>= guard . stateAllowLinks
+  -- Fast rejection: a bare URI must contain ':' (after the scheme) and
+  -- an email address '@', in both cases before any whitespace, since
+  -- neither can contain whitespace.  So if the whitespace-delimited
+  -- token ahead contains neither ':' nor '@', both parsers must fail.
+  -- (If the token extends beyond the current input chunk, we skip the
+  -- check and just try the parsers.)
+  inp <- getInput
+  case unSources inp of
+    (_,t):_ ->
+      case T.find (\c -> isSpace c || c == ':' || c == '@') t of
+        Just ':' -> return ()
+        Just '@' -> return ()
+        _ -> mzero
+    [] -> return ()
   try $ do
     (cls, (orig, src)) <- (("uri",) <$> uri) <|> (("email",) <$> emailAddress)
     notFollowedBy $ try $ spaces >> htmlTag (~== TagClose ("a" :: Text))
@@ -2050,7 +2080,9 @@
       isFragment = T.take 1 path == "#"
       path' = T.unpack path
       isAbsolutePath = Posix.isAbsolute path' || Windows.isAbsolute path'
-   in if T.null path || isFragment || isAbsolutePath || isURI path
+   in if T.null path || isFragment || isAbsolutePath || isURI (T.unpack path)
+         -- note: we use Network.URI.isURI instead of T.P.URI.isURI
+         -- because it doesn't whitelist schemes; see #11858.
          then path
          else
            case takeDirectory fp of
@@ -2121,7 +2153,7 @@
 rawConTeXtEnvironment = try $ do
   string "\\start"
   completion <- inBrackets (letter <|> digit <|> spaceChar)
-               <|> many1Char letter
+               <|> takeWhile1P isLetter
   !contents <- manyTill (rawConTeXtEnvironment <|> countChar 1 anyChar)
                        (try $ string "\\stop" >> textStr completion)
   return $! "\\start" <> completion <> T.concat contents <> "\\stop" <> completion
@@ -2174,7 +2206,9 @@
     string ":::"
     skipMany (char ':')
     skipMany spaceChar
-    attribs <- attributes <|> ((\x -> ("",[x],[])) <$> many1Char nonspaceChar)
+    attribs <- attributes <|> ((\x -> ("",[x],[])) <$>
+                  takeWhile1P (\x -> x /= ' ' && x /= '\t' &&
+                                     x /= '\n' && x /= '\r'))
     skipMany spaceChar
     skipMany (char ':')
     blankline
@@ -2219,7 +2253,7 @@
   guardEnabled Ext_emoji
   try $ do
     char ':'
-    emojikey <- many1Char (alphaNum <|> oneOf "_+-")
+    emojikey <- takeWhile1P (\x -> isAlphaNum x || x `elem` ("_+-" :: [Char]))
     char ':'
     case emojiToInline emojikey of
       Just i -> return (return $ B.singleton i)
diff --git a/src/Text/Pandoc/Readers/Mdoc.hs b/src/Text/Pandoc/Readers/Mdoc.hs
--- a/src/Text/Pandoc/Readers/Mdoc.hs
+++ b/src/Text/Pandoc/Readers/Mdoc.hs
@@ -42,7 +42,7 @@
 import Text.Parsec (modifyState)
 import qualified Text.Pandoc.Parsing as P
 import qualified Data.Foldable as Foldable
-import Text.Pandoc.Shared (stringify)
+import Text.Pandoc.Shared (stringify, stringifyInlines)
 
 #if !MIN_VERSION_base(4,19,0)
 unsnoc :: [a] -> Maybe ([a], a)
@@ -270,7 +270,7 @@
   (Macro m _) <- lookAhead $ macro "Sh" <|> macro "Ss"
   txt <- lineEnclosure m id
   let lvl = if m == "Sh" then 1 else 2
-  when (lvl == 1) $ modifyState $ \s -> s{currentSection = (shToSectionMode . stringify) txt}
+  when (lvl == 1) $ modifyState $ \s -> s{currentSection = (shToSectionMode . stringifyInlines) txt}
   return $ B.header lvl txt
 
 parseNameSection :: PandocMonad m => MdocParser m Blocks
@@ -474,7 +474,7 @@
       return $ openDelim <> xform inlines <> closeDelim
 
 codeLikeInline' :: PandocMonad m => T.Text -> T.Text -> MdocParser m Inlines
-codeLikeInline' nm cl = simpleInline nm (eliminateEmpty (B.codeWith (cls cl) . stringify))
+codeLikeInline' nm cl = simpleInline nm (eliminateEmpty (B.codeWith (cls cl) . stringifyInlines))
 
 codeLikeInline :: PandocMonad m => T.Text -> MdocParser m Inlines
 codeLikeInline nm = codeLikeInline' nm nm
@@ -671,7 +671,7 @@
 parseMt :: PandocMonad m => MdocParser m Inlines
 parseMt = simpleInline "Mt" mailto
   where mailto x | null x = B.link ("mailto:~") "" "~"
-                 | otherwise = B.link ("mailto:" <> stringify x) "" x
+                 | otherwise = B.link ("mailto:" <> stringifyInlines x) "" x
 
 parsePa :: PandocMonad m => MdocParser m Inlines
 parsePa = simpleInline "Pa" p
@@ -713,7 +713,7 @@
 parseAr :: PandocMonad m => MdocParser m Inlines
 parseAr = simpleInline "Ar" ar
   where ar x | null x = B.codeWith (cls "variable") "file ..."
-             | otherwise = B.codeWith (cls "variable") $ stringify x
+             | otherwise = B.codeWith (cls "variable") $ stringifyInlines x
 
 
 parseCm :: PandocMonad m => MdocParser m Inlines
@@ -729,7 +729,7 @@
 parseCd = codeLikeInline "Cd"
 
 parseQl :: PandocMonad m => MdocParser m Inlines
-parseQl = lineEnclosure "Ql" $ B.codeWith (cls "Ql") . stringify
+parseQl = lineEnclosure "Ql" $ B.codeWith (cls "Ql") . stringifyInlines
 
 parseDq :: PandocMonad m => MdocParser m Inlines
 parseDq = lineEnclosure "Dq" B.doubleQuoted
@@ -785,7 +785,7 @@
 parseDl :: PandocMonad m => MdocParser m Blocks
 parseDl = do
   inner <- lineEnclosure "Dl" id
-  return $ B.codeBlock (stringify inner)
+  return $ B.codeBlock (stringifyInlines inner)
 
 parseD1 :: PandocMonad m => MdocParser m Blocks
 parseD1 = do
@@ -801,7 +801,7 @@
     (Just nm, x) | null x ->
       op <> ok nm <> cl
     (_, x) ->
-      op <> (ok . stringify) x <> cl
+      op <> (ok . stringifyInlines) x <> cl
   where
     ok = B.codeWith (cls "Nm")
 
@@ -1193,7 +1193,7 @@
   eol
   lns <- many $ Just . toString <$> (str <|> blank)
             <|> Nothing <$ parseSmToggle
-            <|> Just . stringify <$> parseInline
+            <|> Just . stringifyInlines <$> parseInline
             <|> Just "" <$ emptyMacro "Pp"
   return $ B.codeBlock (T.unlines (catMaybes lns))
 
@@ -1218,7 +1218,7 @@
   emptyMacro "Ef"
   return $ xform ins
   where
-    code = B.code . stringify
+    code = B.code . stringifyInlines
 
 skipListArgument :: (PandocMonad m) => MdocParser m ()
 skipListArgument =
@@ -1307,7 +1307,7 @@
 referenceField m field = do
   macro m
   reference <- currentReference <$> getState
-  contents <- stringify <$> litsAndDelimsToInlines
+  contents <- stringifyInlines <$> litsAndDelimsToInlines
   eol
   modifyState $ \s -> s{currentReference = M.insertWith (++) field [contents] reference}
   return ()
diff --git a/src/Text/Pandoc/Readers/Mdoc/Lex.hs b/src/Text/Pandoc/Readers/Mdoc/Lex.hs
--- a/src/Text/Pandoc/Readers/Mdoc/Lex.hs
+++ b/src/Text/Pandoc/Readers/Mdoc/Lex.hs
@@ -142,7 +142,7 @@
 mdocToken = lexComment <|> lexControlLine <|> lexTextLine
 
 lexMacroName :: PandocMonad m => Lexer m T.Text
-lexMacroName = many1Char (satisfy isMacroChar)
+lexMacroName = takeWhile1P isMacroChar
   where
     isMacroChar '%' = True
     isMacroChar x = isAlphaNum x
diff --git a/src/Text/Pandoc/Readers/MediaWiki.hs b/src/Text/Pandoc/Readers/MediaWiki.hs
--- a/src/Text/Pandoc/Readers/MediaWiki.hs
+++ b/src/Text/Pandoc/Readers/MediaWiki.hs
@@ -37,8 +37,8 @@
 import Text.Pandoc.Options
 import Text.Pandoc.Parsing hiding (tableCaption)
 import Text.Pandoc.Readers.HTML (htmlTag, isCommentTag, toAttr)
-import Text.Pandoc.Shared (formatCode, safeRead, splitTextBy, stringify,
-                           stripTrailingNewlines, trim, tshow)
+import Text.Pandoc.Shared (formatCode, safeRead, splitTextBy, stringifyInlines,
+                           stripTrailingNewlines, trim, tshow, compactifyTable)
 import Text.Pandoc.XML (fromEntities)
 
 -- | Read mediawiki from an input string and return a Pandoc document.
@@ -299,7 +299,8 @@
                           else ([], hdr:rows')
   let toRow = Row nullAttr
       toHeaderRow l = [toRow l | not (null l)]
-  return $ B.table (B.simpleCaption $ B.plain caption)
+  return $ compactifyTable
+         $ B.table (B.simpleCaption $ B.plain caption)
                    cellspecs
                    (TableHead nullAttr $ toHeaderRow headers)
                    [TableBody nullAttr 0 [] $ map toRow rows]
@@ -324,7 +325,7 @@
   char '='
   skipMany spaceChar
   v <- (char '"' >> manyTillChar (satisfy (/='\n')) (char '"'))
-       <|> many1Char (satisfy $ \c -> not (isSpace c) && c /= '|')
+       <|> takeWhile1P (\c -> not (isSpace c) && c /= '|')
   return (k,v)
 
 tableStart :: PandocMonad m => MWParser m ()
@@ -406,7 +407,8 @@
   string "{{"
   notFollowedBy (char '{')
   lookAhead $ letter <|> digit <|> char ':'
-  let chunk = template <|> variable <|> many1Char (noneOf "{}") <|> countChar 1 anyChar
+  let chunk = template <|> variable <|> takeWhile1P (\c -> c /= '{' && c /= '}')
+                <|> countChar 1 anyChar
   contents <- manyTill chunk (try $ string "}}")
   return $ "{{" <> T.concat contents <> "}}"
 
@@ -614,7 +616,7 @@
       <|> special
 
 str :: PandocMonad m => MWParser m Inlines
-str = B.str <$> many1Char (noneOf $ specialChars ++ spaceChars)
+str = B.str <$> takeWhile1P (`notElem` (specialChars ++ spaceChars))
 
 math :: PandocMonad m => MWParser m Inlines
 math = (B.displayMath . trim <$> try (many1 (char ':') >> textInTags "math"))
@@ -711,9 +713,9 @@
 image = try $ do
   sym "[["
   imageIdentifier
-  fname <- addUnderscores <$> many1Char (noneOf "|]")
+  fname <- addUnderscores <$> takeWhile1P (\c -> c /= '|' && c /= ']')
   _ <- many imageOption
-  dims <- try (char '|' *> sepBy (manyChar digit) (char 'x') <* string "px")
+  dims <- try (char '|' *> sepBy (takeWhileP isDigit) (char 'x') <* string "px")
           <|> return []
   _ <- many imageOption
   let kvs = case dims of
@@ -723,7 +725,7 @@
   let attr = ("", [], kvs)
   caption <-   (B.str fname <$ sym "]]")
            <|> try (char '|' *> (mconcat <$> manyTill inline (sym "]]")))
-  return $ B.imageWith attr fname (stringify caption) caption
+  return $ B.imageWith attr fname (stringifyInlines caption) caption
 
 imageOption :: PandocMonad m => MWParser m Text
 imageOption = try $ char '|' *> opt
@@ -744,7 +746,7 @@
 internalLink :: PandocMonad m => MWParser m Inlines
 internalLink = try $ do
   sym "[["
-  pagename <- T.unwords . T.words <$> manyChar (noneOf "|]")
+  pagename <- T.unwords . T.words <$> takeWhileP (\c -> c /= '|' && c /= ']')
   label <- option (B.text pagename) $ char '|' *>
              (  (mconcat <$> many1 (notFollowedBy (char ']') *> inline))
              -- the "pipe trick"
@@ -752,8 +754,8 @@
              <|> return (B.text $ T.drop 1 $ T.dropWhile (/=':') pagename) )
   sym "]]"
   -- see #8525:
-  linktrail <- B.text <$> manyChar (satisfy (\c -> isLetter c && not (isCJK c)))
-  let link = B.linkWith (mempty, ["wikilink"], mempty) (addUnderscores pagename) (stringify label) (label <> linktrail)
+  linktrail <- B.text <$> takeWhileP (\c -> isLetter c && not (isCJK c))
+  let link = B.linkWith (mempty, ["wikilink"], mempty) (addUnderscores pagename) (stringifyInlines label) (label <> linktrail)
   if "Category:" `T.isPrefixOf` pagename
      then do
        updateState $ \st -> st{ mwCategoryLinks = link : mwCategoryLinks st }
diff --git a/src/Text/Pandoc/Readers/Muse.hs b/src/Text/Pandoc/Readers/Muse.hs
--- a/src/Text/Pandoc/Readers/Muse.hs
+++ b/src/Text/Pandoc/Readers/Muse.hs
@@ -22,6 +22,7 @@
 import Control.Monad.Reader
 import Control.Monad.Except (throwError)
 import Data.Bifunctor
+import Data.Char (isAlphaNum, isDigit, isLetter)
 import Data.Default
 import Data.List (transpose)
 import qualified Data.Map as M
@@ -36,7 +37,7 @@
 import Text.Pandoc.Logging
 import Text.Pandoc.Options
 import Text.Pandoc.Parsing
-import Text.Pandoc.Shared (trimr, tshow)
+import Text.Pandoc.Shared (trimr, tshow, compactifyTable)
 
 -- | Read Muse from an input string and return a Pandoc document.
 readMuse :: (PandocMonad m, ToSources a)
@@ -170,7 +171,7 @@
   where
     attr = try $ (,)
       <$  many1 spaceChar
-      <*> many1Char (noneOf "=\n")
+      <*> takeWhile1P (\c -> c /= '=' && c /= '\n')
       <*  string "=\""
       <*> manyTillChar (noneOf "\"") (char '"')
 
@@ -198,7 +199,7 @@
 
 -- While not documented, Emacs Muse allows "-" in directive name
 parseDirectiveKey :: PandocMonad m => MuseParser m Text
-parseDirectiveKey = char '#' *> manyChar (letter <|> char '-')
+parseDirectiveKey = char '#' *> takeWhileP (\c -> isLetter c || c == '-')
 
 parseEmacsDirective :: PandocMonad m => MuseParser m (Text, F Inlines)
 parseEmacsDirective = (,)
@@ -645,6 +646,7 @@
 
 museToPandocTable :: MuseTable -> Blocks
 museToPandocTable (MuseTable caption headers body footers) =
+  compactifyTable $
   B.table (B.simpleCaption $ B.plain caption)
           attrs
           (TableHead nullAttr $ toHeaderRow headRow)
@@ -723,10 +725,29 @@
 tableParseRow :: PandocMonad m
               => Int -- ^ Number of separator characters
               -> MuseParser m (F [Blocks])
-tableParseRow n = try $ sequence <$> tableCells
+tableParseRow n = try $ do
+  -- A table row must contain a cell separator (whitespace followed by
+  -- pipes), which cannot span lines.  Scanning the raw line for one
+  -- before parsing cells avoids expensive inline parsing (that would
+  -- fail and be discarded) of every line this parser is tried on.
+  lineMayBeRow <- rawLineContainsSeparator
+  guard lineMayBeRow
+  sequence <$> tableCells
   where tableCells = (:) <$> tableCell sep <*> (tableCells <|> fmap pure (tableCell eol))
         tableCell p = try $ fmap B.plain . trimInlinesF . mconcat <$> manyTill inline' p
         sep = try $ many1 spaceChar *> count n (char '|') *> lookAhead (void (many1 spaceChar) <|> void eol)
+        pipes = T.replicate n "|"
+        rawLineContainsSeparator = do
+          Sources inps <- getInput
+          return $ case inps of
+            (_,t):rest ->
+              case T.break (== '\n') t of
+                (this, remainder)
+                  | T.null remainder, not (null rest) ->
+                      True -- line may span input chunks; don't reject
+                  | otherwise -> (" " <> pipes) `T.isInfixOf` this ||
+                                 ("\t" <> pipes) `T.isInfixOf` this
+            [] -> False
 
 -- | Parse a table header row.
 tableParseHeader :: PandocMonad m => MuseParser m (F MuseTableElement)
@@ -751,6 +772,7 @@
 
 inline' :: PandocMonad m => MuseParser m (F Inlines)
 inline' = whitespace
+      <|> str -- tried early: all other alternatives start with non-alphanumerics
       <|> br
       <|> anchor
       <|> footnote
@@ -773,7 +795,6 @@
       <|> codeTag
       <|> mathTag
       <|> inlineLiteralTag
-      <|> str
       <|> asterisks
       <|> symbol
       <?> "inline"
@@ -790,7 +811,7 @@
   <$  firstColumn
   <*  char '#'
   <*> letter
-  <*> manyChar (letter <|> digit <|> char '-')
+  <*> takeWhileP (\c -> isLetter c || isDigit c || c == '-')
 
 anchor :: PandocMonad m => MuseParser m (F Inlines)
 anchor = try $ do
@@ -931,13 +952,13 @@
   <*> manyTillChar anyChar (closeTag "literal")
 
 str :: PandocMonad m => MuseParser m (F Inlines)
-str = return . B.str <$> many1Char alphaNum <* updateLastStrPos
+str = return . B.str <$> takeWhile1P isAlphaNum <* updateLastStrPos
 
 -- | Consume asterisks that were not used as emphasis opening.
 -- This prevents series of asterisks from being split into
 -- literal asterisk and emphasis opening.
 asterisks :: PandocMonad m => MuseParser m (F Inlines)
-asterisks = pure . B.str <$> many1Char (char '*')
+asterisks = pure . B.str <$> takeWhile1P (== '*')
 
 symbol :: PandocMonad m => MuseParser m (F Inlines)
 symbol = pure . B.str . T.singleton <$> nonspaceChar
@@ -986,6 +1007,6 @@
           return (ext, width, align)
         imageAttrs = (,)
           <$  many1 spaceChar
-          <*> optionMaybe (many1Char digit)
+          <*> optionMaybe (takeWhile1P isDigit)
           <*  many spaceChar
           <*> optionMaybe (oneOf "rlf")
diff --git a/src/Text/Pandoc/Readers/ODT.hs b/src/Text/Pandoc/Readers/ODT.hs
--- a/src/Text/Pandoc/Readers/ODT.hs
+++ b/src/Text/Pandoc/Readers/ODT.hs
@@ -103,7 +103,7 @@
   let media = filteredFilesFromArchive archive filePathIsODTMedia
   let startState = readerState styles media
   either (\_ -> Left $ PandocParseError "Could not convert opendocument") Right
-    (runConverter' read_body startState contentElem)
+    (runConverter read_body startState contentElem)
 
 
 --
diff --git a/src/Text/Pandoc/Readers/ODT/Arrows/State.hs b/src/Text/Pandoc/Readers/ODT/Arrows/State.hs
deleted file mode 100644
--- a/src/Text/Pandoc/Readers/ODT/Arrows/State.hs
+++ /dev/null
@@ -1,153 +0,0 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE TupleSections     #-}
-{- |
-   Module      : Text.Pandoc.Readers.ODT.Arrows.State
-   Copyright   : Copyright (C) 2015 Martin Linnemann
-   License     : GNU GPL, version 2 or above
-
-   Maintainer  : Martin Linnemann <theCodingMarlin@googlemail.com>
-   Stability   : alpha
-   Portability : portable
-
-An arrow that transports a state. It is in essence a more powerful version of
-the standard state monad. As it is such a simple extension, there are
-other version out there that do exactly the same.
-The implementation is duplicated, though, to add some useful features.
-Most of these might be implemented without access to innards, but it's much
-faster and easier to implement this way.
--}
-
-module Text.Pandoc.Readers.ODT.Arrows.State
-  ( ArrowState(..)
-  , withState
-  , modifyState
-  , ignoringState
-  , fromState
-  , extractFromState
-  , tryModifyState
-  , withSubStateF
-  , withSubStateF'
-  , foldS
-  , iterateS
-  , iterateSL
-  , iterateS'
-  ) where
-
-import qualified Data.List as L
-import Control.Arrow
-import qualified Control.Category as Cat
-import Control.Monad
-import Text.Pandoc.Readers.ODT.Arrows.Utils
-import Text.Pandoc.Readers.ODT.Generic.Fallible
-
-
-newtype ArrowState state a b = ArrowState
-  { runArrowState :: (state, a) -> (state, b) }
-
--- | Constructor
-withState           :: (state -> a -> (state, b)) -> ArrowState state a b
-withState            = ArrowState . uncurry
-
--- | Constructor
-modifyState         :: (state      ->  state    ) -> ArrowState state a a
-modifyState          = ArrowState . first
-
--- | Constructor
-ignoringState       :: (         a ->         b ) -> ArrowState state a b
-ignoringState        = ArrowState . second
-
--- | Constructor
-fromState           :: (state      -> (state, b)) -> ArrowState state a b
-fromState            = ArrowState . (.fst)
-
--- | Constructor
-extractFromState    :: (state      ->         b ) -> ArrowState state x b
-extractFromState   f = ArrowState $ \(state,_) -> (state, f state)
-
--- | Constructor
-tryModifyState      :: (state ->  Either f state)
-                    -> ArrowState state a (Either f a)
-tryModifyState     f = ArrowState $ \(state,a)
-                                  -> (state,).Left ||| (,Right a) $ f state
-
-instance Cat.Category (ArrowState s) where
-  id                = ArrowState id
-  arrow2 . arrow1   = ArrowState $ runArrowState arrow2 . runArrowState arrow1
-
-instance Arrow (ArrowState state) where
-  arr               = ignoringState
-  first  a          = ArrowState $ \(s,(aF,aS))
-                                    -> second (,aS) $ runArrowState a (s,aF)
-  second a          = ArrowState $ \(s,(aF,aS))
-                                    -> second (aF,) $ runArrowState a (s,aS)
-
-instance ArrowChoice (ArrowState state) where
-  left   a          = ArrowState $ \(s,e) -> case e of
-                                 Left  l -> second Left  $ runArrowState a (s,l)
-                                 Right r -> (s, Right r)
-  right  a          = ArrowState $ \(s,e) -> case e of
-                                 Left  l -> (s, Left l)
-                                 Right r -> second Right $ runArrowState a (s,r)
-
-instance ArrowApply (ArrowState state) where
-   app             = ArrowState $ \(s, (f,b)) -> runArrowState f (s,b)
-
--- | Switches the type of the state temporarily.
--- Drops the intermediate result state, behaving like a fallible
--- identity arrow, save for side effects in the state.
-withSubStateF  :: ArrowState s  x (Either f s')
-               -> ArrowState s' s (Either f s )
-               -> ArrowState s  x (Either f x )
-withSubStateF  unlift a = keepingTheValue (withSubStateF' unlift a)
-                          >>^ spreadChoice
-                          >>^ fmap fst
-
--- | Switches the type of the state temporarily.
--- Returns the resulting sub-state.
-withSubStateF' :: ArrowState s  x (Either f s')
-               -> ArrowState s' s (Either f s )
-               -> ArrowState s  x (Either f s')
-withSubStateF' unlift a = ArrowState go
-  where go p@(s,_) = tryRunning unlift
-                                ( tryRunning a (second Right) )
-                                p
-          where tryRunning a' b v = case runArrowState a' v of
-                                      (_ , Left  f) -> (s, Left f)
-                                      (x , Right y) -> b (y,x)
-
--- | Fold a state arrow through something 'Foldable'. Collect the results
--- in a 'Monoid'.
--- Intermediate form of a fold between one with "only" a 'Monoid'
--- and one with any function.
-foldS :: (Foldable f, Monoid m) => ArrowState s x m -> ArrowState s (f x) m
-foldS a = ArrowState $ \(s,f) -> foldr a' (s,mempty) f
-  where a' x (s',m) = second (mappend m)  $ runArrowState a (s',x)
-
--- | Fold a state arrow through something 'Foldable'. Collect the results in a
--- 'MonadPlus'.
-iterateS :: (Foldable f, MonadPlus m)
-         => ArrowState s    x     y
-         -> ArrowState s (f x) (m y)
-iterateS a = ArrowState $ \(s,f) -> foldr a' (s,mzero) f
-  where a' x (s',m) = second (mplus m.return) $ runArrowState a (s',x)
-
--- | Fold a state arrow through something 'Foldable'. Collect the results in a
--- 'MonadPlus'.
-iterateSL :: (Foldable f, MonadPlus m)
-          => ArrowState s    x     y
-          -> ArrowState s (f x) (m y)
-iterateSL a = ArrowState $ \(s,f) -> L.foldl' a' (s,mzero) f
-  where a' (s',m) x = second (mplus m.return) $ runArrowState a (s',x)
-
-
--- | Fold a fallible state arrow through something 'Foldable'.
--- Collect the results in a 'MonadPlus'.
--- If the iteration fails, the state will be reset to the initial one.
-iterateS' :: (Foldable f, MonadPlus m)
-          => ArrowState s    x  (Either e    y )
-          -> ArrowState s (f x) (Either e (m y))
-iterateS' a = ArrowState $ \(s,f) -> foldr (a' s) (s,Right mzero) f
-  where a' s x (s',Right m) = case runArrowState a (s',x) of
-                                (s'',Right m') -> (s'',Right $ mplus m $ return m')
-                                (_  ,Left  e ) -> (s  ,Left  e )
-        a' _ _   e          = e
diff --git a/src/Text/Pandoc/Readers/ODT/Arrows/Utils.hs b/src/Text/Pandoc/Readers/ODT/Arrows/Utils.hs
deleted file mode 100644
--- a/src/Text/Pandoc/Readers/ODT/Arrows/Utils.hs
+++ /dev/null
@@ -1,241 +0,0 @@
-{- |
-   Module      : Text.Pandoc.Readers.ODT.Arrows.Utils
-   Copyright   : Copyright (C) 2015 Martin Linnemann
-   License     : GNU GPL, version 2 or above
-
-   Maintainer  : Martin Linnemann <theCodingMarlin@googlemail.com>
-   Stability   : alpha
-   Portability : portable
-
-Utility functions for Arrows (Kleisli monads).
-
-Some general notes on notation:
-
-* "^" is meant to stand for a pure function that is lifted into an arrow
-based on its usage for that purpose in "Control.Arrow".
-* "?" is meant to stand for the usage of a 'FallibleArrow' or a pure function
-with an equivalent return value.
-* "_" stands for the dropping of a value.
--}
-
--- We export everything
-module Text.Pandoc.Readers.ODT.Arrows.Utils
-  ( and2
-  , and3
-  , and4
-  , and5
-  , and6
-  , liftA2
-  , liftA3
-  , liftA4
-  , liftA5
-  , liftA6
-  , liftA
-  , duplicate
-  , (>>%)
-  , keepingTheValue
-  , (^|||)
-  , (|||^)
-  , (^|||^)
-  , (^&&&)
-  , (&&&^)
-  , choiceToMaybe
-  , maybeToChoice
-  , returnV
-  , FallibleArrow
-  , liftAsSuccess
-  , (>>?)
-  , (>>?^)
-  , (>>?^?)
-  , (^>>?)
-  , (>>?!)
-  , (>>?%)
-  , (>>?%?)
-  , ifFailedDo
-  ) where
-
-import Prelude hiding (Applicative(..))
-import Control.Arrow
-import Control.Monad (join)
-
-import Text.Pandoc.Readers.ODT.Generic.Fallible
-import Text.Pandoc.Readers.ODT.Generic.Utils
-
-and2 :: (Arrow a) => a b c -> a b c' -> a b (c,c')
-and2 = (&&&)
-
-and3 :: (Arrow a)
-     => a b c0->a b c1->a b c2
-     -> a b (c0,c1,c2               )
-and4 :: (Arrow a)
-     => a b c0->a b c1->a b c2->a b c3
-     -> a b (c0,c1,c2,c3            )
-and5 :: (Arrow a)
-     => a b c0->a b c1->a b c2->a b c3->a b c4
-     -> a b (c0,c1,c2,c3,c4         )
-and6 :: (Arrow a)
-     => a b c0->a b c1->a b c2->a b c3->a b c4->a b c5
-     -> a b (c0,c1,c2,c3,c4,c5      )
-
-and3 a b c           = and2 a b &&& c
-                       >>^ \((z,y          ) , x) -> (z,y,x          )
-and4 a b c d         = and3 a b c &&& d
-                       >>^ \((z,y,x        ) , w) -> (z,y,x,w        )
-and5 a b c d e       = and4 a b c d &&& e
-                       >>^ \((z,y,x,w      ) , v) -> (z,y,x,w,v      )
-and6 a b c d e f     = and5 a b c d e &&& f
-                       >>^ \((z,y,x,w,v    ) , u) -> (z,y,x,w,v,u    )
-
-liftA2 :: (Arrow a) => (x -> y -> z) -> a b x -> a b y -> a b z
-liftA2 f a b = a &&& b >>^ uncurry f
-
-liftA3 :: (Arrow a) => (z->y->x                -> r)
-                    -> a b z->a b y->a b x
-                    -> a b r
-liftA4 :: (Arrow a) => (z->y->x->w             -> r)
-                    -> a b z->a b y->a b x->a b w
-                    -> a b r
-liftA5 :: (Arrow a) => (z->y->x->w->v          -> r)
-                    -> a b z->a b y->a b x->a b w->a b v
-                    -> a b r
-liftA6 :: (Arrow a) => (z->y->x->w->v->u       -> r)
-                    -> a b z->a b y->a b x->a b w->a b v->a b u
-                    -> a b r
-
-liftA3 fun a b c           = and3 a b c           >>^ uncurry3 fun
-liftA4 fun a b c d         = and4 a b c d         >>^ uncurry4 fun
-liftA5 fun a b c d e       = and5 a b c d e       >>^ uncurry5 fun
-liftA6 fun a b c d e f     = and6 a b c d e f     >>^ uncurry6 fun
-
-liftA :: (Arrow a) => (y -> z) -> a b y -> a b z
-liftA  fun a = a >>^ fun
-
-
--- | Duplicate a value to subsequently feed it into different arrows.
--- Can almost always be replaced with '(&&&)', 'keepingTheValue',
--- or even '(|||)'.
--- Equivalent to
--- > returnA &&& returnA
-duplicate :: (Arrow a) => a b (b,b)
-duplicate = arr $ join (,)
-
--- | Applies a function to the uncurried result-pair of an arrow-application.
--- (The %-symbol was chosen to evoke an association with pairs.)
-(>>%) :: (Arrow a) => a x (b,c) -> (b -> c -> d) -> a x d
-a >>% f = a >>^ uncurry f
-
-infixr 2 >>%
-
-
--- | Duplicate a value and apply an arrow to the second instance.
--- Equivalent to
--- > \a -> duplicate >>> second a
--- or
--- > \a -> returnA &&& a
-keepingTheValue :: (Arrow a) => a b c -> a b (b,c)
-keepingTheValue a = returnA &&& a
-
-( ^|||  ) :: (ArrowChoice a) => (b -> d) ->  a c d   -> a (Either b c) d
-(  |||^ ) :: (ArrowChoice a) =>  a b d   -> (c -> d) -> a (Either b c) d
-( ^|||^ ) :: (ArrowChoice a) => (b -> d) -> (c -> d) -> a (Either b c) d
-
-l ^|||  r  = arr l |||     r
-l  |||^ r  =     l ||| arr r
-l ^|||^ r  = arr l ||| arr r
-
-infixr 2 ^||| ,  |||^, ^|||^
-
-( ^&&&  ) :: (Arrow a) => (b -> c) ->  a b c'   -> a b (c,c')
-(  &&&^ ) :: (Arrow a) =>  a b c   -> (b -> c') -> a b (c,c')
-
-l ^&&&  r = arr l &&&     r
-l  &&&^ r =     l &&& arr r
-
-infixr 3 ^&&&, &&&^
-
-
--- | Converts @Right a@ into @Just a@ and @Left _@ into @Nothing@.
-choiceToMaybe :: (ArrowChoice a) => a (Either l r) (Maybe r)
-choiceToMaybe = arr eitherToMaybe
-
--- | Converts @Nothing@ into @Left ()@ and @Just a@ into @Right a@.
-maybeToChoice :: (ArrowChoice a) => a (Maybe b) (Fallible b)
-maybeToChoice = arr maybeToEither
-
--- | Lifts a constant value into an arrow
-returnV :: (Arrow a) => c -> a x c
-returnV = arr.const
-
--- | Defines Left as failure, Right as success
-type FallibleArrow a input failure success = a input (Either failure success)
-
---
-liftAsSuccess     :: (ArrowChoice a)
-                  => a x success
-                  -> FallibleArrow a x failure success
-liftAsSuccess a   = a >>^ Right
-
--- | Execute the second arrow if the first succeeds
-(>>?) :: (ArrowChoice a)
-            => FallibleArrow a x       failure success
-            -> FallibleArrow a success failure success'
-            -> FallibleArrow a x       failure success'
-a >>? b = a >>> Left ^||| b
-
--- | Execute the lifted second arrow if the first succeeds
-(>>?^) :: (ArrowChoice a)
-            => FallibleArrow a x       failure success
-            -> (success                     -> success')
-            -> FallibleArrow a x       failure success'
-a >>?^ f = a >>^ Left ^|||^ Right . f
-
--- | Execute the lifted second arrow if the first succeeds
-(>>?^?) :: (ArrowChoice a)
-            => FallibleArrow a x       failure success
-            -> (success      -> Either failure success')
-            -> FallibleArrow a x       failure success'
-a >>?^? b = a >>> Left ^|||^ b
-
--- | Execute the second arrow if the lifted first arrow succeeds
-(^>>?) :: (ArrowChoice a)
-            => (x            -> Either failure success)
-            -> FallibleArrow a success failure success'
-            -> FallibleArrow a x       failure success'
-a ^>>? b = a ^>> Left ^||| b
-
--- | Execute the second, non-fallible arrow if the first arrow succeeds
-(>>?!) :: (ArrowChoice a)
-            => FallibleArrow a x       failure success
-            ->               a success         success'
-            -> FallibleArrow a x       failure success'
-a >>?! f = a >>> right f
-
----
-(>>?%) :: (ArrowChoice a)
-          => FallibleArrow a x f (b,b')
-          -> (b -> b' -> c)
-          -> FallibleArrow a x f c
-a >>?% f = a >>?^ uncurry f
-
-
----
-(>>?%?) :: (ArrowChoice a)
-           => FallibleArrow a x f (b,b')
-           -> (b -> b' -> Either f c)
-           -> FallibleArrow a x f c
-a >>?%? f = a >>?^? uncurry f
-
-infixr 1  >>?,  >>?^,  >>?^?
-infixr 1 ^>>?, >>?!
-infixr 1 >>?%, >>?%?
-
--- | An arrow version of a short-circuit (<|>)
-ifFailedDo :: (ArrowChoice a)
-           => FallibleArrow a x f y
-           -> FallibleArrow a x f y
-           -> FallibleArrow a x f y
-ifFailedDo a b = keepingTheValue a >>> repackage ^>> (b |||^ Right)
-  where repackage (x , Left  _) = Left  x
-        repackage (_ , Right y) = Right y
-
-infixr 1 `ifFailedDo`
diff --git a/src/Text/Pandoc/Readers/ODT/Base.hs b/src/Text/Pandoc/Readers/ODT/Base.hs
deleted file mode 100644
--- a/src/Text/Pandoc/Readers/ODT/Base.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-{- |
-   Module      : Text.Pandoc.Readers.ODT.Base
-   Copyright   : Copyright (C) 2015 Martin Linnemann
-   License     : GNU GPL, version 2 or above
-
-   Maintainer  : Martin Linnemann <theCodingMarlin@googlemail.com>
-   Stability   : alpha
-   Portability : portable
-
-Core types of the odt reader.
--}
-
-module Text.Pandoc.Readers.ODT.Base
-  ( ODTConverterState
-  , XMLReader
-  , XMLReaderSafe
-  ) where
-
-import Text.Pandoc.Readers.ODT.Generic.XMLConverter
-import Text.Pandoc.Readers.ODT.Namespaces
-
-type ODTConverterState s = XMLConverterState Namespace s
-
-type XMLReader     s a b = FallibleXMLConverter Namespace s a b
-
-type XMLReaderSafe s a b =         XMLConverter Namespace s a b
diff --git a/src/Text/Pandoc/Readers/ODT/ContentReader.hs b/src/Text/Pandoc/Readers/ODT/ContentReader.hs
--- a/src/Text/Pandoc/Readers/ODT/ContentReader.hs
+++ b/src/Text/Pandoc/Readers/ODT/ContentReader.hs
@@ -1,10 +1,7 @@
-{-# LANGUAGE Arrows            #-}
-{-# LANGUAGE CPP               #-}
 {-# LANGUAGE DeriveFoldable    #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE PatternGuards     #-}
 {-# LANGUAGE RecordWildCards   #-}
-{-# LANGUAGE TupleSections     #-}
 {-# LANGUAGE ViewPatterns      #-}
 {-# LANGUAGE OverloadedStrings #-}
 {- |
@@ -24,9 +21,7 @@
 , read_body
 ) where
 
-import Prelude hiding (Applicative(..))
-import Control.Applicative hiding (liftA, liftA2, liftA3)
-import Control.Arrow
+import Control.Applicative ((<|>))
 import Control.Monad ((<=<))
 
 import qualified Data.ByteString.Lazy as B
@@ -48,14 +43,10 @@
 
 import Text.Pandoc.Readers.Docx.Combine (combineBlocks)
 
-import Text.Pandoc.Readers.ODT.Base
 import Text.Pandoc.Readers.ODT.Namespaces
 import Text.Pandoc.Readers.ODT.StyleReader
 
-import Text.Pandoc.Readers.ODT.Arrows.State (foldS)
-import Text.Pandoc.Readers.ODT.Arrows.Utils
-import Text.Pandoc.Readers.ODT.Generic.Fallible
-import Text.Pandoc.Readers.ODT.Generic.Utils
+import Text.Pandoc.Readers.ODT.Generic.Utils (findBy)
 import Text.Pandoc.Readers.ODT.Generic.XMLConverter
 
 import Network.URI (parseRelativeReference, URI(uriPath))
@@ -125,13 +116,6 @@
 shiftListLevel diff = modifyListLevel (+ diff)
 
 --
-swapCurrentListStyle :: Maybe ListStyle -> ReaderState
-                     -> (ReaderState, Maybe ListStyle)
-swapCurrentListStyle mListStyle state = ( state { currentListStyle = mListStyle }
-                                        ,  currentListStyle state
-                                        )
-
---
 lookupPrettyAnchor :: Anchor -> ReaderState -> Maybe Anchor
 lookupPrettyAnchor anchor ReaderState{..} = M.lookup anchor bookmarkAnchors
 
@@ -158,85 +142,57 @@
 -- Reader type and associated tools
 --------------------------------------------------------------------------------
 
-type ODTReader      a b = XMLReader     ReaderState a b
-
-type ODTReaderSafe  a b = XMLReaderSafe ReaderState a b
+type ODTReader a = XMLConverter Namespace ReaderState a
 
--- | Extract something from the styles
-fromStyles :: (a -> Styles -> b) -> ODTReaderSafe a b
-fromStyles f =     keepingTheValue
-                     (getExtraState >>^ styleSet)
-               >>% f
+-- | Extract the styles from the reader state
+getStyles :: ODTReader Styles
+getStyles = styleSet <$> getExtraState
 
 --
-getStyleByName :: ODTReader StyleName Style
-getStyleByName = fromStyles lookupStyle >>^ maybeToChoice
+getStyleByName :: StyleName -> ODTReader Style
+getStyleByName name = getStyles >>= fromMaybeF . lookupStyle name
 
 --
-findStyleFamily :: ODTReader Style StyleFamily
-findStyleFamily = fromStyles getStyleFamily >>^ maybeToChoice
-
 --
-lookupListStyle :: ODTReader StyleName ListStyle
-lookupListStyle = fromStyles lookupListStyleByName >>^ maybeToChoice
+lookupListStyle :: StyleName -> ODTReader ListStyle
+lookupListStyle name = getStyles >>= fromMaybeF . lookupListStyleByName name
 
 --
-switchCurrentListStyle :: ODTReaderSafe (Maybe ListStyle) (Maybe ListStyle)
-switchCurrentListStyle =     keepingTheValue getExtraState
-                         >>% swapCurrentListStyle
-                         >>> first setExtraState
-                         >>^ snd
+switchCurrentListStyle :: Maybe ListStyle -> ODTReader (Maybe ListStyle)
+switchCurrentListStyle mListStyle = do
+  state <- getExtraState
+  setExtraState $ state { currentListStyle = mListStyle }
+  return $ currentListStyle state
 
 --
-pushStyle :: ODTReaderSafe Style Style
-pushStyle =     keepingTheValue (
-                  (     keepingTheValue getExtraState
-                    >>% pushStyle'
-                  )
-                  >>> setExtraState
-                )
-            >>^ fst
+pushStyle :: Style -> ODTReader ()
+pushStyle style = modifyExtraState (pushStyle' style)
 
 --
-popStyle :: ODTReaderSafe x x
-popStyle =     keepingTheValue (
-                     getExtraState
-                 >>> arr popStyle'
-                 >>> setExtraState
-               )
-           >>^ fst
+popStyle :: ODTReader ()
+popStyle = modifyExtraState popStyle'
 
 --
-getCurrentListLevel :: ODTReaderSafe a ListLevel
-getCurrentListLevel = getExtraState >>^ currentListLevel
+getCurrentListLevel :: ODTReader ListLevel
+getCurrentListLevel = currentListLevel <$> getExtraState
 
 --
-getListContinuationStartCounters :: ODTReaderSafe a (M.Map ListLevel Int)
-getListContinuationStartCounters = getExtraState >>^ listContinuationStartCounters
-
+getPreviousListStartCounter :: ListLevel -> ODTReader Int
+getPreviousListStartCounter listLevel =
+  M.findWithDefault 0 listLevel . listContinuationStartCounters
+    <$> getExtraState
 
 --
-getPreviousListStartCounter :: ODTReaderSafe ListLevel Int
-getPreviousListStartCounter = proc listLevel -> do
-    counts <- getListContinuationStartCounters -< ()
-    returnA -< M.findWithDefault 0 listLevel counts
+updateMediaWithResource :: (FilePath, B.ByteString) -> ODTReader ()
+updateMediaWithResource resource = modifyExtraState (insertMedia' resource)
 
 --
-updateMediaWithResource :: ODTReaderSafe (FilePath, B.ByteString) (FilePath, B.ByteString)
-updateMediaWithResource = keepingTheValue (
-                 (keepingTheValue getExtraState
-                  >>% insertMedia'
-                  )
-                 >>> setExtraState
-               )
-           >>^ fst
-
-lookupResource :: ODTReaderSafe FilePath (FilePath, B.ByteString)
-lookupResource = proc target -> do
-    state <- getExtraState -< ()
-    case lookup target (getMediaEnv state) of
-      Just bs -> returnV (target, bs) -<< ()
-      Nothing -> returnV ("", B.empty) -< ()
+lookupResource :: FilePath -> ODTReader (FilePath, B.ByteString)
+lookupResource target = do
+  state <- getExtraState
+  case lookup target (getMediaEnv state) of
+    Just bs -> return (target, bs)
+    Nothing -> return ("", B.empty)
 
 type AnchorPrefix = T.Text
 
@@ -255,77 +211,58 @@
 -- | First argument: basis for a new "pretty" anchor if none exists yet
 -- Second argument: a key ("ugly" anchor)
 -- Returns: saved "pretty" anchor or created new one
-getPrettyAnchor :: ODTReaderSafe (AnchorPrefix, Anchor) Anchor
-getPrettyAnchor = proc (baseIdent, uglyAnchor) -> do
-  state <- getExtraState -< ()
+getPrettyAnchor :: AnchorPrefix -> Anchor -> ODTReader Anchor
+getPrettyAnchor baseIdent uglyAnchor = do
+  state <- getExtraState
   case lookupPrettyAnchor uglyAnchor state of
-    Just prettyAnchor -> returnA -< prettyAnchor
+    Just prettyAnchor -> return prettyAnchor
     Nothing           -> do
       let newPretty = uniqueIdentFrom baseIdent (usedAnchors state)
-      modifyExtraState (putPrettyAnchor uglyAnchor newPretty) -<< newPretty
+      modifyExtraState (putPrettyAnchor uglyAnchor newPretty)
+      return newPretty
 
 -- | Input: basis for a new header anchor
 -- Output: saved new anchor
-getHeaderAnchor :: ODTReaderSafe Inlines Anchor
-getHeaderAnchor = proc title -> do
-  state <- getExtraState -< ()
+getHeaderAnchor :: Inlines -> ODTReader Anchor
+getHeaderAnchor title = do
+  state <- getExtraState
   let exts = extensionsFromList [Ext_auto_identifiers]
   let anchor = uniqueIdent exts (toList title)
                 (Set.fromList $ usedAnchors state)
-  modifyExtraState (putPrettyAnchor anchor anchor) -<< anchor
+  modifyExtraState (putPrettyAnchor anchor anchor)
+  return anchor
 
 
 --------------------------------------------------------------------------------
 -- Working with styles
 --------------------------------------------------------------------------------
 
---
-readStyleByName :: ODTReader a (StyleName, Style)
-readStyleByName =
-  findAttr NsText "style-name" >>? keepingTheValue getStyleByName >>^ liftE
-  where
-    liftE :: (StyleName, Fallible Style) -> Fallible (StyleName, Style)
-    liftE (name, Right v) = Right (name, v)
-    liftE (_, Left v)     = Left v
-
---
-isStyleToTrace :: ODTReader Style Bool
-isStyleToTrace = findStyleFamily >>?^ (==FaText)
+-- | Read the style referenced by the current element's
+-- style-name attribute. Fails if there is no such attribute or if the
+-- style cannot be found.
+readStyleByName :: ODTReader (StyleName, Style)
+readStyleByName = do
+  name  <- findAttr NsText "style-name"
+  style <- getStyleByName name
+  return (name, style)
 
 --
-withNewStyle :: ODTReaderSafe x Inlines -> ODTReaderSafe x Inlines
-withNewStyle a = proc x -> do
-  fStyle <- readStyleByName -< ()
+withNewStyle :: ODTReader Inlines -> ODTReader Inlines
+withNewStyle reader = do
+  fStyle <- tryC readStyleByName
   case fStyle of
-    Right (styleName, _) | isCodeStyle styleName -> do
-      inlines <- a -< x
-      arr inlineCode -<< inlines
-    Right (_, style) -> do
-      mFamily    <- arr styleFamily -< style
-      fTextProps <- arr ( maybeToChoice
-                        . textProperties
-                        . styleProperties
-                        )           -< style
-      case fTextProps of
-        Right textProps -> do
-          state        <- getExtraState             -< ()
-          let triple = (state, textProps, mFamily)
-          modifier     <- arr modifierFromStyleDiff -< triple
-          fShouldTrace <- isStyleToTrace            -< style
-          case fShouldTrace of
-            Right shouldTrace ->
-              if shouldTrace
-                then do
-                  pushStyle      -< style
-                  inlines   <- a -< x
-                  popStyle       -< ()
-                  arr modifier   -<< inlines
-                else
-    -- In case anything goes wrong
-                      a -< x
-            Left _ -> a -< x
-        Left _     -> a -< x
-    Left _         -> a -< x
+    Right (styleName, _) | isCodeStyle styleName ->
+      inlineCode <$> reader
+    Right (_, style)
+      | Just textProps <- textProperties (styleProperties style) -> do
+          state <- getExtraState
+          let mFamily  = styleFamily style
+              modifier = modifierFromStyleDiff (state, textProps, mFamily)
+          pushStyle style
+          inlines <- reader
+          popStyle
+          return $ modifier inlines
+    _ -> reader
   where
     isCodeStyle :: StyleName -> Bool
     isCodeStyle "Source_Text" = True
@@ -333,7 +270,7 @@
     isCodeStyle _             = False
 
     inlineCode :: Inlines -> Inlines
-    inlineCode = code . T.concat . map stringify . toList
+    inlineCode = code . stringifyInlines
 
 type PropertyTriple = (ReaderState, TextProperties, Maybe StyleFamily)
 type InlineModifier = Inlines -> Inlines
@@ -342,16 +279,15 @@
 -- an instance of 'Inlines'
 modifierFromStyleDiff :: PropertyTriple -> InlineModifier
 modifierFromStyleDiff propertyTriple  =
-  composition $
+  foldr (.) id $
   getVPosModifier propertyTriple
-  : map (first ($ propertyTriple) >>> ifThen_else ignore)
+  : map (\(hasChanged', modifier) ->
+           if hasChanged' propertyTriple then modifier else ignore)
         [ (hasEmphChanged           , emph      )
         , (hasChanged isStrong      , strong    )
         , (hasChanged strikethrough , strikeout )
         ]
   where
-    ifThen_else else' (if',then') = if if' then then' else else'
-
     ignore = id :: InlineModifier
 
     getVPosModifier :: PropertyTriple -> InlineModifier
@@ -367,9 +303,9 @@
     getVPosModifier' ( _      ,  _        ) = ignore
 
     hasEmphChanged :: PropertyTriple -> Bool
-    hasEmphChanged = swing any [ hasChanged  isEmphasised
-                               , hasChanged  underline
-                               ]
+    hasEmphChanged triple = any ($ triple) [ hasChanged  isEmphasised
+                                           , hasChanged  underline
+                                           ]
 
     hasChanged property triple@(_, property -> newProperty, _) =
         (/= Just newProperty) (lookupPreviousValue property triple)
@@ -414,20 +350,18 @@
      = False
 
 --
-constructPara :: ODTReaderSafe a Blocks -> ODTReaderSafe a Blocks
-constructPara reader = proc blocks -> do
-  fStyle <- readStyleByName -< blocks
+constructPara :: ODTReader Blocks -> ODTReader Blocks
+constructPara reader = do
+  fStyle <- tryC readStyleByName
   case fStyle of
-    Left   _    -> reader -< blocks
-    Right (styleName, _) | isTableCaptionStyle styleName -> do
-      blocks' <- reader   -< blocks
-      arr tableCaptionP  -< blocks'
+    Left   _    -> reader
+    Right (styleName, _) | isTableCaptionStyle styleName ->
+      tableCaptionP <$> reader
     Right (_, style) -> do
-      props <- fromStyles extendedStylePropertyChain -< [style]
-      listLevel <- getCurrentListLevel -< ()
-      let modifier = getParaModifier listLevel props
-      blocks' <- reader   -<  blocks
-      arr modifier        -<< blocks'
+      styles <- getStyles
+      let props = extendedStylePropertyChain [style] styles
+      listLevel <- getCurrentListLevel
+      getParaModifier listLevel props <$> reader
   where
     isTableCaptionStyle :: StyleName -> Bool
     isTableCaptionStyle "Table" = True
@@ -466,78 +400,72 @@
 -- Then prepares the state for eventual child lists and constructs the list from
 -- the results.
 -- Two main cases are handled: The list may provide its own style or it may
--- rely on a parent list's style. I the former case the current style in the
+-- rely on a parent list's style. In the former case the current style in the
 -- state must be switched before and after the call to the child converter
 -- while in the latter the child converter can be called directly.
 -- If anything goes wrong, a default ordered-list-constructor is used.
-constructList :: ODTReaderSafe x [Blocks] -> ODTReaderSafe x Blocks
-constructList reader = proc x -> do
-  modifyExtraState (shiftListLevel 1)                                  -< ()
-  listLevel                    <- getCurrentListLevel                  -< ()
-  listContinuationStartCounter <- getPreviousListStartCounter          -< listLevel
-  fStyleName                   <- findAttr NsText "style-name"         -< ()
-  fContNumbering               <- findAttr NsText "continue-numbering" -< ()
-  listItemCount                <- reader >>^ length                    -< x
+constructList :: ODTReader [Blocks] -> ODTReader Blocks
+constructList reader = do
+  modifyExtraState (shiftListLevel 1)
+  listLevel                    <- getCurrentListLevel
+  listContinuationStartCounter <- getPreviousListStartCounter listLevel
+  fStyleName                   <- tryC (findAttr NsText "style-name")
+  fContNumbering               <- tryC (findAttr NsText "continue-numbering")
 
-  let continueNumbering = case fContNumbering of
-                            Right "true" -> True
-                            _            -> False
+  let continueNumbering = fContNumbering == Right "true"
 
-  let startNumForListLevelStyle = listStartingNumber continueNumbering listContinuationStartCounter
-  let defaultOrderedListConstructor = constructOrderedList (startNumForListLevelStyle Nothing) listLevel listItemCount
+      startNumForListLevelStyle mListLevelStyle
+        | continueNumbering      = listContinuationStartCounter
+        | isJust mListLevelStyle = listItemStart (fromJust mListLevelStyle)
+        | otherwise              = 1
 
+      constructWith startNum constructor = do
+        items <- reader
+        modifyExtraState (shiftListLevel (-1))
+        modifyExtraState (modifyListContinuationStartCounter listLevel
+                           (startNum + length items))
+        return $ constructor items
+
+      constructOrderedList =
+        let startNum = startNumForListLevelStyle Nothing
+        in  constructWith startNum
+              (orderedListWith (startNum, DefaultStyle, DefaultDelim))
+
+      constructListWith listLevelStyle =
+        let startNum = startNumForListLevelStyle (Just listLevelStyle)
+        in  constructWith startNum (getListConstructor listLevelStyle startNum)
+
   case fStyleName of
     Right styleName -> do
-      fListStyle <- lookupListStyle -< styleName
+      fListStyle <- tryC (lookupListStyle styleName)
       case fListStyle of
-        Right listStyle -> do
-          fListLevelStyle <- arr (uncurry getListLevelStyle) -< (listLevel, listStyle)
-          case fListLevelStyle of
+        Right listStyle ->
+          case getListLevelStyle listLevel listStyle of
             Just listLevelStyle -> do
-              let startNum = startNumForListLevelStyle $ Just listLevelStyle
-              oldListStyle <- switchCurrentListStyle                    -<  Just listStyle
-              blocks       <- constructListWith listLevelStyle startNum listLevel listItemCount -<< x
-              switchCurrentListStyle                                    -<  oldListStyle
-              returnA                                                   -<  blocks
-            Nothing             -> defaultOrderedListConstructor -<< x
-        Left _                  -> defaultOrderedListConstructor -<< x
+              oldListStyle <- switchCurrentListStyle (Just listStyle)
+              blocks       <- constructListWith listLevelStyle
+              _            <- switchCurrentListStyle oldListStyle
+              return blocks
+            Nothing             -> constructOrderedList
+        Left _                  -> constructOrderedList
     Left _ -> do
-      state      <- getExtraState        -< ()
-      mListStyle <- arr currentListStyle -< state
+      mListStyle <- currentListStyle <$> getExtraState
       case mListStyle of
-        Just listStyle -> do
-          fListLevelStyle <- arr (uncurry getListLevelStyle) -< (listLevel, listStyle)
-          case fListLevelStyle of
-            Just listLevelStyle -> do
-              let startNum = startNumForListLevelStyle $ Just listLevelStyle
-              constructListWith listLevelStyle startNum listLevel listItemCount -<< x
-            Nothing             -> defaultOrderedListConstructor -<< x
-        Nothing                 -> defaultOrderedListConstructor -<< x
-  where
-    listStartingNumber continueNumbering listContinuationStartCounter mListLevelStyle
-      | continueNumbering                = listContinuationStartCounter
-      | isJust mListLevelStyle           = listItemStart (fromJust mListLevelStyle)
-      | otherwise                        = 1
-    constructOrderedList startNum listLevel listItemCount =
-          reader
-      >>> modifyExtraState (shiftListLevel (-1))
-      >>> modifyExtraState (modifyListContinuationStartCounter listLevel (startNum + listItemCount))
-      >>^ orderedListWith (startNum, DefaultStyle, DefaultDelim)
-    constructListWith listLevelStyle startNum listLevel listItemCount =
-          reader
-      >>> getListConstructor listLevelStyle startNum
-      ^>> modifyExtraState (shiftListLevel (-1))
-      >>> modifyExtraState (modifyListContinuationStartCounter listLevel (startNum + listItemCount))
+        Just listStyle ->
+          case getListLevelStyle listLevel listStyle of
+            Just listLevelStyle -> constructListWith listLevelStyle
+            Nothing             -> constructOrderedList
+        Nothing                 -> constructOrderedList
 
 --------------------------------------------------------------------------------
 -- Readers
 --------------------------------------------------------------------------------
 
-type ElementMatcher result = (Namespace, ElementName, ODTReader result result)
+type Matcher result = ElementMatcher Namespace ReaderState result
 
-type InlineMatcher = ElementMatcher Inlines
+type InlineMatcher = Matcher Inlines
 
-type BlockMatcher  = ElementMatcher Blocks
+type BlockMatcher  = Matcher Blocks
 
 newtype FirstMatch a = FirstMatch (Alt Maybe a)
   deriving (Foldable, Monoid, Semigroup)
@@ -554,33 +482,14 @@
   mempty = CombiningBlocks mempty
 
 --
-matchingElement :: (Monoid e)
-                => Namespace -> ElementName
-                -> ODTReaderSafe  e e
-                -> ElementMatcher e
-matchingElement ns name reader = (ns, name, asResultAccumulator reader)
-  where
-   asResultAccumulator :: (ArrowChoice a, Monoid m) => a m m -> a m (Fallible m)
-   asResultAccumulator a = liftAsSuccess $ keepingTheValue a >>% mappend
-
---
-matchChildContent'   :: (Monoid result)
-                     => [ElementMatcher result]
-                     ->  ODTReaderSafe a result
-matchChildContent' ls = returnV mempty >>> matchContent' ls
-
---
-matchSmushedChildBlocks' :: [ElementMatcher CombiningBlocks] ->  ODTReaderSafe a Blocks
-matchSmushedChildBlocks' ls = liftA unCombiningBlocks
-                              $ returnV mempty
-                                >>> matchContent' ls
+matchingElement :: Namespace -> ElementName
+                -> ODTReader e
+                -> Matcher e
+matchingElement ns name reader = (ns, name, reader)
 
 --
-matchChildContent    :: (Monoid result)
-                     => [ElementMatcher result]
-                     ->  ODTReaderSafe  (result, XML.Content) result
-                     ->  ODTReaderSafe a result
-matchChildContent ls fallback = returnV mempty >>> matchContent ls fallback
+matchSmushedChildBlocks' :: [Matcher CombiningBlocks] -> ODTReader Blocks
+matchSmushedChildBlocks' ls = unCombiningBlocks <$> matchContent' ls
 
 --------------------------------------------
 -- Matchers
@@ -591,94 +500,83 @@
 ----------------------
 
 --
--- | Open Document allows several consecutive spaces if they are marked up
-read_plain_text :: ODTReaderSafe (Inlines, XML.Content) Inlines
-read_plain_text =  fst ^&&& read_plain_text' >>% recover
-  where
-    -- fallible version
-    read_plain_text' :: ODTReader (Inlines, XML.Content) Inlines
-    read_plain_text' =      (     second ( arr extractText )
-                              >>^ spreadChoice >>?! second text
-                            )
-                       >>?% mappend
-    --
-    extractText     :: XML.Content -> Fallible T.Text
-    extractText (XML.Text cData) = succeedWith (XML.cdData cData)
-    extractText         _        = failEmpty
+-- | Open Document allows several consecutive spaces if they are marked up.
+-- Text content of the current element is read with this fallback converter.
+read_plain_text :: XML.Content -> ODTReader Inlines
+read_plain_text (XML.Text cData) = return $ text $ XML.cdData cData
+read_plain_text _                = return mempty
 
 read_text_seq :: InlineMatcher
 read_text_seq  = matchingElement NsText "sequence"
-                 $ matchChildContent [] read_plain_text
+                 $ matchContent [] read_plain_text
 
 
 -- specifically. I honor that, although the current implementation of 'mappend'
 -- for 'Inlines' in "Text.Pandoc.Builder" will collapse them again.
 -- The rational is to be prepared for future modifications.
 read_spaces      :: InlineMatcher
-read_spaces       = matchingElement NsText "s" (
-                          readAttrWithDefault NsText "c" 1 -- how many spaces?
-                      >>^ fromList.(`replicate` Space)
-                    )
+read_spaces       = matchingElement NsText "s" $ do
+                      count <- readAttrWithDefault NsText "c" 1 -- how many spaces?
+                      return $ fromList (replicate count Space)
 --
 read_line_break  :: InlineMatcher
 read_line_break   = matchingElement NsText "line-break"
-                    $ returnV linebreak
+                    $ return linebreak
 --
 read_tab         :: InlineMatcher
 read_tab          = matchingElement NsText "tab"
-                    $ returnV space
+                    $ return space
 --
 read_span        :: InlineMatcher
 read_span         = matchingElement NsText "span"
                     $ withNewStyle
-                    $ matchChildContent [ read_span
-                                        , read_spaces
-                                        , read_line_break
-                                        , read_tab
-                                        , read_link
-                                        , read_frame
-                                        , read_note
-                                        , read_citation
-                                        , read_bookmark
-                                        , read_bookmark_start
-                                        , read_reference_start
-                                        , read_bookmark_ref
-                                        , read_reference_ref
-                                        ] read_plain_text
+                    $ matchContent [ read_span
+                                   , read_spaces
+                                   , read_line_break
+                                   , read_tab
+                                   , read_link
+                                   , read_frame
+                                   , read_note
+                                   , read_citation
+                                   , read_bookmark
+                                   , read_bookmark_start
+                                   , read_reference_start
+                                   , read_bookmark_ref
+                                   , read_reference_ref
+                                   ] read_plain_text
 
 --
-read_paragraph   :: ElementMatcher CombiningBlocks
-read_paragraph    = matchingElement NsText "p" $
-                      liftA CombiningBlocks $ proc blocks -> do
-                        fStyle <- readStyleByName -< blocks
-                        case fStyle of
-                          Right style | isPreformattedStyle style -> do
-                            liftA (codeBlock . stringify) $ matchParagraphContent -< blocks
-                          _ ->
-                            constructPara $ liftA para $ withNewStyle matchParagraphContent -< blocks
-                        where
-                          isPreformattedStyle :: (StyleName, Style) -> Bool
-                          isPreformattedStyle ("Preformatted_20_Text", _) = True
-                          isPreformattedStyle (_, Style { styleParentName = Just "Preformatted_20_Text" }) = True
-                          isPreformattedStyle _ = False
+read_paragraph   :: Matcher CombiningBlocks
+read_paragraph    = matchingElement NsText "p" $ fmap CombiningBlocks $ do
+                      fStyle <- tryC readStyleByName
+                      case fStyle of
+                        Right style | isPreformattedStyle style ->
+                          codeBlock . stringifyInlines <$> matchParagraphContent
+                        _ ->
+                          constructPara (para <$> withNewStyle matchParagraphContent)
+                    where
+                      isPreformattedStyle :: (StyleName, Style) -> Bool
+                      isPreformattedStyle ("Preformatted_20_Text", _) = True
+                      isPreformattedStyle (_, Style { styleParentName = Just "Preformatted_20_Text" }) = True
+                      isPreformattedStyle _ = False
 
 
-matchParagraphContent :: ODTReaderSafe a Inlines
-matchParagraphContent = matchChildContent [ read_span
-                                          , read_spaces
-                                          , read_line_break
-                                          , read_tab
-                                          , read_link
-                                          , read_note
-                                          , read_citation
-                                          , read_bookmark
-                                          , read_bookmark_start
-                                          , read_reference_start
-                                          , read_bookmark_ref
-                                          , read_reference_ref
-                                          , read_frame
-                                          , read_text_seq
-                                          ] read_plain_text
+matchParagraphContent :: ODTReader Inlines
+matchParagraphContent = matchContent [ read_span
+                                     , read_spaces
+                                     , read_line_break
+                                     , read_tab
+                                     , read_link
+                                     , read_note
+                                     , read_citation
+                                     , read_bookmark
+                                     , read_bookmark_start
+                                     , read_reference_start
+                                     , read_bookmark_ref
+                                     , read_reference_ref
+                                     , read_frame
+                                     , read_text_seq
+                                     ] read_plain_text
 
 
 ----------------------
@@ -686,72 +584,69 @@
 ----------------------
 
 --
-read_header      :: ElementMatcher CombiningBlocks
-read_header       = matchingElement NsText "h"
-                    $  proc blocks -> do
-  level    <- ( readAttrWithDefault NsText "outline-level" 1
-              ) -< blocks
-  children <- ( matchChildContent [ read_span
-                                  , read_spaces
-                                  , read_line_break
-                                  , read_tab
-                                  , read_link
-                                  , read_note
-                                  , read_citation
-                                  , read_bookmark
-                                  , read_bookmark_start
-                                  , read_reference_start
-                                  , read_bookmark_ref
-                                  , read_reference_ref
-                                  , read_frame
-                                  ] read_plain_text
-              ) -< blocks
-  anchor   <- getHeaderAnchor -< children
+read_header      :: Matcher CombiningBlocks
+read_header       = matchingElement NsText "h" $ do
+  level    <- readAttrWithDefault NsText "outline-level" 1
+  children <- matchContent [ read_span
+                           , read_spaces
+                           , read_line_break
+                           , read_tab
+                           , read_link
+                           , read_note
+                           , read_citation
+                           , read_bookmark
+                           , read_bookmark_start
+                           , read_reference_start
+                           , read_bookmark_ref
+                           , read_reference_ref
+                           , read_frame
+                           ] read_plain_text
+  anchor   <- getHeaderAnchor children
   let idAttr = (anchor, [], []) -- no classes, no key-value pairs
-  arr (CombiningBlocks . uncurry3 headerWith) -< (idAttr, level, children)
+  return $ CombiningBlocks $ headerWith idAttr level children
 
 ----------------------
 -- Lists
 ----------------------
 
 --
-read_list        :: ElementMatcher CombiningBlocks
+read_list        :: Matcher CombiningBlocks
 read_list         = matchingElement NsText "list"
-                    $ liftA CombiningBlocks
-                    $ constructList
-                    $ matchChildContent' [ read_list_item
-                                         , read_list_header
-                                         ]
+                    $ CombiningBlocks
+                    <$> constructList
+                        ( matchContent' [ read_list_item
+                                        , read_list_header
+                                        ] )
 --
-read_list_item   :: ElementMatcher [Blocks]
+read_list_item   :: Matcher [Blocks]
 read_list_item    = read_list_element "list-item"
 
-read_list_header :: ElementMatcher [Blocks]
+read_list_header :: Matcher [Blocks]
 read_list_header  = read_list_element "list-header"
 
-read_list_element               :: ElementName -> ElementMatcher [Blocks]
+read_list_element               :: ElementName -> Matcher [Blocks]
 read_list_element listElement   = matchingElement NsText listElement
-                                  $ liftA (compactify.(:[]))
-                                    ( matchSmushedChildBlocks' [ read_paragraph
-                                                               , read_header
-                                                               , read_list
-                                                               , read_section
-                                                               ]
-                                    )
+                                  $ compactify . (:[])
+                                  <$> matchSmushedChildBlocks'
+                                        [ read_paragraph
+                                        , read_header
+                                        , read_list
+                                        , read_section
+                                        ]
 
 ----------------------
 -- Sections
 ----------------------
 
-read_section :: ElementMatcher CombiningBlocks
+read_section :: Matcher CombiningBlocks
 read_section = matchingElement NsText "section"
-                 $ liftA (CombiningBlocks . divWith nullAttr)
-                 $ matchSmushedChildBlocks' [ read_paragraph
-                                            , read_header
-                                            , read_list
-                                            , read_table
-                                            , read_section
-                                            ]
+                 $ CombiningBlocks . divWith nullAttr
+                 <$> matchSmushedChildBlocks' [ read_paragraph
+                                              , read_header
+                                              , read_list
+                                              , read_table
+                                              , read_section
+                                              ]
 
 
 ----------------------
@@ -760,19 +655,19 @@
 
 read_link        :: InlineMatcher
 read_link         = matchingElement NsText "a"
-                    $ liftA3 link
-                      ( findAttrTextWithDefault NsXLink  "href"  ""
-                        >>> arr fixRelativeLink                              )
-                      ( findAttrTextWithDefault NsOffice "title" ""          )
-                      ( matchChildContent [ read_span
-                                          , read_note
-                                          , read_citation
-                                          , read_bookmark
-                                          , read_bookmark_start
-                                          , read_reference_start
-                                          , read_bookmark_ref
-                                          , read_reference_ref
-                                          ] read_plain_text                  )
+                    $ link
+                      <$> (fixRelativeLink
+                             <$> findAttrWithDefault NsXLink  "href"  "")
+                      <*> findAttrWithDefault NsOffice "title" ""
+                      <*> matchContent [ read_span
+                                       , read_note
+                                       , read_citation
+                                       , read_bookmark
+                                       , read_bookmark_start
+                                       , read_reference_start
+                                       , read_bookmark_ref
+                                       , read_reference_ref
+                                       ] read_plain_text
 
 fixRelativeLink :: T.Text -> T.Text
 fixRelativeLink uri =
@@ -789,8 +684,7 @@
 
 read_note        :: InlineMatcher
 read_note         = matchingElement NsText "note"
-                    $ liftA note
-                    $ matchChildContent' [ read_note_body ]
+                    $ note <$> matchContent' [ read_note_body ]
 
 read_note_body   :: BlockMatcher
 read_note_body    = matchingElement NsText "note-body"
@@ -802,12 +696,11 @@
 
 read_citation    :: InlineMatcher
 read_citation     = matchingElement NsText "bibliography-mark"
-                    $ liftA2 cite
-                      ( liftA2 makeCitation
-                        ( findAttrTextWithDefault NsText "identifier" "" )
-                        ( readAttrWithDefault NsText "number" 0          )
-                      )
-                      ( matchChildContent [] read_plain_text             )
+                    $ cite
+                      <$> ( makeCitation
+                              <$> findAttrWithDefault NsText "identifier" ""
+                              <*> readAttrWithDefault NsText "number" 0 )
+                      <*> matchContent [] read_plain_text
   where
    makeCitation :: T.Text -> Int -> [Citation]
    makeCitation citeId num = [Citation citeId [] [] NormalCitation num 0]
@@ -818,16 +711,16 @@
 ----------------------
 
 --
-read_table        :: ElementMatcher CombiningBlocks
+read_table        :: Matcher CombiningBlocks
 read_table         = matchingElement NsTable "table"
-                     $ liftA (CombiningBlocks . table')
-                     $ (matchChildContent' [read_table_header]) &&&
-                       (matchChildContent' [read_table_row])
+                     $ fmap (CombiningBlocks . table')
+                     $ (,) <$> matchContent' [read_table_header]
+                           <*> matchContent' [read_table_row]
 
 -- | A table without a caption.
 table' :: ([[Cell]], [[Cell]]) -> Blocks
-table' (headers, rows) =
-  table emptyCaption (replicate numcols defaults) th [tb] tf
+table' (headers, rows) = compactifyTable $
+    table emptyCaption (replicate numcols defaults) th [tb] tf
   where
     defaults = (AlignDefault, ColWidthDefault)
     numcols = maximum $ map length $ headers ++ rows
@@ -837,27 +730,27 @@
     tf = TableFoot nullAttr []
 
 --
-read_table_header :: ElementMatcher [[Cell]]
+read_table_header :: Matcher [[Cell]]
 read_table_header = matchingElement NsTable "table-header-rows"
-                      $ matchChildContent' [ read_table_row
-                                           ]
+                      $ matchContent' [ read_table_row
+                                      ]
 
 --
-read_table_row    :: ElementMatcher [[Cell]]
+read_table_row    :: Matcher [[Cell]]
 read_table_row     = matchingElement NsTable "table-row"
-                     $ liftA (:[])
-                     $ matchChildContent'  [ read_table_cell
-                                           ]
+                     $ (:[])
+                     <$> matchContent' [ read_table_cell
+                                       ]
 
 --
-read_table_cell   :: ElementMatcher [Cell]
+read_table_cell   :: Matcher [Cell]
 read_table_cell    = matchingElement NsTable "table-cell"
-                     $ liftA3 cell'
-                       (readAttrWithDefault NsTable "number-rows-spanned" 1 >>^ RowSpan)
-                       (readAttrWithDefault NsTable "number-columns-spanned" 1 >>^ ColSpan)
-                     $ matchSmushedChildBlocks' [ read_paragraph
-                                                , read_list
-                                                ]
+                     $ cell'
+                       <$> (RowSpan <$> readAttrWithDefault NsTable "number-rows-spanned" 1)
+                       <*> (ColSpan <$> readAttrWithDefault NsTable "number-columns-spanned" 1)
+                       <*> matchSmushedChildBlocks' [ read_paragraph
+                                                    , read_list
+                                                    ]
   where
     cell' rowSpan colSpan blocks = map (cell AlignDefault rowSpan colSpan) $ compactify [blocks]
 
@@ -867,39 +760,39 @@
 
 --
 read_frame :: InlineMatcher
-read_frame = matchingElement NsDraw "frame"
-             $ filterChildrenName' NsDraw (`elem` ["image", "object", "text-box"])
-           >>> foldS read_frame_child
-           >>> arr fold
+read_frame = matchingElement NsDraw "frame" $ do
+  children <- filterChildrenName' NsDraw (`elem` ["image", "object", "text-box"])
+  fold . mconcat <$> mapM read_frame_child children
 
-read_frame_child :: ODTReaderSafe XML.Element (FirstMatch Inlines)
-read_frame_child =
-  proc child -> case elName child of
-    "image"    -> read_frame_img      -< child
-    "object"   -> read_frame_mathml   -< child
-    "text-box" -> read_frame_text_box -< child
-    _          -> returnV mempty      -< ()
+read_frame_child :: XML.Element -> ODTReader (FirstMatch Inlines)
+read_frame_child child =
+  case elName child of
+    "image"    -> read_frame_img      child
+    "object"   -> read_frame_mathml   child
+    "text-box" -> read_frame_text_box child
+    _          -> return mempty
 
-read_frame_img :: ODTReaderSafe XML.Element (FirstMatch Inlines)
-read_frame_img =
-  proc img -> do
-    src <- executeIn (findAttr' NsXLink "href") -< img
-    case fold src of
-      ""   -> returnV mempty -< ()
-      src' -> do
-        let exts = extensionsFromList [Ext_auto_identifiers]
-            src'' = fixRelativeLink src'
-        resource   <- lookupResource                          -< T.unpack src''
-        _          <- updateMediaWithResource                 -< resource
-        w          <- findAttrText' NsSVG "width"             -< ()
-        h          <- findAttrText' NsSVG "height"            -< ()
-        titleNodes <- matchChildContent' [ read_frame_title ] -< ()
-        alt        <- matchChildContent [] read_plain_text    -< ()
-        arr (firstMatch . uncurry4 imageWith)                 -<
-          (image_attributes w h, src'', inlineListToIdentifier exts (toList titleNodes), alt)
+read_frame_img :: XML.Element -> ODTReader (FirstMatch Inlines)
+read_frame_img img = do
+  src <- executeIn img (findAttr' NsXLink "href")
+  case fold src of
+    ""   -> return mempty
+    src' -> do
+      let exts = extensionsFromList [Ext_auto_identifiers]
+          src'' = fixRelativeLink src'
+      resource   <- lookupResource (T.unpack src'')
+      updateMediaWithResource resource
+      w          <- findAttr' NsSVG "width"
+      h          <- findAttr' NsSVG "height"
+      titleNodes <- matchContent' [ read_frame_title ]
+      alt        <- matchContent [] read_plain_text
+      return $ firstMatch
+             $ imageWith (image_attributes w h) src''
+                         (inlineListToIdentifier exts (toList titleNodes))
+                         alt
 
 read_frame_title :: InlineMatcher
-read_frame_title = matchingElement NsSVG "title" (matchChildContent [] read_plain_text)
+read_frame_title = matchingElement NsSVG "title" (matchContent [] read_plain_text)
 
 image_attributes :: Maybe T.Text -> Maybe T.Text -> Attr
 image_attributes x y =
@@ -909,24 +802,23 @@
     dim name (Just v) = [(name, v)]
     dim _ Nothing     = []
 
-read_frame_mathml :: ODTReaderSafe XML.Element (FirstMatch Inlines)
-read_frame_mathml =
-  proc obj -> do
-    src <- executeIn (findAttr' NsXLink "href") -< obj
-    case fold src of
-      ""   -> returnV mempty -< ()
-      src' -> do
-        let path = T.unpack $
-                    fromMaybe src' (T.stripPrefix "./" src') <> "/content.xml"
-        (_, mathml) <- lookupResource -< path
-        case readMathML (UTF8.toText $ B.toStrict mathml) of
-          Left _     -> returnV mempty -< ()
-          Right exps -> arr (firstMatch . displayMath . writeTeX) -< exps
+read_frame_mathml :: XML.Element -> ODTReader (FirstMatch Inlines)
+read_frame_mathml obj = do
+  src <- executeIn obj (findAttr' NsXLink "href")
+  case fold src of
+    ""   -> return mempty
+    src' -> do
+      let path = T.unpack $
+                  fromMaybe src' (T.stripPrefix "./" src') <> "/content.xml"
+      (_, mathml) <- lookupResource path
+      case readMathML (UTF8.toText $ B.toStrict mathml) of
+        Left _     -> return mempty
+        Right exps -> return $ firstMatch $ displayMath $ writeTeX exps
 
-read_frame_text_box :: ODTReaderSafe XML.Element (FirstMatch Inlines)
-read_frame_text_box = proc box -> do
-    paragraphs <- executeIn (matchSmushedChildBlocks' [ read_paragraph ]) -< box
-    arr read_img_with_caption -< toList paragraphs
+read_frame_text_box :: XML.Element -> ODTReader (FirstMatch Inlines)
+read_frame_text_box box = do
+  paragraphs <- executeIn box (matchSmushedChildBlocks' [ read_paragraph ])
+  return $ read_img_with_caption $ toList paragraphs
 
 read_img_with_caption :: [Block] -> FirstMatch Inlines
 read_img_with_caption (Para [Image attr alt (src,title)] : _) =
@@ -946,26 +838,20 @@
 _ANCHOR_PREFIX_ = "anchor"
 
 --
-readAnchorAttr :: ODTReader a Anchor
-readAnchorAttr = findAttrText NsText "name"
+readAnchorAttr :: ODTReader Anchor
+readAnchorAttr = findAttr NsText "name"
 
 -- | Beware: may fail
-findAnchorName :: ODTReader AnchorPrefix Anchor
-findAnchorName = (      keepingTheValue readAnchorAttr
-                   >>^  spreadChoice
-                 ) >>?! getPrettyAnchor
-
+findAnchorName :: AnchorPrefix -> ODTReader Anchor
+findAnchorName anchorPrefix = do
+  uglyAnchor <- readAnchorAttr
+  getPrettyAnchor anchorPrefix uglyAnchor
 
 --
-maybeAddAnchorFrom :: ODTReader Inlines AnchorPrefix
-                   -> ODTReaderSafe Inlines Inlines
+maybeAddAnchorFrom :: ODTReader AnchorPrefix -> ODTReader Inlines
 maybeAddAnchorFrom anchorReader =
-  keepingTheValue (anchorReader >>? findAnchorName >>?^ toAnchorElem)
-  >>>
-  proc (inlines, fAnchorElem) -> do
-  case fAnchorElem of
-    Right anchorElem -> returnA -< anchorElem
-    Left _           -> returnA -< inlines
+      (toAnchorElem <$> (anchorReader >>= findAnchorName))
+  <|> return mempty
   where
     toAnchorElem :: Anchor -> Inlines
     toAnchorElem anchorID = spanWith (anchorID, [], []) mempty
@@ -974,12 +860,12 @@
 --
 read_bookmark     :: InlineMatcher
 read_bookmark      = matchingElement NsText "bookmark"
-                     $ maybeAddAnchorFrom (liftAsSuccess $ returnV _ANCHOR_PREFIX_)
+                     $ maybeAddAnchorFrom (return _ANCHOR_PREFIX_)
 
 --
 read_bookmark_start :: InlineMatcher
 read_bookmark_start = matchingElement NsText "bookmark-start"
-                     $ maybeAddAnchorFrom (liftAsSuccess $ returnV _ANCHOR_PREFIX_)
+                     $ maybeAddAnchorFrom (return _ANCHOR_PREFIX_)
 
 --
 read_reference_start :: InlineMatcher
@@ -987,20 +873,18 @@
                      $ maybeAddAnchorFrom readAnchorAttr
 
 -- | Beware: may fail
-findAnchorRef :: ODTReader a Anchor
-findAnchorRef = (      findAttrText NsText "ref-name"
-                  >>?^ (_ANCHOR_PREFIX_,)
-                ) >>?! getPrettyAnchor
-
+findAnchorRef :: ODTReader Anchor
+findAnchorRef = do
+  uglyAnchor <- findAttr NsText "ref-name"
+  getPrettyAnchor _ANCHOR_PREFIX_ uglyAnchor
 
 --
-maybeInAnchorRef :: ODTReaderSafe Inlines Inlines
-maybeInAnchorRef = proc inlines -> do
-  fRef <- findAnchorRef -< ()
+maybeInAnchorRef :: Inlines -> ODTReader Inlines
+maybeInAnchorRef inlines = do
+  fRef <- tryC findAnchorRef
   case fRef of
-    Right anchor ->
-      arr (toAnchorRef anchor) -<< inlines
-    Left _ -> returnA -< inlines
+    Right anchor -> return $ toAnchorRef anchor inlines
+    Left _       -> return inlines
   where
     toAnchorRef :: Anchor -> Inlines -> Inlines
     toAnchorRef anchor = link ("#" <> anchor) "" -- no title
@@ -1008,28 +892,25 @@
 --
 read_bookmark_ref :: InlineMatcher
 read_bookmark_ref = matchingElement NsText "bookmark-ref"
-                    $    maybeInAnchorRef
-                     <<< matchChildContent [] read_plain_text
+                    $ matchContent [] read_plain_text >>= maybeInAnchorRef
 
 --
 read_reference_ref :: InlineMatcher
 read_reference_ref = matchingElement NsText "reference-ref"
-                    $    maybeInAnchorRef
-                     <<< matchChildContent [] read_plain_text
+                    $ matchContent [] read_plain_text >>= maybeInAnchorRef
 
 
 ----------------------
 -- Entry point
 ----------------------
 
-read_text :: ODTReaderSafe a Pandoc
-read_text = matchSmushedChildBlocks' [ read_header
-                                     , read_paragraph
-                                     , read_list
-                                     , read_section
-                                     , read_table
-                                     ]
-            >>^ doc
+read_text :: ODTReader Pandoc
+read_text = doc <$> matchSmushedChildBlocks' [ read_header
+                                             , read_paragraph
+                                             , read_list
+                                             , read_section
+                                             , read_table
+                                             ]
 
 post_process :: Pandoc -> Pandoc
 post_process (Pandoc m blocks) =
@@ -1040,11 +921,10 @@
   = Table attr (Caption Nothing blks) specs th tb tf : post_process' xs
 post_process' bs = bs
 
-read_body :: ODTReader a (Pandoc, MediaBag)
+read_body :: ODTReader (Pandoc, MediaBag)
 read_body = executeInSub NsOffice "body"
           $ executeInSub NsOffice "text"
-          $ liftAsSuccess
-          $ proc inlines -> do
-             txt   <- read_text     -< inlines
-             state <- getExtraState -< ()
-             returnA                -< (post_process txt, getMediaBag state)
+          $ do
+             txt   <- read_text
+             state <- getExtraState
+             return (post_process txt, getMediaBag state)
diff --git a/src/Text/Pandoc/Readers/ODT/Generic/Fallible.hs b/src/Text/Pandoc/Readers/ODT/Generic/Fallible.hs
--- a/src/Text/Pandoc/Readers/ODT/Generic/Fallible.hs
+++ b/src/Text/Pandoc/Readers/ODT/Generic/Fallible.hs
@@ -9,28 +9,13 @@
 
 Data types and utilities representing failure. Most of it is based on the
 "Either" type in its usual configuration (left represents failure).
-
-In most cases, the failure type is implied or required to be a "Monoid".
-
-The choice of "Either" instead of a custom type makes it easier to write
-compatible instances of "ArrowChoice".
 -}
 
--- We export everything
 module Text.Pandoc.Readers.ODT.Generic.Fallible
   ( Failure
   , Fallible
-  , maybeToEither
-  , eitherToMaybe
-  , recover
-  , failWith
-  , failEmpty
-  , succeedWith
-  , collapseEither
   , chooseMax
   , chooseMaxWith
-  , ChoiceVector(..)
-  , SuccessList(..)
   ) where
 
 -- | Default for now. Will probably become a class at some point.
@@ -38,41 +23,6 @@
 
 type Fallible a = Either Failure a
 
-
---
-maybeToEither :: Maybe a -> Fallible a
-maybeToEither (Just a) = Right a
-maybeToEither Nothing  = Left  ()
-
---
-eitherToMaybe :: Either _l a -> Maybe a
-eitherToMaybe (Left  _) = Nothing
-eitherToMaybe (Right a) = Just a
-
--- | > recover a === either (const a) id
-recover :: a -> Either _f a -> a
-recover a (Left  _) = a
-recover _ (Right a) = a
-
--- | I would love to use 'fail'. Alas, 'Monad.fail'...
-failWith :: failure -> Either failure _x
-failWith f = Left f
-
---
-failEmpty :: (Monoid failure) => Either failure _x
-failEmpty = failWith mempty
-
---
-succeedWith :: a -> Either _x a
-succeedWith = Right
-
---
-collapseEither :: Either failure (Either failure x)
-               -> Either failure x
-collapseEither (Left f         ) = Left f
-collapseEither (Right (Left  f)) = Left f
-collapseEither (Right (Right x)) = Right x
-
 -- | If either of the values represents a  non-error, the result is a
 -- (possibly combined) non-error. If both values represent an error, an error
 -- is returned.
@@ -90,24 +40,3 @@
 chooseMaxWith  _   (Left  a) (Left  b) = Left  $ a `mappend` b
 chooseMaxWith  _   (Right a)     _     = Right a
 chooseMaxWith  _       _     (Right b) = Right b
-
-
--- | Class of containers that can escalate contained 'Either's.
--- The word "Vector" is meant in the sense of a disease transmitter.
-class ChoiceVector v where
-  spreadChoice :: v (Either f a) -> Either f (v a)
-
-instance ChoiceVector ((,) a) where
-  spreadChoice (_, Left  f) = Left  f
-  spreadChoice (x, Right y) = Right (x,y)
-  -- Wasn't there a newtype somewhere with the elements flipped?
-
--- | Wrapper for a list. While the normal list instance of 'ChoiceVector'
--- fails whenever it can, this type will never fail.
-newtype SuccessList a = SuccessList { collectNonFailing :: [a] }
-  deriving ( Eq, Ord, Show )
-
-instance ChoiceVector SuccessList  where
-  spreadChoice = Right . SuccessList . foldr unTagRight [] . collectNonFailing
-    where unTagRight (Right x) = (x:)
-          unTagRight _         = id
diff --git a/src/Text/Pandoc/Readers/ODT/Generic/Utils.hs b/src/Text/Pandoc/Readers/ODT/Generic/Utils.hs
--- a/src/Text/Pandoc/Readers/ODT/Generic/Utils.hs
+++ b/src/Text/Pandoc/Readers/ODT/Generic/Utils.hs
@@ -12,71 +12,24 @@
 -}
 
 module Text.Pandoc.Readers.ODT.Generic.Utils
-( uncurry3
-, uncurry4
-, uncurry5
-, uncurry6
-, swap
-, reverseComposition
-, tryToRead
+( tryToRead
 , Lookupable(..)
 , readLookupable
 , readPercent
 , findBy
-, swing
-, composition
 ) where
 
-import Control.Category (Category, (<<<), (>>>))
-import qualified Control.Category as Cat (id)
 import Data.Char (isSpace)
-import qualified Data.Foldable as F (Foldable, foldr)
 import Data.Maybe
 import Data.Text (Text)
 import qualified Data.Text as T
 
--- | Equivalent to
--- > foldr (.) id
--- where '(.)' are 'id' are the ones from "Control.Category"
--- and 'foldr' is the one from "Data.Foldable".
--- The noun-form was chosen to be consistent with 'sum', 'product' etc
--- based on the discussion at
--- <https://groups.google.com/forum/#!topic/haskell-cafe/VkOZM1zaHOI>
--- (that I was not part of)
-composition        :: (Category cat, F.Foldable f) => f (cat a a) -> cat a a
-composition        = F.foldr (<<<) Cat.id
-
--- | Equivalent to
--- > foldr (flip (.)) id
--- where '(.)' are 'id' are the ones from "Control.Category"
--- and 'foldr' is the one from "Data.Foldable".
--- A reversed version of 'composition'.
-reverseComposition :: (Category cat, F.Foldable f) => f (cat a a) -> cat a a
-reverseComposition = F.foldr (>>>) Cat.id
-
--- | This function often makes it possible to switch values with the functions
--- that are applied to them.
---
--- Examples:
--- > swing map :: [a -> b] -> a -> [b]
--- > swing any :: [a -> Bool] -> a -> Bool
--- > swing foldr :: b -> a -> [a -> b -> b] -> b
--- > swing scanr :: c -> a -> [a -> c -> c] -> c
--- > swing zipWith :: [a -> b -> c] -> a -> [b] -> [c]
--- > swing find :: [a -> Bool] -> a -> Maybe (a -> Bool)
---
--- Stolen from <https://wiki.haskell.org/Pointfree>
-swing :: (((a -> b) -> b) -> c -> d) -> c -> a -> d
-swing = flip.(.flip id)
--- swing f c a = f ($ a) c
-
-
 -- | Alternative to 'read'/'reads'. The former of these throws errors
 -- (nobody wants that) while the latter returns "to much" for simple purposes.
 -- This function instead applies 'reads' and returns the first match (if any)
 -- in a 'Maybe'.
 tryToRead :: (Read r) => Text -> Maybe r
-tryToRead = (reads . T.unpack) >>> listToMaybe >>> fmap fst
+tryToRead = fmap fst . listToMaybe . reads . T.unpack
 
 -- | A version of 'reads' that requires a '%' sign after the number
 readPercent :: ReadS Int
@@ -93,19 +46,6 @@
 readLookupable :: (Lookupable a) => Text -> Maybe a
 readLookupable s =
   lookup (T.takeWhile (not . isSpace) $ T.dropWhile isSpace s) lookupTable
-
-uncurry3 :: (a->b->c                -> z) -> (a,b,c          ) -> z
-uncurry4 :: (a->b->c->d             -> z) -> (a,b,c,d        ) -> z
-uncurry5 :: (a->b->c->d->e          -> z) -> (a,b,c,d,e      ) -> z
-uncurry6 :: (a->b->c->d->e->f       -> z) -> (a,b,c,d,e,f    ) -> z
-
-uncurry3 fun (a,b,c          ) = fun a b c
-uncurry4 fun (a,b,c,d        ) = fun a b c d
-uncurry5 fun (a,b,c,d,e      ) = fun a b c d e
-uncurry6 fun (a,b,c,d,e,f    ) = fun a b c d e f
-
-swap :: (a,b) -> (b,a)
-swap (a,b) = (b,a)
 
 -- | A version of "Data.List.find" that uses a converter to a Maybe instance.
 -- The returned value is the first which the converter returns in a 'Just'
diff --git a/src/Text/Pandoc/Readers/ODT/Generic/XMLConverter.hs b/src/Text/Pandoc/Readers/ODT/Generic/XMLConverter.hs
--- a/src/Text/Pandoc/Readers/ODT/Generic/XMLConverter.hs
+++ b/src/Text/Pandoc/Readers/ODT/Generic/XMLConverter.hs
@@ -1,8 +1,4 @@
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections   #-}
-{-# LANGUAGE GADTs           #-}
-{-# LANGUAGE LambdaCase      #-}
-{-# LANGUAGE PatternGuards   #-}
 {- |
    Module      : Text.Pandoc.Readers.ODT.Generic.XMLConverter
    Copyright   : Copyright (C) 2015 Martin Linnemann
@@ -12,35 +8,33 @@
    Stability   : alpha
    Portability : portable
 
-A generalized XML parser based on stateful arrows.
-It might be sufficient to define this reader as a comonad, but there is
-not a lot of use in trying.
+A generalized monadic XML parser. The parser navigates through an XML
+tree, always looking at a \"current element\", and carries some
+additional, converter-specific state.
 -}
 
 module Text.Pandoc.Readers.ODT.Generic.XMLConverter
 ( ElementName
-, XMLConverterState
 , XMLConverter
-, FallibleXMLConverter
-, runConverter'
+, runConverter
+, fromMaybeF
+, fromFallible
+, tryC
 , getExtraState
 , setExtraState
 , modifyExtraState
-, producingExtraState
-, findChild'
+, getCurrentElement
+, elName
 , filterChildrenName'
 , isSet'
 , isSetWithDefault
-, elName
 , searchAttr
 , lookupAttr
 , lookupAttr'
 , lookupDefaultingAttr
 , findAttr'
-, findAttrText'
 , findAttr
-, findAttrText
-, findAttrTextWithDefault
+, findAttrWithDefault
 , readAttr
 , readAttr'
 , readAttrWithDefault
@@ -49,271 +43,157 @@
 , executeInSub
 , withEveryL
 , tryAll
+, ElementMatcher
 , matchContent'
 , matchContent
 ) where
 
-import           Prelude hiding (Applicative(..))
-import           Control.Applicative  hiding ( liftA, liftA2 )
-import           Control.Monad               ( MonadPlus )
-import           Control.Arrow
+import Control.Applicative ( Alternative(..), optional )
+import Control.Monad ( filterM, foldM )
+import Control.Monad.Except ( ExceptT, runExceptT, throwError, catchError )
+import Control.Monad.State ( State, evalState, get, gets, put, modify )
 
-import           Data.Bool ( bool )
-import           Data.Either ( rights )
-import qualified Data.Map             as M
-import           Data.Text (Text)
-import           Data.Default
-import           Data.Maybe
-import qualified Data.List as L
-import qualified Data.List.NonEmpty as NonEmpty
-import           Data.List.NonEmpty (NonEmpty(..))
+import qualified Data.Map as M
+import Data.Text (Text)
+import Data.Default
+import Data.Maybe
 
 import qualified Text.Pandoc.XML.Light as XML
 
-import           Text.Pandoc.Readers.ODT.Arrows.State
-import           Text.Pandoc.Readers.ODT.Arrows.Utils
-import           Text.Pandoc.Readers.ODT.Generic.Namespaces
-import           Text.Pandoc.Readers.ODT.Generic.Utils
-import           Text.Pandoc.Readers.ODT.Generic.Fallible
+import Text.Pandoc.Readers.ODT.Generic.Namespaces
+import Text.Pandoc.Readers.ODT.Generic.Utils
+import Text.Pandoc.Readers.ODT.Generic.Fallible
 
 --------------------------------------------------------------------------------
 --  Basis types for readability
 --------------------------------------------------------------------------------
 
---
 type ElementName           = Text
 type AttributeName         = Text
 type AttributeValue        = Text
-type TextAttributeValue    = Text
 
---
 type NameSpacePrefix       = Text
 
---
-type NameSpacePrefixes nsID = M.Map nsID NameSpacePrefix
-
 --------------------------------------------------------------------------------
--- Main converter state
+-- Converter state
 --------------------------------------------------------------------------------
 
--- GADT so some of the NameSpaceID restrictions can be deduced
-data XMLConverterState nsID extraState where
-  XMLConverterState :: NameSpaceID nsID
-    => { -- | A stack of parent elements. The top element is the current one.
-         -- Arguably, a real Zipper would be better. But that is an
-         -- optimization that can be made at a later time, e.g. when
-         -- replacing Text.XML.Light.
-         parentElements    :: NonEmpty XML.Element
-         -- | A map from internal namespace IDs to the namespace prefixes
-         -- used in XML elements
-       , namespacePrefixes :: NameSpacePrefixes nsID
-         -- | A map from internal namespace IDs to namespace IRIs
-         -- (Only necessary for matching namespace IDs and prefixes)
-       , namespaceIRIs     :: NameSpaceIRIs nsID
-         -- | A place to put "something else". This feature is used heavily
-         -- to keep the main code cleaner. More specifically, the main reader
-         -- is divided into different stages. Each stage lifts something up
-         -- here, which the next stage can then use. This could of course be
-         -- generalized to a state-tree or used for the namespace IRIs. The
-         -- border between states and values is an imaginary one, after all.
-         -- But the separation as it is seems to be enough for now.
-       , moreState         :: extraState
-       }
-    -> XMLConverterState nsID extraState
+data XMLConverterState nsID extraState = XMLConverterState
+  { -- | The element that is currently being read
+    currentElement    :: XML.Element
+    -- | A map from internal namespace IDs to the namespace prefixes
+    -- used in XML elements
+  , namespacePrefixes :: M.Map nsID NameSpacePrefix
+    -- | A map from internal namespace IDs to namespace IRIs
+    -- (Only necessary for matching namespace IDs and prefixes)
+  , namespaceIRIs     :: NameSpaceIRIs nsID
+    -- | Converter-specific state
+  , moreState         :: extraState
+  }
 
---
 createStartState :: (NameSpaceID nsID)
-                    => XML.Element
-                    -> extraState
-                    -> XMLConverterState nsID extraState
+                 => XML.Element
+                 -> extraState
+                 -> XMLConverterState nsID extraState
 createStartState element extraState =
   XMLConverterState
-       { parentElements    = element :| []
+       { currentElement    = element
        , namespacePrefixes = M.empty
        , namespaceIRIs     = getInitialIRImap
        , moreState         = extraState
        }
 
--- | Functor over extra state
-instance Functor (XMLConverterState nsID) where
-  fmap f ( XMLConverterState parents prefixes iRIs    extraState  )
-       =   XMLConverterState parents prefixes iRIs (f extraState)
-
---
-replaceExtraState   :: extraState
-                    -> XMLConverterState nsID _x
-                    -> XMLConverterState nsID extraState
-replaceExtraState x s
-                     = fmap (const x) s
-
---
-currentElement      :: XMLConverterState nsID extraState
-                    -> XML.Element
-currentElement state = NonEmpty.head (parentElements state)
-
--- | Replace the current position by another, modifying the extra state
--- in the process
-swapStack'          :: XMLConverterState nsID extraState
-                    -> NonEmpty XML.Element
-                    -> ( XMLConverterState nsID extraState
-                       , NonEmpty XML.Element )
-swapStack' state stack
-                     = ( state { parentElements = stack }
-                       , parentElements state
-                       )
-
---
-pushElement         :: XML.Element
-                    -> XMLConverterState nsID extraState
-                    -> XMLConverterState nsID extraState
-pushElement e state  = state { parentElements =
-                                 NonEmpty.cons e (parentElements state) }
-
--- | Pop the top element from the call stack, unless it is the last one.
-popElement          :: XMLConverterState nsID extraState
-                    -> Maybe (XMLConverterState nsID extraState)
-popElement state
-  | _ :| (e:es) <- parentElements state
-                = Just $ state { parentElements = e :| es }
-  | otherwise   = Nothing
-
 --------------------------------------------------------------------------------
 -- Main type
 --------------------------------------------------------------------------------
 
--- It might be a good idea to pack the converters in a GADT
--- Downside: data instead of type
--- Upside: 'Failure' could be made a parameter as well.
+-- | A converter that can read from an XML tree, may fail (with
+-- 'throwError' \/ 'empty'), and carries some additional state.
+-- Note that state modifications survive failure; in particular, the
+-- current element must be restored explicitly where necessary
+-- (see 'executeIn').
+type XMLConverter nsID extraState
+   = ExceptT () (State (XMLConverterState nsID extraState))
 
---
-type XMLConverter nsID extraState input output
-      = ArrowState (XMLConverterState nsID extraState ) input output
+-- | Run a converter on an XML element, with a given initial extra state.
+runConverter :: (NameSpaceID nsID)
+             => XMLConverter nsID extraState a
+             -> extraState
+             -> XML.Element
+             -> Fallible a
+runConverter converter extraState element
+  = evalState (runExceptT (readNSattributes >> converter))
+              (createStartState element extraState)
 
-type FallibleXMLConverter nsID extraState input output
-     = XMLConverter nsID extraState input (Fallible output)
+-- | Lift a 'Maybe' value into the converter, failing on 'Nothing'.
+fromMaybeF :: Maybe a -> XMLConverter nsID extraState a
+fromMaybeF = maybe (throwError ()) return
 
---
-runConverter     :: XMLConverter nsID extraState input output
-                 -> XMLConverterState nsID extraState
-                 -> input
-                 -> output
-runConverter converter state input = snd $ runArrowState converter (state,input)
+-- | Lift a 'Fallible' value into the converter.
+fromFallible :: Fallible a -> XMLConverter nsID extraState a
+fromFallible = either throwError return
 
-runConverter' :: (NameSpaceID nsID)
-              => FallibleXMLConverter nsID extraState () success
-              -> extraState
-              -> XML.Element
-              -> Fallible success
-runConverter' converter extraState element = runConverter (readNSattributes >>? converter) (createStartState element extraState) ()
+-- | Run a converter, catching failure.
+tryC :: XMLConverter nsID extraState a
+     -> XMLConverter nsID extraState (Fallible a)
+tryC converter = catchError (Right <$> converter) (return . Left)
 
 --
-getCurrentElement :: XMLConverter nsID extraState x XML.Element
-getCurrentElement  = extractFromState currentElement
+getCurrentElement :: XMLConverter nsID extraState XML.Element
+getCurrentElement = gets currentElement
 
 --
-getExtraState     :: XMLConverter nsID extraState x extraState
-getExtraState      = extractFromState moreState
+getExtraState :: XMLConverter nsID extraState extraState
+getExtraState = gets moreState
 
 --
-setExtraState     :: XMLConverter nsID extraState extraState extraState
-setExtraState      = withState $ \state extra
-                                  -> (replaceExtraState extra state , extra)
-
-
--- | Lifts a function to the extra state.
-modifyExtraState  :: (extraState -> extraState)
-                  -> XMLConverter nsID extraState x x
-modifyExtraState   = modifyState.fmap
-
+setExtraState :: extraState -> XMLConverter nsID extraState ()
+setExtraState x = modify $ \state -> state { moreState = x }
 
--- | First sets the extra state to the new value. Then modifies the original
--- extra state with a converter that uses the new state. Finally, the
--- intermediate state is dropped and the extra state is lifted into the
--- state as it was at the beginning of the function.
--- As a result, exactly the extra state and nothing else is changed.
--- The resulting converter even behaves like an identity converter on the
--- value level.
 --
--- (The -ing form is meant to be mnemonic in a sequence of arrows as in
---  convertingExtraState () converter >>> doOtherStuff)
---
-convertingExtraState :: extraState'
-                     -> FallibleXMLConverter nsID extraState' extraState extraState
-                     -> FallibleXMLConverter nsID extraState x x
-convertingExtraState v a = withSubStateF setVAsExtraState modifyWithA
-  where
-    setVAsExtraState     = liftAsSuccess $ extractFromState id >>^ replaceExtraState v
-    modifyWithA          = keepingTheValue (moreState ^>> a)
-                           >>^ spreadChoice >>?% flip replaceExtraState
-
--- | First sets the extra state to the new value. Then produces a new
--- extra state with a converter that uses the new state. Finally, the
--- intermediate state is dropped and the extra state is lifted into the
--- state as it was at the beginning of the function.
--- As a result, exactly the extra state and nothing else is changed.
--- The resulting converter even behaves like an identity converter on the
--- value level.
---
--- Equivalent to
---
--- > \v x a -> convertingExtraState v (returnV x >>> a)
---
--- (The -ing form is meant to be mnemonic in a sequence of arrows as in
---  producingExtraState () () producer >>> doOtherStuff)
---
-producingExtraState  :: extraState'
-                     -> a
-                     -> FallibleXMLConverter nsID extraState' a extraState
-                     -> FallibleXMLConverter nsID extraState x x
-producingExtraState v x a = convertingExtraState v (returnV x >>> a)
-
+modifyExtraState :: (extraState -> extraState)
+                 -> XMLConverter nsID extraState ()
+modifyExtraState f = modify $ \state -> state { moreState = f (moreState state) }
 
 --------------------------------------------------------------------------------
 -- Work in namespaces
 --------------------------------------------------------------------------------
 
--- | Arrow version of 'getIRI'
-lookupNSiri             :: (NameSpaceID nsID)
-                        => nsID
-                        -> XMLConverter nsID extraState x (Maybe NameSpaceIRI)
-lookupNSiri nsID        = extractFromState
-                          $ \state -> getIRI nsID $ namespaceIRIs state
+--
+lookupNSiri :: (NameSpaceID nsID)
+            => nsID
+            -> XMLConverter nsID extraState (Maybe NameSpaceIRI)
+lookupNSiri nsID = gets $ getIRI nsID . namespaceIRIs
 
 --
-lookupNSprefix           :: (NameSpaceID nsID)
-                         => nsID
-                         -> XMLConverter nsID extraState x (Maybe NameSpacePrefix)
-lookupNSprefix nsID      = extractFromState
-                           $ \state -> M.lookup nsID $ namespacePrefixes state
+lookupNSprefix :: (NameSpaceID nsID)
+               => nsID
+               -> XMLConverter nsID extraState (Maybe NameSpacePrefix)
+lookupNSprefix nsID = gets $ M.lookup nsID . namespacePrefixes
 
 -- | Extracts namespace attributes from the current element and tries to
 -- update the current mapping accordingly
-readNSattributes         :: (NameSpaceID nsID)
-                         => FallibleXMLConverter nsID extraState x ()
-readNSattributes         = fromState $ \state -> maybe (state, failEmpty     )
-                                                       (     , succeedWith ())
-                                                       (extractNSAttrs state )
+readNSattributes :: (NameSpaceID nsID) => XMLConverter nsID extraState ()
+readNSattributes = do
+  state <- get
+  maybe (throwError ()) put (extractNSAttrs state)
   where
-    extractNSAttrs       :: (NameSpaceID nsID)
-                         => XMLConverterState nsID extraState
-                         -> Maybe (XMLConverterState nsID extraState)
-    extractNSAttrs startState
-                         = L.foldl' (\state d -> state >>= addNS d)
-                                 (Just startState)
-                                 nsAttribs
-      where nsAttribs    = mapMaybe readNSattr (XML.elAttribs element)
-            element      = currentElement startState
+    extractNSAttrs :: (NameSpaceID nsID)
+                   => XMLConverterState nsID extraState
+                   -> Maybe (XMLConverterState nsID extraState)
+    extractNSAttrs startState = foldM addNS startState nsAttribs
+      where nsAttribs    = mapMaybe readNSattr
+                                    (XML.elAttribs $ currentElement startState)
             readNSattr (XML.Attr (XML.QName name _ (Just "xmlns")) iri)
                          = Just (name, iri)
             readNSattr _ = Nothing
-    addNS  (prefix, iri) state = fmap updateState
-                                 $ getNamespaceID iri
-                                 $ namespaceIRIs state
-      where updateState (iris,nsID)
-                         = state { namespaceIRIs     = iris
-                                 , namespacePrefixes = M.insert nsID prefix
-                                                       $ namespacePrefixes state
-                                 }
+    addNS state (prefix, iri) = updateState
+                                <$> getNamespaceID iri (namespaceIRIs state)
+      where updateState (iris, nsID)
+              = state { namespaceIRIs     = iris
+                      , namespacePrefixes = M.insert nsID prefix
+                                            $ namespacePrefixes state
+                      }
 
 --------------------------------------------------------------------------------
 -- Common namespace accessors
@@ -321,29 +201,30 @@
 
 -- | Given a namespace id and an element name, creates a 'XML.QName' for
 -- internal use
-qualifyName              :: (NameSpaceID nsID)
-                         => nsID -> ElementName
-                         -> XMLConverter nsID extraState x XML.QName
-qualifyName nsID name    =         lookupNSiri nsID
-                               &&& lookupNSprefix nsID
-                           >>% XML.QName name
+qualifyName :: (NameSpaceID nsID)
+            => nsID -> ElementName
+            -> XMLConverter nsID extraState XML.QName
+qualifyName nsID name = XML.QName name <$> lookupNSiri nsID
+                                       <*> lookupNSprefix nsID
 
 -- | Checks if a given element matches both a specified namespace id
 -- and a predicate
-elemNameMatches          :: (NameSpaceID nsID)
-                         => nsID -> (ElementName -> Bool)
-                         -> XMLConverter nsID extraState XML.Element Bool
-elemNameMatches nsID f    = keepingTheValue (lookupNSiri nsID) >>% hasMatchingName
-  where hasMatchingName e iri = let name = XML.elName e
-                                in     f (XML.qName name)
-                                    && XML.qURI name == iri
+elemNameMatches :: (NameSpaceID nsID)
+                => nsID -> (ElementName -> Bool)
+                -> XML.Element
+                -> XMLConverter nsID extraState Bool
+elemNameMatches nsID f element = do
+  iri <- lookupNSiri nsID
+  let name = XML.elName element
+  return $ f (XML.qName name) && XML.qURI name == iri
 
 -- | Checks if a given element matches both a specified namespace id
 -- and a specified element name
-elemNameIs               :: (NameSpaceID nsID)
-                         => nsID -> ElementName
-                         -> XMLConverter nsID extraState XML.Element Bool
-elemNameIs nsID name     = elemNameMatches nsID (== name)
+elemNameIs :: (NameSpaceID nsID)
+           => nsID -> ElementName
+           -> XML.Element
+           -> XMLConverter nsID extraState Bool
+elemNameIs nsID name = elemNameMatches nsID (== name)
 
 --------------------------------------------------------------------------------
 -- General content
@@ -352,394 +233,245 @@
 elName :: XML.Element -> ElementName
 elName = XML.qName . XML.elName
 
---
-elContent               :: XMLConverter nsID extraState x [XML.Content]
-elContent               =     getCurrentElement
-                           >>^ XML.elContent
-
 --------------------------------------------------------------------------------
 -- Children
 --------------------------------------------------------------------------------
 
 --
---
-findChildren             :: (NameSpaceID nsID)
-                         => nsID -> ElementName
-                         -> XMLConverter nsID extraState x [XML.Element]
-findChildren nsID name   =         qualifyName nsID name
-                               &&& getCurrentElement
-                           >>% XML.findChildren
+findChildren :: (NameSpaceID nsID)
+             => nsID -> ElementName
+             -> XMLConverter nsID extraState [XML.Element]
+findChildren nsID name = XML.findChildren <$> qualifyName nsID name
+                                          <*> getCurrentElement
 
 --
-findChild'              :: (NameSpaceID nsID)
-                        => nsID
-                        -> ElementName
-                        -> XMLConverter nsID extraState x (Maybe XML.Element)
-findChild' nsID name    =         qualifyName nsID name
-                              &&& getCurrentElement
-                          >>% XML.findChild
+findChild' :: (NameSpaceID nsID)
+           => nsID -> ElementName
+           -> XMLConverter nsID extraState (Maybe XML.Element)
+findChild' nsID name = XML.findChild <$> qualifyName nsID name
+                                     <*> getCurrentElement
 
 --
-findChild              :: (NameSpaceID nsID)
-                       => nsID -> ElementName
-                       -> FallibleXMLConverter nsID extraState x XML.Element
-findChild nsID name    =     findChild' nsID name
-                         >>> maybeToChoice
+findChild :: (NameSpaceID nsID)
+          => nsID -> ElementName
+          -> XMLConverter nsID extraState XML.Element
+findChild nsID name = findChild' nsID name >>= fromMaybeF
 
-filterChildrenName'        :: (NameSpaceID nsID)
-                           => nsID
-                           -> (ElementName -> Bool)
-                           -> XMLConverter nsID extraState x [XML.Element]
-filterChildrenName' nsID f =     getCurrentElement
-                             >>> arr XML.elChildren
-                             >>> iterateS (keepingTheValue (elemNameMatches nsID f))
-                             >>> arr (map fst . filter snd)
+--
+filterChildrenName' :: (NameSpaceID nsID)
+                    => nsID
+                    -> (ElementName -> Bool)
+                    -> XMLConverter nsID extraState [XML.Element]
+filterChildrenName' nsID f = getCurrentElement
+                             >>= filterM (elemNameMatches nsID f) . XML.elChildren
 
 --------------------------------------------------------------------------------
 -- Attributes
 --------------------------------------------------------------------------------
 
 --
-isSet'                   :: (NameSpaceID nsID)
-                         => nsID -> AttributeName
-                         -> XMLConverter nsID extraState x (Maybe Bool)
-isSet' nsID attrName     =     findAttr' nsID attrName
-                           >>^ (>>= stringToBool')
+isSet' :: (NameSpaceID nsID)
+       => nsID -> AttributeName
+       -> XMLConverter nsID extraState (Maybe Bool)
+isSet' nsID attrName = (>>= stringToBool') <$> findAttr' nsID attrName
 
-isSetWithDefault         :: (NameSpaceID nsID)
-                         => nsID -> AttributeName
-                         -> Bool
-                         -> XMLConverter nsID extraState x Bool
-isSetWithDefault nsID attrName def'
-                         =     isSet' nsID attrName
-                           >>^ fromMaybe def'
+isSetWithDefault :: (NameSpaceID nsID)
+                 => nsID -> AttributeName
+                 -> Bool
+                 -> XMLConverter nsID extraState Bool
+isSetWithDefault nsID attrName def' =
+  fromMaybe def' <$> isSet' nsID attrName
 
 -- | Lookup value in a dictionary, fail if no attribute found or value
 -- not in dictionary
-searchAttrIn             :: (NameSpaceID nsID)
-                         => nsID -> AttributeName
-                         -> [(AttributeValue,a)]
-                         -> FallibleXMLConverter nsID extraState x a
-searchAttrIn nsID attrName dict
-                         =       findAttr nsID attrName
-                           >>?^? maybeToChoice.(`lookup` dict )
+searchAttrIn :: (NameSpaceID nsID)
+             => nsID -> AttributeName
+             -> [(AttributeValue,a)]
+             -> XMLConverter nsID extraState a
+searchAttrIn nsID attrName dict = do
+  value <- findAttr nsID attrName
+  fromMaybeF $ lookup value dict
 
 -- | Lookup value in a dictionary. If attribute or value not found,
 -- return default value
-searchAttr               :: (NameSpaceID nsID)
-                         => nsID -> AttributeName
-                         -> a
-                         -> [(AttributeValue,a)]
-                         -> XMLConverter nsID extraState x a
-searchAttr nsID attrName defV dict
-                         =     searchAttrIn nsID attrName dict
-                           >>> const defV ^|||^ id
+searchAttr :: (NameSpaceID nsID)
+           => nsID -> AttributeName
+           -> a
+           -> [(AttributeValue,a)]
+           -> XMLConverter nsID extraState a
+searchAttr nsID attrName defV dict =
+  searchAttrIn nsID attrName dict <|> return defV
 
 -- | Read a 'Lookupable' attribute. Fail if no match.
-lookupAttr               :: (NameSpaceID nsID, Lookupable a)
-                         => nsID -> AttributeName
-                         -> FallibleXMLConverter nsID extraState x a
-lookupAttr nsID attrName =     lookupAttr' nsID attrName
-                           >>^ maybeToChoice
-
+lookupAttr :: (NameSpaceID nsID, Lookupable a)
+           => nsID -> AttributeName
+           -> XMLConverter nsID extraState a
+lookupAttr nsID attrName = lookupAttr' nsID attrName >>= fromMaybeF
 
 -- | Read a 'Lookupable' attribute. Return the result as a 'Maybe'.
-lookupAttr'              :: (NameSpaceID nsID, Lookupable a)
-                         => nsID -> AttributeName
-                         -> XMLConverter nsID extraState x (Maybe a)
-lookupAttr' nsID attrName
-                         =     findAttr' nsID attrName
-                           >>^ (>>= readLookupable)
+lookupAttr' :: (NameSpaceID nsID, Lookupable a)
+            => nsID -> AttributeName
+            -> XMLConverter nsID extraState (Maybe a)
+lookupAttr' nsID attrName =
+  (>>= readLookupable) <$> findAttr' nsID attrName
 
 -- | Read a 'Lookupable' attribute with explicit default
-lookupAttrWithDefault    :: (NameSpaceID nsID, Lookupable a)
-                         => nsID -> AttributeName
-                         -> a
-                         -> XMLConverter nsID extraState x a
-lookupAttrWithDefault nsID attrName deflt
-                         =     lookupAttr' nsID attrName
-                           >>^ fromMaybe deflt
+lookupAttrWithDefault :: (NameSpaceID nsID, Lookupable a)
+                      => nsID -> AttributeName
+                      -> a
+                      -> XMLConverter nsID extraState a
+lookupAttrWithDefault nsID attrName deflt =
+  fromMaybe deflt <$> lookupAttr' nsID attrName
 
 -- | Read a 'Lookupable' attribute with implicit default
-lookupDefaultingAttr     :: (NameSpaceID nsID, Lookupable a, Default a)
-                         => nsID -> AttributeName
-                         -> XMLConverter nsID extraState x a
-lookupDefaultingAttr nsID attrName
-                         = lookupAttrWithDefault nsID attrName def
-
--- | Return value as a (Maybe Text)
-findAttr'               :: (NameSpaceID nsID)
-                        => nsID -> AttributeName
-                        -> XMLConverter nsID extraState x (Maybe AttributeValue)
-findAttr' nsID attrName =         qualifyName nsID attrName
-                              &&& getCurrentElement
-                          >>% XML.findAttr
+lookupDefaultingAttr :: (NameSpaceID nsID, Lookupable a, Default a)
+                     => nsID -> AttributeName
+                     -> XMLConverter nsID extraState a
+lookupDefaultingAttr nsID attrName =
+  lookupAttrWithDefault nsID attrName def
 
 -- | Return value as a (Maybe Text)
-findAttrText'           :: (NameSpaceID nsID)
-                        => nsID -> AttributeName
-                        -> XMLConverter nsID extraState x (Maybe TextAttributeValue)
-findAttrText' nsID attrName
-                        =         qualifyName nsID attrName
-                              &&& getCurrentElement
-                          >>% XML.findAttr
-
--- | Return value as string or fail
-findAttr               :: (NameSpaceID nsID)
-                       => nsID -> AttributeName
-                       -> FallibleXMLConverter nsID extraState x AttributeValue
-findAttr nsID attrName =     findAttr' nsID attrName
-                         >>> maybeToChoice
+findAttr' :: (NameSpaceID nsID)
+          => nsID -> AttributeName
+          -> XMLConverter nsID extraState (Maybe AttributeValue)
+findAttr' nsID attrName = XML.findAttr <$> qualifyName nsID attrName
+                                       <*> getCurrentElement
 
--- | Return value as text or fail
-findAttrText           :: (NameSpaceID nsID)
-                       => nsID -> AttributeName
-                       -> FallibleXMLConverter nsID extraState x TextAttributeValue
-findAttrText nsID attrName
-                       = findAttr' nsID attrName
-                         >>> maybeToChoice
+-- | Return value or fail
+findAttr :: (NameSpaceID nsID)
+         => nsID -> AttributeName
+         -> XMLConverter nsID extraState AttributeValue
+findAttr nsID attrName = findAttr' nsID attrName >>= fromMaybeF
 
--- | Return value as string or return provided default value
-findAttrTextWithDefault :: (NameSpaceID nsID)
-                        => nsID -> AttributeName
-                        -> TextAttributeValue
-                        -> XMLConverter nsID extraState x TextAttributeValue
-findAttrTextWithDefault nsID attrName deflt
-                       = findAttr' nsID attrName
-                         >>^ fromMaybe deflt
+-- | Return value or return provided default value
+findAttrWithDefault :: (NameSpaceID nsID)
+                    => nsID -> AttributeName
+                    -> AttributeValue
+                    -> XMLConverter nsID extraState AttributeValue
+findAttrWithDefault nsID attrName deflt =
+  fromMaybe deflt <$> findAttr' nsID attrName
 
 -- | Read and return value or fail
-readAttr               :: (NameSpaceID nsID, Read attrValue)
-                       => nsID -> AttributeName
-                       -> FallibleXMLConverter nsID extraState x attrValue
-readAttr nsID attrName =     readAttr' nsID attrName
-                         >>> maybeToChoice
+readAttr :: (NameSpaceID nsID, Read attrValue)
+         => nsID -> AttributeName
+         -> XMLConverter nsID extraState attrValue
+readAttr nsID attrName = readAttr' nsID attrName >>= fromMaybeF
 
 -- | Read and return value or return Nothing
-readAttr'              :: (NameSpaceID nsID, Read attrValue)
-                       => nsID -> AttributeName
-                       -> XMLConverter nsID extraState x (Maybe attrValue)
-readAttr' nsID attrName =     findAttr' nsID attrName
-                          >>^ (>>= tryToRead)
+readAttr' :: (NameSpaceID nsID, Read attrValue)
+          => nsID -> AttributeName
+          -> XMLConverter nsID extraState (Maybe attrValue)
+readAttr' nsID attrName = (>>= tryToRead) <$> findAttr' nsID attrName
 
 -- | Read and return value or return provided default value
-readAttrWithDefault    :: (NameSpaceID nsID, Read attrValue)
-                       => nsID -> AttributeName
-                       -> attrValue
-                       -> XMLConverter nsID extraState x attrValue
-readAttrWithDefault nsID attrName deflt
-                       =     findAttr' nsID attrName
-                         >>^ (>>= tryToRead)
-                         >>^ fromMaybe deflt
+readAttrWithDefault :: (NameSpaceID nsID, Read attrValue)
+                    => nsID -> AttributeName
+                    -> attrValue
+                    -> XMLConverter nsID extraState attrValue
+readAttrWithDefault nsID attrName deflt =
+  fromMaybe deflt <$> readAttr' nsID attrName
 
 -- | Read and return value or return default value from 'Default' instance
-getAttr                :: (NameSpaceID nsID, Read attrValue, Default attrValue)
-                       => nsID -> AttributeName
-                       -> XMLConverter nsID extraState x attrValue
-getAttr nsID attrName  = readAttrWithDefault nsID attrName def
+getAttr :: (NameSpaceID nsID, Read attrValue, Default attrValue)
+        => nsID -> AttributeName
+        -> XMLConverter nsID extraState attrValue
+getAttr nsID attrName = readAttrWithDefault nsID attrName def
 
 --------------------------------------------------------------------------------
 -- Movements
 --------------------------------------------------------------------------------
 
---
-jumpThere              :: XMLConverter nsID extraState XML.Element XML.Element
-jumpThere              = withState (\state element
-                                     -> ( pushElement element state , element )
-                                   )
-
---
-swapStack             :: XMLConverter nsID extraState (NonEmpty XML.Element)
-                                                      (NonEmpty XML.Element)
-swapStack             = withState swapStack'
-
---
-jumpBack               :: FallibleXMLConverter nsID extraState _x _x
-jumpBack               = tryModifyState (popElement >>> maybeToChoice)
-
--- | Support function for "procedural" converters: jump to an element, execute
--- a converter, jump back.
--- This version is safer than 'executeThere', because it does not rely on the
--- internal stack. As a result, the converter can not move around in arbitrary
--- ways. The downside is of course that some of the environment is not
--- accessible to the converter.
-switchingTheStack      :: XMLConverter nsID moreState a b
-                       -> XMLConverter nsID moreState (a, XML.Element) b
-switchingTheStack a    =     second ( (:| []) ^>> swapStack )
-                         >>> first  a
-                         >>> second swapStack
-                         >>^ fst
-
--- | Support function for "procedural" converters: jumps to an element, executes
--- a converter, jumps back.
--- Make sure that the converter is well-behaved; that is it should
--- return to the exact position it started from in /every possible path/ of
--- execution, even if it "fails". If it does not, you may encounter
--- strange bugs. If you are not sure about the behaviour or want to use
--- shortcuts, you can often use 'switchingTheStack' instead.
-executeThere           :: FallibleXMLConverter nsID moreState a b
-                       -> FallibleXMLConverter nsID moreState (a, XML.Element) b
-executeThere a         =      second jumpThere
-                          >>> fst
-                          ^>> a
-                          >>> jumpBack -- >>? jumpBack  would not ensure the jump.
-                          >>^ collapseEither
-
-
--- | Do something in a specific element, then come back
-executeIn   :: XMLConverter nsID extraState XML.Element s
-            -> XMLConverter nsID extraState XML.Element s
-executeIn a = duplicate >>> switchingTheStack a
+-- | Execute a converter in a specific element, then come back.
+-- The current element is restored even if the converter fails.
+executeIn :: XML.Element
+          -> XMLConverter nsID extraState a
+          -> XMLConverter nsID extraState a
+executeIn element converter = do
+  oldElement <- getCurrentElement
+  modify $ \state -> state { currentElement = element }
+  result <- tryC converter
+  modify $ \state -> state { currentElement = oldElement }
+  fromFallible result
 
--- | Do something in a sub-element, then come back
-executeInSub              :: (NameSpaceID nsID)
-                          => nsID -> ElementName
-                          -> FallibleXMLConverter nsID extraState f s
-                          -> FallibleXMLConverter nsID extraState f s
-executeInSub nsID name a  =     keepingTheValue
-                                  (findChild nsID name)
-                            >>> ignoringState liftFailure
-                            >>? switchingTheStack a
-  where liftFailure (_, Left  f) = Left  f
-        liftFailure (x, Right e) = Right (x, e)
+-- | Execute a converter in a sub-element of the current element,
+-- then come back. Fails if there is no such sub-element.
+executeInSub :: (NameSpaceID nsID)
+             => nsID -> ElementName
+             -> XMLConverter nsID extraState a
+             -> XMLConverter nsID extraState a
+executeInSub nsID name converter = do
+  child <- findChild nsID name
+  executeIn child converter
 
 --------------------------------------------------------------------------------
 -- Iterating over children
 --------------------------------------------------------------------------------
 
--- Helper converter to prepare different types of iterations.
--- It lifts the children (of a certain type) of the current element
--- into the value level and pairs each one with the current input value.
-prepareIteration       :: (NameSpaceID nsID)
-                       => nsID -> ElementName
-                       -> XMLConverter nsID extraState b [(b, XML.Element)]
-prepareIteration nsID name =     keepingTheValue
-                                   (findChildren nsID name)
-                             >>% distributeValue
-
---
-withEveryL             :: (NameSpaceID nsID)
-                       => nsID -> ElementName
-                       -> FallibleXMLConverter nsID extraState a  b
-                       -> FallibleXMLConverter nsID extraState a [b]
-withEveryL = withEvery
-
 -- | Applies a converter to every child element of a specific type.
--- Collects results in a 'MonadPlus'.
 -- Fails completely if any conversion fails.
-withEvery              :: (NameSpaceID nsID, MonadPlus m)
-                       => nsID -> ElementName
-                       -> FallibleXMLConverter nsID extraState a    b
-                       -> FallibleXMLConverter nsID extraState a (m b)
-withEvery nsID name a      =     prepareIteration nsID name
-                             >>> iterateS' (switchingTheStack a)
+withEveryL :: (NameSpaceID nsID)
+           => nsID -> ElementName
+           -> XMLConverter nsID extraState a
+           -> XMLConverter nsID extraState [a]
+withEveryL nsID name converter = do
+  children <- findChildren nsID name
+  mapM (`executeIn` converter) children
 
 -- | Applies a converter to every child element of a specific type.
 -- Collects all successful results in a list.
-tryAll                 :: (NameSpaceID nsID)
-                       => nsID -> ElementName
-                       -> FallibleXMLConverter nsID extraState b  a
-                       ->         XMLConverter nsID extraState b [a]
-tryAll nsID name a         =     prepareIteration nsID name
-                             >>> iterateS (switchingTheStack a)
-                             >>^ rights
+tryAll :: (NameSpaceID nsID)
+       => nsID -> ElementName
+       -> XMLConverter nsID extraState a
+       -> XMLConverter nsID extraState [a]
+tryAll nsID name converter = do
+  children <- findChildren nsID name
+  catMaybes <$> mapM (\child -> optional (executeIn child converter)) children
 
 --------------------------------------------------------------------------------
 -- Matching children
 --------------------------------------------------------------------------------
 
-type IdXMLConverter nsID moreState x
-   = XMLConverter   nsID moreState x x
-
-type MaybeCConverter nsID moreState x
-   = Maybe (IdXMLConverter nsID moreState (x, XML.Content))
-
--- Chainable converter that helps deciding which converter to actually use.
-type ContentMatchConverter nsID extraState x
-   = IdXMLConverter  nsID
-                     extraState
-                     (MaybeCConverter nsID extraState x, XML.Content)
-
--- Helper function: The @c@ is actually a converter that is to be selected by
--- matching XML content to the first two parameters.
--- The fold used to match elements however is very simple, so to use it,
--- this function wraps the converter in another converter that unifies
--- the accumulator. Think of a lot of converters with the resulting type
--- chained together. The accumulator not only transports the element
--- unchanged to the next matcher, it also does the actual selecting by
--- combining the intermediate results with '(<|>)'.
-makeMatcherC           :: (NameSpaceID nsID)
-                       => nsID -> ElementName
-                       -> FallibleXMLConverter  nsID extraState a a
-                       -> ContentMatchConverter nsID extraState a
-makeMatcherC nsID name c = (    second (    contentToElem
-                                         >>> returnV Nothing
-                                         ||| (    elemNameIs nsID name
-                                              >>^ bool Nothing (Just cWithJump)
-                                             )
-                                        )
-                             >>% (<|>)
-                           ) &&&^ snd
-  where cWithJump =      ( fst
-                           ^&&& (      second contentToElem
-                                  >>>  spreadChoice
-                                  ^>>? executeThere c
-                                )
-                            >>% recover)
-                    &&&^ snd
-        contentToElem :: FallibleXMLConverter nsID extraState XML.Content XML.Element
-        contentToElem = arr $ \case
-                                     XML.Elem e' -> succeedWith e'
-                                     _           -> failEmpty
-
--- Creates and chains a bunch of matchers
-prepareMatchersC      :: (NameSpaceID nsID)
-                       => [(nsID, ElementName, FallibleXMLConverter nsID extraState x x)]
-                       -> ContentMatchConverter nsID extraState x
---prepareMatchersC      = foldSs . (map $ uncurry3  makeMatcherC)
-prepareMatchersC      = reverseComposition . map (uncurry3  makeMatcherC)
+-- | A converter for a child element with a specific name in a specific
+-- namespace. The converter produces that element's contribution to the
+-- overall result.
+type ElementMatcher nsID extraState a
+   = (nsID, ElementName, XMLConverter nsID extraState a)
 
--- | Takes a list of element-data - converter groups and
--- * Finds all content of the current element
--- * Matches each group to each piece of content in order
---   (at most one group per piece of content)
--- * Filters non-matched content
--- * Chains all found converters in content-order
--- * Applies the chain to the input element
-matchContent'           :: (NameSpaceID nsID)
-                       => [(nsID, ElementName, FallibleXMLConverter nsID extraState a a)]
-                       -> XMLConverter nsID extraState a a
-matchContent' lookups   = matchContent lookups (arr fst)
+-- | Like 'matchContent', but ignores non-matching content.
+matchContent' :: (NameSpaceID nsID, Monoid a)
+              => [ElementMatcher nsID extraState a]
+              -> XMLConverter nsID extraState a
+matchContent' lookups = matchContent lookups (\_ -> return mempty)
 
--- | Takes a list of element-data - converter groups and
--- * Finds all content of the current element
--- * Matches each group to each piece of content in order
---   (at most one group per piece of content)
--- * Adds a default converter for all non-matched content
--- * Chains all found converters in content-order
--- * Applies the chain to the input element
-matchContent          :: (NameSpaceID nsID)
-                       => [(nsID, ElementName, FallibleXMLConverter nsID extraState a a)]
-                       -> XMLConverter nsID extraState (a,XML.Content) a
-                       -> XMLConverter nsID extraState a a
-matchContent lookups fallback
-                        = let matcher = prepareMatchersC lookups
-                          in  keepingTheValue (
-                                   elContent
-                               >>> map (Nothing,)
-                               ^>> iterateSL matcher
-                               >>^ map swallowOrFallback
-                              -- >>> foldSs
-                               >>> reverseComposition
-                             )
-                         >>> swap
-                         ^>> app
+-- | Takes a list of element matchers and a fallback converter, and
+-- converts the content of the current element in order: for each child
+-- element, the first matcher with a matching name (if any) is applied
+-- in that element; all other content is passed to the fallback
+-- converter. The results are combined with 'mappend'.
+-- If a matched converter fails, the corresponding element contributes
+-- nothing to the result.
+matchContent :: (NameSpaceID nsID, Monoid a)
+             => [ElementMatcher nsID extraState a]
+             -> (XML.Content -> XMLConverter nsID extraState a)
+             -> XMLConverter nsID extraState a
+matchContent lookups fallback = do
+  contents <- XML.elContent <$> getCurrentElement
+  mconcat <$> mapM matchOne contents
   where
-        -- let the converter swallow the content and drop the content
-        -- in the return value
-        swallowOrFallback (Just converter,content) = (,content) ^>> converter >>^ fst
-        swallowOrFallback (Nothing       ,content) = (,content) ^>> fallback
+    matchOne content@(XML.Elem element) = do
+      mConverter <- findConverter element lookups
+      case mConverter of
+        Just converter -> executeIn element converter <|> return mempty
+        Nothing        -> fallback content
+    matchOne content = fallback content
 
+    findConverter _ [] = return Nothing
+    findConverter element ((nsID, name, converter):rest) = do
+      matches <- elemNameIs nsID name element
+      if matches
+        then return $ Just converter
+        else findConverter element rest
+
 --------------------------------------------------------------------------------
 -- Internals
 --------------------------------------------------------------------------------
@@ -750,32 +482,3 @@
                   | otherwise              = Nothing
   where trueValues  = ["true" ,"on" ,"1"]
         falseValues = ["false","off","0"]
-
-
-distributeValue ::  a -> [b] -> [(a,b)]
-distributeValue = map.(,)
-
---------------------------------------------------------------------------------
-
-{-
-NOTES
-It might be a good idea to refactor the namespace stuff.
-E.g.: if a namespace constructor took a string as a parameter, things like
-> a ?>/< (NsText,"body")
-would be nicer.
-Together with a rename and some trickery, something like
-> |< NsText "body" >< NsText "p" ?> a </> </>|
-might even be possible.
-
-Some day, XML.Light should be replaced by something better.
-While doing that, it might be useful to replace String as the type of element
-names with something else, too. (Of course with OverloadedStrings).
-While doing that, maybe the types can be created in a way that something like
-> NsText:"body"
-could be used. Overloading (:) does not sounds like the best idea, but if the
-element name type was a list, this might be possible.
-Of course that would be a bit hackish, so the "right" way would probably be
-something like
-> InNS NsText "body"
-but isn't that a bit boring? ;)
--}
diff --git a/src/Text/Pandoc/Readers/ODT/StyleReader.hs b/src/Text/Pandoc/Readers/ODT/StyleReader.hs
--- a/src/Text/Pandoc/Readers/ODT/StyleReader.hs
+++ b/src/Text/Pandoc/Readers/ODT/StyleReader.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE CPP             #-}
-{-# LANGUAGE Arrows          #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TupleSections   #-}
 {-# LANGUAGE OverloadedStrings #-}
@@ -39,9 +37,7 @@
 , readStylesAt
 ) where
 
-import Prelude hiding (Applicative(..))
-import Control.Applicative hiding (liftA, liftA2, liftA3)
-import Control.Arrow
+import Control.Applicative ((<|>), optional)
 
 import Data.Default
 import qualified Data.Foldable as F
@@ -56,18 +52,15 @@
 
 import Text.Pandoc.Shared (safeRead, tshow)
 
-import Text.Pandoc.Readers.ODT.Arrows.Utils
-
 import Text.Pandoc.Readers.ODT.Generic.Fallible
 import qualified Text.Pandoc.Readers.ODT.Generic.SetMap as SM
 import Text.Pandoc.Readers.ODT.Generic.Utils
 import Text.Pandoc.Readers.ODT.Generic.XMLConverter
 
-import Text.Pandoc.Readers.ODT.Base
 import Text.Pandoc.Readers.ODT.Namespaces
 
 readStylesAt :: XML.Element -> Fallible Styles
-readStylesAt e = runConverter' readAllStyles mempty e
+readStylesAt e = runConverter readAllStyles mempty e
 
 --------------------------------------------------------------------------------
 -- Reader for font declarations and font pitches
@@ -99,41 +92,29 @@
 type FontPitches = M.Map FontFaceName FontPitch
 
 -- To get there, the fonts have to be read and the pitches extracted.
--- But the resulting map are only needed at one later place, so it should not be
--- transported on the value level, especially as we already use a state arrow.
--- So instead, the resulting map is lifted into the state of the reader.
--- (An alternative might be ImplicitParams, but again, we already have a state.)
+-- But the resulting map is only needed at one later place, so it should not be
+-- transported on the value level. Instead, it is lifted into the state of the
+-- reader.
 --
--- So the main style readers will have the types
-type StyleReader     a b  = XMLReader     FontPitches a b
--- and
-type StyleReaderSafe a b  = XMLReaderSafe FontPitches a b
--- respectively.
+-- So the main style readers will have the type
+type StyleReader a = XMLConverter Namespace FontPitches a
 --
--- But before we can work with these, we need to define the reader that reads
--- the fonts:
+-- But before we can work with this, we need to define the reader that reads
+-- the fonts. It is polymorphic in the extra state, since the pitches are not
+-- yet available while it runs.
 
 -- | A reader for font pitches
-fontPitchReader :: XMLReader s a FontPitches
-fontPitchReader = executeInSub NsOffice "font-face-decls" (
-                          withEveryL NsStyle "font-face" (liftAsSuccess (
-                              findAttr' NsStyle "name"
-                              &&&
-                              lookupDefaultingAttr NsStyle "font-pitch"
-                            ))
-                    >>?^ ( M.fromList . L.foldl' accumLegalPitches [] )
-                  ) `ifFailedDo` returnV (Right M.empty)
-  where accumLegalPitches ls (Nothing,_) = ls
-        accumLegalPitches ls (Just n,p)  = (n,p):ls
-
-
--- | A wrapper around the font pitch reader that lifts the result into the
--- state.
-readFontPitches :: StyleReader a a
-readFontPitches = producingExtraState () () fontPitchReader
-
+fontPitchReader :: XMLConverter Namespace s FontPitches
+fontPitchReader =
+  executeInSub NsOffice "font-face-decls" (do
+    fonts <- withEveryL NsStyle "font-face" $ do
+               name  <- findAttr' NsStyle "name"
+               pitch <- lookupDefaultingAttr NsStyle "font-pitch"
+               return (name, pitch)
+    return $ M.fromList [ (n, p) | (Just n, p) <- fonts ])
+  <|> return M.empty
 
--- | Looking up a pitch in the state of the arrow.
+-- | Looking up a pitch in the extra state.
 --
 -- The function does the following:
 -- * Look for the font pitch in an attribute.
@@ -141,15 +122,12 @@
 --   and use the pitch from there.
 -- * Return the result in a Maybe
 --
-findPitch :: XMLReaderSafe FontPitches a (Maybe FontPitch)
-findPitch =     ( lookupAttr NsStyle "font-pitch"
-                  `ifFailedDo`     findAttr NsStyle "font-name"
-                               >>? (     keepingTheValue getExtraState
-                                     >>% M.lookup
-                                     >>^ maybeToChoice
-                                   )
-                )
-            >>> choiceToMaybe
+findPitch :: StyleReader (Maybe FontPitch)
+findPitch = optional
+  $     lookupAttr NsStyle "font-pitch"
+    <|> (do fontName <- findAttr NsStyle "font-name"
+            pitches  <- getExtraState
+            fromMaybeF $ M.lookup fontName pitches)
 
 --------------------------------------------------------------------------------
 -- Definitions of main data
@@ -416,84 +394,77 @@
 --------------------------------------------------------------------------------
 
 --
-readAllStyles :: StyleReader a Styles
-readAllStyles = (      readFontPitches
-                  >>?! (     readAutomaticStyles
-                         &&& readStyles ))
-                  >>?%? chooseMax
+readAllStyles :: StyleReader Styles
+readAllStyles = do
+  fontPitchReader >>= setExtraState
+  autoStyles <- tryC readAutomaticStyles
+  styles     <- tryC readStyles
+  fromFallible $ chooseMax autoStyles styles
  -- all top elements are always on the same hierarchy level
 
 --
-readStyles :: StyleReader a Styles
-readStyles = executeInSub NsOffice "styles" $ liftAsSuccess
-  $ liftA3 Styles
-    ( tryAll NsStyle "style"         readStyle        >>^ M.fromList )
-    ( tryAll NsText  "list-style"    readListStyle    >>^ M.fromList )
-    ( tryAll NsStyle "default-style" readDefaultStyle >>^ M.fromList )
+readStyles :: StyleReader Styles
+readStyles = executeInSub NsOffice "styles" $
+  Styles <$> (M.fromList <$> tryAll NsStyle "style"         readStyle       )
+         <*> (M.fromList <$> tryAll NsText  "list-style"    readListStyle   )
+         <*> (M.fromList <$> tryAll NsStyle "default-style" readDefaultStyle)
 
 --
-readAutomaticStyles :: StyleReader a Styles
-readAutomaticStyles = executeInSub NsOffice "automatic-styles" $ liftAsSuccess
-  $ liftA3 Styles
-    ( tryAll NsStyle "style"         readStyle        >>^ M.fromList )
-    ( tryAll NsText  "list-style"    readListStyle    >>^ M.fromList )
-    ( returnV M.empty                                                )
+readAutomaticStyles :: StyleReader Styles
+readAutomaticStyles = executeInSub NsOffice "automatic-styles" $
+  Styles <$> (M.fromList <$> tryAll NsStyle "style"         readStyle       )
+         <*> (M.fromList <$> tryAll NsText  "list-style"    readListStyle   )
+         <*> pure M.empty
 
 --
-readDefaultStyle :: StyleReader a (StyleFamily, StyleProperties)
-readDefaultStyle =      lookupAttr NsStyle "family"
-                   >>?! keepingTheValue readStyleProperties
+readDefaultStyle :: StyleReader (StyleFamily, StyleProperties)
+readDefaultStyle = (,) <$> lookupAttr NsStyle "family"
+                       <*> readStyleProperties
 
 --
-readStyle :: StyleReader a (StyleName,Style)
-readStyle =      findAttr NsStyle "name"
-            >>?! keepingTheValue
-                   ( liftA4 Style
-                       ( lookupAttr' NsStyle "family"            )
-                       ( findAttr'   NsStyle "parent-style-name" )
-                       ( findAttr'   NsStyle "list-style-name"   )
-                       readStyleProperties
-                   )
+readStyle :: StyleReader (StyleName, Style)
+readStyle = do
+  name  <- findAttr NsStyle "name"
+  style <- Style <$> lookupAttr' NsStyle "family"
+                 <*> findAttr'   NsStyle "parent-style-name"
+                 <*> findAttr'   NsStyle "list-style-name"
+                 <*> readStyleProperties
+  return (name, style)
 
 --
-readStyleProperties :: StyleReaderSafe a StyleProperties
-readStyleProperties = liftA2 SProps
-                       ( readTextProperties >>> choiceToMaybe )
-                       ( readParaProperties >>> choiceToMaybe )
+readStyleProperties :: StyleReader StyleProperties
+readStyleProperties = SProps <$> optional readTextProperties
+                             <*> optional readParaProperties
 
 --
-readTextProperties :: StyleReader a TextProperties
+readTextProperties :: StyleReader TextProperties
 readTextProperties =
-  executeInSub NsStyle "text-properties" $ liftAsSuccess
-    ( liftA6 PropT
-       ( searchAttr   NsXSL_FO "font-style"  False isFontEmphasised )
-       ( searchAttr   NsXSL_FO "font-weight" False isFontBold       )
-       findPitch
-       ( getAttr      NsStyle  "text-position"                      )
-       readUnderlineMode
-       readStrikeThroughMode
-     )
+  executeInSub NsStyle "text-properties" $
+    PropT <$> searchAttr NsXSL_FO "font-style"  False isFontEmphasised
+          <*> searchAttr NsXSL_FO "font-weight" False isFontBold
+          <*> findPitch
+          <*> getAttr    NsStyle  "text-position"
+          <*> readUnderlineMode
+          <*> readStrikeThroughMode
   where isFontEmphasised = [("normal",False),("italic",True),("oblique",True)]
         isFontBold = ("normal",False):("bold",True)
                     :map ((,True) . tshow) ([100,200..900]::[Int])
 
-readUnderlineMode     :: StyleReaderSafe a (Maybe UnderlineMode)
+readUnderlineMode     :: StyleReader (Maybe UnderlineMode)
 readUnderlineMode     = readLineMode "text-underline-mode"
                                      "text-underline-style"
 
-readStrikeThroughMode :: StyleReaderSafe a (Maybe UnderlineMode)
+readStrikeThroughMode :: StyleReader (Maybe UnderlineMode)
 readStrikeThroughMode = readLineMode "text-line-through-mode"
                                      "text-line-through-style"
 
-readLineMode :: Text -> Text -> StyleReaderSafe a (Maybe UnderlineMode)
-readLineMode modeAttr styleAttr = proc x -> do
-  isUL <- searchAttr  NsStyle styleAttr False isLinePresent -< x
-  mode <- lookupAttr' NsStyle  modeAttr                     -< x
-  if isUL
-    then case mode of
-           Just m  -> returnA -< Just m
-           Nothing -> returnA -< Just UnderlineModeNormal
-    else              returnA -< Nothing
+readLineMode :: Text -> Text -> StyleReader (Maybe UnderlineMode)
+readLineMode modeAttr styleAttr = do
+  isUL <- searchAttr  NsStyle styleAttr False isLinePresent
+  mode <- lookupAttr' NsStyle  modeAttr
+  return $ if isUL
+             then mode <|> Just UnderlineModeNormal
+             else Nothing
   where
     isLinePresent = ("none",False) : map (,True)
                     [ "dash"      , "dot-dash" , "dot-dot-dash" , "dotted"
@@ -501,20 +472,16 @@
                     ]
 
 --
-readParaProperties :: StyleReader a ParaProperties
+readParaProperties :: StyleReader ParaProperties
 readParaProperties =
-   executeInSub NsStyle "paragraph-properties" $ liftAsSuccess
-     ( liftA3 PropP
-       ( liftA2 readNumbering
-         ( isSet'           NsText   "number-lines"           )
-         ( readAttr'        NsText   "line-number"            )
-       )
-       ( liftA2 readIndentation
-         ( isSetWithDefault NsStyle  "auto-text-indent" False )
-         ( getAttr          NsXSL_FO "text-indent"            )
-       )
-       (   getAttr          NsXSL_FO "margin-left"            )
-     )
+  executeInSub NsStyle "paragraph-properties" $
+    PropP <$> ( readNumbering
+                  <$> isSet'    NsText "number-lines"
+                  <*> readAttr' NsText "line-number" )
+          <*> ( readIndentation
+                  <$> isSetWithDefault NsStyle  "auto-text-indent" False
+                  <*> getAttr          NsXSL_FO "text-indent" )
+          <*> getAttr NsXSL_FO "margin-left"
   where readNumbering (Just True) (Just n) = NumberingRestart n
         readNumbering (Just True)  _       = NumberingKeep
         readNumbering      _       _       = NumberingNone
@@ -527,39 +494,37 @@
 ----
 
 --
-readListStyle :: StyleReader a (StyleName, ListStyle)
-readListStyle =
-       findAttr NsStyle "name"
-  >>?! keepingTheValue
-       ( liftA ListStyle
-         $ liftA3 SM.union3
-             ( readListLevelStyles NsText "list-level-style-number" LltNumbered )
-             ( readListLevelStyles NsText "list-level-style-bullet" LltBullet   )
-             ( readListLevelStyles NsText "list-level-style-image"  LltImage    ) >>^ M.mapMaybe chooseMostSpecificListLevelStyle
-       )
+readListStyle :: StyleReader (StyleName, ListStyle)
+readListStyle = do
+  name   <- findAttr NsStyle "name"
+  styles <- SM.union3
+              <$> readListLevelStyles NsText "list-level-style-number" LltNumbered
+              <*> readListLevelStyles NsText "list-level-style-bullet" LltBullet
+              <*> readListLevelStyles NsText "list-level-style-image"  LltImage
+  return ( name
+         , ListStyle $ M.mapMaybe chooseMostSpecificListLevelStyle styles )
+
 --
 readListLevelStyles :: Namespace -> ElementName
                     -> ListLevelType
-                    -> StyleReaderSafe a (SM.SetMap Int ListLevelStyle)
+                    -> StyleReader (SM.SetMap Int ListLevelStyle)
 readListLevelStyles namespace elementName levelType =
-  tryAll namespace elementName (readListLevelStyle levelType)
-    >>^ SM.fromList
+  SM.fromList <$> tryAll namespace elementName (readListLevelStyle levelType)
 
 --
-readListLevelStyle :: ListLevelType -> StyleReader a (Int, ListLevelStyle)
-readListLevelStyle levelType =      readAttr NsText "level"
-                               >>?! keepingTheValue
-                                    ( liftA5 toListLevelStyle
-                                      ( returnV       levelType             )
-                                      ( findAttr'     NsStyle "num-prefix"  )
-                                      ( findAttr'     NsStyle "num-suffix"  )
-                                      ( getAttr       NsStyle "num-format"  )
-                                      ( findAttrText' NsText  "start-value" )
-                                    )
+readListLevelStyle :: ListLevelType -> StyleReader (Int, ListLevelStyle)
+readListLevelStyle levelType = do
+  level <- readAttr NsText "level"
+  style <- toListLevelStyle
+             <$> findAttr' NsStyle "num-prefix"
+             <*> findAttr' NsStyle "num-suffix"
+             <*> getAttr   NsStyle "num-format"
+             <*> findAttr' NsText  "start-value"
+  return (level, style)
   where
-  toListLevelStyle _ p s LinfNone b         = ListLevelStyle LltBullet p s LinfNone (startValue b)
-  toListLevelStyle _ p s f@(LinfString _) b = ListLevelStyle LltBullet p s f (startValue b)
-  toListLevelStyle t p s f b                = ListLevelStyle t      p s f (startValue b)
+  toListLevelStyle p s LinfNone b         = ListLevelStyle LltBullet p s LinfNone (startValue b)
+  toListLevelStyle p s f@(LinfString _) b = ListLevelStyle LltBullet p s f (startValue b)
+  toListLevelStyle p s f b                = ListLevelStyle levelType p s f (startValue b)
   startValue mbx = fromMaybe 1 (mbx >>= safeRead)
 
 --
@@ -605,7 +570,7 @@
 parents               :: Style       -> Styles ->      [Style]
 parents style styles = L.unfoldr findNextParent style -- Ha!
   where findNextParent Style{..}
-          = fmap duplicate $ (`lookupStyle` styles) =<< styleParentName
+          = fmap (\p -> (p, p)) $ (`lookupStyle` styles) =<< styleParentName
 
 -- | Looks up the style family of the current style. Normally, every style
 -- should have one. But if not, all parents are searched.
diff --git a/src/Text/Pandoc/Readers/Org/BlockStarts.hs b/src/Text/Pandoc/Readers/Org/BlockStarts.hs
--- a/src/Text/Pandoc/Readers/Org/BlockStarts.hs
+++ b/src/Text/Pandoc/Readers/Org/BlockStarts.hs
@@ -24,6 +24,7 @@
   ) where
 
 import Control.Monad (void, guard)
+import Data.Char (isAlphaNum, isDigit)
 import Data.Text (Text)
 import Text.Pandoc.Readers.Org.Parsing
 import Text.Pandoc.Definition as Pandoc
@@ -65,7 +66,7 @@
   pure name
  where
    latexEnvName :: Monad m => OrgParser m Text
-   latexEnvName = try $ mappend <$> many1Char alphaNum <*> option "" (textStr "*")
+   latexEnvName = try $ mappend <$> takeWhile1P isAlphaNum <*> option "" (textStr "*")
 
 listCounterCookie :: Monad m => OrgParser m Int
 listCounterCookie = try $
@@ -73,7 +74,7 @@
   *> parseNum
   <* char ']'
   <* (skipSpaces <|> lookAhead eol)
-  where parseNum = (safeRead =<< many1Char digit)
+  where parseNum = (safeRead =<< takeWhile1P isDigit)
                    <|> snd <$> (lowerAlpha <|> upperAlpha)
 
 bulletListStart :: Monad m => OrgParser m Int
@@ -93,7 +94,7 @@
   ind <- length <$> many spaceChar
   fancy <- option False $ True <$ guardEnabled Ext_fancy_lists
   -- Ordered list markers allowed in org-mode
-  let styles = (many1Char digit $> (if fancy
+  let styles = (takeWhile1P isDigit $> (if fancy
                                        then Decimal
                                        else DefaultStyle))
                : if fancy
diff --git a/src/Text/Pandoc/Readers/Org/Blocks.hs b/src/Text/Pandoc/Readers/Org/Blocks.hs
--- a/src/Text/Pandoc/Readers/Org/Blocks.hs
+++ b/src/Text/Pandoc/Readers/Org/Blocks.hs
@@ -31,11 +31,11 @@
 import Text.Pandoc.Class.PandocMonad (PandocMonad)
 import Text.Pandoc.Definition
 import Text.Pandoc.Options
-import Text.Pandoc.Shared (compactify, compactifyDL, safeRead)
+import Text.Pandoc.Shared (compactify, compactifyDL, safeRead, compactifyTable)
 
 import Control.Monad (foldM, guard, mzero, void)
 import Data.Bifunctor (bimap)
-import Data.Char (isSpace)
+import Data.Char (isAlphaNum, isDigit, isSpace)
 import Data.Default (Default)
 import Data.Functor (($>))
 import qualified Data.List as L
@@ -162,7 +162,8 @@
   manyTill ((,) <$> key <*> value) newline
  where
    key :: Monad m => OrgParser m Text
-   key = try $ skipSpaces *> char ':' *> many1Char nonspaceChar
+   key = try $ skipSpaces *> char ':' *>
+           takeWhile1P (\c -> c /= ' ' && c /= '\t' && c /= '\n' && c /= '\r')
 
    value :: Monad m => OrgParser m Text
    value = skipSpaces *> manyTillChar anyChar endOfValue
@@ -212,7 +213,7 @@
      skipSpaces
      metaLineStart
      stringAnyCase "begin_"
-     many1Char (satisfy (not . isSpace))
+     takeWhile1P (not . isSpace)
 
 admonitionBlock :: PandocMonad m
                 => Text -> BlockAttributes -> Text -> OrgParser m (F Blocks)
@@ -273,11 +274,15 @@
    rawLine :: Monad m => OrgParser m Text
    rawLine = try $ ("" <$ blankline) <|> anyLine
 
+   -- Remove one comma from lines consisting of any number of commas
+   -- followed by "*" or "#+", like Emacs' org-unescape-code-in-region.
    commaEscaped suff = case T.uncons suff of
      Just (',', cs)
-       | "*"  <- T.take 1 cs -> cs
-       | "#+" <- T.take 2 cs -> cs
-     _                       -> suff
+       | isEscaped cs -> cs
+     _                -> suff
+    where
+     isEscaped cs = let cs' = T.dropWhile (== ',') cs
+                    in "*" `T.isPrefixOf` cs' || "#+" `T.isPrefixOf` cs'
 
 -- | Read but ignore all remaining block headers.
 ignHeaders :: Monad m => OrgParser m ()
@@ -425,7 +430,7 @@
 -- | Reads a line number switch option. The line number switch can be used with
 -- example and source blocks.
 lineNumberSwitch :: Monad m => OrgParser m (Char, Maybe Text, SwitchPolarity)
-lineNumberSwitch = genericSwitch 'n' (manyChar digit)
+lineNumberSwitch = genericSwitch 'n' (takeWhileP isDigit)
 
 blockOption :: Monad m => OrgParser m (Text, Text)
 blockOption = try $ do
@@ -437,7 +442,7 @@
 orgParamValue = try $
   skipSpaces
     *> notFollowedBy orgArgKey
-    *> ((char '"' *> manyChar (noneOf "\n\r\"") <* char '"') <|>
+    *> ((char '"' *> takeWhileP (`notElem` ("\n\r\"" :: [Char])) <* char '"') <|>
         noneOf "\n\r" `many1TillChar` endOfValue)
     <* skipSpaces
  where
@@ -579,7 +584,7 @@
 include = try $ do
   metaLineStart <* stringAnyCase "include:" <* skipSpaces
   filename <- includeTarget
-  includeArgs <- many (try $ skipSpaces *> many1Char alphaNum)
+  includeArgs <- many (try $ skipSpaces *> takeWhile1P isAlphaNum)
   params <- keyValues
   blocksParser <- case includeArgs of
       ("example" : _) -> return $ pure . B.codeBlock <$> parseRaw
@@ -608,7 +613,7 @@
     manyTill (noneOf "\n\r\t") (char '"')
 
   parseRaw :: PandocMonad m => OrgParser m Text
-  parseRaw = manyChar anyChar
+  parseRaw = takeWhileP (const True)
 
   blockFilter :: [(Text, Text)] -> [Block] -> [Block]
   blockFilter params blks =
@@ -701,8 +706,8 @@
                                 pure $ B.simpleCaption . B.plain $ ils'
                     let attr = (fromMaybe mempty identMb, [],
                                  blockAttrKeyValues blockAttrs)
-                    pure $ B.tableWith attr capt cs th tb tf
-                  _ -> tbl   -- should not happen
+                    pure $ compactifyTable $ B.tableWith attr capt cs th tb tf
+                  _ -> compactifyTable <$> tbl   -- should not happen
               else mempty
 
 -- | A normal org table
@@ -718,7 +723,8 @@
   let totalWidth = if any (isJust . columnRelWidth) colProps
                    then Just . sum $ map (fromMaybe 1 . columnRelWidth) colProps
                    else Nothing
-  in B.tableWith nullAttr (Caption Nothing mempty)
+  in compactifyTable $
+     B.tableWith nullAttr (Caption Nothing mempty)
                  (map (convertColProp totalWidth) colProps)
                  (TableHead nullAttr $ toHeaderRow heads)
                  [TableBody nullAttr 0 [] $ map toRow lns]
@@ -762,7 +768,7 @@
                  <$> (skipSpaces
                       *> char '<'
                       *> optionMaybe tableAlignFromChar)
-                 <*> (optionMaybe (many1Char digit >>= safeRead)
+                 <*> (optionMaybe (takeWhile1P isDigit >>= safeRead)
                       <* char '>'
                       <* emptyOrgCell)
 
@@ -782,8 +788,10 @@
 
 rowsToTable :: [OrgTableRow]
             -> F OrgTable
-rowsToTable = foldM rowToContent emptyTable
+rowsToTable = fmap unreverseRows . foldM rowToContent emptyTable
  where emptyTable = OrgTable mempty mempty mempty
+       -- Rows are accumulated in reverse order (see 'appendToBody').
+       unreverseRows tbl = tbl{ orgTableRows = reverse (orgTableRows tbl) }
 
 normalizeTable :: OrgTable -> OrgTable
 normalizeTable (OrgTable colProps heads rows) =
@@ -821,10 +829,9 @@
    appendToBody :: F [Blocks] -> F OrgTable
    appendToBody frow = do
      newRow <- frow
-     let oldRows = orgTableRows tbl
-     -- NOTE: This is an inefficient O(n) operation.  This should be changed
-     -- if performance ever becomes a problem.
-     return tbl{ orgTableRows = oldRows ++ [newRow] }
+     -- Rows are prepended to avoid quadratic behavior on long tables;
+     -- the final list is reversed in 'rowsToTable'.
+     return tbl{ orgTableRows = newRow : orgTableRows tbl }
 
 
 --
diff --git a/src/Text/Pandoc/Readers/Org/ExportSettings.hs b/src/Text/Pandoc/Readers/Org/ExportSettings.hs
--- a/src/Text/Pandoc/Readers/Org/ExportSettings.hs
+++ b/src/Text/Pandoc/Readers/Org/ExportSettings.hs
@@ -24,7 +24,7 @@
 
 -- | Read and handle space separated org-mode export settings.
 exportSettings :: PandocMonad m => OrgParser m ()
-exportSettings = void $ sepBy skipSpaces exportSetting
+exportSettings = void $ sepEndBy exportSetting skipSpaces
 
 -- | Setter function for export settings.
 type ExportSettingSetter a = a -> ExportSettings -> ExportSettings
@@ -32,7 +32,7 @@
 -- | Read and process a single org-mode export option.
 exportSetting :: PandocMonad m => OrgParser m ()
 exportSetting = choice
-  [ booleanSetting "^" (\val es -> es { exportSubSuperscripts = val })
+  [ subSupSetting "^" (\val es -> es { exportSubSuperscripts = val })
   , booleanSetting "'" (\val es -> es { exportSmartQuotes = val })
   , booleanSetting "*" (\val es -> es { exportEmphasizedText = val })
   , booleanSetting "-" (\val es -> es { exportSpecialStrings = val })
@@ -143,6 +143,22 @@
      char '"'
        *> manyTillChar alphaNum (char '"')
 
+-- | Parses either @t@, @{}@, or @nil@ into a 'SubSupOption' value.
+subSupSetting :: Monad m
+              => Text
+              -> ExportSettingSetter SubSupOption
+              -> OrgParser m ()
+subSupSetting = genericExportSetting $ subSupBraced <|> subSupBoolean
+ where
+   subSupBraced = SubSupBraced <$ optionString "{}"
+
+   subSupBoolean = try $ do
+     exportBool <- elispBoolean
+     return $
+       if exportBool
+       then SubSupAll
+       else SubSupNone
+
 -- | Parses either @t@, @nil@, or @verbatim@ into a 'TeXExport' value.
 texSetting :: Monad m
            => Text
@@ -166,7 +182,7 @@
 -- | Read any setting string, but ignore it and emit a warning.
 ignoreAndWarn :: PandocMonad m => OrgParser m ()
 ignoreAndWarn = try $ do
-  opt <- many1Char nonspaceChar
+  opt <- takeWhile1P (\c -> c /= ' ' && c /= '\t' && c /= '\n' && c /= '\r')
   report (UnknownOrgExportOption opt)
   return ()
 
diff --git a/src/Text/Pandoc/Readers/Org/Inlines.hs b/src/Text/Pandoc/Readers/Org/Inlines.hs
--- a/src/Text/Pandoc/Readers/Org/Inlines.hs
+++ b/src/Text/Pandoc/Readers/Org/Inlines.hs
@@ -36,6 +36,7 @@
 import Control.Monad.Trans (lift)
 import Data.Char (isAlphaNum, isSpace)
 import qualified Data.Map as M
+import qualified Data.Set as Set
 import Data.Text (Text)
 import qualified Data.Text as T
 
@@ -124,7 +125,7 @@
 
 str :: PandocMonad m => OrgParser m (F Inlines)
 str = return . B.str <$>
-      ( many1Char (noneOf $ specialChars ++ "\n\r ") >>= updatePositions' )
+      ( takeWhile1P (`notElem` (specialChars ++ "\n\r ")) >>= updatePositions' )
       <* updateLastStrPos
   where
     updatePositions' str' = str' <$
@@ -222,7 +223,7 @@
 orgCiteKey :: PandocMonad m => OrgParser m Text
 orgCiteKey = do
   char '@'
-  T.pack <$> many1 (satisfy orgCiteKeyChar)
+  takeWhile1P orgCiteKeyChar
 
 orgCiteKeyChar :: Char -> Bool
 orgCiteKeyChar c =
@@ -409,7 +410,7 @@
 inlineNote :: PandocMonad m => OrgParser m (F Inlines)
 inlineNote = try $ do
   string "[fn:"
-  ref <- manyChar alphaNum
+  ref <- takeWhileP (\c -> isAlphaNum c || c == '-' || c == '_')
   char ':'
   note <- fmap B.para . trimInlinesF . mconcat <$> many1Till inline (char ']')
   unless (T.null ref) $
@@ -501,7 +502,7 @@
 internalLink :: Text -> Inlines -> F Inlines
 internalLink link title = do
   ids <- asksF orgStateAnchorIds
-  if link `elem` ids
+  if link `Set.member` ids
     then return $ B.link ("#" <> link) "" title
     else let attr' = ("", ["spurious-link"] , [("target", link)])
          in return $ B.spanWith attr' (B.emph title)
@@ -547,7 +548,7 @@
    orgInlineParamValue = try $
      skipSpaces
        *> notFollowedBy (char ':')
-       *> many1Char (noneOf "\t\n\r ]")
+       *> takeWhile1P (`notElem` ("\t\n\r ]" :: [Char]))
        <* skipSpaces
 
 
@@ -801,21 +802,26 @@
 
 -- | Read a sub- or superscript expression
 subOrSuperExpr :: PandocMonad m => OrgParser m (F Inlines)
-subOrSuperExpr = try $
-  simpleSubOrSuperText <|>
-  (choice [ charsInBalanced '{' '}' (T.singleton <$> noneOf "\n\r")
-          , enclosing ('(', ')') <$> charsInBalanced '(' ')' (T.singleton <$> noneOf "\n\r")
-          ] >>= parseFromString (mconcat <$> many inline))
- where enclosing (left, right) s = T.cons left $ T.snoc s right
+subOrSuperExpr = try $ do
+  subSupOption <- getExportSetting exportSubSuperscripts
+  case subSupOption of
+    SubSupNone   -> mzero
+    SubSupBraced -> bracedText
+    SubSupAll    -> simpleSubOrSuperText <|> bracedText <|> parenText
+ where
+   bracedText = charsInBalanced '{' '}' (T.singleton <$> noneOf "\n\r")
+                >>= parseFromString (mconcat <$> many inline)
+   parenText = (enclosing ('(', ')') <$>
+                charsInBalanced '(' ')' (T.singleton <$> noneOf "\n\r"))
+               >>= parseFromString (mconcat <$> many inline)
+   enclosing (left, right) s = T.cons left $ T.snoc s right
 
 simpleSubOrSuperText :: PandocMonad m => OrgParser m (F Inlines)
-simpleSubOrSuperText = try $ do
-  state <- getState
-  guard . exportSubSuperscripts . orgStateExportSettings $ state
+simpleSubOrSuperText = try $
   return . B.str <$>
     choice [ textStr "*"
            , mappend <$> option "" (T.singleton <$> oneOf "+-")
-                     <*> many1Char alphaNum
+                     <*> takeWhile1P isAlphaNum
            ]
 
 inlineLaTeX :: PandocMonad m => OrgParser m (F Inlines)
@@ -891,7 +897,7 @@
   recursionDepth <- orgStateMacroDepth <$> getState
   guard $ recursionDepth < 15
   string "{{{"
-  name <- manyChar alphaNum
+  name <- takeWhileP isAlphaNum
   args <- ([] <$ string "}}}")
           <|> char '(' *> argument `sepBy` char ',' <* eoa
   expander <- lookupMacro name <$> getState
@@ -922,7 +928,7 @@
   guard =<< getExportSetting exportSpecialStrings
   choice [orgDash, orgEllipses, shyHyphen]
   where
-    shyHyphen   = pure <$> (B.str "\173" <$ string "\\-") <* updatePositions '-'
+    shyHyphen   = pure <$> (B.str "\173" <$ try (string "\\-")) <* updatePositions '-'
     orgDash     = pure <$> dash <* updatePositions '-'
     orgEllipses = pure <$> ellipses <* updatePositions '.'
 
diff --git a/src/Text/Pandoc/Readers/Org/Meta.hs b/src/Text/Pandoc/Readers/Org/Meta.hs
--- a/src/Text/Pandoc/Readers/Org/Meta.hs
+++ b/src/Text/Pandoc/Readers/Org/Meta.hs
@@ -30,6 +30,7 @@
 import Text.Pandoc.URI (urlEncode)
 
 import Control.Monad (mzero, void)
+import Data.Char (isAlphaNum, isDigit)
 import Data.List (intercalate, intersperse)
 import Data.Map (Map)
 import Data.Maybe (fromMaybe)
@@ -61,13 +62,13 @@
 
 keywordLine :: PandocMonad m => OrgParser m ()
 keywordLine = try $ do
-  key   <- T.toLower <$> metaKey
+  key   <- metaKey
   case Map.lookup key keywordHandlers of
     Nothing -> fail $ "Unknown keyword: " ++ T.unpack key
     Just hd -> hd
 
 metaKey :: Monad m => OrgParser m Text
-metaKey = T.toLower <$> many1Char (noneOf ": \n\r")
+metaKey = T.toLower <$> takeWhile1P (`notElem` (": \n\r" :: [Char]))
                     <*  char ':'
                     <*  skipSpaces
 
@@ -175,7 +176,9 @@
 -- | Parse a link type definition (like @wp https://en.wikipedia.org/wiki/@).
 addLinkFormatter :: Monad m => OrgParser m ()
 addLinkFormatter = try $ do
-  linkType <- T.cons <$> letter <*> manyChar (alphaNum <|> oneOf "-_") <* skipSpaces
+  linkType <- T.cons <$> letter
+              <*> takeWhileP (\c -> isAlphaNum c || c == '-' || c == '_')
+              <* skipSpaces
   formatter <- parseFormat
   updateState $ \s ->
     let fs = orgStateLinkFormatters s
@@ -253,7 +256,7 @@
  where
    todoKeyword :: Monad m => OrgParser m Text
    todoKeyword = do
-     keyword <- many1Char nonspaceChar
+     keyword <- takeWhile1P (\c -> c /= ' ' && c /= '\t' && c /= '\n' && c /= '\r')
      let cleanKeyword = T.takeWhile (/= '(') keyword
      skipSpaces
      return cleanKeyword
@@ -278,14 +281,15 @@
 
 macroDefinition :: Monad m => OrgParser m (Text, [Text] -> Text)
 macroDefinition = try $ do
-  macroName <- many1Char nonspaceChar <* skipSpaces
+  macroName <- takeWhile1P (\c -> c /= ' ' && c /= '\t' && c /= '\n' && c /= '\r')
+                <* skipSpaces
   firstPart <- expansionPart
   (elemOrder, parts) <- unzip <$> many ((,) <$> placeholder <*> expansionPart)
   let expander = mconcat . alternate (firstPart:parts) . reorder elemOrder
   return (macroName, expander)
  where
   placeholder :: Monad m => OrgParser m Int
-  placeholder = try . fmap (fromMaybe 1 . safeRead) $ char '$' *> many1Char digit
+  placeholder = try . fmap (fromMaybe 1 . safeRead) $ char '$' *> takeWhile1P isDigit
 
   expansionPart :: Monad m => OrgParser m Text
   expansionPart = try $ manyChar (notFollowedBy placeholder *> noneOf "\n\r")
diff --git a/src/Text/Pandoc/Readers/Org/ParserState.hs b/src/Text/Pandoc/Readers/Org/ParserState.hs
--- a/src/Text/Pandoc/Readers/Org/ParserState.hs
+++ b/src/Text/Pandoc/Readers/Org/ParserState.hs
@@ -35,6 +35,7 @@
   , returnF
   , ExportSettings (..)
   , ArchivedTreesOption (..)
+  , SubSupOption (..)
   , TeXExport (..)
   , optionsToParserState
   ) where
@@ -91,7 +92,7 @@
 
 -- | Org-mode parser state
 data OrgParserState = OrgParserState
-  { orgStateAnchorIds            :: [Text]
+  { orgStateAnchorIds            :: Set.Set Text
   , orgStateEmphasisCharStack    :: [Char]
   , orgStateEmphasisPreChars     :: [Char] -- ^ Chars allowed to occur before
                                            -- emphasis; spaces and newlines are
@@ -163,7 +164,7 @@
 
 defaultOrgParserState :: OrgParserState
 defaultOrgParserState = OrgParserState
-  { orgStateAnchorIds = []
+  { orgStateAnchorIds = Set.empty
   , orgStateEmphasisPreChars = "-\t ('\"{\x200B"
   , orgStateEmphasisPostChars  = "-\t\n .,:!?;'\")}[\x200B"
   , orgStateEmphasisCharStack = []
@@ -240,6 +241,14 @@
   | ArchivedTreesNoExport     -- ^ Exclude archived trees from exporting
   | ArchivedTreesHeadlineOnly -- ^ Export only the headline, discard the contents
 
+-- | Options for the handling of TeX-like sub- and superscript syntax.
+-- Represents allowed values of Emacs variable
+-- @org-export-with-sub-superscripts@.
+data SubSupOption
+  = SubSupAll                 -- ^ Interpret all sub- and superscripts (@t@)
+  | SubSupBraced              -- ^ Interpret only expressions in braces (@{}@)
+  | SubSupNone                -- ^ Never interpret sub-/superscripts (@nil@)
+
 -- | Options for the handling of LaTeX environments and fragments.
 -- Represents allowed values of Emacs variable @org-export-with-latex@.
 data TeXExport
@@ -261,7 +270,8 @@
   , exportPreserveBreaks   :: Bool -- ^ Whether to preserve linebreaks
   , exportSmartQuotes      :: Bool -- ^ Parse quotes smartly
   , exportSpecialStrings   :: Bool -- ^ Parse ellipses and dashes smartly
-  , exportSubSuperscripts  :: Bool -- ^ TeX-like syntax for sub- and superscripts
+  , exportSubSuperscripts  :: SubSupOption
+  -- ^ TeX-like syntax for sub- and superscripts
   , exportWithAuthor       :: Bool -- ^ Include author in final meta-data
   , exportWithCreator      :: Bool -- ^ Include creator in final meta-data
   , exportWithEmail        :: Bool -- ^ Include email in final meta-data
@@ -286,7 +296,7 @@
   , exportPreserveBreaks = False
   , exportSmartQuotes = False
   , exportSpecialStrings = True
-  , exportSubSuperscripts = True
+  , exportSubSuperscripts = SubSupAll
   , exportWithAuthor = True
   , exportWithCreator = True
   , exportWithEmail = True
diff --git a/src/Text/Pandoc/Readers/Org/Parsing.hs b/src/Text/Pandoc/Readers/Org/Parsing.hs
--- a/src/Text/Pandoc/Readers/Org/Parsing.hs
+++ b/src/Text/Pandoc/Readers/Org/Parsing.hs
@@ -37,6 +37,8 @@
   , manyChar
   , many1Char
   , manyTillChar
+  , takeWhileP
+  , takeWhile1P
   , many1Till
   , many1TillChar
   , notFollowedBy'
@@ -94,6 +96,7 @@
   , try
   , sepBy
   , sepBy1
+  , sepEndBy
   , sepEndBy1
   , endBy1
   , option
@@ -106,6 +109,7 @@
   , getPosition
   ) where
 
+import qualified Data.Set as Set
 import Data.Text (Text)
 import Text.Pandoc.Readers.Org.ParserState
 
@@ -223,7 +227,7 @@
 orgAnchor :: Monad m => OrgParser m Text
 orgAnchor = try $ do
   string "<<"
-  anchorId <- many1Char (noneOf "\t\n\r<>\"' ")
+  anchorId <- takeWhile1P (`notElem` ("\t\n\r<>\"' " :: [Char]))
   string ">>"
   skipSpaces
   recordAnchorId anchorId
@@ -231,4 +235,4 @@
 
 recordAnchorId :: Monad m => Text -> OrgParser m ()
 recordAnchorId i = updateState $ \s ->
-  s{ orgStateAnchorIds = i : orgStateAnchorIds s }
+  s{ orgStateAnchorIds = Set.insert i (orgStateAnchorIds s) }
diff --git a/src/Text/Pandoc/Readers/Org/Shared.hs b/src/Text/Pandoc/Readers/Org/Shared.hs
--- a/src/Text/Pandoc/Readers/Org/Shared.hs
+++ b/src/Text/Pandoc/Readers/Org/Shared.hs
@@ -32,7 +32,8 @@
                        `elem` imageExtensions
    isKnownProtocolUri = any (\x -> (x <> "://") `T.isPrefixOf` fp) protocols
 
-   imageExtensions = [ ".jpeg", ".jpg", ".png", ".gif", ".svg", ".webp", ".jxl", ".avif" ]
+   imageExtensions = [ ".jpeg", ".jpg", ".png", ".gif", ".svg", ".webp", ".jxl", ".avif",
+                       ".pdf" ]
    protocols = [ "file", "http", "https" ]
 
 -- | Cleanup and canonicalize a string describing a link.  Return @Nothing@ if
diff --git a/src/Text/Pandoc/Readers/Pod.hs b/src/Text/Pandoc/Readers/Pod.hs
--- a/src/Text/Pandoc/Readers/Pod.hs
+++ b/src/Text/Pandoc/Readers/Pod.hs
@@ -16,7 +16,7 @@
 
 import Control.Monad (void)
 import Control.Monad.Except (throwError)
-import Data.Char (isAsciiUpper, digitToInt)
+import Data.Char (isAlphaNum, isAsciiUpper, isDigit, isLetter, digitToInt)
 import Data.Default (Default)
 import Text.Pandoc.Logging
 import Text.Pandoc.Options
@@ -30,7 +30,7 @@
 import qualified Text.Pandoc.Builder as B
 import qualified Data.Text as T
 import qualified Data.Text.Read as TR
-import Text.Pandoc.Shared (stringify, textToIdentifier, tshow)
+import Text.Pandoc.Shared (stringifyInlines, textToIdentifier, tshow)
 import Data.Set (Set)
 import Data.Functor (($>))
 import Data.Maybe (listToMaybe, fromMaybe)
@@ -210,7 +210,7 @@
   lookAhead (char '<')
   case ctrl of
     'B' -> B.strong <$> argument
-    'C' -> B.code . stringify <$> argument
+    'C' -> B.code . stringifyInlines <$> argument
     'F' -> B.spanWith (mempty, ["filename"], mempty) <$> argument
     'I' -> B.emph <$> argument
     'S' -> argument  -- TODO map nbsps
@@ -218,7 +218,7 @@
     'Z' -> argument $> mempty
 
     'E' -> do
-      a <- stringify <$> argument
+      a <- stringifyInlines <$> argument
       case entity a of
              -- per spec:
              --   Pod parsers, when faced with some unknown "E<identifier>" code,
@@ -294,9 +294,9 @@
       let name' = fromMaybe (defaultLinkName dest) name in
           case dest of
             LinkUrl _ href -> B.link href "" name'
-            LinkMan nm Nothing ->  B.linkWith (mempty, mempty, [("manual", stringify nm)]) "" "" name'
-            LinkMan nm (Just sc) -> B.linkWith (mempty, mempty, [("manual", stringify nm), ("section", stringify sc)]) "" "" name'
-            LinkInternal sc -> B.link ("#" <> identifier (stringify sc)) "" name'
+            LinkMan nm Nothing ->  B.linkWith (mempty, mempty, [("manual", stringifyInlines nm)]) "" "" name'
+            LinkMan nm (Just sc) -> B.linkWith (mempty, mempty, [("manual", stringifyInlines nm), ("section", stringifyInlines sc)]) "" "" name'
+            LinkInternal sc -> B.link ("#" <> identifier (stringifyInlines sc)) "" name'
 
     linkName sp ex = optionMaybe $ try $ many
         (try format
@@ -312,12 +312,12 @@
     -- manual page reference, so what we are doing here is roughly equivalent
     -- even though it is nonsense
     url ex = do
-      scheme <- many1Char (letter <|> digit <|> char '_')
+      scheme <- takeWhile1P (\c -> isLetter c || isDigit c || c == '_')
       colon <- T.singleton <$> char ':' <* notFollowedBy (char ':')
       rst <- many (format <|> B.str <$> many1Char (podCharLess ex))
       return $ LinkUrl
                  (B.str scheme <> B.str colon <> mconcat rst)
-                 (scheme <> colon <> stringify rst)
+                 (scheme <> colon <> stringifyInlines (mconcat rst))
     quotedSection sp close ex = do
       let mystr = B.str <$> many1Char (podCharLess ('\"':ex) <|> try (char '"' <* notFollowedBy close))
       char '"'
@@ -353,7 +353,7 @@
 
 nonEmptyLine :: PandocMonad m => PodParser m T.Text
 nonEmptyLine = try $ do
-  pre <- manyChar spaceChar
+  pre <- takeWhileP (\c -> c == ' ' || c == '\t')
   something <- T.singleton <$> nonspaceChar
   post <- anyLineNewline
   return $ pre <> something <> post
@@ -368,7 +368,7 @@
   optional blanklines
   return $ B.codeBlock $ mconcat $ start:lns
   where
-    startVerbatimLine = many1Char spaceChar <> nonEmptyLine
+    startVerbatimLine = takeWhile1P (\c -> c == ' ' || c == '\t') <> nonEmptyLine
 
 -- =begin/=end/=for and data paragraphs
 -- The =begin/=end (and single-paragraph =for variant) markers in Pod are
@@ -390,7 +390,7 @@
 -- structure. It seems unlikely this would be encountered in the wild.
 
 regionIdentifier :: PandocMonad m => PodParser m T.Text
-regionIdentifier = many1Char (alphaNum <|> oneOf "-_")
+regionIdentifier = takeWhile1P (\c -> isAlphaNum c || c == '-' || c == '_')
 
 for :: PandocMonad m => PodParser m Blocks
 for = do
diff --git a/src/Text/Pandoc/Readers/RST.hs b/src/Text/Pandoc/Readers/RST.hs
--- a/src/Text/Pandoc/Readers/RST.hs
+++ b/src/Text/Pandoc/Readers/RST.hs
@@ -18,10 +18,11 @@
 import Control.Monad (forM_, guard, liftM, mplus, mzero, when, unless, void)
 import Control.Monad.Except (throwError)
 import Control.Monad.Identity (Identity (..))
-import Data.Char (isHexDigit, isSpace, toUpper, isAlphaNum, generalCategory,
+import Data.Char (isDigit, isHexDigit, isSpace, toUpper, isAlphaNum,
+                  generalCategory,
                   GeneralCategory(OpenPunctuation, InitialQuote, FinalQuote,
                                   DashPunctuation, OtherSymbol))
-import Data.List (deleteFirstsBy, elemIndex, partition, sort, transpose)
+import Data.List (elemIndex, partition, sort, transpose)
 import qualified Data.Map as M
 import Data.Maybe (fromMaybe, maybeToList, isJust, isNothing, catMaybes)
 import Data.Sequence (ViewR (..), viewr)
@@ -75,8 +76,35 @@
 underlineChars = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
 
 -- treat these as potentially non-text when parsing inline:
-specialChars :: [Char]
-specialChars = "\\`|*_<>$:/[]{}()-.\"'\8216\8217\8220\8221"
+-- (equivalent to the character class "\\`|*_<>$:/[]{}()-.\"'\8216\8217\8220\8221")
+isSpecialChar :: Char -> Bool
+isSpecialChar c =
+  case c of
+    '\\' -> True
+    '`'  -> True
+    '|'  -> True
+    '*'  -> True
+    '_'  -> True
+    '<'  -> True
+    '>'  -> True
+    '$'  -> True
+    ':'  -> True
+    '/'  -> True
+    '['  -> True
+    ']'  -> True
+    '{'  -> True
+    '}'  -> True
+    '('  -> True
+    ')'  -> True
+    '-'  -> True
+    '.'  -> True
+    '"'  -> True
+    '\'' -> True
+    '\8216' -> True
+    '\8217' -> True
+    '\8220' -> True
+    '\8221' -> True
+    _    -> False
 
 --
 -- parsing documents
@@ -121,8 +149,10 @@
 metaFromDefList ds meta = adjustAuthors $ foldr f meta ds
  where f (k,v) =
          case v of
-           [[Plain ils]] ->  setMeta (T.toLower (stringify k)) $ MetaInlines ils
-           _ -> setMeta (T.toLower (stringify k)) $ mconcat $ map fromList v
+           [[Plain ils]] ->  setMeta (T.toLower (stringifyInlines k)) $
+                                MetaInlines ils
+           _ -> setMeta (T.toLower (stringifyInlines k)) $
+                  mconcat $ map fromList v
        adjustAuthors (Meta metamap) = Meta $ M.adjust splitAuthors "author"
                                            $ M.mapKeys (\k ->
                                                  if k == "authors"
@@ -163,9 +193,15 @@
   let (blocks', meta') = if standalone
                             then titleTransform (blocks, meta)
                             else (blocks, meta)
-  let reversedNotes = stateNotes state
-  updateState $ \s -> s { stateNotes = reverse reversedNotes }
-  doc <- walkM resolveReferences =<<
+  let notes = reverse $ stateNotes state
+  -- Named notes are never removed, so we can look them up in a Map;
+  -- auto-numbered notes are consumed in order of occurrence, so we
+  -- keep them in a list (in stateNotes):
+  let isAutoNote (r, _) = r == "*" || r == "#"
+  let namedNotes = M.fromList $ reverse $ filter (not . isAutoNote) notes
+        -- reverse, so that the first occurrence of a label wins
+  updateState $ \s -> s { stateNotes = filter isAutoNote notes }
+  doc <- walkM (resolveReferences namedNotes) =<<
          walkM resolveBlockSubstitutions
          (Pandoc meta' (blocks' ++ refBlock))
   reportLogMessages
@@ -187,22 +223,27 @@
                    bls -> return $ Div nullAttr bls
 resolveBlockSubstitutions x = return x
 
-resolveReferences :: PandocMonad m => Inline -> RSTParser m Inline
-resolveReferences = resolveReferences' Set.empty
+resolveReferences :: PandocMonad m
+                  => M.Map Text Text  -- ^ named notes
+                  -> Inline -> RSTParser m Inline
+resolveReferences namedNotes = resolveReferences' namedNotes Set.empty
 
-resolveReferences' :: PandocMonad m => Set.Set Key -> Inline -> RSTParser m Inline
-resolveReferences' seen x@(Link _ ils (s,_))
+resolveReferences' :: PandocMonad m
+                   => M.Map Text Text -> Set.Set Key -> Inline
+                   -> RSTParser m Inline
+resolveReferences' namedNotes seen x@(Link _ ils (s,_))
   | Just ref <- T.stripPrefix "##REF##" s = do
       let isAnonKey (Key (T.uncons -> Just ('_',_))) = True
           isAnonKey _                                = False
       state <- getState
       let keyTable = stateKeys state
-      let anonKeys = sort $ filter isAnonKey $ M.keys keyTable
       key <-  if ref == "_" -- anonymous key
                 then
-                  case anonKeys of
-                    []    -> mzero -- TODO log?
-                    (k:_) -> return k
+                  -- anonymous keys are named _0000, _0001, ... so the
+                  -- next one to use is the smallest key >= "_":
+                  case M.lookupGE (Key "_") keyTable of
+                    Just (k, _) | isAnonKey k -> return k
+                    _                         -> mzero -- TODO log?
                 else return $ toKey ref
       if key `Set.member` seen
          then do
@@ -214,27 +255,31 @@
            ((src,tit), attr) <- lookupKey [] key
            when (isAnonKey key) $ updateState $ \st ->
                                    st{ stateKeys = M.delete key keyTable }
-           resolveReferences' (Set.insert key seen) (Link attr ils (src, tit))
+           resolveReferences' namedNotes (Set.insert key seen)
+             (Link attr ils (src, tit))
   | Just ref <- T.stripPrefix "##NOTE##" s = do
       state <- getState
-      let notes = stateNotes state
-      case lookup ref notes of
+      let autoNotes = stateNotes state
+      let mbnote = if ref == "*" || ref == "#" -- auto-numbered
+                      -- consume the note, so the next auto-numbered
+                      -- note doesn't get the same contents:
+                      then case break ((== ref) . fst) autoNotes of
+                             (xs, (_, raw) : ys) -> Just (raw, xs ++ ys)
+                             _                   -> Nothing
+                      else (\raw -> (raw, autoNotes)) <$>
+                             M.lookup ref namedNotes
+      case mbnote of
         Nothing   -> do
           pos <- getPosition
           logMessage $ ReferenceNotFound ref pos
           return x
-        Just raw  -> do
+        Just (raw, newnotes) -> do
           -- We temporarily empty the note list while parsing the note,
           -- so that we don't get infinite loops with notes inside notes...
           -- Note references inside other notes are allowed in reST, but
           -- not yet in this implementation.
           updateState $ \st -> st{ stateNotes = [] }
           contents <- parseFromString' parseBlocks raw
-          let newnotes = if ref == "*" || ref == "#" -- auto-numbered
-                            -- delete the note so the next auto-numbered note
-                            -- doesn't get the same contents:
-                            then deleteFirstsBy (==) notes [(ref,raw)]
-                            else notes
           updateState $ \st -> st{ stateNotes = newnotes }
           return $ Note (B.toList contents)
   | Just ref <- T.stripPrefix "##SUBST##" s = do
@@ -255,9 +300,9 @@
                    [Para [t]] -> return t
                    [Para xs] -> return $ Span nullAttr xs
                    bls -> return $ Span nullAttr $ blocksToInlines bls
-                 resolveReferences' (Set.insert key seen) resolved
+                 resolveReferences' namedNotes (Set.insert key seen) resolved
   | otherwise = return x
-resolveReferences' _ x = return x
+resolveReferences' _ _ x = return x
 
 parseCitation :: PandocMonad m
               => (Text, Text) -> RSTParser m (Inlines, [Blocks])
@@ -433,7 +478,7 @@
         Nothing  -> (headerTable ++ [DoubleHeader c], length headerTable + 1)
   setState (state { stateHeaderTable = headerTable' })
   attr@(ident,_,_) <- registerHeader nullAttr txt
-  let key = toKey (stringify txt)
+  let key = toKey (stringifyInlines txt)
   updateState $ \s ->
     s { stateKeys = M.insert key (("#" <> ident,""), nullAttr) $ stateKeys s }
   return $ B.headerWith attr level txt
@@ -465,7 +510,7 @@
         Nothing  -> (headerTable ++ [SingleHeader c], length headerTable + 1)
   setState (state { stateHeaderTable = headerTable' })
   attr@(ident,_,_) <- registerHeader nullAttr txt
-  let key = toKey (stringify txt)
+  let key = toKey (stringifyInlines txt)
   updateState $ \s ->
     s { stateKeys = M.insert key (("#" <> ident,""), nullAttr) $ stateKeys s }
   return $ B.headerWith attr level txt
@@ -473,7 +518,14 @@
 singleHeader' :: PandocMonad m => RSTParser m (Inlines, Char)
 singleHeader' = try $ do
   notFollowedBy' whitespace
-  lookAhead $ anyLine >> oneOf underlineChars
+  -- check that the next line is a full underline before committing
+  -- to parsing the header text (otherwise we'd parse the first line
+  -- of many paragraphs twice):
+  lookAhead $ do
+    anyLine
+    c <- oneOf underlineChars
+    skipMany (char c)
+    blankline
   txt <- trimInlines . mconcat <$> many1 (notFollowedBy blankline >> inline)
   pos <- getPosition
   let len = sourceColumn pos - 1
@@ -1044,22 +1096,15 @@
                  _ -> replicate numOfCols ColWidthDefault
          let toRow = Row nullAttr . map B.simpleCell
              toHeaderRow l = [toRow l | not (null l)]
-         return $ B.table (B.simpleCaption $ B.plain title)
+         return $ compactifyTable
+                $ B.table (B.simpleCaption $ B.plain title)
                           (zip (replicate numOfCols AlignDefault) widths)
                           (TableHead nullAttr $ toHeaderRow headerRow)
                           [TableBody nullAttr 0 [] $ map toRow bodyRows]
                           (TableFoot nullAttr [])
 
-singleParaToPlain :: Blocks -> Blocks
-singleParaToPlain bs =
-  case B.toList bs of
-    [Para ils] -> B.fromList [Plain ils]
-    _          -> bs
-
 parseCell :: PandocMonad m => Text -> RSTParser m Blocks
-parseCell t = singleParaToPlain
-   <$> parseFromString' parseBlocks (trim t <> "\n\n")
-
+parseCell t = parseFromString' parseBlocks (trim t <> "\n\n")
 
 -- TODO:
 --  - Only supports :format: fields with a single format for :raw: roles,
@@ -1176,8 +1221,8 @@
                                      then stripTrailingNewlines
                                      else id
           attribs = (ident, classes', kvs)
-          classes' = lang
-                    : ["numberLines" | isJust (lookup "number-lines" fields)]
+          classes' =   [ lang | not (T.null lang) ]
+                    ++ ["numberLines" | isJust (lookup "number-lines" fields)]
                     ++ classes
           kvs = [(k,v) | (k,v) <- fields, k /= "number-lines", k /= "class",
                                           k /= "id", k /= "name"]
@@ -1235,7 +1280,7 @@
 noteMarker :: Monad m => RSTParser m Text
 noteMarker = do
   char '['
-  res <- many1Char digit
+  res <- takeWhile1P isDigit
       <|>
                   try (char '#' >> liftM ("#" <>) simpleReferenceName)
       <|> countChar 1 (oneOf "#*")
@@ -1275,9 +1320,9 @@
 targetURI = do
   skipSpaces
   optional $ try $ newline >> notFollowedBy blankline
-  contents <- trim <$>
-     many1Char (satisfy (/='\n')
-     <|> try (newline >> many1 spaceChar >> noneOf " \t\n"))
+  contents <- trim . mconcat <$>
+     many1 (takeWhile1P (/='\n')
+     <|> T.singleton <$> try (newline >> many1 spaceChar >> noneOf " \t\n"))
   blanklines
   return $ stripBackticks contents
   where
@@ -1514,7 +1559,8 @@
   gridTableWith (Identity <$> parseBlocks)
 
 table :: PandocMonad m => RSTParser m Blocks
-table = gridTable <|> simpleTable False <|> simpleTable True <?> "table"
+table = compactifyTable <$>
+  (gridTable <|> simpleTable False <|> simpleTable True <?> "table")
 
 --
 -- inline
@@ -1559,7 +1605,7 @@
 
 symbol :: Monad m => RSTParser m Inlines
 symbol = do
-  c <- oneOf specialChars
+  c <- satisfy isSpecialChar
   unless (canPrecedeOpener c) updateLastStrPos
   return $ B.str $ T.singleton c
 
@@ -1720,10 +1766,11 @@
 
 str :: Monad m => RSTParser m Inlines
 str = do
-  let strChar = noneOf ("\t\n " ++ specialChars)
-  result <- many1Char strChar
+  result <- takeWhile1P isStrChar
   updateLastStrPos
   return $ B.str result
+ where
+  isStrChar c = c /= '\t' && c /= '\n' && c /= ' ' && not (isSpecialChar c)
 
 -- an endline character that can be treated as a space, not a structural break
 endline :: Monad m => RSTParser m Inlines
@@ -1741,8 +1788,29 @@
 --
 
 link :: PandocMonad m => RSTParser m Inlines
-link = choice [explicitLink, referenceLink, autoLink]  <?> "link"
+link = do
+  linkPossible
+  choice [explicitLink, referenceLink, autoLink]  <?> "link"
 
+-- Fail fast if no link parser can succeed here: each of them
+-- requires one of the characters @`[_:\@@ before the next
+-- whitespace ('`' for explicit links and quoted reference names,
+-- '[' for citation names, '_' for reference links, ':' after the
+-- scheme of a URI, '@' in an email address).  Checking this on the
+-- raw input is much cheaper than running each link parser over the
+-- next word only to have it fail.
+linkPossible :: Monad m => RSTParser m ()
+linkPossible = do
+  Sources inps <- getInput
+  case inps of
+    [] -> mzero
+    (_, t) : rest -> do
+      let (w, t') = T.break (\c -> c == ' ' || c == '\t' || c == '\n') t
+      guard $ (T.null t' && not (null rest))
+                -- word may continue in next chunk: inconclusive
+            || T.any (\c -> c == '`' || c == '[' || c == '_' ||
+                            c == ':' || c == '@') w
+
 explicitLink :: PandocMonad m => RSTParser m Inlines
 explicitLink = try $ do
   char '`'
@@ -1763,7 +1831,7 @@
   let label'' = if label' == mempty
                    then B.str src
                    else label'
-  let key = toKey $ stringify label'
+  let key = toKey $ stringifyInlines label'
   unless (key == Key mempty) $ do
     updateState $ \s -> s{
       stateKeys = M.insert key ((src',""), nullAttr) $ stateKeys s }
diff --git a/src/Text/Pandoc/Readers/RTF.hs b/src/Text/Pandoc/Readers/RTF.hs
--- a/src/Text/Pandoc/Readers/RTF.hs
+++ b/src/Text/Pandoc/Readers/RTF.hs
@@ -286,11 +286,11 @@
     x <- hexDigit
     y <- hexDigit
     return $ hexToWord (T.pack [x,y])
-  letterSequence = T.pack <$> many1 (satisfy (\c -> isAscii c && isLetter c))
+  letterSequence = takeWhile1P (\c -> isAscii c && isLetter c)
   unformattedText = do
-    ts <-  filter (\c -> c /= '\r' && c /= '\n') <$>
-           ( many1 (satisfy (\c -> not (isSpecial c) || c == '\r' || c == '\n')))
-    return $! UnformattedText $! T.pack ts
+    ts <-  T.filter (\c -> c /= '\r' && c /= '\n') <$>
+           takeWhile1P (\c -> not (isSpecial c) || c == '\r' || c == '\n')
+    return $! UnformattedText ts
   grouped = do
     char '{'
     skipMany nl
diff --git a/src/Text/Pandoc/Readers/Roff.hs b/src/Text/Pandoc/Readers/Roff.hs
--- a/src/Text/Pandoc/Readers/Roff.hs
+++ b/src/Text/Pandoc/Readers/Roff.hs
@@ -34,7 +34,7 @@
 import Control.Monad.Except (throwError)
 import Text.Pandoc.Class.PandocMonad
        (getResourcePath, readFileFromDirs, PandocMonad(..), report)
-import Data.Char (isLower, toLower, toUpper, isAlphaNum)
+import Data.Char (isLetter, isLower, toLower, toUpper, isAlphaNum)
 import Data.Default (Default)
 import qualified Data.Map as M
 import Data.List (intercalate)
@@ -202,7 +202,7 @@
   guard $ sourceColumn pos == 1 || afterConditional st
   char '.' <|> char '\''
   skipMany spacetab
-  macroName <- manyChar (satisfy isAlphaNum)
+  macroName <- takeWhileP isAlphaNum
   case macroName of
     "nop" -> return mempty
     "ie"  -> lexConditional "ie"
@@ -286,7 +286,7 @@
 
 tableOption :: PandocMonad m => RoffLexer m TableOption
 tableOption = do
-  k <- many1Char letter
+  k <- takeWhile1P isLetter
   v <- option "" $ try $ do
          skipMany spacetab
          char '('
@@ -366,7 +366,7 @@
 expression :: PandocMonad m => RoffLexer m (Maybe Bool)
 expression = do
   raw <- charsInBalanced '(' ')' (T.singleton <$> (satisfy (/= '\n')))
-      <|> many1Char nonspaceChar
+      <|> takeWhile1P (\c -> c /= ' ' && c /= '\t' && c /= '\n' && c /= '\r')
   returnValue $
     case raw of
       "1"  -> Just True
diff --git a/src/Text/Pandoc/Readers/Roff/Escape.hs b/src/Text/Pandoc/Readers/Roff/Escape.hs
--- a/src/Text/Pandoc/Readers/Roff/Escape.hs
+++ b/src/Text/Pandoc/Readers/Roff/Escape.hs
@@ -13,7 +13,7 @@
     ( PandocMonad(..), report, PandocMonad(..), report )
 import Control.Monad
     ( mzero, mplus, mzero, mplus )
-import Data.Char (chr, isAscii, isAlphaNum)
+import Data.Char (chr, isAscii, isAlphaNum, isDigit)
 import qualified Data.Map as M
 import qualified Data.Text as T
 import Text.Pandoc.Logging (LogMessage(..))
@@ -205,7 +205,7 @@
 signedNumber :: (PandocMonad m, RoffLikeLexer x) => Lexer m x T.Text
 signedNumber = try $ do
   sign <- option "" ("-" <$ char '-' <|> "" <$ char '+')
-  ds <- many1Char digit
+  ds <- takeWhile1P isDigit
   return (sign <> ds)
 
 -- Parses: [..] or (..
diff --git a/src/Text/Pandoc/Readers/TWiki.hs b/src/Text/Pandoc/Readers/TWiki.hs
--- a/src/Text/Pandoc/Readers/TWiki.hs
+++ b/src/Text/Pandoc/Readers/TWiki.hs
@@ -28,7 +28,7 @@
 import Text.Pandoc.Options
 import Text.Pandoc.Parsing hiding (enclosed)
 import Text.Pandoc.Readers.HTML (htmlTag, isCommentTag)
-import Text.Pandoc.Shared (tshow)
+import Text.Pandoc.Shared (tshow, compactifyTable)
 import Text.Pandoc.XML (fromEntities)
 
 -- | Read twiki from an input string and return a Pandoc document.
@@ -217,7 +217,8 @@
   return $ buildTable mempty rows $ fromMaybe (align rows, columns rows) thead
   where
     buildTable caption rows (aligns, heads)
-                    = B.table (B.simpleCaption $ B.plain caption)
+                    = compactifyTable $ B.table
+                              (B.simpleCaption $ B.plain caption)
                               aligns
                               (TableHead nullAttr $ toHeaderRow heads)
                               [TableBody nullAttr 0 [] $ map toRow rows]
@@ -418,7 +419,8 @@
              => TWParser m a -> TWParser m Text
 nestedString end = innerSpace <|> countChar 1 nonspaceChar
   where
-    innerSpace = try $ many1Char spaceChar <* notFollowedBy end
+    innerSpace = try $ takeWhile1P (\c -> c == ' ' || c == '\t')
+                        <* notFollowedBy end
 
 boldCode :: PandocMonad m => TWParser m B.Inlines
 boldCode = try $ B.strong . B.code . fromEntities <$> enclosed (string "==") nestedString
@@ -449,14 +451,15 @@
       | otherwise = isAlphaNum c
 
 str :: PandocMonad m => TWParser m B.Inlines
-str = B.str <$> (many1Char alphaNum <|> characterReference)
+str = B.str <$> (takeWhile1P isAlphaNum <|> characterReference)
 
 nop :: PandocMonad m => TWParser m B.Inlines
 nop = try $ (void exclamation <|> void nopTag) >> followContent
   where
     exclamation   = char '!'
     nopTag        = stringAnyCase "<nop>"
-    followContent = B.str . fromEntities <$> many1Char nonspaceChar
+    followContent = B.str . fromEntities <$>
+      takeWhile1P (\c -> c /= ' ' && c /= '\t' && c /= '\n' && c /= '\r')
 
 symbol :: PandocMonad m => TWParser m B.Inlines
 symbol = B.str <$> countChar 1 nonspaceChar
diff --git a/src/Text/Pandoc/Readers/Textile.hs b/src/Text/Pandoc/Readers/Textile.hs
--- a/src/Text/Pandoc/Readers/Textile.hs
+++ b/src/Text/Pandoc/Readers/Textile.hs
@@ -56,7 +56,7 @@
 import Text.Pandoc.Parsing
 import Text.Pandoc.Readers.HTML (htmlTag, isBlockTag, isInlineTag)
 import Text.Pandoc.Readers.LaTeX (rawLaTeXBlock, rawLaTeXInline)
-import Text.Pandoc.Shared (trim, tshow)
+import Text.Pandoc.Shared (trim, tshow, compactifyTable)
 import Text.Read (readMaybe)
 
 -- | Parse a Textile text and return a Pandoc document.
@@ -452,7 +452,8 @@
                 transpose $ map (map (snd . fst)) (headers:rows)
   let toRow = Row nullAttr . map B.simpleCell
       toHeaderRow l = [toRow l | not (null l)]
-  return $ B.table (B.simpleCaption $ B.plain caption)
+  return $ compactifyTable
+    $ B.table (B.simpleCaption $ B.plain caption)
     (zip aligns (replicate nbOfCols ColWidthDefault))
     (TableHead nullAttr $ toHeaderRow $ map snd headers)
     [TableBody nullAttr 0 [] $ map (toRow . map snd) rows]
@@ -679,7 +680,7 @@
   let attr = case lookup "style" kvs of
                Just stls -> (ident, cls, pickStylesToKVs ["width", "height"] stls)
                Nothing   -> (ident, cls, kvs)
-  src <- T.pack <$> many1 (noneOf " \t\n\r!(")
+  src <- takeWhile1P (`notElem` (" \t\n\r!(" :: [Char]))
   alt <- fmap T.pack $ option "" $ try $ char '(' *> manyTill anyChar (char ')')
   char '!'
   let img = B.imageWith attr src alt (B.str alt)
diff --git a/src/Text/Pandoc/Readers/TikiWiki.hs b/src/Text/Pandoc/Readers/TikiWiki.hs
--- a/src/Text/Pandoc/Readers/TikiWiki.hs
+++ b/src/Text/Pandoc/Readers/TikiWiki.hs
@@ -614,7 +614,7 @@
 wikiLinkText :: PandocMonad m => Text -> Text -> Text -> TikiWikiParser m (Text, Text, Text)
 wikiLinkText start middle end = do
   string (T.unpack start)
-  url <- T.pack <$> many1 (noneOf $ T.unpack middle ++ "\n")
+  url <- takeWhile1P (`notElem` (T.unpack middle ++ "\n"))
   seg1 <- option url linkContent
   seg2 <- option "" linkContent
   string (T.unpack end)
@@ -626,7 +626,7 @@
   where
     linkContent      = do
       char '|'
-      T.pack <$> many (noneOf $ T.unpack middle)
+      takeWhileP (`notElem` T.unpack middle)
 
 externalLink :: PandocMonad m => TikiWikiParser m B.Inlines
 externalLink = makeLink "[" "]|" "]"
diff --git a/src/Text/Pandoc/Readers/Txt2Tags.hs b/src/Text/Pandoc/Readers/Txt2Tags.hs
--- a/src/Text/Pandoc/Readers/Txt2Tags.hs
+++ b/src/Text/Pandoc/Readers/Txt2Tags.hs
@@ -18,6 +18,7 @@
 import Control.Monad (guard, void, when)
 import Control.Monad.Except (catchError, throwError)
 import Control.Monad.Reader (Reader, asks, runReader)
+import Data.Char (isAlphaNum)
 import Data.Default
 import Data.List (intercalate, transpose)
 import Data.List.NonEmpty (nonEmpty)
@@ -33,7 +34,7 @@
 import Text.Pandoc.Definition
 import Text.Pandoc.Options
 import Text.Pandoc.Parsing hiding (space, spaces, uri)
-import Text.Pandoc.Shared (compactify, compactifyDL)
+import Text.Pandoc.Shared (compactify, compactifyDL, compactifyTable)
 import Text.Pandoc.URI (escapeURI)
 
 type T2T = ParsecT Sources ParserState (Reader T2TMeta)
@@ -132,7 +133,7 @@
 setting :: T2T (Keyword, Value)
 setting = do
   string "%!"
-  keyword <- ignoreSpacesCap (many1Char alphaNum)
+  keyword <- ignoreSpacesCap (takeWhile1P isAlphaNum)
   char ':'
   value <- ignoreSpacesCap (manyTillChar anyChar newline)
   return (keyword, value)
@@ -274,7 +275,8 @@
   let headerPadded = if null tableHeader then mempty else pad size tableHeader
   let toRow = Row nullAttr . map B.simpleCell
       toHeaderRow l = [toRow l | not (null l)]
-  return $ B.table B.emptyCaption
+  return $ compactifyTable
+         $ B.table B.emptyCaption
                     (zip aligns (replicate ncolumns ColWidthDefault))
                       (TableHead nullAttr $ toHeaderRow headerPadded)
                       [TableBody nullAttr 0 [] $ map toRow rowsPadded]
@@ -415,7 +417,7 @@
              -> (Text -> a) -- Special Case to handle ******
              -> T2T Inlines
 inlineMarkup p f c special = try $ do
-  start <- many1Char (char c)
+  start <- takeWhile1P (== c)
   let l = T.length start
   guard (l >= 2)
   when (l == 2) (void $ notFollowedBy space)
@@ -426,7 +428,7 @@
   case body of
     Just middle -> do
       lastChar <- anyChar
-      end <- many1Char (char c)
+      end <- takeWhile1P (== c)
       let parser inp = parseFromString' (mconcat <$> many p) inp
       let start' = case T.drop 2 start of
                           "" -> mempty
@@ -518,7 +520,7 @@
 t2tURI = do
   start <- try ((<>) <$> proto <*> urlLogin) <|> guess
   domain <- many1Char chars
-  sep <- manyChar (char '/')
+  sep <- takeWhileP (== '/')
   form' <- option mempty (T.cons <$> char '?' <*> many1Char form)
   anchor' <- option mempty (T.cons <$> char '#' <*> manyChar anchor)
   return (start <> domain <> sep <> form' <> anchor')
@@ -528,7 +530,7 @@
     guess = (<>) <$> (((<>) <$> stringAnyCase "www" <*> option mempty (T.singleton <$> oneOf "23"))
               <|> stringAnyCase "ftp") <*> (T.singleton <$> char '.')
     login = alphaNum <|> oneOf "_.-"
-    pass = manyChar (noneOf " @")
+    pass = takeWhileP (\x -> x /= ' ' && x /= '@')
     chars = alphaNum <|> oneOf "%._/~:,=$@&+-"
     anchor = alphaNum <|> oneOf "%._0"
     form = chars <|> oneOf ";*"
@@ -572,7 +574,7 @@
   return B.softbreak
 
 str :: T2T Inlines
-str = try $ B.str <$> many1Char (noneOf $ specialChars ++ "\n\r ")
+str = try $ B.str <$> takeWhile1P (`notElem` (specialChars ++ "\n\r "))
 
 whitespace :: T2T Inlines
 whitespace = try $ B.space <$ spaceChar
diff --git a/src/Text/Pandoc/Readers/Typst.hs b/src/Text/Pandoc/Readers/Typst.hs
--- a/src/Text/Pandoc/Readers/Typst.hs
+++ b/src/Text/Pandoc/Readers/Typst.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE LambdaCase #-}
@@ -30,14 +31,14 @@
 import Typst ( parseTypst, evaluateTypst )
 import Text.Pandoc.Error (PandocError(..))
 import Text.Pandoc.Translations (Term(References), translateTerm)
-import Text.Pandoc.Shared (tshow, blocksToInlines)
+import Text.Pandoc.Shared (tshow, blocksToInlines, compactifyTable)
 import Text.Pandoc.Parsing (registerHeader, reportLogMessages)
 import Control.Monad.Except (throwError)
 import Control.Monad (MonadPlus (mplus), void, guard, foldM)
 import Control.Monad.Trans (lift)
 import qualified Data.Foldable as F
 import qualified Data.Map as M
-import Data.Maybe (catMaybes, fromMaybe, isJust)
+import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe)
 import Data.Sequence (Seq)
 import qualified Data.Sequence as Seq
 import qualified Data.Set as Set
@@ -96,7 +97,7 @@
           ignored ("unknown block element " <> tname <>
                    " at " <> tshow pos)
           pure mempty
-        Just handler -> handler pos mbident fields
+        Just (BlockHandler handler) -> handler pos mbident fields
     _ -> pure mempty
 
 pInline :: PandocMonad m => P m B.Inlines
@@ -110,54 +111,130 @@
       , tname /= "math.equation" ->
           B.math . writeTeX <$> pMathMany (Seq.singleton res)
     Elt name@(Identifier tname) pos fields -> do
-      labs <- sLabels <$> getState
-      labelTarget <- (do result <- getField "target" fields
-                         case result of
-                           VLabel t | t `elem` labs -> pure True
-                           _ -> pure False)
-                  <|> pure False
-      if tname == "ref" && not labelTarget
-         then do
-           -- @foo is a citation unless it links to a lab in the doc:
-           let targetToKey (Identifier "target") = Identifier "key"
-               targetToKey k = k
-           case M.lookup "cite" inlineHandlers of
-             Nothing -> do
-               ignored ("unknown inline element " <> tname <>
-                        " at " <> tshow pos)
-               pure mempty
-             Just handler -> handler pos Nothing (M.mapKeys targetToKey fields)
+      let runInlineHandler =
+            case M.lookup name inlineHandlers of
+              Nothing -> do
+                ignored ("unknown inline element " <> tname <>
+                         " at " <> tshow pos)
+                pure mempty
+              Just (InlineHandler handler) -> handler pos Nothing fields
+      if tname /= "ref"
+         then runInlineHandler
          else do
-          case M.lookup name inlineHandlers of
-            Nothing -> do
-              ignored ("unknown inline element " <> tname <>
-                       " at " <> tshow pos)
-              pure mempty
-            Just handler -> handler pos Nothing fields
+           labs <- sLabels <$> getState
+           labelTarget <- (do result <- getField "target" fields
+                              case result of
+                                VLabel t | t `Set.member` labs -> pure True
+                                _ -> pure False)
+                       <|> pure False
+           if labelTarget
+              then runInlineHandler
+              else do
+                -- @foo is a citation unless it links to a lab in the doc:
+                let targetToKey (Identifier "target") = Identifier "key"
+                    targetToKey k = k
+                case M.lookup "cite" inlineHandlers of
+                  Nothing -> do
+                    ignored ("unknown inline element " <> tname <>
+                             " at " <> tshow pos)
+                    pure mempty
+                  Just (InlineHandler handler) ->
+                    handler pos Nothing (M.mapKeys targetToKey fields)
 
--- Pull block elements out of inline elements, e.g.
--- Elt "smallcaps" [ Elt "heading" [..] ] ->
--- Elt "heading" [ Elt "smallcaps" [..]]. See #11017.
+-- Ensure that inline elements contain only inline content: split
+-- them at paragraph breaks, and pull block children out, applying the
+-- element to a child's own contents (#11017, #11881).  A pandoc inline
+-- cannot span paragraphs, so this is the closest structural rendering;
+-- e.g. Elt "emph" [Txt "hi", parbreak, Txt "there"] becomes
+-- Elt "emph" [Txt "hi"], parbreak, Elt "emph" [Txt "there"].
 fixNesting :: Content -> Content
-fixNesting el@(Elt name pos fields)
-  | Just (VContent elts) <- M.lookup "body" fields
-  = let elts' = fmap fixNesting elts
-        fields' = M.insert "body" (VContent elts') fields
-        in if isBlock el
-              then Elt name pos fields'
-              else case getField "body" fields' of
-                        Just ([el'@(Elt name' pos' fields'')] :: Seq Content)
-                          | isBlock el'
-                          , not (isInline el')
-                          , "body" `M.member` fields''
-                          -> Elt name' pos' $
-                               M.insert "body" (VContent
-                                                (Seq.singleton
-                                                  (Elt name pos fields'')))
-                                        fields'
-                        _ -> Elt name pos fields'
+fixNesting el@(Elt name _ _)
+  | Identifier tname <- name
+  , "math." `T.isPrefixOf` tname = el   -- math has its own grammar
+fixNesting (Elt name pos fields) = Elt name pos (M.map fixVal fields)
 fixNesting x = x
 
+fixVal :: Val -> Val
+fixVal (VContent cs) = VContent (fixSeq cs)
+fixVal (VArray vs) = VArray (fmap fixVal vs)
+fixVal (VTermItem t d) = VTermItem (fixSeq t) (fixSeq d)
+fixVal v = v
+
+fixSeq :: Seq Content -> Seq Content
+fixSeq = foldMap expand . fmap fixNesting
+
+-- Split an inline element whose body contains block content.
+expand :: Content -> Seq Content
+expand el@(Elt name pos fields)
+  | isSplittable name
+  , Just (field, VContent body) <- contentField fields
+  , F.any isStrictlyBlock body
+  = splitInlineBody name pos fields field body
+  | otherwise = Seq.singleton el
+expand x = Seq.singleton x
+
+-- Whether the element's body is parsed with pInlines, and thus cannot
+-- contain block content: anything without a block handler, except
+-- footnote (body parsed as blocks), the block-body table elements, and
+-- math elements.
+isSplittable :: Identifier -> Bool
+isSplittable name@(Identifier tname) =
+  name `Set.notMember` blockKeys
+    && name `Set.notMember` blockBodyElements
+    && not ("math." `T.isPrefixOf` tname)
+
+-- Elements without block handlers whose content is nonetheless parsed
+-- as block content.
+blockBodyElements :: Set.Set Identifier
+blockBodyElements = Set.fromList
+  [ "footnote", "grid.cell", "table.cell", "grid.header"
+  , "table.header", "grid.footer", "table.footer" ]
+
+-- Strictly block content, not consumable by 'pInline'.
+isStrictlyBlock :: Content -> Bool
+isStrictlyBlock c = isBlock c && not (isInline c)
+
+-- The element's content field.  Only body and text: other content
+-- fields, such as ref's supplement, are parameters rather than bodies.
+contentField :: M.Map Identifier Val -> Maybe (Identifier, Val)
+contentField fields =
+  listToMaybe
+    [ kv | kv@(k, VContent _) <- M.toAscList fields
+         , k == Identifier "body" || k == Identifier "text" ]
+
+-- Split the element's body at block content: inline runs are wrapped
+-- back in the element, a parbreak separates paragraphs, and a block
+-- child gets the element applied to its own body, if it has one.
+splitInlineBody
+  :: Identifier -> Maybe SourcePos -> M.Map Identifier Val
+  -> Identifier -> Seq Content -> Seq Content
+splitInlineBody name pos fields field =
+  Seq.fromList . go [] . F.toList
+ where
+  wrap cs = Elt name pos (M.insert field (VContent (Seq.fromList cs)) fields)
+
+  go run [] = flush run
+  go run (c : cs)
+    | isStrictlyBlock c = flush run ++ splitOff c ++ go [] cs
+    | otherwise = go (c : run) cs
+
+  flush run = [ wrap (reverse run) | not (null run) ]
+
+  splitOff c
+    | isParbreak c = [Elt "parbreak" pos mempty]
+    | otherwise = case c of
+        Elt bname bpos bfields
+          | Just (VContent inner) <- M.lookup (Identifier "body") bfields ->
+              [ Elt bname bpos
+                  ( M.insert (Identifier "body")
+                      (VContent (expand (wrap (F.toList inner))))
+                      bfields ) ]
+        _ -> [c]
+
+isParbreak :: Content -> Bool
+isParbreak (Elt "parbreak" _ _) = True
+isParbreak _ = False
+
 pPandoc :: PandocMonad m => P m B.Pandoc
 pPandoc = do
   Elt "document" _ fields <- pTok isDocument
@@ -231,28 +308,35 @@
 isInline Txt{} = True
 
 blockKeys :: Set.Set Identifier
-blockKeys = Set.fromList $ M.keys
-  (blockHandlers :: M.Map Identifier
-     (Maybe SourcePos -> Maybe Text ->
-      M.Map Identifier Val -> P PandocPure B.Blocks))
+blockKeys = Set.fromList $ M.keys blockHandlers
 
 inlineKeys :: Set.Set Identifier
-inlineKeys = Set.fromList $ M.keys
-  (inlineHandlers :: M.Map Identifier
-     (Maybe SourcePos -> Maybe Text ->
-      M.Map Identifier Val -> P PandocPure B.Inlines))
+inlineKeys = Set.fromList $ M.keys inlineHandlers
 
-blockHandlers :: PandocMonad m =>
-                   M.Map Identifier
-                   (Maybe SourcePos -> Maybe Text ->
-                    M.Map Identifier Val -> P m B.Blocks)
+-- The handler maps are wrapped in newtypes with polymorphic fields so
+-- that the maps themselves are monomorphic.  This guarantees that they
+-- are constant applicative forms, constructed only once; a
+-- @PandocMonad m => M.Map ...@ would be a function taking a typeclass
+-- dictionary, liable to be rebuilt at each lookup.
+
+newtype BlockHandler = BlockHandler
+  (forall m. PandocMonad m
+    => Maybe SourcePos -> Maybe Text -> M.Map Identifier Val
+    -> P m B.Blocks)
+
+newtype InlineHandler = InlineHandler
+  (forall m. PandocMonad m
+    => Maybe SourcePos -> Maybe Text -> M.Map Identifier Val
+    -> P m B.Inlines)
+
+blockHandlers :: M.Map Identifier BlockHandler
 blockHandlers = M.fromList
-  [("text", \_ _ fields -> do
+  [("text", BlockHandler $ \_ _ fields -> do
       body <- getField "body" fields
       -- sometimes text elements include para breaks
       notFollowedBy $ void $ pWithContents pInlines body
       pWithContents pBlocks body)
-  ,("title", \_ _ fields -> do
+  ,("title", BlockHandler $ \_ _ fields -> do
       body <- getField "body" fields
       case body of
         VContent cs -> do
@@ -260,16 +344,16 @@
           updateState $ \s -> s{ sMeta = B.setMeta "title" ils (sMeta s) }
           pure mempty
         _ -> pure mempty)
-  ,("box", \_ _ fields -> do
+  ,("box", BlockHandler $ \_ _ fields -> do
       body <- getField "body" fields
       B.divWith ("", ["box"], []) <$> pWithContents pBlocks body)
-  ,("heading", \_ mbident fields -> do
+  ,("heading", BlockHandler $ \_ mbident fields -> do
       body <- getField "body" fields
       lev <- getField "level" fields <|> pure 1
       ils <- pWithContents pInlines body
       attr <- registerHeader (fromMaybe "" mbident,[],[]) ils
       pure $ B.headerWith attr lev ils)
-  ,("quote", \_ _ fields -> do
+  ,("quote", BlockHandler $ \_ _ fields -> do
       getField "block" fields >>= guard
       body <- getField "body" fields >>= pWithContents pBlocks
       attribution' <- getField "attribution" fields
@@ -278,11 +362,11 @@
                         else (\x -> B.para ("\x2014\xa0" <> x)) <$>
                               (pWithContents pInlines attribution')
       pure $ B.blockQuote $ body <> attribution)
-  ,("list", \_ _ fields -> do
+  ,("list", BlockHandler $ \_ _ fields -> do
       children <- V.toList <$> getField "children" fields
       B.bulletList <$> mapM (pWithContents pBlocks) children)
-  ,("list.item", \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)
-  ,("enum", \_ _ fields -> do
+  ,("list.item", BlockHandler $ \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)
+  ,("enum", BlockHandler $ \_ _ fields -> do
       children <- V.toList <$> getField "children" fields
       mbstart <- getField "start" fields
       start <- case mbstart of
@@ -311,8 +395,8 @@
               _ -> (B.DefaultStyle, B.DefaultDelim)
       let listAttr = (start, sty, delim)
       B.orderedListWith listAttr <$> mapM (pWithContents pBlocks) children)
-  ,("enum.item", \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)
-  ,("terms", \_ _ fields -> do
+  ,("enum.item", BlockHandler $ \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)
+  ,("terms", BlockHandler $ \_ _ fields -> do
       children <- V.toList <$> getField "children" fields
       B.definitionList
         <$> mapM
@@ -324,38 +408,41 @@
               _ -> pure (mempty, [])
           )
           children)
-  ,("terms.item", \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)
-  ,("raw", \_ mbident fields -> do
+  ,("terms.item", BlockHandler $ \_ _ fields -> getField "body" fields >>= pWithContents pBlocks)
+  ,("raw", BlockHandler $ \_ mbident fields -> do
       txt <- T.filter (/= '\r') <$> getField "text" fields
       mblang <- getField "lang" fields
       let attr = (fromMaybe "" mbident, maybe [] (\l -> [l]) mblang, [])
       pure $ B.codeBlockWith attr txt)
-  ,("parbreak", \_ _ _ -> pure mempty)
-  ,("block", \_ mbident fields ->
+  ,("parbreak", BlockHandler $ \_ _ _ -> pure mempty)
+  ,("par", BlockHandler $ \_ mbident fields -> do
+      maybe B.para (\ident -> B.divWith (ident, [], []) . B.para) mbident
+        <$> (getField "body" fields >>= pWithContents pInlines))
+  ,("block", BlockHandler $ \_ mbident fields ->
       maybe id (\ident -> B.divWith (ident, [], [])) mbident
         <$> (getField "body" fields >>= pWithContents pBlocks))
-  ,("place", \_ _ fields -> do
+  ,("place", BlockHandler $ \_ _ fields -> do
       ignored "parameters of place"
       getField "body" fields >>= pWithContents pBlocks)
-  ,("columns", \_ _ fields -> do
+  ,("columns", BlockHandler $ \_ _ fields -> do
       (cnt :: Integer) <- getField "count" fields
       B.divWith ("", ["columns-flow"], [("count", T.pack (show cnt))])
         <$> (getField "body" fields >>= pWithContents pBlocks))
-  ,("rect", \_ _ fields ->
+  ,("rect", BlockHandler $ \_ _ fields ->
       B.divWith ("", ["rect"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
-  ,("circle", \_ _ fields ->
+  ,("circle", BlockHandler $ \_ _ fields ->
       B.divWith ("", ["circle"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
-  ,("ellipse", \_ _ fields ->
+  ,("ellipse", BlockHandler $ \_ _ fields ->
       B.divWith ("", ["ellipse"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
-  ,("polygon", \_ _ fields ->
+  ,("polygon", BlockHandler $ \_ _ fields ->
       B.divWith ("", ["polygon"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
-  ,("square", \_ _ fields ->
+  ,("square", BlockHandler $ \_ _ fields ->
       B.divWith ("", ["square"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
-  ,("align", \_ _ fields -> do
+  ,("align", BlockHandler $ \_ _ fields -> do
       alignment <- getField "alignment" fields
       B.divWith ("", [], [("align", repr alignment)])
         <$> (getField "body" fields >>= pWithContents pBlocks))
-  ,("stack", \_ _ fields -> do
+  ,("stack", BlockHandler $ \_ _ fields -> do
       (dir :: Direction) <- getField "dir" fields `mplus` pure Ltr
       rawchildren <- getField "children" fields
       children <-
@@ -370,9 +457,9 @@
         B.divWith ("", [], [("stack", repr (VDirection dir))]) $
           mconcat $
             map (B.divWith ("", [], [])) children)
-  ,("grid", \_ mbident fields -> parseTable mbident fields)
-  ,("table", \_ mbident fields -> parseTable mbident fields)
-  ,("figure", \_ mbident fields -> do
+  ,("grid", BlockHandler $ \_ mbident fields -> parseTable mbident fields)
+  ,("table", BlockHandler $ \_ mbident fields -> parseTable mbident fields)
+  ,("figure", BlockHandler $ \_ mbident fields -> do
       body <- getField "body" fields >>= pWithContents pBlocks
       (mbCaption :: Maybe (Seq Content)) <- getField "caption" fields
       (caption :: B.Blocks) <- maybe mempty (pWithContents pBlocks) mbCaption
@@ -382,14 +469,14 @@
             (B.Table attr (B.Caption Nothing (B.toList caption)) colspecs thead tbodies tfoot)
         _ -> B.figureWith (fromMaybe "" mbident, [], [])
                           (B.Caption Nothing (B.toList caption)) body)
-  ,("line", \_ _ fields ->
+  ,("line", BlockHandler $ \_ _ fields ->
       case ( M.lookup "start" fields
               >> M.lookup "end" fields
               >> M.lookup "angle" fields ) of
         Nothing -> pure B.horizontalRule
         _ -> pure mempty)
-  ,("divider", \_ _ _fields -> pure B.horizontalRule)
-  ,("numbering", \_ _ fields -> do
+  ,("divider", BlockHandler $ \_ _ _fields -> pure B.horizontalRule)
+  ,("numbering", BlockHandler $ \_ _ fields -> do
       numStyle <- getField "numbering" fields
       (nums :: V.Vector Integer) <- getField "numbers" fields
       let toText v = fromMaybe "" $ fromVal v
@@ -402,12 +489,12 @@
                   Failure _ -> "?"
               _ -> "?"
       pure $ B.plain . B.text . mconcat . map toNum $ V.toList nums)
-  ,("footnote.entry", \_ _ fields ->
+  ,("footnote.entry", BlockHandler $ \_ _ fields ->
       getField "body" fields >>= pWithContents pBlocks)
-  ,("pad", \_ _ fields ->  -- ignore paddingy
+  ,("pad", BlockHandler $ \_ _ fields ->  -- ignore paddingy
       getField "body" fields >>= pWithContents pBlocks)
-  ,("pagebreak", \_ _ _ -> pure $ B.divWith ("", ["page-break"], [("wrapper", "1")]) B.horizontalRule)
-  ,("bibliography", \_ _ fields -> do
+  ,("pagebreak", BlockHandler $ \_ _ _ -> pure $ B.divWith ("", ["page-break"], [("wrapper", "1")]) B.horizontalRule)
+  ,("bibliography", BlockHandler $ \_ _ fields -> do
       let getSources v = case v of
                       VString t -> MetaString t
                       VArray xs -> MetaList $ map getSources $ V.toList xs
@@ -426,7 +513,7 @@
                    _ -> Just . B.text <$> lift (translateTerm References)
       let hdr = maybe mempty (B.header 1) mbTitle
       pure $ hdr <> B.divWith ("refs", [], []) mempty)
-  ,("rotate", \_ _ fields -> do
+  ,("rotate", BlockHandler $ \_ _ fields -> do
       body <- getField "body" fields >>= pWithContents pBlocks
       let kvs = case M.lookup "angle" fields of
                     Just (VAngle ang) -> [("angle", T.pack $ show ang)]
@@ -434,11 +521,9 @@
       pure $ B.divWith ("", ["rotate"], kvs) body)
   ]
 
-inlineHandlers :: PandocMonad m =>
-    M.Map Identifier (Maybe SourcePos -> Maybe Text ->
-                      M.Map Identifier Val -> P m B.Inlines)
+inlineHandlers :: M.Map Identifier InlineHandler
 inlineHandlers = M.fromList
-  [("ref", \_ _ fields -> do
+  [("ref", InlineHandler $ \_ _ fields -> do
       VLabel target <- getField "target" fields
       supplement' <- getField "supplement" fields
       supplement <- case supplement' of
@@ -449,17 +534,17 @@
                            pure $ B.text ("[" <> target <> "]")
                       _ -> pure mempty
       pure $ B.linkWith ("", ["ref"], []) ("#" <> target) "" supplement)
-  ,("linebreak", \_ _ _ -> pure B.linebreak)
-  ,("text", \_ _ fields -> do
+  ,("linebreak", InlineHandler $ \_ _ _ -> pure B.linebreak)
+  ,("text", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       (mbweight :: Maybe Text) <- getField "weight" fields
       case mbweight of
         Just "bold" -> B.strong <$> pWithContents pInlines body
         _ -> pWithContents pInlines body)
-  ,("raw", \_ _ fields -> B.code . T.filter (/= '\r') <$> getField "text" fields)
-  ,("footnote", \_ _ fields ->
+  ,("raw", InlineHandler $ \_ _ fields -> B.code . T.filter (/= '\r') <$> getField "text" fields)
+  ,("footnote", InlineHandler $ \_ _ fields ->
       B.note <$> (getField "body" fields >>= pWithContents pBlocks))
-  ,("cite", \_ _ fields -> do
+  ,("cite", InlineHandler $ \_ _ fields -> do
       VLabel key <- getField "key" fields
       (form :: Text) <- getField "form" fields <|> pure "normal"
       let citation =
@@ -468,44 +553,50 @@
                 B.citationPrefix = mempty,
                 B.citationSuffix = mempty,
                 B.citationMode = case form of
+                                    "prose" -> B.AuthorInText
                                     "year" -> B.SuppressAuthor
+                                    -- "author" and "full" have no pandoc
+                                    -- equivalent; fall back to normal
                                     _ -> B.NormalCitation,
                 B.citationNoteNum = 0,
                 B.citationHash = 0
               }
       pure $ B.cite [citation] (B.text $ "[" <> key <> "]"))
-  ,("lower", \_ _ fields -> do
+  ,("lower", InlineHandler $ \_ _ fields -> do
       body <- getField "text" fields
       walk (modString T.toLower) <$> pWithContents pInlines body)
-  ,("upper", \_ _ fields -> do
+  ,("upper", InlineHandler $ \_ _ fields -> do
       body <- getField "text" fields
       walk (modString T.toUpper) <$> pWithContents pInlines body)
-  ,("emph", \_ _ fields -> do
+  ,("emph", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       B.emph <$> pWithContents pInlines body)
-  ,("strong", \_ _ fields -> do
+  ,("strong", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       B.strong <$> pWithContents pInlines body)
-  ,("sub", \_ _ fields -> do
+  ,("sub", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       B.subscript <$> pWithContents pInlines body)
-  ,("super", \_ _ fields -> do
+  ,("super", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       B.superscript <$> pWithContents pInlines body)
-  ,("strike", \_ _ fields -> do
+  ,("strike", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       B.strikeout <$> pWithContents pInlines body)
-  ,("smallcaps", \_ _ fields -> do
+  ,("smallcaps", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       B.smallcaps <$> pWithContents pInlines body)
-  ,("underline", \_ _ fields -> do
+  ,("underline", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       B.underline <$> pWithContents pInlines body)
-  ,("quote", \_ _ fields -> do
+  ,("highlight", InlineHandler $ \_ _ fields -> do
+      body <- getField "body" fields
+      B.spanWith ("", ["mark"], []) <$> pWithContents pInlines body)
+  ,("quote", InlineHandler $ \_ _ fields -> do
       (getField "block" fields <|> pure False) >>= guard . not
-      body <- getInlineBody fields >>= pWithContents pInlines
+      body <- getField "body" fields >>= pWithContents pInlines
       pure $ B.doubleQuoted $ B.trimInlines body)
-  ,("link", \_ _ fields -> do
+  ,("link", InlineHandler $ \_ _ fields -> do
       dest <- getField "dest" fields
       src <- case dest of
         VString t -> pure t
@@ -530,7 +621,7 @@
                pWithContents
                 (B.fromList . blocksToInlines . B.toList <$> pBlocks) body
       pure $ B.link src "" description)
-  ,("image", \mbpos _ fields -> do
+  ,("image", InlineHandler $ \mbpos _ fields -> do
       path <- getField "source" fields <|> getField "path" fields
       alt <- (B.text <$> getField "alt" fields) `mplus` pure mempty
       let basedir = maybe "." (takeDirectory . sourceName) mbpos
@@ -550,10 +641,10 @@
                 ++ maybe [] (\x -> [("height", x)]) mbheight
             )
       pure $ B.imageWith attr path' "" alt)
-  ,("box", \_ _ fields -> do
+  ,("box", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       B.spanWith ("", ["box"], []) <$> pWithContents pInlines body)
-  ,("h", \_ _ fields -> do
+  ,("h", InlineHandler $ \_ _ fields -> do
       amount <- getField "amount" fields `mplus` pure (LExact 1 LEm)
       let em = case amount of
             LExact x LEm -> toRational x
@@ -561,21 +652,21 @@
               LExact x LPt -> toRational x / 12
               _ -> 1 / 3 -- guess!
       pure $ B.text $ getSpaceChars em)
-  ,("place", \_ _ fields -> do
+  ,("place", InlineHandler $ \_ _ fields -> do
       ignored "parameters of place"
       getField "body" fields >>= pWithContents pInlines)
-  ,("align", \_ _ fields -> do
+  ,("align", InlineHandler $ \_ _ fields -> do
       alignment <- getField "alignment" fields
       B.spanWith ("", [], [("align", repr alignment)])
         <$> (getField "body" fields >>= pWithContents pInlines))
-  ,("sys.version", \_ _ _ -> pure $ B.text "typst-hs")
-  ,("math.equation", \_ _ fields -> do
+  ,("sys.version", InlineHandler $ \_ _ _ -> pure $ B.text "typst-hs")
+  ,("math.equation", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields
       display <- getField "block" fields
       (if display then B.displayMath else B.math) . writeTeX <$> pMathMany body)
-  ,("pad", \_ _ fields ->  -- ignore paddingy
+  ,("pad", InlineHandler $ \_ _ fields ->  -- ignore paddingy
       getField "body" fields >>= pWithContents pInlines)
-  ,("rotate", \_ _ fields -> do
+  ,("rotate", InlineHandler $ \_ _ fields -> do
       body <- getField "body" fields >>= pWithContents pInlines
       let kvs = case M.lookup "angle" fields of
                     Just (VAngle ang) -> [("angle", T.pack $ show ang)]
@@ -583,19 +674,6 @@
       pure $ B.spanWith ("", ["rotate"], kvs) body)
   ]
 
-getInlineBody :: PandocMonad m => M.Map Identifier Val -> P m (Seq Content)
-getInlineBody fields =
-  parbreaksToLinebreaks <$> getField "body" fields
-
-parbreaksToLinebreaks :: Seq Content -> Seq Content
-parbreaksToLinebreaks =
-  fmap go . Seq.dropWhileL isParbreak . Seq.dropWhileR isParbreak
- where
-   go (Elt "parbreak" pos _) = Elt "linebreak" pos mempty
-   go x = x
-   isParbreak (Elt "parbreak" _ _) = True
-   isParbreak _ = False
-
 pPara :: PandocMonad m => P m B.Blocks
 pPara = do
   ils <- B.trimInlines . collapseAdjacentCites . mconcat <$> many1 pInline
@@ -634,20 +712,22 @@
      Cite (cs1 ++ cs2) (ils1 <> ils2) : xs
    go (Cite cs1 ils1) (Space : Cite cs2 ils2 : xs) =
      Cite (cs1 ++ cs2) (ils1 <> ils2) : xs
+   go (Cite cs1 ils1) (SoftBreak : Cite cs2 ils2 : xs) =
+     Cite (cs1 ++ cs2) (ils1 <> ils2) : xs
    go x xs = x:xs
 
 modString :: (Text -> Text) -> B.Inline -> B.Inline
 modString f (B.Str t) = B.Str (f t)
 modString _ x = x
 
-findLabels :: Seq.Seq Content -> [Text]
-findLabels = foldr go []
+findLabels :: Seq.Seq Content -> Set.Set Text
+findLabels = F.foldl' go Set.empty
  where
-   go (Txt{}) = id
-   go (Lab t) = (t :)
-   go (Elt{ eltFields = fs }) = \ts -> foldr go' ts fs
-   go' (VContent cs) = (findLabels cs ++)
-   go' _ = id
+   go acc Txt{} = acc
+   go acc (Lab t) = Set.insert t acc
+   go acc (Elt{ eltFields = fs }) = F.foldl' go' acc fs
+   go' acc (VContent cs) = F.foldl' go acc cs
+   go' acc _ = acc
 
 parseTable :: PandocMonad m
            => Maybe Text -> M.Map Identifier Val -> P m B.Blocks
@@ -760,14 +840,13 @@
   let headRows = getRows THeader tableData
   let bodyRows = getRows TBody tableData
   let footRows = getRows TFooter tableData
-  pure $
-    B.tableWith
-      (fromMaybe "" mbident, [], [])
-      (B.Caption mempty mempty)
-      colspecs
-      (B.TableHead B.nullAttr headRows)
-      [B.TableBody B.nullAttr 0 [] bodyRows]
-      (B.TableFoot B.nullAttr footRows)
+  pure $ compactifyTable $ B.tableWith
+        (fromMaybe "" mbident, [], [])
+        (B.Caption mempty mempty)
+        colspecs
+        (B.TableHead B.nullAttr headRows)
+        [B.TableBody B.nullAttr 0 [] bodyRows]
+        (B.TableFoot B.nullAttr footRows)
 
 data TableSection = THeader | TBody | TFooter
   deriving (Show, Ord, Eq)
diff --git a/src/Text/Pandoc/Readers/Typst/Parsing.hs b/src/Text/Pandoc/Readers/Typst/Parsing.hs
--- a/src/Text/Pandoc/Readers/Typst/Parsing.hs
+++ b/src/Text/Pandoc/Readers/Typst/Parsing.hs
@@ -36,7 +36,7 @@
 import Text.Pandoc.Definition
 
 data PState = PState
-        { sLabels :: [Text]
+        { sLabels :: Set Text
         , sMeta :: Meta
         , sOptions :: ReaderOptions
         , sIdentifiers :: Set Text
@@ -57,7 +57,7 @@
 defaultPState :: PState
 defaultPState =
   PState
-  { sLabels = []
+  { sLabels = Set.empty
   , sMeta = mempty
   , sOptions = def
   , sIdentifiers = Set.empty
diff --git a/src/Text/Pandoc/Readers/Vimwiki.hs b/src/Text/Pandoc/Readers/Vimwiki.hs
--- a/src/Text/Pandoc/Readers/Vimwiki.hs
+++ b/src/Text/Pandoc/Readers/Vimwiki.hs
@@ -66,15 +66,17 @@
 import Text.Pandoc.Parsing (ParserState, ParsecT, blanklines, emailAddress,
                             many1Till, orderedListMarker, readWithM,
                             registerHeader, spaceChar, stateMeta,
-                            stateOptions, uri, manyTillChar, manyChar, textStr,
-                            many1Char, countChar, many1TillChar,
+                            stateOptions, uri, manyTillChar, textStr,
+                            countChar, many1TillChar,
+                            takeWhileP, takeWhile1P,
                             alphaNum, anyChar, char, newline, noneOf, oneOf,
                             space, spaces, string, choice, eof, lookAhead,
                             many1, many, manyTill, notFollowedBy,
                             skipMany1, try, option,
                             updateState, getState, (<|>))
 import Text.Pandoc.Sources (ToSources(..), Sources)
-import Text.Pandoc.Shared (splitTextBy, stringify, stripFirstAndLast, tshow)
+import Text.Pandoc.Shared (splitTextBy, stringifyInlines, stripFirstAndLast,
+                           tshow)
 import Text.Pandoc.URI (isURI)
 
 readVimwiki :: (PandocMonad m, ToSources a)
@@ -200,7 +202,7 @@
 definitionTerm :: PandocMonad m => VwParser m Inlines
 definitionTerm = try $ do
   x <- definitionTerm1 <|> definitionTerm2
-  guard (stringify x /= "")
+  guard (stringifyInlines x /= "")
   return x
 
 definitionTerm1 :: PandocMonad m => VwParser m Inlines
@@ -223,7 +225,7 @@
 preformatted :: PandocMonad m => VwParser m Blocks
 preformatted = try $ do
   many spaceChar >> string "{{{"
-  attrText <- manyChar (noneOf "\n")
+  attrText <- takeWhileP (/= '\n')
   lookAhead newline
   contents <- manyTillChar anyChar (try (char '\n' >> many spaceChar >> string "}}}"
     >> many spaceChar >> newline))
@@ -233,8 +235,11 @@
 
 makeAttr :: Text -> Attr
 makeAttr s =
-  let xs = splitTextBy (`elem` (" \t" :: String)) s in
-    ("", syntax xs, mapMaybe nameValue xs)
+  let xs = splitTextBy (`elem` (" \t" :: String)) s
+      kvs = mapMaybe nameValue xs
+      cls = syntax xs ++ maybe [] T.words (lookup "class" kvs)
+      ident = fromMaybe "" $ lookup "id" kvs
+  in (ident, cls, [(k,v) | (k,v) <- kvs, k /= "class" && k /= "id"])
 
 syntax :: [Text] -> [Text]
 syntax (s:_) | not $ T.isInfixOf "=" s = [s]
@@ -479,7 +484,7 @@
 inlineML = choice $ whitespace endlineML:inlineList
 
 str :: PandocMonad m => VwParser m Inlines
-str = B.str <$> many1Char (noneOf $ spaceChars ++ specialChars)
+str = B.str <$> takeWhile1P (`notElem` (spaceChars ++ specialChars))
 
 whitespace :: PandocMonad m => VwParser m () -> VwParser m Inlines
 whitespace endline = B.space <$ (skipMany1 spaceChar <|>
@@ -507,7 +512,7 @@
   return $ B.spanWith (makeId contents, [], []) mempty <> B.strong contents
 
 makeId :: Inlines -> Text
-makeId i = T.concat (stringify <$> toList i)
+makeId i = stringifyInlines i
 
 emph :: PandocMonad m => VwParser m Inlines
 emph = try $ do
diff --git a/src/Text/Pandoc/Readers/XML.hs b/src/Text/Pandoc/Readers/XML.hs
--- a/src/Text/Pandoc/Readers/XML.hs
+++ b/src/Text/Pandoc/Readers/XML.hs
@@ -25,6 +25,7 @@
 import Data.Text (Text)
 import qualified Data.Text as T
 import Data.Text.Lazy (fromStrict)
+import qualified Data.Text.Read as TR
 import Data.Version (Version, makeVersion)
 import Text.Pandoc.Builder
 import Text.Pandoc.Class.PandocMonad
@@ -36,7 +37,6 @@
 import Text.Pandoc.XML (lookupEntity)
 import Text.Pandoc.XML.Light
 import Text.Pandoc.XMLFormat
-import Text.Read (readMaybe)
 
 -- TODO: use xmlPath state to give better context when an error occurs
 
@@ -45,7 +45,6 @@
 data XMLReaderState = XMLReaderState
   { xmlApiVersion :: Version,
     xmlMeta :: Meta,
-    xmlContent :: [Content],
     xmlPath :: [Text]
   }
   deriving (Show)
@@ -55,7 +54,6 @@
     XMLReaderState
       { xmlApiVersion = pandocVersion,
         xmlMeta = mempty,
-        xmlContent = [],
         xmlPath = ["root"]
       }
 
@@ -65,7 +63,7 @@
   tree <-
     either (throwError . PandocXMLError "") return $
       parseXMLContents (fromStrict . sourcesToText $ sources)
-  (bs, st') <- flip runStateT (def {xmlContent = tree}) $ mapM parseBlock tree
+  (bs, st') <- flip runStateT def $ mapM parseBlock tree
   let blockList = toList $ concatMany bs
   return $ Pandoc (xmlMeta st') blockList
 
@@ -117,7 +115,7 @@
         "Header" -> (headerWith attr level) <$> getInlines (elContent e)
           where
             level = textToInt (attrValue atNameLevel e) 1
-            attr = filterAttrAttributes [atNameLevel] $ attrFromElement e
+            attr = attrFromElementExcept [atNameLevel] e
         "HorizontalRule" -> return horizontalRule
         "BlockQuote" -> do
           contents <- getBlocks e
@@ -192,25 +190,39 @@
       _ -> Nothing
 
 strContentRecursive :: Element -> Text
-strContentRecursive =
-  strContent
-    . (\e' -> e' {elContent = map elementToStr $ elContent e'})
-
-elementToStr :: Content -> Content
-elementToStr (Elem e') = Text $ CData CDataText (strContentRecursive e') Nothing
-elementToStr x = x
+strContentRecursive e = case elContent e of
+  [Text (CData _ s _)] -> s -- the common case: no nested elements
+  cs -> T.concat $ map contentToStr cs
+  where
+    contentToStr (Text (CData _ s _)) = s
+    contentToStr (Elem e') = strContentRecursive e'
+    contentToStr (CRef _) = mempty
 
 textToInt :: Text -> Int -> Int
-textToInt t deflt =
-  let safe_to_int :: Text -> Maybe Int
-      safe_to_int s = readMaybe $ T.unpack s
-   in case (safe_to_int t) of
-        Nothing -> deflt
-        Just (n) -> n
+textToInt t deflt = case TR.signed TR.decimal (T.strip t) of
+  Right (n, rest) | T.null rest -> n
+  _ -> deflt
 
+-- | Split text into 'Str', 'Space' and 'SoftBreak' inlines.  This is
+-- 'Text.Pandoc.Builder.text', but it builds a list instead of a 'Seq'.
+textToInlines :: Text -> [Inline]
+textToInlines t
+  | T.null t = []
+  | isSpaceChar (T.head t) =
+      case T.span isSpaceChar t of
+        (spaces, rest) ->
+          (if T.any isNewlineChar spaces then SoftBreak else Space)
+            : textToInlines rest
+  | otherwise =
+      case T.break isSpaceChar t of
+        (word, rest) -> Str word : textToInlines rest
+  where
+    isSpaceChar c = c == ' ' || c == '\n' || c == '\r' || c == '\t'
+    isNewlineChar c = c == '\n' || c == '\r'
+
 parseInline :: (PandocMonad m) => Content -> XMLReader m Inlines
 parseInline (Text (CData _ s _)) =
-  return $ text s
+  return $ fromList $ textToInlines s
 parseInline (CRef ref) =
   return $
     maybe (text $ T.toUpper ref) text $
@@ -245,12 +257,12 @@
           where
             url = attrValue atNameLinkUrl e
             title = attrValue atNameTitle e
-            attr = filterAttrAttributes [atNameLinkUrl, atNameTitle] $ attrFromElement e
+            attr = attrFromElementExcept [atNameLinkUrl, atNameTitle] e
         "Image" -> innerInlines $ imageWith attr url title
           where
             url = attrValue atNameImageUrl e
             title = attrValue atNameTitle e
-            attr = filterAttrAttributes [atNameImageUrl, atNameTitle] $ attrFromElement e
+            attr = attrFromElementExcept [atNameImageUrl, atNameTitle] e
         "RawInline" -> do
           let format = (attrValue atNameFormat e)
           return $ rawInline format $ strContentRecursive e
@@ -308,6 +320,8 @@
   "AlignCenter" -> AlignCenter
   _ -> AlignDefault
 
+-- NB. 'reads' is used rather than 'Data.Text.Read.double', which is
+-- not correctly rounded and so would not round-trip column widths.
 getColWidth :: Text -> ColWidth
 getColWidth txt = case reads (T.unpack txt) of
   [(value, "")] -> if value == 0.0 then ColWidthDefault else ColWidth value
@@ -322,7 +336,7 @@
 
 getTableBody :: (PandocMonad m) => Element -> XMLReader m (Maybe TableBody)
 getTableBody body_el = do
-  let attr = filterAttrAttributes [atNameRowHeadColumns] $ attrFromElement body_el
+  let attr = attrFromElementExcept [atNameRowHeadColumns] body_el
       bh = childrenNamed tgNameBodyHeader body_el
       bb = childrenNamed tgNameBodyBody body_el
       headcols = textToInt (attrValue atNameRowHeadColumns body_el) 0
@@ -355,7 +369,7 @@
   let alignment = alignmentFromText $ attrValue atNameAlignment c
       rowspan = RowSpan $ textToInt (attrValue atNameRowspan c) 1
       colspan = ColSpan $ textToInt (attrValue atNameColspan c) 1
-      attr = filterAttrAttributes [atNameAlignment, atNameRowspan, atNameColspan] $ attrFromElement c
+      attr = attrFromElementExcept [atNameAlignment, atNameRowspan, atNameColspan] c
   blocks <- getBlocks c
   return $ Cell attr alignment rowspan colspan (toList blocks)
 
@@ -461,22 +475,26 @@
 
 type PandocAttr = (Text, [Text], [(Text, Text)])
 
-filterAttributes :: S.Set Text -> [(Text, Text)] -> [(Text, Text)]
-filterAttributes to_be_removed a = filter keep_attr a
-  where
-    keep_attr (k, _) = not (k `S.member` to_be_removed)
-
-filterAttrAttributes :: [Text] -> PandocAttr -> PandocAttr
-filterAttrAttributes to_be_removed (idn, classes, a) = (idn, classes, filtered)
-  where
-    filtered = filterAttributes (S.fromList to_be_removed) a
-
 attrFromElement :: Element -> PandocAttr
-attrFromElement e = filterAttrAttributes ["id", "class"] (idn, classes, attributes)
+attrFromElement = attrFromElementExcept []
+
+-- | Build a pandoc 'Attr' from an element's XML attributes, skipping
+-- the attributes whose (decoded) names are listed in the first argument
+-- in addition to @id@ and @class@, which become the identifier and the
+-- classes of the 'Attr'.
+attrFromElementExcept :: [Text] -> Element -> PandocAttr
+attrFromElementExcept skip e = go (elAttribs e) "" "" []
   where
-    idn = attrValue "id" e
-    classes = T.words $ attrValue "class" e
-    attributes = map (\a -> (qName $ attrKey a, attrVal a)) $ elAttribs e
+    go [] idn classes kvs = (idn, T.words classes, reverse kvs)
+    go (a : as) idn classes kvs =
+      case qName (attrKey a) of
+        "id" -> go as (attrVal a) classes kvs
+        "class" -> go as idn (attrVal a) kvs
+        name ->
+          let name' = decodeAttrName name
+           in if name' `elem` skip
+                then go as idn classes kvs
+                else go as idn classes ((name', attrVal a) : kvs)
 
 addMeta :: (PandocMonad m) => (ToMetaValue a) => Text -> a -> XMLReader m ()
 addMeta field val = modify (setMeta field val)
diff --git a/src/Text/Pandoc/SelfContained.hs b/src/Text/Pandoc/SelfContained.hs
--- a/src/Text/Pandoc/SelfContained.hs
+++ b/src/Text/Pandoc/SelfContained.hs
@@ -16,16 +16,16 @@
 the HTML using data URIs.
 -}
 module Text.Pandoc.SelfContained ( makeDataURI, makeSelfContained ) where
-import Codec.Compression.GZip as Gzip
+import qualified Codec.Compression.Zlib.Internal as Zlib
 import Control.Applicative ((<|>))
 import Data.ByteString (ByteString)
 import Data.ByteString.Base64 (encode)
 import qualified Data.ByteString.Char8 as B
 import qualified Data.ByteString.Lazy as L
 import qualified Data.Text as T
-import Data.Char (isAlphaNum, isAscii)
+import Data.Char (toLower)
 import Crypto.Hash (hashWith, SHA1(SHA1))
-import Network.URI (escapeURIString)
+import Network.URI (escapeURIString, isUnescapedInURI)
 import System.FilePath (takeDirectory, takeExtension, (</>))
 import Text.HTML.TagSoup
 import Text.Pandoc.Class.PandocMonad (PandocMonad (..), fetchItem,
@@ -44,19 +44,16 @@
 import qualified Data.Map as M
 import Control.Monad.State
 
-isOk :: Char -> Bool
-isOk c = isAscii c && isAlphaNum c
-
 makeDataURI :: (MimeType, ByteString) -> T.Text
 makeDataURI (mime, raw) =
   if textual
-     then "data:" <> mime' <> "," <> T.pack (escapeURIString isOk (toString raw))
+     then "data:" <> mime' <> "," <> T.pack (escapeURIString isUnescapedInURI (toString raw))
      else "data:" <> mime' <> ";base64," <> toText (encode raw')
   where textual = "text/" `T.isPrefixOf` mime
         raw' = if "+xml" `T.isSuffixOf` mime
                   then B.filter (/= '\r') raw  -- strip off CRs
                   else raw
-        mime' = if textual && T.any (== ';') mime
+        mime' = if textual && not (T.any (== ';') mime)
                    then mime <> ";charset=utf-8"
                    else mime  -- mime type already has charset
 
@@ -70,9 +67,10 @@
 
 data ConvertState =
   ConvertState
-  { isHtml5 :: Bool
-  , svgMap  :: M.Map T.Text (T.Text, [Attribute T.Text])
+  { svgMap  :: M.Map T.Text (T.Text, [Attribute T.Text])
     -- map from hash to (id, svg attributes)
+  , fetchCache :: M.Map (MimeType, T.Text) GetDataResult
+    -- cache of fetched resources, keyed on mime type hint and url
   } deriving (Show)
 
 convertTags :: PandocMonad m =>
@@ -102,7 +100,7 @@
                   | ("text/javascript" `T.isPrefixOf` mime ||
                      "application/javascript" `T.isPrefixOf` mime ||
                      "application/x-javascript" `T.isPrefixOf` mime) &&
-                     not ("</script" `B.isInfixOf` bs) ->
+                     not ("</script" `B.isInfixOf` B.map toLower bs) ->
                      return $
                        TagOpen "script" [(k,v) | (k,v) <- as
                                                , k == "type" ||
@@ -155,12 +153,15 @@
                           Nothing -> False
                           Just cs -> "inline-svg" `elem` cs
        as' <- mapM (processAttribute inlineSvgs) as
-       let attrs = addRole "img" $ addAriaLabel $ rights as'
+       let attrs = rights as'
        let svgContents = lefts as'
        rest <- convertTags ts
        case svgContents of
          [] -> return $ TagOpen tagname attrs : rest
          ((hash, tags) : _) -> do
+             -- inlining the SVG loses the img element's alt text, so
+             -- we add role and aria-label to the svg element:
+             let svgImgAttrs = addRole "img" $ addAriaLabel attrs
              -- drop "</img>" if present
              let rest' = case rest of
                            TagClose tn : xs | tn == tagname ->  xs
@@ -168,7 +169,8 @@
              svgmap <- gets svgMap
              case M.lookup hash svgmap of
                Just (svgid, svgattrs) -> do
-                 let attrs' = [(k,v) | (k,v) <- combineSvgAttrs svgattrs attrs
+                 let attrs' = [(k,v) | (k,v) <- combineSvgAttrs svgattrs
+                                                  svgImgAttrs
                                      , k /= "id"]
                  return $ TagOpen "svg" attrs' :
                           TagOpen "use" [("href", "#" <> svgid),
@@ -180,7 +182,7 @@
                Nothing ->
                   case dropWhile (not . isTagOpenName "svg") tags of
                     TagOpen "svg" svgattrs : tags' -> do
-                      let attrs' = combineSvgAttrs svgattrs attrs
+                      let attrs' = combineSvgAttrs svgattrs svgImgAttrs
                       let svgid = case lookup "id" attrs' of
                                      Just id' -> id'
                                      Nothing -> "svg_" <> hash
@@ -188,11 +190,7 @@
                                     [(k,v) | (k,v) <- attrs', k /= "id"]
                       modify $ \st ->
                         st{ svgMap = M.insert hash (svgid, attrs'') (svgMap st) }
-                      let fixUrl x =
-                            case T.breakOn "url(#" x of
-                              (_,"") -> x
-                              (before, after) -> before <>
-                                  "url(#" <> svgid <> "_" <> T.drop 5 after
+                      let fixUrl = T.replace "url(#" ("url(#" <> svgid <> "_")
                       let addIdPrefix ("id", x) = ("id", svgid <> "_" <> x)
                           addIdPrefix (k, x)
                            | k == "xlink:href" || k == "href" =
@@ -290,7 +288,7 @@
                     _ -> []
 
 cssURLs :: PandocMonad m
-        => FilePath -> ByteString -> m ByteString
+        => FilePath -> ByteString -> StateT ConvertState m ByteString
 cssURLs d orig = do
   res <- runParserT (parseCSSUrls d) () "css" orig
   case res of
@@ -300,21 +298,22 @@
        Right bs  -> return bs
 
 parseCSSUrls :: PandocMonad m
-             => FilePath -> ParsecT ByteString () m ByteString
+             => FilePath
+             -> ParsecT ByteString () (StateT ConvertState m) ByteString
 parseCSSUrls d = B.concat <$> P.many
   (pCSSWhite <|> pCSSComment <|> pCSSImport d <|> pCSSUrl d <|> pCSSOther)
 
 pCSSImport :: PandocMonad m
-           => FilePath -> ParsecT ByteString () m ByteString
+           => FilePath
+           -> ParsecT ByteString () (StateT ConvertState m) ByteString
 pCSSImport d = P.try $ do
   P.string "@import"
   P.spaces
   res <- (pQuoted <|> pUrl) >>= handleCSSUrl d
   P.spaces
   P.char ';'
-  P.spaces
   case res of
-       Left b       -> return $ B.pack "@import " <> b
+       Left b       -> return $ B.pack "@import " <> b <> B.pack ";"
        Right (_, b) -> return b
 
 -- Note: some whitespace in CSS is significant, so we can't collapse it!
@@ -334,7 +333,8 @@
   (B.singleton <$> P.char '/')
 
 pCSSUrl :: PandocMonad m
-        => FilePath -> ParsecT ByteString () m ByteString
+        => FilePath
+        -> ParsecT ByteString () (StateT ConvertState m) ByteString
 pCSSUrl d = P.try $ do
   res <- pUrl >>= handleCSSUrl d
   case res of
@@ -366,7 +366,7 @@
 
 handleCSSUrl :: PandocMonad m
              => FilePath -> (T.Text, ByteString)
-             -> ParsecT ByteString () m
+             -> ParsecT ByteString () (StateT ConvertState m)
                   (Either ByteString (MimeType, ByteString))
 handleCSSUrl d (url, fallback) =
   case escapeURIString (/='|') (T.unpack $ trim url) of
@@ -382,7 +382,7 @@
                       (mt, b) <- if "text/css" `T.isPrefixOf` mt'
                                     -- see #5725: in HTML5, content type
                                     -- isn't allowed on style type attribute
-                                    then ("text/css",) <$> cssURLs d raw
+                                    then ("text/css",) <$> lift (cssURLs d raw)
                                     else return (mt', raw)
                       return $ Right (mt, b)
                     CouldNotFetch _ -> return $ Left fallback
@@ -393,19 +393,45 @@
   | Fetched (MimeType, ByteString)
   deriving (Show)
 
+-- | Decompress gzipped data, catching decompression errors instead
+-- of throwing an imprecise exception from pure code.
+decompressGzip :: ByteString -> Either T.Text ByteString
+decompressGzip bs =
+  B.concat <$>
+    Zlib.foldDecompressStreamWithInput
+      (\chunk rest -> (chunk :) <$> rest)
+      (const (Right []))
+      (Left . T.pack . show)
+      (Zlib.decompressST Zlib.gzipFormat Zlib.defaultDecompressParams)
+      (L.fromStrict bs)
+
 getData :: PandocMonad m
         => MimeType -> T.Text
-        -> m GetDataResult
+        -> StateT ConvertState m GetDataResult
 getData mimetype src
   | "data:" `T.isPrefixOf` src = return $ AlreadyDataURI src -- already data: uri
-  | otherwise = catchError fetcher handler
+  | otherwise = do
+      cache <- gets fetchCache
+      case M.lookup (mimetype, src) cache of
+        Just res -> return res
+        Nothing -> do
+          res <- catchError fetcher handler
+          modify $ \st ->
+            st{ fetchCache = M.insert (mimetype, src) res (fetchCache st) }
+          return res
  where
    fetcher = do
       let ext = T.toLower $ T.pack $ takeExtension $ T.unpack src
       (raw, respMime) <- fetchItem src
-      let raw' = if ext `elem` [".gz", ".svgz"]
-                 then B.concat $ L.toChunks $ Gzip.decompress $ L.fromChunks [raw]
-                 else raw
+      if ext `elem` [".gz", ".svgz"]
+         then case decompressGzip raw of
+                Right raw' -> processFetched raw' respMime
+                Left err -> do
+                  let msg = "could not decompress: " <> err
+                  report $ CouldNotFetchResource src msg
+                  return $ CouldNotFetch $ PandocSomeError msg
+         else processFetched raw respMime
+   processFetched raw' respMime = do
       let mime = case (mimetype, respMime) of
                   ("",Nothing) -> "application/octet-stream"
                   (x, Nothing) -> x
@@ -441,10 +467,7 @@
 makeSelfContained :: PandocMonad m => T.Text -> m T.Text
 makeSelfContained inp = do
   let tags = parseTags inp
-  let html5 = case tags of
-                  (TagOpen "!DOCTYPE" [("html","")]:_) -> True
-                  _ -> False
-  let convertState = ConvertState { isHtml5 = html5,
-                                    svgMap = mempty }
+  let convertState = ConvertState { svgMap = mempty,
+                                    fetchCache = mempty }
   out' <- evalStateT (convertTags tags) convertState
   return $ renderTags' out'
diff --git a/src/Text/Pandoc/Shared.hs b/src/Text/Pandoc/Shared.hs
--- a/src/Text/Pandoc/Shared.hs
+++ b/src/Text/Pandoc/Shared.hs
@@ -45,9 +45,11 @@
                      removeFormatting,
                      deNote,
                      stringify,
+                     stringifyInlines,
                      capitalize,
                      compactify,
                      compactifyDL,
+                     compactifyTable,
                      linesToPara,
                      figureDiv,
                      makeSections,
@@ -96,7 +98,7 @@
 import qualified Data.List as L
 import qualified Data.Map as M
 import Data.Maybe (mapMaybe)
-import Data.Monoid (Any (..) )
+import Data.Monoid (Any (..), All (..) )
 import Data.Semigroup (Min (..))
 import Data.Sequence (ViewL (..), ViewR (..), viewl, viewr)
 import qualified Data.Set as Set
@@ -369,17 +371,17 @@
 -- Footnotes are skipped (since we don't want their contents in link
 -- labels).
 stringify :: Walkable Inline a => a -> T.Text
-stringify = query go . walk fixInlines
-  where go :: Inline -> T.Text
-        go Space                                       = " "
-        go SoftBreak                                   = " "
-        go (Str x)                                     = x
-        go (Code _ x)                                  = x
-        go (Math _ x)                                  = x
-        go (RawInline (Format "html") (T.unpack -> ('<':'b':'r':_)))
-                                                       = " " -- see #2105
-        go LineBreak                                   = " "
-        go _                                           = ""
+stringify = T.concat . query go . walk fixInlines
+  where go :: Inline -> [T.Text]
+        go Space        = [" "]
+        go SoftBreak    = [" "]
+        go (Str x)      = [x]
+        go (Code _ x)   = [x]
+        go (Math _ x)   = [x]
+        go (RawInline (Format "html") t)
+          | "<br" `T.isPrefixOf` t = [" "] -- see #2105
+        go LineBreak    = [" "]
+        go _            = []
 
         fixInlines :: Inline -> Inline
         fixInlines (Cite _ ils) = Cite [] ils
@@ -387,6 +389,38 @@
         fixInlines (q@Quoted{}) = deQuote q
         fixInlines x = x
 
+-- | Like 'stringify', but specialized to sequences of inlines
+-- (e.g. @['Inline']@ or 'Inlines').  Produces the same result as
+-- 'stringify' in a single pass, without rebuilding the tree.
+stringifyInlines :: Foldable t => t Inline -> T.Text
+stringifyInlines ils0 = T.concat $ foldr go [] ils0
+  where
+    go :: Inline -> [T.Text] -> [T.Text]
+    go il acc = case il of
+      Str x           -> x : acc
+      Space           -> " " : acc
+      SoftBreak       -> " " : acc
+      Code _ x        -> x : acc
+      Math _ x        -> x : acc
+      LineBreak       -> " " : acc
+      RawInline (Format "html") t
+        | "<br" `T.isPrefixOf` t -> " " : acc -- see #2105
+      RawInline _ _   -> acc
+      Note _          -> acc -- footnotes are skipped
+      Cite _ ils      -> foldr go acc ils -- citation metadata is dropped
+      Quoted SingleQuote ils -> "\8216" : foldr go ("\8217" : acc) ils
+      Quoted DoubleQuote ils -> "\8220" : foldr go ("\8221" : acc) ils
+      Emph ils        -> foldr go acc ils
+      Underline ils   -> foldr go acc ils
+      Strong ils      -> foldr go acc ils
+      Strikeout ils   -> foldr go acc ils
+      Superscript ils -> foldr go acc ils
+      Subscript ils   -> foldr go acc ils
+      SmallCaps ils   -> foldr go acc ils
+      Span _ ils      -> foldr go acc ils
+      Link _ ils _    -> foldr go acc ils
+      Image _ ils _   -> foldr go acc ils
+
 -- | Unwrap 'Quoted' inline elements, enclosing the contents with
 -- English-style Unicode quotes instead.
 deQuote :: Inline -> Inline
@@ -441,6 +475,26 @@
              _     -> items
         _          -> items
 
+-- | If every cell of the table is either empty or consists of a
+-- single Para or Plain element, convert all Para to Plain for a compact
+-- table.
+compactifyTable ::
+  (Walkable Block a, Walkable [Block] a, Walkable Inline a) => a -> a
+compactifyTable x = if isSimpleTable x
+                       then walk fixNotes $ walk paraToPlain x
+                       else x
+ where
+  isSimpleCell :: [Block] -> All
+  isSimpleCell [] = All True
+  isSimpleCell [Para _] = All True
+  isSimpleCell [Plain _] = All True
+  isSimpleCell _ = All False
+  isSimpleTable = getAll . query isSimpleCell
+  paraToPlain (Para ils) = Plain ils
+  paraToPlain b = b
+  -- walk descends into the notes, so we need to fix them back up:
+  fixNotes (Note [Plain ils]) = Note [Para ils]
+  fixNotes i = i
 
 -- | Combine a list of lines by adding hard linebreaks.
 combineLines :: [[Inline]] -> [Inline]
@@ -463,7 +517,8 @@
               , ["figure"] `union` classes
               , kv
               )
-      captkv = maybe mempty (\s -> [("short-caption", stringify s)]) shortcapt
+      captkv = maybe mempty (\s -> [("short-caption", stringifyInlines s)])
+                 shortcapt
       capt = [Div ("", ["caption"], captkv) longcapt | not (null longcapt)]
   in Div divattr (body ++ capt)
 
@@ -475,7 +530,7 @@
 -- | Convert Pandoc inline list to plain text identifier.
 inlineListToIdentifier :: Extensions -> [Inline] -> T.Text
 inlineListToIdentifier exts =
-  textToIdentifier exts . stringify . unEmojify
+  textToIdentifier exts . stringifyInlines . unEmojify
   where
     unEmojify :: [Inline] -> [Inline]
     unEmojify
@@ -672,7 +727,7 @@
 taskListItemFromAscii = handleTaskListItem fromMd
   where
     fromMd (Str "[" : Space : Str "]" : Space : is) = Str "☐" : Space : is
-    fromMd (Str "[ ]"                 : Space : is) = Str "☒" : Space : is
+    fromMd (Str "[ ]"                 : Space : is) = Str "☐" : Space : is
     fromMd (Str "[x]"                 : Space : is) = Str "☒" : Space : is
     fromMd (Str "[X]"                 : Space : is) = Str "☒" : Space : is
     fromMd [Str "[" , Space , Str "]"] = [Str "☐"]
@@ -743,7 +798,8 @@
     fmt = concatMap go . groupBy (\a b -> isPlaintext a && isPlaintext b)
       where
         go xs
-          | all isPlaintext xs = B.toList $ B.codeWith attr $ stringify xs
+          | all isPlaintext xs = B.toList $ B.codeWith attr $
+                                   stringifyInlines xs
           | otherwise = xs
 
 --
diff --git a/src/Text/Pandoc/Sources.hs b/src/Text/Pandoc/Sources.hs
--- a/src/Text/Pandoc/Sources.hs
+++ b/src/Text/Pandoc/Sources.hs
@@ -27,6 +27,8 @@
   , ensureFinalNewlines
   , addToInput
   , satisfy
+  , takeWhileP
+  , takeWhile1P
   , oneOf
   , noneOf
   , anyChar
@@ -43,6 +45,8 @@
 where
 import qualified Text.Parsec as P
 import Text.Parsec (Stream(..), ParsecT)
+import Text.Parsec.Prim (mkPT, Consumed(..), Reply(..), State(..))
+import Text.Parsec.Error (ParseError, newErrorMessage, Message(SysUnExpect))
 import Text.Parsec.Pos as P
 import Data.Text (Text)
 import qualified Data.Text as T
@@ -157,6 +161,80 @@
 satisfy f = P.tokenPrim show updateSourcePos matcher
  where
   matcher !c = if f c then Just c else Nothing
+
+-- | Consume characters while the predicate holds, returning them as
+-- a 'Text'.  Always succeeds (returning an empty 'Text' if no
+-- characters match).  Equivalent to @'Data.Text.pack' \<$> 'P.many'
+-- ('satisfy' f)@ (including source position and error behavior), but
+-- faster, because it processes whole chunks of text at a time.
+takeWhileP :: Monad m => (Char -> Bool) -> ParsecT Sources u m Text
+takeWhileP f = mkPT $ \st ->
+  case spanSources f st of
+    (t, st', err)
+      | T.null t  -> return (Empty (return (Ok t st' err)))
+      | otherwise -> return (Consumed (return (Ok t st' err)))
+
+-- | Like 'takeWhileP', but requires at least one matching character.
+-- Equivalent to @'Data.Text.pack' \<$> 'P.many1' ('satisfy' f)@.
+takeWhile1P :: Monad m => (Char -> Bool) -> ParsecT Sources u m Text
+takeWhile1P f = mkPT $ \st ->
+  case spanSources f st of
+    (t, st', err)
+      | T.null t  -> return (Empty (return (Error err)))
+      | otherwise -> return (Consumed (return (Ok t st' err)))
+
+-- Consume characters matching the predicate from the beginning of
+-- the input, returning the consumed text, the updated parser state,
+-- and the error that a corresponding sequence of 'satisfy' parsers
+-- would have recorded at the position where it stopped.
+spanSources :: (Char -> Bool)
+            -> State Sources u
+            -> (Text, State Sources u, ParseError)
+spanSources f (State (Sources input0) pos0 usr) = go pos0 input0 []
+ where
+  -- pos and committed are the position and input after the last
+  -- consumed character (uncons drops leading empty chunks only when a
+  -- character is actually consumed, so they must be retained if we
+  -- stop at a chunk boundary).
+  go !pos committed acc =
+    case dropWhile (T.null . snd) committed of
+      [] -> stop pos committed acc ""
+      (p, t) : rest ->
+        case T.span f t of
+          (pre, post)
+            | T.null pre -> stop pos committed acc (show (T.head t))
+            | T.null post ->
+                -- consumed the whole chunk: as with 'satisfy', the
+                -- position jumps to the stored position of the next
+                -- chunk, if any.
+                let pos' = case rest of
+                             (pnext, _) : _ -> pnext
+                             []             -> advancePos pos pre
+                in go pos' ((p, post) : rest) (pre : acc)
+            | otherwise ->
+                let pos' = advancePos pos pre
+                in stop pos' ((p, post) : rest) (pre : acc)
+                        (show (T.head post))
+  stop pos committed acc msg =
+    ( case acc of
+        []  -> mempty
+        [t] -> t
+        _   -> T.concat (reverse acc)
+    , State (Sources committed) pos usr
+    , newErrorMessage (SysUnExpect msg) pos )
+
+-- Advance a source position over a stretch of text, using the same
+-- position updates as the 'UpdateSourcePos' instance for 'Sources'.
+advancePos :: SourcePos -> Text -> SourcePos
+advancePos pos t
+  | T.any (\c -> c == '\n' || c == '\t') t = T.foldl' advanceChar pos t
+  | otherwise = incSourceColumn pos (T.length t)
+ where
+  advanceChar p c =
+    case c of
+      '\n' -> incSourceLine (setSourceColumn p 1) 1
+      '\t' -> incSourceColumn p (4 - ((sourceColumn p - 1) `mod` 4))
+      _    -> incSourceColumn p 1
 
 oneOf :: (Monad m, Stream s m Char, UpdateSourcePos s Char)
       => [Char] -> ParsecT s u m Char
diff --git a/src/Text/Pandoc/TeX.hs b/src/Text/Pandoc/TeX.hs
--- a/src/Text/Pandoc/TeX.hs
+++ b/src/Text/Pandoc/TeX.hs
@@ -50,5 +50,49 @@
 data Macro = Macro MacroScope ExpansionPoint [ArgSpec] (Maybe [Tok]) [Tok]
      deriving Show
 
-data ArgSpec = ArgNum Int | Pattern [Tok]
+data ArgSpec = ArgNum Int
+             | Pattern [Tok]
+             | BoolArg Bool Tok
+               -- ^ xparse @s@ and @t@ specifiers: an optional token
+               -- (e.g. a star); expands to @\\BooleanTrue@ or
+               -- @\\BooleanFalse@.  The Bool is False if the @!@
+               -- modifier was used (no space-skipping before the
+               -- token).
+             | DelimArg Bool Tok Tok (Maybe [Tok])
+               -- ^ xparse @o@, @O@, @d@, @D@, @r@, @R@ specifiers:
+               -- argument between opening and closing delimiter
+               -- tokens, with an optional default; when absent and
+               -- no default is given, expands to @\\NoValue@.  The
+               -- Bool is False if the @!@ modifier was used (no
+               -- space-skipping before the opening delimiter).
+             | VerbArg
+               -- ^ xparse @v@ specifier: a verbatim argument, either
+               -- braced or between two identical delimiter
+               -- characters; substituted as a single Word token
+               -- containing the raw text.
+             | EmbellishArg [(Tok, Maybe [Tok])]
+               -- ^ xparse @e@ and @E@ specifiers: a set of optional
+               -- \"embellishments\" (a token followed by an
+               -- argument), matched in any order, each with an
+               -- optional default; a missing embellishment expands
+               -- to its default, or to @\\NoValue@.
+             | ProcessedArg [[Tok]] ArgSpec
+               -- ^ xparse @>{processor}@ modifier: the processors
+               -- are applied to the grabbed argument from right to
+               -- left (i.e., the one nearest the specifier first).
+             | BodyArg Bool Int
+               -- ^ xparse @b@ specifier (environments only): the
+               -- environment body, grabbed up to the following
+               -- 'Pattern' (the @\\end{...}@).  The Bool is False if
+               -- the @!@ modifier was used (no space-trimming at the
+               -- ends of the body).
+             | VerbBodyArg Bool Int
+               -- ^ xparse @c@ specifier (environments only): the
+               -- environment body, grabbed verbatim up to the
+               -- following 'Pattern' (the @\\end{...}@); substituted
+               -- as one raw Word token per line, with spaces
+               -- replaced by U+2423 and lines separated by @\\\\@,
+               -- mirroring how LaTeX typesets it.  The Bool is
+               -- False if the @!@ modifier was used (no trimming of
+               -- leading and trailing blank lines).
      deriving Show
diff --git a/src/Text/Pandoc/Translations.hs b/src/Text/Pandoc/Translations.hs
--- a/src/Text/Pandoc/Translations.hs
+++ b/src/Text/Pandoc/Translations.hs
@@ -46,7 +46,12 @@
 -- used.
 setTranslations :: PandocMonad m => Lang -> m ()
 setTranslations lang =
-  modifyCommonState $ \st -> st{ stTranslations = Just (lang, Nothing) }
+  modifyCommonState $ \st ->
+    case stTranslations st of
+      -- if a translation table is already loaded for this language,
+      -- keep it, so we don't have to parse the translation file again:
+      Just (l, Just _) | l == lang -> st
+      _ -> st{ stTranslations = Just (lang, Nothing) }
 
 -- | Load term map.
 getTranslations :: PandocMonad m => m Translations
diff --git a/src/Text/Pandoc/Translations/Types.hs b/src/Text/Pandoc/Translations/Types.hs
--- a/src/Text/Pandoc/Translations/Types.hs
+++ b/src/Text/Pandoc/Translations/Types.hs
@@ -33,7 +33,6 @@
 import qualified Data.Map as M
 import qualified Data.Text as T
 import GHC.Generics (Generic)
-import Text.Pandoc.Shared (safeRead)
 
 data Term =
     Abstract
@@ -58,13 +57,21 @@
   | SeeAlso
   | Table
   | To
-  deriving (Show, Eq, Ord, Generic, Enum, Read)
+  deriving (Show, Eq, Ord, Generic, Enum, Bounded, Read)
 
 newtype Translations = Translations (M.Map Term T.Text)
         deriving (Show, Generic, Semigroup, Monoid)
 
+-- | Map from term names to terms.  This is much faster than
+-- using the derived 'Read' instance to parse term names.
+termNameMap :: M.Map T.Text Term
+termNameMap = M.fromList [(T.pack (show t), t) | t <- [minBound..maxBound]]
+
+readTerm :: T.Text -> Maybe Term
+readTerm t = M.lookup t termNameMap
+
 instance FromJSON Term where
-  parseJSON (String t) = case safeRead t of
+  parseJSON (String t) = case readTerm t of
                                Just t' -> pure t'
                                Nothing -> Prelude.fail $ "Invalid Term name " ++
                                                  show t
@@ -75,7 +82,7 @@
     xs <- parseJSON o >>= mapM addItem . M.toList
     return $ Translations (M.fromList xs)
     where addItem (k,v) =
-            case safeRead k of
+            case readTerm k of
                  Nothing -> Prelude.fail $ "Invalid Term name " ++ show k
                  Just t  ->
                    case v of
diff --git a/src/Text/Pandoc/URI.hs b/src/Text/Pandoc/URI.hs
--- a/src/Text/Pandoc/URI.hs
+++ b/src/Text/Pandoc/URI.hs
@@ -126,7 +126,7 @@
 pBase64DataURI = base64uri
  where
   base64uri = do
-    A.string "data:"
+    A.asciiCI "data:"  -- the scheme is case-insensitive (RFC 3986)
     mime <- do
       n1 <- restrictedName
       A.char '/'
diff --git a/src/Text/Pandoc/UTF8.hs b/src/Text/Pandoc/UTF8.hs
--- a/src/Text/Pandoc/UTF8.hs
+++ b/src/Text/Pandoc/UTF8.hs
@@ -50,9 +50,7 @@
                   putStrLn, readFile, writeFile)
 
 readFile :: FilePath -> IO Text
-readFile f = do
-  h <- openFile (encodePath f) ReadMode
-  hGetContents h
+readFile f = withFile (encodePath f) ReadMode hGetContents
 
 getContents :: IO Text
 getContents = hGetContents stdin
@@ -103,7 +101,11 @@
          if "\xEF\xBB\xBF" `B.isPrefixOf` bs
             then B.drop 3 bs
             else bs
-        filterCRs = B.filter (/='\r')
+        -- Only allocate a filtered copy if a CR is actually present;
+        -- B.elem compiles to a fast memchr.
+        filterCRs bs = if '\r' `B.elem` bs
+                          then B.filter (/='\r') bs
+                          else bs
 
 -- | Convert UTF8-encoded ByteString to String, also
 -- removing '\\r' characters.
@@ -118,7 +120,13 @@
          if "\xEF\xBB\xBF" `BL.isPrefixOf` bs
             then BL.drop 3 bs
             else bs
-        filterCRs = BL.filter (/='\r')
+        -- Work chunk-wise (rather than using BL.elem on the whole
+        -- input) to preserve laziness; skip allocation for chunks
+        -- that contain no CRs.
+        filterCRs = BL.fromChunks . map filterChunk . BL.toChunks
+        filterChunk bs = if '\r' `B.elem` bs
+                            then B.filter (/='\r') bs
+                            else bs
 
 -- | Convert UTF8-encoded ByteString to String, also
 -- removing '\\r' characters.
diff --git a/src/Text/Pandoc/Writers/ANSI.hs b/src/Text/Pandoc/Writers/ANSI.hs
--- a/src/Text/Pandoc/Writers/ANSI.hs
+++ b/src/Text/Pandoc/Writers/ANSI.hs
@@ -190,7 +190,12 @@
 
 blockToANSI opts (BlockQuote blocks) = do
   contents <- withFewerColumns 2 $ blockListToANSI opts blocks
-  return ( D.prefixed "│ " contents $$ D.blankline)
+  -- D.cr ensures we start the blockquote on its own line: without it,
+  -- a blockquote that begins mid-line (e.g. as the first block of a
+  -- list item, sharing a line with the item's marker) would have its
+  -- prefix omitted on the first line, since D.prefixed only prefixes
+  -- lines that begin at column 0.
+  return ( D.cr <> D.prefixed "│ " contents $$ D.blankline)
 
 blockToANSI opts (Table _ (Caption _ caption) colSpecs (TableHead _ thead) tbody (TableFoot _ tfoot)) = do
   let captionInlines = blocksToInlines caption
diff --git a/src/Text/Pandoc/Writers/ChunkedHTML.hs b/src/Text/Pandoc/Writers/ChunkedHTML.hs
--- a/src/Text/Pandoc/Writers/ChunkedHTML.hs
+++ b/src/Text/Pandoc/Writers/ChunkedHTML.hs
@@ -16,7 +16,7 @@
   ) where
 import Text.Pandoc.Definition
 import Text.Pandoc.Options (WriterOptions(..))
-import Text.Pandoc.Shared (stringify, tshow)
+import Text.Pandoc.Shared (stringifyInlines, tshow)
 import Text.Pandoc.Class (PandocMonad, getPOSIXTime, runPure,
                           fetchItem, insertMedia, getMediaBag)
 import Text.Pandoc.MediaBag (mediaItems)
@@ -133,7 +133,8 @@
  where
   opts' = opts{ writerVariables =
                   addContextVars opts' topChunk chunk $ writerVariables opts }
-  meta' = setMeta "pagetitle" (MetaString (stringify $ chunkHeading chunk)) meta
+  meta' = setMeta "pagetitle"
+            (MetaString (stringifyInlines $ chunkHeading chunk)) meta
   blocks = chunkContents chunk
 
 tocTreeToContext :: Tree SecInfo -> Context Text
@@ -146,7 +147,7 @@
 secInfoToContext :: SecInfo -> Context Text
 secInfoToContext sec =
   Context $ M.fromList
-  [ ("title", SimpleVal $ literal $ stringify $ secTitle sec)
+  [ ("title", SimpleVal $ literal $ stringifyInlines $ secTitle sec)
   , ("number", maybe NullVal (SimpleVal . literal) (secNumber sec))
   , ("id", SimpleVal $ literal $ secId sec)
   , ("path", SimpleVal $ literal $ secPath sec)
diff --git a/src/Text/Pandoc/Writers/Djot.hs b/src/Text/Pandoc/Writers/Djot.hs
--- a/src/Text/Pandoc/Writers/Djot.hs
+++ b/src/Text/Pandoc/Writers/Djot.hs
@@ -28,8 +28,8 @@
 import qualified Data.Map as M
 import qualified Text.Pandoc.UTF8 as UTF8
 import Text.Pandoc.Writers.Shared ( metaToContext, defField, toLegacyTable )
-import Text.Pandoc.Shared (isTightList, tshow, stringify, onlySimpleTableCells,
-                           makeSections)
+import Text.Pandoc.Shared (isTightList, tshow, stringifyInlines,
+                           onlySimpleTableCells, makeSections)
 import Text.DocLayout
 import Text.DocTemplates (renderTemplate)
 
@@ -240,7 +240,7 @@
 inlineToDjot (Link attr ils (src,tit)) = do
   opts <- gets options
   description <- inlinesToDjot ils
-  let ilstring = stringify ils
+  let ilstring = stringifyInlines ils
   let autolink = ilstring == src
   let email = ("mailto:" <> ilstring) == src
   let removeClass name (ident, cls, kvs) = (ident, filter (/= name) cls, kvs)
diff --git a/src/Text/Pandoc/Writers/DocBook.hs b/src/Text/Pandoc/Writers/DocBook.hs
--- a/src/Text/Pandoc/Writers/DocBook.hs
+++ b/src/Text/Pandoc/Writers/DocBook.hs
@@ -457,7 +457,7 @@
       alt = if null ils
                then mempty
                else inTagsIndented "textobject" $
-                    inTagsSimple "phrase" $ literal (stringify ils)
+                    inTagsSimple "phrase" $ literal (stringifyInlines ils)
   in  inTagsIndented "inlinemediaobject" $
         inTagsIndented "imageobject"
           (titleDoc $$ imageToDocBook opts attr src)
diff --git a/src/Text/Pandoc/Writers/Docx.hs b/src/Text/Pandoc/Writers/Docx.hs
--- a/src/Text/Pandoc/Writers/Docx.hs
+++ b/src/Text/Pandoc/Writers/Docx.hs
@@ -23,6 +23,7 @@
       findEntryByPath,
       fromArchive,
       toArchive,
+      toArchiveOrFail,
       toEntry,
       Entry(eRelativePath) )
 import Control.Monad (MonadPlus(mplus), foldM)
@@ -506,10 +507,21 @@
   P.setUserDataDir oldUserDataDir
   let distArchive = toArchive $ BL.fromStrict res
   refArchive <- case writerReferenceDoc opts of
-                   Just f  -> toArchive . BL.fromStrict . fst
+                   Just f  -> do
+                     arch <- toArchiveOrFail . BL.fromStrict . fst
                                  <$> P.fetchItem (T.pack f)
-                   Nothing -> toArchive . BL.fromStrict <$>
-                        readDataFile "reference.docx"
+                     case arch of
+                       Left err -> throwError $ PandocParseError $
+                         "Could not parse reference-doc " <>
+                           tshow f <> ": " <> T.pack err
+                       Right x -> pure x
+                   Nothing -> do
+                     arch <- toArchiveOrFail . BL.fromStrict <$>
+                                 readDataFile "reference.docx"
+                     case arch of
+                       Left err -> throwError $ PandocParseError $
+                         "Could not parse reference.docx: " <> T.pack err
+                       Right x -> pure x
   return (refArchive, distArchive, username, utctime)
 
 isWmlNamespace :: QName -> Bool
@@ -761,7 +773,7 @@
 mkCorePropsEntry :: Integer -> UTCTime -> Meta -> Entry
 mkCorePropsEntry epochtime utctime meta =
   let metaValueToText (MetaString s)    = s
-      metaValueToText (MetaInlines ils) = stringify ils
+      metaValueToText (MetaInlines ils) = stringifyInlines ils
       metaValueToText (MetaBlocks bs)   = stringify bs
       metaValueToText (MetaBool b)      = T.pack (show b)
       metaValueToText _                 = ""
@@ -785,8 +797,8 @@
           ,("xmlns:dcterms","http://purl.org/dc/terms/")
           ,("xmlns:dcmitype","http://purl.org/dc/dcmitype/")
           ,("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance")]
-          $ mktnode "dc:title" [] (stringify $ docTitle meta)
-          : mktnode "dc:creator" [] (T.intercalate "; " (map stringify $ docAuthors meta))
+          $ mktnode "dc:title" [] (stringifyInlines $ docTitle meta)
+          : mktnode "dc:creator" [] (T.intercalate "; " (map stringifyInlines $ docAuthors meta))
           : [ mktnode (M.findWithDefault "" k extraCorePropsMap) [] (lookupMetaString' k meta)
             | k <- M.keys (unMeta meta), k `elem` extraCoreProps]
           ++ mknode "cp:keywords" [] (T.intercalate ", " keywords)
diff --git a/src/Text/Pandoc/Writers/Docx/OpenXML.hs b/src/Text/Pandoc/Writers/Docx/OpenXML.hs
--- a/src/Text/Pandoc/Writers/Docx/OpenXML.hs
+++ b/src/Text/Pandoc/Writers/Docx/OpenXML.hs
@@ -25,7 +25,7 @@
 import Control.Monad.Except (catchError)
 import Crypto.Hash (hashWith, SHA1(SHA1))
 import qualified Data.ByteString.Lazy as BL
-import Data.Char (isLetter, isSpace)
+import Data.Char (isSpace, isAlphaNum)
 import Text.Pandoc.Char (isCJK)
 import Data.Ord (comparing)
 import Data.String (fromString)
@@ -110,18 +110,60 @@
     , "oMath"
     ] [0..])
 
-sortSquashed :: [Element] -> [Element]
-sortSquashed l =
+-- from wml.xsd EG_PPrBase
+pPrTagOrder :: M.Map Text Int
+pPrTagOrder =
+  M.fromList
+  (zip [ "pStyle"
+    , "keepNext"
+    , "keepLines"
+    , "pageBreakBefore"
+    , "framePr"
+    , "widowControl"
+    , "numPr"
+    , "suppressLineNumbers"
+    , "pBdr"
+    , "shd"
+    , "tabs"
+    , "suppressAutoHyphens"
+    , "kinsoku"
+    , "wordWrap"
+    , "overflowPunct"
+    , "topLinePunct"
+    , "autoSpaceDE"
+    , "autoSpaceDN"
+    , "bidi"
+    , "adjustRightInd"
+    , "snapToGrid"
+    , "spacing"
+    , "ind"
+    , "contextualSpacing"
+    , "mirrorIndents"
+    , "suppressOverlap"
+    , "jc"
+    , "textDirection"
+    , "textAlignment"
+    , "textboxTightWrap"
+    , "outlineLvl"
+    , "divId"
+    , "cnfStyle"
+    , "rPr"
+    , "sectPr"
+    , "pPrChange"
+    ] [0..])
+
+sortSquashed :: M.Map Text Int -> [Element] -> [Element]
+sortSquashed tagOrder l =
   sortBy (comparing tagIndex) l
   where
     tagIndex :: Element -> Int
     tagIndex el =
-      fromMaybe 0 (M.lookup tag rPrTagOrder)
+      fromMaybe 0 (M.lookup tag tagOrder)
       where tag = (qName . elName) el
 
-squashProps :: EnvProps -> [Element]
-squashProps (EnvProps Nothing es) = sortSquashed es
-squashProps (EnvProps (Just e) es) = sortSquashed (e : es)
+squashProps :: M.Map Text Int -> EnvProps -> [Element]
+squashProps tagOrder (EnvProps Nothing es) = sortSquashed tagOrder es
+squashProps tagOrder (EnvProps (Just e) es) = sortSquashed tagOrder (e : es)
 
 -- | Certain characters are invalid in XML even if escaped.
 -- See #1992
@@ -236,12 +278,18 @@
              -> WS m (Text, [Element], [Element])
 writeOpenXML opts (Pandoc meta blocks) = do
   setupTranslations meta
+  -- Cache the rStyle element for each highlighting token type, so that
+  -- it need not be recomputed for every Code inline.  It depends only
+  -- on the style maps, which don't change during writing.
+  tokTypesMap <- M.fromList <$>
+    mapM (\tt -> (tt,) <$> rStyleM (fromString $ show tt)) [KeywordTok ..]
+  modify $ \st -> st{ stTokTypesMap = tokTypesMap }
   let includeTOC = writerTableOfContents opts || lookupMetaBool "toc" meta
   let includeLOF = writerListOfFigures opts || lookupMetaBool "lof" meta
   let includeLOT = writerListOfTables opts || lookupMetaBool "lot" meta
   abstractTitle <- case lookupMeta "abstract-title" meta of
       Just (MetaBlocks bs)   -> pure $ stringify bs
-      Just (MetaInlines ils) -> pure $ stringify ils
+      Just (MetaInlines ils) -> pure $ stringifyInlines ils
       Just (MetaString s)    -> pure s
       _                      -> translateTerm Abstract
   abstract <-
@@ -326,9 +374,13 @@
 
 -- | Convert a list of Pandoc blocks to OpenXML.
 blocksToOpenXML :: (PandocMonad m) => WriterOptions -> [Block] -> WS m [Content]
-blocksToOpenXML opts =
-  fmap concat . mapM (blockToOpenXML opts)
-  . separateTables . filter (not . isForeignRawBlock)
+blocksToOpenXML opts bs = do
+  oldFirstPara <- gets stFirstPara
+  modify $ \st -> st{ stFirstPara = True }
+  result <- concat <$> mapM (blockToOpenXML opts)
+            (separateTables (filter (not . isForeignRawBlock) bs))
+  modify $ \st -> st{ stFirstPara = oldFirstPara }
+  pure result
 
 isForeignRawBlock :: Block -> Bool
 isForeignRawBlock (RawBlock format _) = format /= "openxml"
@@ -358,12 +410,34 @@
 dynamicStyleKey :: Text
 dynamicStyleKey = "custom-style"
 
+-- | Paragraph properties for a CSL-generated bibliography, derived from
+-- the hints @Text.Pandoc.Citeproc@ puts on the bibliography's Div:
+-- a @hanging-indent@ class and @line-spacing@/@entry-spacing@ attributes.
+cslBibParaProps :: [Text] -> [(Text, Text)] -> [Element]
+cslBibParaProps classes kvs =
+  [ mknode "w:ind" [("w:left", "720"), ("w:hanging", "720")] ()
+  | "hanging-indent" `elem` classes ] ++
+  [ mknode "w:spacing" spacingAttrs () | not (null spacingAttrs) ]
+  where
+    spacingAttrs = lineAttr ++ entryAttr
+    lineAttr = case lookup "line-spacing" kvs >>= safeRead of
+                 Just ls | ls > (1 :: Double) ->
+                   [ ("w:line", tshow (round (ls * 240) :: Int))
+                   , ("w:lineRule", "auto") ]
+                 _ -> []
+    entryAttr = case lookup "entry-spacing" kvs >>= safeRead of
+                  Just es | es > (0 :: Double) ->
+                    -- entry-spacing is given in em; 1 em ~ 240 twips
+                    [ ("w:after", tshow (round (es * 240) :: Int)) ]
+                  _ -> []
+
 -- | Convert a Pandoc block element to OpenXML.
 blockToOpenXML :: (PandocMonad m) => WriterOptions -> Block -> WS m [Content]
 blockToOpenXML opts blk = withDirection $ blockToOpenXML' opts blk
 
 blockToOpenXML' :: (PandocMonad m) => WriterOptions -> Block -> WS m [Content]
-blockToOpenXML' opts (Div (ident,_classes,kvs) bs) = do
+blockToOpenXML' opts (Div (ident,classes,kvs) bs) = do
+  when ("math" `elem` classes) $ setFirstPara
   stylemod <- case lookup dynamicStyleKey kvs of
                    Just (fromString . T.unpack -> sty) -> do
                       modify $ \s ->
@@ -384,9 +458,18 @@
   let langmod = case lookup "lang" kvs of
                   Nothing -> id
                   Just lang -> local (\env -> env{envLang = Just lang})
+  -- citeproc adds formatting hints for bibliographies generated
+  -- from a CSL style; see Text.Pandoc.Citeproc (#11871).
+  let isCslBib = ident == "refs" || "csl-bib-body" `elem` classes
+  let cslmod = if not isCslBib
+                  then id
+                  else case cslBibParaProps classes kvs of
+                         []    -> id
+                         props -> foldr (.) id (map withParaProp props)
   header <- dirmod $ stylemod $ blocksToOpenXML opts hs
-  contents <- dirmod $ bibmod $ stylemod $ langmod $ blocksToOpenXML opts bs'
+  contents <- dirmod $ bibmod $ cslmod $ stylemod $ langmod $ blocksToOpenXML opts bs'
   wrapBookmark ident $ header <> contents
+
 blockToOpenXML' opts (Header lev (ident,_,kvs) lst) = do
   setFirstPara
   let isSection = case writerTopLevelDivision opts of
@@ -447,16 +530,16 @@
       let displayMathPara = case lst of
                                  [x] -> isDisplayMath x
                                  _   -> False
-      paraProps <- getParaProps displayMathPara
       bodyTextStyle <- pStyleM $ if isFirstPara
                        then "First Paragraph"
                        else "Body Text"
-      let paraProps' = case paraProps of
-            []               -> [mknode "w:pPr" [] [bodyTextStyle]]
-            ps               -> ps
+      paraProps <- local (\env -> env{ envParaProperties =
+                                        envParaProperties env <>
+                                        EnvProps (Just bodyTextStyle) [] })
+                      (getParaProps displayMathPara)
       modify $ \s -> s { stFirstPara = False }
       contents <- inlinesToOpenXML opts lst
-      return [Elem $ mknode "w:p" [] (map Elem paraProps' ++ contents)]
+      return [Elem $ mknode "w:p" [] (map Elem paraProps ++ contents)]
 blockToOpenXML' opts (LineBlock lns) = blockToOpenXML opts $ linesToPara lns
 blockToOpenXML' _ b@(RawBlock format str)
   | format == Format "openxml" = return [
@@ -678,7 +761,7 @@
                    Nothing -> mempty
                    Just l  -> EnvProps Nothing
                                [mknode "w:lang" [("w:val", l)] ()]
-  let squashed = squashProps (props <> langnode)
+  let squashed = squashProps rPrTagOrder (props <> langnode)
   return [mknode "w:rPr" [] squashed | (not . null) squashed]
 
 withTextProp :: PandocMonad m => Element -> WS m a -> WS m a
@@ -704,7 +787,7 @@
   let listPr = [mknode "w:numPr" []
                 [ mknode "w:ilvl" [("w:val",tshow listLevel)] ()
                 , mknode "w:numId" [("w:val",tshow numid')] () ] | listLevel >= 0 && not displayMathPara]
-  return $ case squashProps (EnvProps Nothing listPr <> props) of
+  return $ case squashProps pPrTagOrder (EnvProps Nothing listPr <> props) of
                 [] -> []
                 ps -> [mknode "w:pPr" [] ps]
 
@@ -754,8 +837,8 @@
 inlineToOpenXML' :: PandocMonad m => WriterOptions -> Inline -> WS m [Content]
 inlineToOpenXML' _ (Str str) =
   map Elem <$> formattedString str
-inlineToOpenXML' opts Space = inlineToOpenXML opts (Str " ")
-inlineToOpenXML' opts SoftBreak = inlineToOpenXML opts (Str " ")
+inlineToOpenXML' opts Space = inlineToOpenXML' opts (Str " ")
+inlineToOpenXML' opts SoftBreak = inlineToOpenXML' opts (Str " ")
 inlineToOpenXML' opts (Span ("",["mark"],[]) ils) =
   withTextProp (mknode "w:highlight" [("w:val","yellow")] ()) $
     inlinesToOpenXML opts ils
@@ -773,16 +856,13 @@
 inlineToOpenXML' opts (Span ("",["csl-indent"],[]) ils) =
   inlinesToOpenXML opts ils
 inlineToOpenXML' _ (Span (ident,["comment-start"],kvs) ils) = do
-  -- prefer the "id" in kvs, since that is the one produced by the docx
-  -- reader.
-  let ident' = fromMaybe ident (lookup "id" kvs)
-      kvs' = filter (("id" /=) . fst) kvs
+  let ident' = fromMaybe ident (lookup "comment-id" kvs <|> lookup "id" kvs)
+      kvs' = filter ((\x -> x /= "comment-id" && x /= "id") . fst) kvs
   modify $ \st -> st{ stComments = (("id",ident'):kvs', ils) : stComments st }
   return [ Elem $ mknode "w:commentRangeStart" [("w:id", ident')] () ]
 inlineToOpenXML' opts (Span (ident,["comment-end"],kvs) content) = do
-  -- prefer the "id" in kvs, since that is the one produced by the docx
-  -- reader.
-  let ident' = fromMaybe ident (lookup "id" kvs)
+  -- now we use comment-id, but support id for legacy compat:
+  let ident' = fromMaybe ident (lookup "comment-id" kvs <|> lookup "id" kvs)
   -- process nested content: see #8189
   nestedContent <- inlinesToOpenXML opts content
   let thisCommentEnd =
@@ -887,15 +967,14 @@
        Left il -> inlineToOpenXML' opts il
 inlineToOpenXML' opts (Cite _ lst) = inlinesToOpenXML opts lst
 inlineToOpenXML' opts (Code attrs str) = do
-  let alltoktypes = [KeywordTok ..]
-  tokTypesMap <- mapM (\tt -> (,) tt <$> rStyleM (fromString $ show tt)) alltoktypes
+  tokTypesMap <- gets stTokTypesMap
   let unhighlighted = (map Elem . intercalate [br]) `fmap`
                        mapM formattedString (T.lines str)
       formatOpenXML _fmtOpts = intercalate [br] . map (map toHlTok)
       toHlTok (toktype,tok) =
         mknode "w:r" []
           [ mknode "w:rPr" [] $
-            maybeToList (lookup toktype tokTypesMap)
+            maybeToList (M.lookup toktype tokTypesMap)
             , mknode "w:t" [("xml:space","preserve")] tok ]
   let highlighted =
         case highlight (writerSyntaxMap opts) formatOpenXML attrs str of
@@ -911,7 +990,6 @@
 inlineToOpenXML' opts (Note bs) = do
   notes <- gets stFootnotes
   notenum <- getUniqueId
-  oldFirstPara <- gets stFirstPara
   footnoteStyle <- rStyleM "Footnote Reference"
   let notemarker = mknode "w:r" []
                    [ mknode "w:rPr" [] footnoteStyle
@@ -927,19 +1005,19 @@
                                 , envInNote = True })
               (withParaPropM (pStyleM "Footnote Text") $
                blocksToOpenXML opts $ insertNoteRef bs)
-  modify $ \s -> s{ stFirstPara = oldFirstPara }
   let newnote = mknode "w:footnote" [("w:id", notenum)] contents
   modify $ \s -> s{ stFootnotes = newnote : notes }
   return [ Elem $ mknode "w:r" []
            [ mknode "w:rPr" [] footnoteStyle
            , mknode "w:footnoteReference" [("w:id", notenum)] () ] ]
 -- internal link:
-inlineToOpenXML' opts (Link _ txt (T.uncons -> Just ('#', xs),_)) = do
+inlineToOpenXML' opts (Link _ txt (T.uncons -> Just ('#', xs),title)) = do
   contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt
   return
-    [ Elem $ mknode "w:hyperlink" [("w:anchor", toBookmarkName xs)] contents ]
+    [ Elem $ mknode "w:hyperlink"
+        (("w:anchor", toBookmarkName xs) : tooltipAttr title) contents ]
 -- external link:
-inlineToOpenXML' opts (Link _ txt (src,_)) = do
+inlineToOpenXML' opts (Link _ txt (src,title)) = do
   contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt
   extlinks <- gets stExternalLinks
   id' <- case M.lookup src extlinks of
@@ -949,7 +1027,8 @@
               modify $ \st -> st{ stExternalLinks =
                         M.insert src i extlinks }
               return i
-  return [ Elem $ mknode "w:hyperlink" [("r:id",id')] contents ]
+  return [ Elem $ mknode "w:hyperlink" (("r:id",id') : tooltipAttr title)
+             contents ]
 inlineToOpenXML' opts (Image attr@(imgident, _, _) alt (src, title)) = do
   pageWidth <- asks envPrintWidth
   imgs <- gets stImages
@@ -995,10 +1074,23 @@
         (xpt,ypt) = desiredSizeInPoints opts attr
                (either (const def) id (imageSize opts img))
         -- 12700 emu = 1 pt
-        pageWidthPt = case dimension Width attr of
-                        Just (Percent a) -> pageWidth * floor (a * 127)
-                        _                -> pageWidth * 12700
-        (xemu,yemu) = fitToPage (xpt * 12700, ypt * 12700) pageWidthPt
+        pageWidthPt = fromIntegral pageWidth
+        pageWidthEmu = pageWidth * 12700
+        (xpt', ypt') =
+          case (dimension Width attr, dimension Height attr) of
+                 (Just (Percent a), Just (Percent b))
+                   -> ((a / 100.0) * pageWidthPt, (b / 100.0) * pageWidthPt)
+                      -- note, should use pageHeightPt but we don't have this
+                      -- information.
+                 (Just (Percent a), _)
+                   -> ((a / 100.0) * pageWidthPt,
+                       (a / 100.0) * pageWidthPt * (ypt / xpt))
+                 (_, Just (Percent b))
+                   -> ((b / 100.0) * pageWidthPt * (xpt / ypt),
+                       (b / 100.0) * pageWidthPt)
+                 (_, _) -> (xpt, ypt)
+        (xemu,yemu) = fitToPage (xpt' * 12700,
+                                 ypt' * 12700) pageWidthEmu
         cNvPicPr = mknode "pic:cNvPicPr" [] $
                          mknode "a:picLocks" [("noChangeArrowheads","1")
                                              ,("noChangeAspect","1")] ()
@@ -1041,7 +1133,7 @@
               , mknode "wp:effectExtent"
                 [("b","0"),("l","0"),("r","0"),("t","0")] ()
               , mknode "wp:docPr"
-                [ ("descr", stringify alt)
+                [ ("descr", stringifyInlines alt)
                 , ("title", title)
                 , ("id", docprid)
                 , ("name","Picture")
@@ -1102,11 +1194,17 @@
   -- We want to clean all bidirection (bidi) and right-to-left (rtl)
   -- properties from the props first. This is because we don't want
   -- them to stack up.
-  let paraProps' = filter (\e -> (qName . elName) e /= "bidi") (otherElements paraProps)
+  let hasBidi = any (\e -> (qName . elName) e == "bidi") (otherElements paraProps)
+      hasRtl = any (\e -> (qName . elName) e == "rtl") (otherElements textProps)
+      paraProps' = filter (\e -> (qName . elName) e /= "bidi") (otherElements paraProps)
       textProps' = filter (\e -> (qName . elName) e /= "rtl") (otherElements textProps)
       paraStyle = styleElement paraProps
       textStyle = styleElement textProps
-  if isRTL
+  if not isRTL && not hasBidi && not hasRtl
+    -- fast path: LTR with no bidi/rtl props to remove, so the
+    -- environment is unchanged; skip the 'local' rebuild.
+    then x
+    else if isRTL
     -- if we are going right-to-left, we (re?)add the properties.
     then flip local x $
          \env -> env { envParaProperties = EnvProps paraStyle $ mknode "w:bidi" [] () : paraProps'
@@ -1127,20 +1225,35 @@
   return $ Elem bookmarkStart : contents ++ [Elem bookmarkEnd]
 
 -- Word imposes a 40 character limit on bookmark names and requires
--- that they begin with a letter.  So we just use a hash of the
--- identifier when otherwise we'd have an illegal bookmark name.
+-- that they begin with a letter or @_@ and contain only letters,
+-- numbers or underscores. Bookmarks beginning with @_@ are
+-- hidden in the user interface (and in particular hidden from screen
+-- readers, which we want); these are to be used for cross-references.
+-- When the id is otherwise illegal we use a hash of the identifier.
 toBookmarkName :: Text -> Text
 toBookmarkName s
-  | Just (c, _) <- T.uncons s
-  , isLetter c
-  , T.length s <= 40 = s
-  | otherwise = T.pack $ 'X' : drop 1 (show (hashWith SHA1 (fromText s)))
+  | T.length s < 40
+  , T.all (\c -> isAlphaNum c || c == '_') s
+    = "_" <> s
+  | otherwise = "_" <> T.pack (drop 1 (show (hashWith SHA1 (fromText s))))
+  -- we drop 1 because a SHA1 is 40 characters and we need room for the `_`
 
+-- A link's title is written as its ScreenTip (@w:tooltip@).
+tooltipAttr :: Text -> [(Text, Text)]
+tooltipAttr title = [("w:tooltip", title) | not (T.null title)]
+
 maxListLevel :: Int
 maxListLevel = 8
 
+-- Merge adjacent Strs, and any Space between two Strs, into a single
+-- Str.  Chunks are accumulated and concatenated all at once, to avoid
+-- quadratic copying when a long Str/Space sequence (e.g. an entire
+-- paragraph) collapses into one Str.
 convertSpace :: [Inline] -> [Inline]
-convertSpace (Str x : Space : Str y : xs) = convertSpace (Str (x <> " " <> y) : xs)
-convertSpace (Str x : Str y : xs)         = convertSpace (Str (x <> y) : xs)
-convertSpace (x:xs)                       = x : convertSpace xs
-convertSpace []                           = []
+convertSpace (Str x : xs) = go [x] xs
+  where
+    go acc (Str y : ys)         = go (y : acc) ys
+    go acc (Space : Str y : ys) = go (y : " " : acc) ys
+    go acc ys = Str (T.concat (reverse acc)) : convertSpace ys
+convertSpace (x:xs) = x : convertSpace xs
+convertSpace [] = []
diff --git a/src/Text/Pandoc/Writers/Docx/Table.hs b/src/Text/Pandoc/Writers/Docx/Table.hs
--- a/src/Text/Pandoc/Writers/Docx/Table.hs
+++ b/src/Text/Pandoc/Writers/Docx/Table.hs
@@ -178,7 +178,9 @@
     rowwidth  = round (fullrow * sum widths) :: Int
     widthToTwips w = floor (textwidth * w)   :: Int
     mkGridCol w = mknode "w:gridCol" [("w:w", tshow (widthToTwips w))] ()
-  in if all (== 0) widths
+    -- A "mixed" table with both default and given widths is treated as
+    -- a table with all default widths.
+  in if any (== 0) widths
      then ( replicate ncols $ mkGridCol (1.0 / fromIntegral ncols)
           , [ ("w:type", "auto"), ("w:w", "0")])
      else ( map mkGridCol widths
diff --git a/src/Text/Pandoc/Writers/Docx/Types.hs b/src/Text/Pandoc/Writers/Docx/Types.hs
--- a/src/Text/Pandoc/Writers/Docx/Types.hs
+++ b/src/Text/Pandoc/Writers/Docx/Types.hs
@@ -27,6 +27,7 @@
 import Control.Monad.Reader
 import Control.Monad.State.Strict
 import Data.Text (Text)
+import Skylighting (TokenType)
 import Text.Pandoc.Class.PandocMonad (PandocMonad)
 import Text.Pandoc.Definition
 import Text.Pandoc.MIME (MimeType)
@@ -68,7 +69,7 @@
 
 data EnvProps = EnvProps{ styleElement  :: Maybe Element
                         , otherElements :: [Element]
-                        }
+                        } deriving (Show)
 
 instance Semigroup EnvProps where
   EnvProps s es <> EnvProps s' es' = EnvProps (s <|> s') (es ++ es')
@@ -87,7 +88,7 @@
   , envInNote         :: Bool
   , envChangesAuthor  :: Text
   , envChangesDate    :: Text
-  , envPrintWidth     :: Integer
+  , envPrintWidth     :: Integer  -- in points
   , envLang           :: Maybe Text
   , envSectPr         :: Maybe Element
   }
@@ -120,6 +121,9 @@
        , stInsId          :: Int
        , stDelId          :: Int
        , stStyleMaps      :: StyleMaps
+       , stTokTypesMap    :: M.Map TokenType Element
+         -- ^ cached rStyle element for each highlighting token type;
+         --   computed once from stStyleMaps at the start of writing
        , stFirstPara      :: Bool
        , stFirstSectionHeader :: Bool  -- ^ True until first section header is processed
        , stNumIdUsed      :: Bool  -- ^ True if the current numId (envListNumId) has been used.
@@ -146,6 +150,7 @@
       , stInsId          = 1
       , stDelId          = 1
       , stStyleMaps      = StyleMaps M.empty M.empty
+      , stTokTypesMap    = M.empty
       , stFirstPara      = False
       , stFirstSectionHeader = True
       , stNumIdUsed      = False
diff --git a/src/Text/Pandoc/Writers/EPUB.hs b/src/Text/Pandoc/Writers/EPUB.hs
--- a/src/Text/Pandoc/Writers/EPUB.hs
+++ b/src/Text/Pandoc/Writers/EPUB.hs
@@ -22,10 +22,11 @@
 import Control.Monad.Except (catchError, throwError)
 import Control.Monad.State.Strict (State, StateT, evalState, evalStateT, get,
                                    gets, lift, modify)
+import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as B
 import qualified Data.ByteString.Lazy.Char8 as B8
 import Data.Char (isAlphaNum, isAscii, isDigit)
-import Data.List (isInfixOf, isPrefixOf)
+import Data.List (isPrefixOf)
 import qualified Data.Map as M
 import Data.Maybe (fromMaybe, isNothing, mapMaybe, isJust, catMaybes)
 import qualified Data.Set as Set
@@ -38,7 +39,6 @@
 import Text.Pandoc.Writers.Shared (ensureValidXmlIdentifiers)
 import Data.Tree (Tree(..))
 import Text.Pandoc.Class (PandocMonad, report)
-import qualified Text.Pandoc.Class.PandocPure as P
 import Text.Pandoc.Data (readDataFile)
 import qualified Text.Pandoc.Class.PandocMonad as P
 import Data.Time
@@ -52,7 +52,7 @@
                             ObfuscationMethod (NoObfuscation), WrapOption (..),
                             WriterOptions (..))
 import Text.Pandoc.Shared (normalizeDate, renderTags',
-                           stringify, uniqueIdent, tshow)
+                           stringify, stringifyInlines, uniqueIdent, tshow)
 import qualified Text.Pandoc.UTF8 as UTF8
 import Text.Pandoc.UUID (getRandomUUID)
 import Text.Pandoc.Walk (walk, walkM)
@@ -215,7 +215,7 @@
        if any (\c -> creatorRole c == Just "aut") $ epubCreator m
           then return m
           else do
-            let authors' = map stringify $ docAuthors meta
+            let authors' = map stringifyInlines $ docAuthors meta
             let toAuthor name = Creator{ creatorText = name
                                        , creatorRole = Just "aut"
                                        , creatorFileAs = Nothing }
@@ -280,7 +280,7 @@
 
 metaValueToString :: MetaValue -> Text
 metaValueToString (MetaString s)    = s
-metaValueToString (MetaInlines ils) = stringify ils
+metaValueToString (MetaInlines ils) = stringifyInlines ils
 metaValueToString (MetaBlocks bs)   = stringify bs
 metaValueToString (MetaBool True)   = "true"
 metaValueToString (MetaBool False)  = "false"
@@ -483,7 +483,7 @@
                         [] -> case epubTitle metadata of
                                    []    -> "UNTITLED"
                                    (x:_) -> titleText x
-                        x  -> stringify x
+                        x  -> stringifyInlines x
 
   -- stylesheet
   stylesheets <- case epubStylesheets metadata of
@@ -589,14 +589,11 @@
                                     [("page-progression-direction", "rtl")]
                                   _  -> []
 
-  -- incredibly inefficient (TODO):
-  let containsMathML ent = epub3 &&
-                           "<math" `isInfixOf`
-        B8.unpack (fromEntry ent)
-  let containsSVG ent    = epub3 &&
-                           "<svg" `isInfixOf`
-        B8.unpack (fromEntry ent)
-  let props ent = ["mathml" | containsMathML ent] ++ ["svg" | containsSVG ent]
+  let props ent
+        | epub3 = let contents = B.toStrict (fromEntry ent)
+                  in  ["mathml" | "<math" `BS.isInfixOf` contents] ++
+                      ["svg" | "<svg" `BS.isInfixOf` contents]
+        | otherwise = []
 
   let chapterNode ent = unode "item" !
                            ([("id", toId $ makeRelative epubSubdir
@@ -859,7 +856,7 @@
           let secnum' = case secNumber secinfo of
                           Just t -> t <> " "
                           Nothing -> ""
-          let title' = secnum' <> stringify (secTitle secinfo)
+          let title' = secnum' <> stringifyInlines (secTitle secinfo)
           return $ Just $ unode "navPoint" !
                    [("id", "navPoint-" <> tshow n)] $
                       [ unode "navLabel" $ unode "text" title'
@@ -869,7 +866,7 @@
 
   let tpNode = unode "navPoint" !  [("id", "navPoint-0")] $
                   [ unode "navLabel" $ unode "text"
-                     (stringify $ docTitle' meta)
+                     (stringifyInlines $ docTitle' meta)
                   , unode "content" ! [("src", "text/title_page.xhtml")]
                   $ () ]
 
@@ -913,8 +910,7 @@
                 -> StateT EPUBState m Entry
 createNavEntry opts meta metadata
                vars cssvars writeHtml tocTitle version (Node _ secs) = do
-  let mkItem :: Tree SecInfo -> State Int (Maybe Element)
-      mkItem (Node secinfo subsecs)
+  let mkItem (Node secinfo subsecs)
         | secLevel secinfo > writerTOCDepth opts = return Nothing
         | otherwise = do
           n <- get
@@ -929,13 +925,14 @@
           let clean (Link _ ils _) = Span ("", [], []) ils
               clean (Note _)       = Str ""
               clean x              = x
-          let titRendered = case P.runPure
-                                  (writeHtmlStringForEPUB version
-                                    opts{ writerTemplate = Nothing }
-                                    (Pandoc nullMeta
-                                      [Plain $ walk clean title'])) of
-                                  Left _  -> stringify title'
-                                  Right x -> x
+          -- render in the host monad, so that a translation table
+          -- loaded for one title is reused for the others:
+          titRendered <- lift $ catchError
+                           (writeHtmlStringForEPUB version
+                              opts{ writerTemplate = Nothing }
+                              (Pandoc nullMeta
+                                [Plain $ walk clean title']))
+                           (\_ -> return $ stringifyInlines title')
           let titElements = either (const []) id $
                                   parseXMLContents (TL.fromStrict titRendered)
 
@@ -949,7 +946,7 @@
                            (_:_) -> [unode "ol" ! [("class","toc")] $ subs]
 
   let navtag = if version == EPUB3 then "nav" else "div"
-  let tocBlocks = evalState (catMaybes <$> mapM mkItem secs) 1
+  tocBlocks <- lift $ evalStateT (catMaybes <$> mapM mkItem secs) (1 :: Int)
   let navBlocks = [RawBlock (Format "html")
                   $ showElement $ -- prettyprinting introduces bad spaces
                    unode navtag ! ([("epub:type","toc") | version == EPUB3] ++
diff --git a/src/Text/Pandoc/Writers/FB2.hs b/src/Text/Pandoc/Writers/FB2.hs
--- a/src/Text/Pandoc/Writers/FB2.hs
+++ b/src/Text/Pandoc/Writers/FB2.hs
@@ -38,7 +38,7 @@
 import Text.Pandoc.Logging
 import Text.Pandoc.Options (MathMethod (..), WriterOptions (..), def)
 import Text.Pandoc.Shared (blocksToInlines, capitalize, orderedListMarkers,
-                           makeSections, tshow, stringify)
+                           makeSections, tshow, stringifyInlines)
 import Text.Pandoc.Walk (walk)
 import Text.Pandoc.Writers.Shared (lookupMetaString, toLegacyTable,
                                    ensureValidXmlIdentifiers)
@@ -118,7 +118,7 @@
         im <- insertImage InlineImage img
         return [el "coverpage" im]
   coverpage <- case lookupMeta "cover-image" meta' of
-                    Just (MetaInlines ils) -> coverimage (stringify ils)
+                    Just (MetaInlines ils) -> coverimage (stringifyInlines ils)
                     Just (MetaString s) -> coverimage s
                     _       -> return []
   return $ el "description"
diff --git a/src/Text/Pandoc/Writers/HTML.hs b/src/Text/Pandoc/Writers/HTML.hs
--- a/src/Text/Pandoc/Writers/HTML.hs
+++ b/src/Text/Pandoc/Writers/HTML.hs
@@ -48,7 +48,7 @@
 import Text.Pandoc.URI (urlEncode)
 import Numeric (showHex)
 import Text.DocLayout (render, literal, Doc)
-import Text.Blaze.Internal (MarkupM (Empty), customLeaf, customParent)
+import Text.Blaze.Internal (MarkupM (Append, Empty), customLeaf, customParent)
 import Text.DocTemplates (FromContext (lookupContext), Context (..), Val(..))
 import qualified Text.DocTemplates.Internal as DT
 import Text.Blaze.Html hiding (contents)
@@ -125,20 +125,24 @@
 
 strToHtml :: Text -> Html
 strToHtml t
-    | T.any isSpecial t =
-       let !x = L.foldl' go mempty $ T.groupBy samegroup t
-        in x
+    | T.any isSpecial t = go t
     | otherwise = toHtml t
   where
-    samegroup c d = d == '\xFE0E' || not (isSpecial c || isSpecial d)
     isSpecial '\'' = True
     isSpecial '"' = True
     isSpecial c = needsVariationSelector c
-    go h "\'" = h <> preEscapedString "\'"
-    go h "\"" = h <> preEscapedString "\""
-    go h txt | T.length txt == 1 && T.all needsVariationSelector txt
-           = h <> preEscapedString (T.unpack txt <> "\xFE0E")
-    go h txt = h <> toHtml txt
+    go s =
+      let (plain, rest) = T.break isSpecial s
+          html = if T.null plain then mempty else toHtml plain
+      in  case T.uncons rest of
+            Nothing -> html
+            Just ('\'', rest') -> html <> preEscapedText "'" <> go rest'
+            Just ('"', rest')  -> html <> preEscapedText "\"" <> go rest'
+            Just (c, rest')
+              -- don't add a variation selector if one is already there:
+              | T.take 1 rest' == "\xFE0E" -> html <> toHtml c <> go rest'
+              | otherwise -> html <> preEscapedText (T.pack [c, '\xFE0E'])
+                                  <> go rest'
 
 -- See #5469: this prevents iOS from substituting emojis.
 needsVariationSelector :: Char -> Bool
@@ -150,6 +154,14 @@
 nl :: Html
 nl = preEscapedString "\n"
 
+-- | True if the markup contains no content at all.  'mconcat' and
+-- '<>' on 'MarkupM' build 'Append' nodes without collapsing empty
+-- markup, so simply matching on 'Empty' is not enough.
+isEmptyMarkup :: MarkupM a -> Bool
+isEmptyMarkup (Empty _)    = True
+isEmptyMarkup (Append x y) = isEmptyMarkup x && isEmptyMarkup y
+isEmptyMarkup _            = False
+
 -- | Convert Pandoc document to Html 5 string.
 writeHtml5String :: PandocMonad m => WriterOptions -> Pandoc -> m Text
 writeHtml5String = writeHtmlString'
@@ -286,7 +298,7 @@
               (fmap layoutMarkup . blockListToHtml opts)
               (fmap layoutMarkup . inlineListToHtml opts)
               meta
-  let stringifyHTML = escapeStringForXML . stringify
+  let stringifyHTML = escapeStringForXML . stringifyInlines
   let authsMeta = map (literal . stringifyHTML) $ docAuthors meta
   let dateMeta  = stringifyHTML $ docDate meta
   let descriptionMeta = literal $ escapeStringForXML $
@@ -352,7 +364,7 @@
             ]
           nl
           H.link ! A.rel "stylesheet" !
-            A.href (toValue $ toURI html5 url <> "katex.min.css")
+            A.href (toValue $ toURI html5 $ url <> "katex.min.css")
 
         _ -> mempty
   let mCss :: Maybe [Text] = lookupContext "css" metadata
@@ -572,7 +584,7 @@
   let container x
         | html5
         , epubVersion == Just EPUB3
-                = H5.section ! A.id (fromString idName)
+                = H5.section ! prefixedId opts (fromString idName)
                              ! A.class_ className
                              ! customAttribute "epub:type" "footnotes" $ x
         | html5
@@ -633,16 +645,24 @@
               (linkText, altText) =
                  if txt == T.drop 7 s' -- autolink
                     then ("e", name' <> " at " <> domain')
-                    else ("'" <> obfuscateString txt <> "'",
+                    else ("'" <>
+                          T.replace "</" "<\\/" (obfuscateMarkup txt) <> "'",
                           txt <> " (" <> name' <> " at " <> domain' <> ")")
-              (_, classNames, _) = attr
+              (ident, classNames, kvs) = attr
               classNamesStr = T.concat $ map (" "<>) classNames
+              otherAttrsStr = T.concat $
+                [ " id=\"" <>
+                  escapeJSAttrVal (writerIdentifierPrefix opts <> ident) <>
+                  "\"" | not (T.null ident) ] ++
+                [ " " <> k <> "=\"" <> escapeJSAttrVal v <> "\""
+                | (k, v) <- kvs ]
           in  case meth of
                 ReferenceObfuscation ->
-                     -- need to use preEscapedString or &'s are escaped to &amp; in URL
-                     return $
-                     preEscapedText $ "<a href=\"" <> obfuscateString s'
-                     <> "\" class=\"email\">" <> obfuscateString txt <> "</a>"
+                     -- preEscaped is needed or the &'s in the
+                     -- entity-obfuscated text are escaped to &amp;
+                     addAttrs opts (ident, "email":classNames, kvs) $
+                       H.a ! A.href (preEscapedToValue $ obfuscateString s')
+                           $ preEscapedText $ obfuscateMarkup txt
                 JavascriptObfuscation ->
                      return $
                      (H.script ! A.type_ "text/javascript" $
@@ -650,12 +670,12 @@
                      obfuscateString domain <> "';a='" <> at' <> "';n='" <>
                      obfuscateString name' <> "';e=n+a+h;\n" <>
                      "document.write('<a h'+'ref'+'=\"ma'+'ilto'+':'+e+'\" clas'+'s=\"em' + 'ail" <>
-                     classNamesStr <> "\">'+" <>
+                     classNamesStr <> "\"" <> otherAttrsStr <> ">'+" <>
                      linkText  <> "+'<\\/'+'a'+'>');\n// -->\n")) >>
-                     H.noscript (preEscapedText $ obfuscateString altText)
+                     H.noscript (preEscapedText $ obfuscateMarkup altText)
                 _ -> throwError $ PandocSomeError $ "Unknown obfuscation method: " <> tshow meth
         _ -> addAttrs opts attr $ H.a ! A.href (toValue $ toURI html5 s)
-                                      $ toHtml txt  -- malformed email
+                                      $ preEscapedText txt  -- malformed email
 
 -- | Obfuscate character as entity.
 obfuscateChar :: Char -> Text
@@ -668,6 +688,37 @@
 obfuscateString :: Text -> Text
 obfuscateString = T.concatMap obfuscateChar . fromEntities
 
+-- | Obfuscate the character data in a rendered HTML fragment,
+-- leaving the tags themselves intact.
+obfuscateMarkup :: Text -> Text
+obfuscateMarkup t
+  | T.null t = ""
+  | otherwise =
+      let (chars, rest)  = T.break (== '<') t
+          (tag, rest') = T.break (== '>') rest
+      in  obfuscateString chars <>
+          case T.uncons rest' of
+            Just ('>', rest'') -> tag <> ">" <> obfuscateMarkup rest''
+            _ -> tag  -- unterminated tag; emit as is
+
+-- | Escape text for an HTML attribute value that is embedded in a
+-- single-quoted JavaScript string literal (as used in
+-- 'JavascriptObfuscation').  Everything problematic is replaced
+-- with an entity, which the HTML parser decodes when the string is
+-- written to the document.
+escapeJSAttrVal :: Text -> Text
+escapeJSAttrVal = T.concatMap $ \c ->
+  case c of
+    '&'  -> "&amp;"
+    '<'  -> "&lt;"
+    '>'  -> "&gt;"
+    '"'  -> "&quot;"
+    '\'' -> "&#39;"
+    '\\' -> "&#92;"
+    '\n' -> "&#10;"
+    '\r' -> "&#13;"
+    _    -> T.singleton c
+
 -- | Create HTML tag with attributes.
 tagWithAttributes :: WriterOptions
                   -> Bool -- ^ True for HTML5
@@ -703,7 +754,7 @@
   addAttr html5 mbEpubVersion x y
     | T.null x = id  -- see #7546
     | html5
-      = if (x `Set.member` (html5Attributes <> rdfaAttributes)
+      = if (x `Set.member` html5AttrsPlusRdfa
             && x /= "label") -- #10048
              || T.any (== ':') x -- e.g. epub: namespace
              || "data-" `T.isPrefixOf` x
@@ -711,12 +762,19 @@
            then (customAttribute (textTag x) (toValue y) :)
            else (customAttribute (textTag ("data-" <> x)) (toValue y) :)
     | mbEpubVersion == Just EPUB2
-    , not (x `Set.member` (html4Attributes <> rdfaAttributes) ||
+    , not (x `Set.member` html4AttrsPlusRdfa ||
       "xml:" `T.isPrefixOf` x)
       = id
     | otherwise
       = (customAttribute (textTag x) (toValue y) :)
 
+-- Top-level constants, so that the set unions are computed only once.
+html5AttrsPlusRdfa :: Set.Set Text
+html5AttrsPlusRdfa = html5Attributes <> rdfaAttributes
+
+html4AttrsPlusRdfa :: Set.Set Text
+html4AttrsPlusRdfa = html4Attributes <> rdfaAttributes
+
 attrsToHtml :: PandocMonad m
             => WriterOptions -> Attr -> StateT WriterState m [Attribute]
 attrsToHtml opts (id',classes',keyvals) = do
@@ -764,9 +822,9 @@
           inlineToHtml opts (Image attr txt (src, tit))
     _ -> do
       contents <- inlineListToHtml opts lst
-      case contents of
-        Empty _ | not (isEnabled Ext_empty_paragraphs opts) -> return mempty
-        _ -> return $ H.p contents
+      if isEmptyMarkup contents && not (isEnabled Ext_empty_paragraphs opts)
+         then return mempty
+         else return $ H.p contents
 blockToHtmlInner opts (LineBlock lns) = do
   htmlLines <- inlineListToHtml opts $ intercalate [LineBreak] lns
   return $ H.div ! A.class_ "line-block" $ htmlLines
@@ -796,17 +854,18 @@
   let inDiv' zs = RawBlock (Format "html") ("<div class=\""
                        <> fragmentClass <> "\">") :
                    (zs ++ [RawBlock (Format "html") "</div>"])
-  let breakOnPauses zs
-        | slide = case splitBy isPause zs of
+  let breakOnPauses zs = case splitBy isPause zs of
                            []   -> []
                            y:ys -> y ++ concatMap inDiv' ys
-        | otherwise = zs
+  let breakPauses = if slide
+                       then walk breakOnPauses
+                       else id  -- avoid a pointless traversal
   let (titleBlocks, innerSecs) =
         if titleSlide
            -- title slides have no content of their own
            then let (as, bs) = break isSec xs
-                in  (walk breakOnPauses as, bs)
-           else ([], walk breakOnPauses xs)
+                in  (breakPauses as, bs)
+           else ([], breakPauses xs)
   let secttag  = if html5
                     then H5.section
                     else H.div
@@ -910,7 +969,7 @@
                  then -- we don't use blockListToHtml because it inserts
                       -- a newline between the column divs, which throws
                       -- off widths! see #4028
-                      mconcat <$> mapM (blockToHtml opts) bs'
+                      mconcat <$> mapM (blockToHtml opts') bs'
                  else blockListToHtml opts' bs'
   let contents' = nl >> contents >> nl
   let (divtag, classes'') = if html5 && "section" `elem` classes'
@@ -1110,7 +1169,7 @@
     else foldl (!) H.div (A.class_ "float" : figAttrs) innards
  where
   captionIsAlt capt [Plain [Image (_, _, kv) desc _]] =
-    let alt = fromMaybe (stringify desc) $ lookup "alt" kv
+    let alt = fromMaybe (stringifyInlines desc) $ lookup "alt" kv
     in stringify capt == alt
   captionIsAlt _ _ = False
 
@@ -1169,7 +1228,7 @@
   let attr' = case lookup "style" kvs of
                 Nothing | totalWidth < 1 && totalWidth > 0
                   -> (ident,classes, ("style","width:" <>
-                         T.pack (show (round (totalWidth * 100) :: Int))
+                         T.pack (show (truncate (totalWidth * 100) :: Int))
                          <> "%;"):kvs)
                 _ -> attr
   addAttrs opts attr' $ H.table $ do
@@ -1378,10 +1437,8 @@
 blockListToHtml :: PandocMonad m
                 => WriterOptions -> [Block] -> StateT WriterState m Html
 blockListToHtml opts lst =
-  mconcat . intersperse (nl) . filter nonempty
+  mconcat . intersperse (nl) . filter (not . isEmptyMarkup)
     <$> mapM (blockToHtml opts) lst
-  where nonempty (Empty _) = False
-        nonempty _         = True
 
 -- | Convert list of Pandoc inline elements to HTML.
 inlineListToHtml :: PandocMonad m => WriterOptions -> [Inline] -> StateT WriterState m Html
@@ -1601,7 +1658,7 @@
                                     else link' ! A.title (toValue tit)
     (Image attr@(_, _, attrList) txt (s, tit)) -> do
                         epubVersion <- gets stEPUBVersion
-                        let alternate = stringify txt
+                        let alternate = stringifyInlines txt
                         slideVariant <- gets stSlideVariant
                         let isReveal = slideVariant == RevealJsSlides
                         attrs <- imgAttrsToHtml opts attr
@@ -1788,7 +1845,7 @@
 intrinsicEventsHTML4 :: [Text]
 intrinsicEventsHTML4 =
   [ "onclick", "ondblclick", "onmousedown", "onmouseup", "onmouseover"
-  , "onmouseout", "onmouseout", "onkeypress", "onkeydown", "onkeyup"]
+  , "onmousemove", "onmouseout", "onkeypress", "onkeydown", "onkeyup"]
 
 
 -- | Check to see if Format is valid HTML
diff --git a/src/Text/Pandoc/Writers/Haddock.hs b/src/Text/Pandoc/Writers/Haddock.hs
--- a/src/Text/Pandoc/Writers/Haddock.hs
+++ b/src/Text/Pandoc/Writers/Haddock.hs
@@ -264,7 +264,7 @@
 inlineToHaddock _ Space = return space
 inlineToHaddock opts (Cite _ lst) = inlineListToHaddock opts lst
 inlineToHaddock _ (Link _ txt (src, _)) = do
-  let linktext = literal $ escapeString $ stringify txt
+  let linktext = literal $ escapeString $ stringifyInlines txt
   let useAuto = isURI src &&
                 case txt of
                       [Str s] | escapeURI s == src -> True
diff --git a/src/Text/Pandoc/Writers/JATS.hs b/src/Text/Pandoc/Writers/JATS.hs
--- a/src/Text/Pandoc/Writers/JATS.hs
+++ b/src/Text/Pandoc/Writers/JATS.hs
@@ -445,7 +445,7 @@
   -- Remove the alt text from images if it's the same as the caption text.
   let unsetAltIfDupl = \case
         Image attr alt tgt
-          | stringify alt == stringify longcapt -> Image attr [] tgt
+          | stringifyInlines alt == stringify longcapt -> Image attr [] tgt
         inline -> inline
   capt <- if null longcapt
           then pure empty
@@ -487,7 +487,7 @@
   where
    fixCitations [] = []
    fixCitations (x:xs) | needsFixing x =
-     x : Str (stringify ys) : fixCitations zs
+     x : Str (stringifyInlines ys) : fixCitations zs
      where
        needsFixing (RawInline (Format "jats") z) =
            "<pub-id pub-id-type=" `T.isPrefixOf` z
@@ -616,7 +616,7 @@
 inlineToJATS opts (Link (ident,_,kvs) txt (T.uncons -> Just ('#', src), _)) = do
   let attr = mconcat
              [ [("id", escapeNCName ident) | not (T.null ident)]
-             , [("alt", stringify txt) | not (null txt)]
+             , [("alt", stringifyInlines txt) | not (null txt)]
              , [("rid", escapeNCName src)]
              , [(k,v) | (k,v) <- kvs, k `elem` ["ref-type", "specific-use"]]
              , [("ref-type", "bibr") | "ref-" `T.isPrefixOf` src]
@@ -670,7 +670,7 @@
   if null alt
   then Nothing
   else Just . inTagsSimple "alt-text" .
-       hsep . map literal . T.words $ stringify alt
+       hsep . map literal . T.words $ stringifyInlines alt
 
 imageMimeType :: Text -> [(Text, Text)] -> (Text, Text)
 imageMimeType src kvs =
diff --git a/src/Text/Pandoc/Writers/Jira.hs b/src/Text/Pandoc/Writers/Jira.hs
--- a/src/Text/Pandoc/Writers/Jira.hs
+++ b/src/Text/Pandoc/Writers/Jira.hs
@@ -25,7 +25,7 @@
 import Text.Pandoc.Definition
 import Text.Pandoc.Options (WriterOptions (writerTemplate, writerWrapText),
                             WrapOption (..))
-import Text.Pandoc.Shared (linesToPara, stringify)
+import Text.Pandoc.Shared (linesToPara, stringifyInlines)
 import Text.Pandoc.Templates (renderTemplate)
 import Text.Pandoc.Writers.Math (texMathToInlines)
 import Text.Pandoc.Writers.Shared (defField, metaToContext, toLegacyTable)
@@ -253,7 +253,7 @@
             -> JiraConverter m [Jira.Inline]
 imageToJira (_, classes, kvs) caption src title =
   let imageWithParams ps = Jira.Image ps (Jira.URL src)
-      alt = stringify caption
+      alt = stringifyInlines caption
   in pure . singleton . imageWithParams $
      if "thumbnail" `elem` classes
      then [Jira.Parameter "thumbnail" ""]
@@ -270,7 +270,8 @@
            -> JiraConverter m [Jira.Inline]
 toJiraLink (_, classes, _) (url, _) alias = do
   let (linkType, url') = toLinkType url
-  description <- if url `elem` [stringify alias, "mailto:" <> stringify alias]
+  description <- if url `elem` [stringifyInlines alias,
+                                "mailto:" <> stringifyInlines alias]
                  then pure mempty
                  else toJiraInlines alias
   pure . singleton $ Jira.Link linkType description (Jira.URL url')
diff --git a/src/Text/Pandoc/Writers/LaTeX.hs b/src/Text/Pandoc/Writers/LaTeX.hs
--- a/src/Text/Pandoc/Writers/LaTeX.hs
+++ b/src/Text/Pandoc/Writers/LaTeX.hs
@@ -33,7 +33,7 @@
 import Crypto.Hash (hashWith, MD5(MD5))
 import Data.Containers.ListUtils (nubOrd)
 import Data.Char (isDigit, isAscii, isLetter)
-import Data.List (intersperse, partition, (\\))
+import Data.List (find, intersperse, partition)
 import qualified Data.Set as Set
 import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe, mapMaybe, isNothing)
 import Data.Monoid (Any (..))
@@ -128,11 +128,6 @@
   let blocks' = if method == Biblatex || method == Natbib
                    then filter (not . isRefsDiv) blocks
                    else blocks
-  -- see if there are internal links
-  let isInternalLink (Link _ _ (s,_))
-        | Just ('#', xs) <- T.uncons s = [xs]
-      isInternalLink _                 = []
-  modify $ \s -> s{ stInternalLinks = query isInternalLink blocks' }
   let colwidth = if writerWrapText options == WrapAuto
                     then Just $ writerColumns options
                     else Nothing
@@ -186,22 +181,26 @@
   biblioTitle <- inlineListToLaTeX lastHeader
   st <- get
   titleMeta <- escapeCommas <$> -- see #10501
-                stringToLaTeX TextString (stringify $ docTitle meta)
-  subtitleMeta <- stringToLaTeX TextString (stringify $ lookupMetaInlines "subtitle" meta)
-  authorsMeta <- mapM (stringToLaTeX TextString . stringify) $ docAuthors meta
+                stringToLaTeX TextString (stringifyInlines $ docTitle meta)
+  subtitleMeta <- stringToLaTeX TextString
+                    (stringifyInlines $ lookupMetaInlines "subtitle" meta)
+  authorsMeta <- mapM (stringToLaTeX TextString . stringifyInlines) $
+                   docAuthors meta
   -- The trailer ID is as hash used to identify the PDF. Taking control of its
   -- value is important when aiming for reproducible PDF generation. Setting
   -- `SOURCE_DATE_EPOCH` is the traditional method used to control
   -- reproducible builds. There are no cryptographic requirements for the ID,
   -- so the 128bits (16 bytes) of MD5 are appropriate.
   reproduciblePDF <- isJust <$> lookupEnv "SOURCE_DATE_EPOCH"
-  trailerID <- do
-    time <- getPOSIXTime
-    let hash = T.pack . show . hashWith MD5 $ mconcat
-               [ UTF8.fromString $ show time
-               , UTF8.fromText $ render Nothing main
-               ]
-    pure $ mconcat [ "<", hash, "> <", hash, ">" ]
+  trailerID <- if reproduciblePDF
+    then do
+      time <- getPOSIXTime
+      let hash = T.pack . show . hashWith MD5 $ mconcat
+                 [ UTF8.fromString $ show time
+                 , UTF8.fromText $ render Nothing main
+                 ]
+      pure $ Just $ mconcat [ "<", hash, "> <", hash, ">" ]
+    else pure Nothing
   -- we need a default here since lang is used in template conditionals
   let hasStringValue x = isJust (getField x metadata :: Maybe (Doc Text))
   let geometryFromMargins = mconcat $ intersperse ("," :: Doc Text) $
@@ -297,9 +296,7 @@
                         | not (T.null ds) && T.all isDigit ds
                           -> resetField "papersize" ("a" <> ds)
                       _   -> id) .
-                  (if reproduciblePDF
-                    then defField "pdf-trailer-id" trailerID
-                    else id) $
+                  maybe id (defField "pdf-trailer-id") trailerID $
                   (if not (null (pdfStandards pdfStd)) || isJust (pdfVersion pdfStd)
                     then resetField "pdfstandard" $ MapVal $ Context $ M.fromList
                            [ ("standards", ListVal $ map (SimpleVal . literal) (pdfStandards pdfStd))
@@ -809,21 +806,19 @@
   let unnumbered = "unnumbered" `elem` classes
   let unlisted = "unlisted" `elem` classes
   txt <- inlineListToLaTeX lst
-  plain <- stringToLaTeX TextString $ T.concat $ map stringify lst
+  plain <- stringToLaTeX TextString $ stringifyInlines lst
   let removeInvalidInline (Note _)             = []
       removeInvalidInline (Span (id', _, _) _) | not (T.null id') = []
       removeInvalidInline Image{}            = []
       removeInvalidInline x                    = [x]
   let lstNoNotes = foldr (mappend . (\x -> walkM removeInvalidInline x)) mempty lst
-  txtNoNotes <- inlineListToLaTeX lstNoNotes
-  txtNoLinksNoNotes <- inlineListToLaTeX (removeLinks lstNoNotes)
   -- footnotes in sections don't work (except for starred variants)
   -- unless you specify an optional argument:
   -- \section[mysec]{mysec\footnote{blah}}
   optional <- if unnumbered || lstNoNotes == lst || null lstNoNotes
                  then return empty
                  else
-                   return $ brackets txtNoNotes
+                   brackets <$> inlineListToLaTeX lstNoNotes
   let contents = if render Nothing txt == plain
                     then braces txt
                     else braces (text "\\texorpdfstring"
@@ -862,45 +857,46 @@
   lab <- labelFor ident
   let star = if unnumbered then text "*" else empty
   let title = star <> optional <> contents
+  tocEntry <- if unnumbered && not unlisted
+                 then do
+                   txtNoLinksNoNotes <- inlineListToLaTeX
+                                          (removeLinks lstNoNotes)
+                   pure $ "\\addcontentsline{toc}" <>
+                            braces (text sectionType) <>
+                            braces txtNoLinksNoNotes
+                 else pure empty
   return $ if level' > 5
               then txt
               else prefix
                    $$ text ('\\':sectionType) <> title <> lab
-                   $$ if unnumbered && not unlisted
-                         then "\\addcontentsline{toc}" <>
-                                braces (text sectionType) <>
-                                braces txtNoLinksNoNotes
-                         else empty
+                   $$ tocEntry
 
 -- | Convert list of inline elements to LaTeX.
 inlineListToLaTeX :: PandocMonad m
                   => [Inline]  -- ^ Inlines to convert
                   -> LW m (Doc Text)
 inlineListToLaTeX lst = hcat <$>
-  mapM inlineToLaTeX
-    (addKerns . fixLineInitialSpaces . fixInitialLineBreaks $ lst)
-    -- nonbreaking spaces (~) in LaTeX don't work after line breaks,
-    -- so we insert a strut: this is mostly used in verse.
- where fixLineInitialSpaces [] = []
-       fixLineInitialSpaces (LineBreak : Str s : xs)
-         | Just ('\160', _) <- T.uncons s
-         = LineBreak : RawInline "latex" "\\strut " : Str s
-            : fixLineInitialSpaces xs
-       fixLineInitialSpaces (x:xs) = x : fixLineInitialSpaces xs
-       -- We need \hfill\break for a line break at the start
+  mapM inlineToLaTeX (fixInlines . fixInitialLineBreaks $ lst)
+ where -- We need \hfill\break for a line break at the start
        -- of a paragraph. See #5591.
        fixInitialLineBreaks (LineBreak:xs) =
          RawInline (Format "latex") "\\hfill\\break\n" :
            fixInitialLineBreaks xs
        fixInitialLineBreaks xs = xs
-       addKerns [] = []
-       addKerns (Str s : q@Quoted{} : rest)
+       fixInlines [] = []
+       -- nonbreaking spaces (~) in LaTeX don't work after line breaks,
+       -- so we insert a strut: this is mostly used in verse.
+       fixInlines (LineBreak : Str s : xs)
+         | Just ('\160', _) <- T.uncons s
+         = LineBreak : RawInline "latex" "\\strut " : fixInlines (Str s : xs)
+       -- insert a thin space (kern) between adjacent quote characters:
+       fixInlines (Str s : q@Quoted{} : rest)
          | isQuote (T.takeEnd 1 s) =
-           Str s : RawInline (Format "latex") "\\," : addKerns (q:rest)
-       addKerns (q@Quoted{} : Str s : rest)
+           Str s : RawInline (Format "latex") "\\," : fixInlines (q:rest)
+       fixInlines (q@Quoted{} : Str s : rest)
          | isQuote (T.take 1 s) =
-           q : RawInline (Format "latex") "\\," : addKerns (Str s : rest)
-       addKerns (x:xs) = x : addKerns xs
+           q : RawInline (Format "latex") "\\," : fixInlines (Str s : rest)
+       fixInlines (x:xs) = x : fixInlines xs
        isQuote "\"" = True
        isQuote "'" = True
        isQuote "\x2018" = True
@@ -994,9 +990,9 @@
                                    listingsopts) <> "]"
         inNote <- gets stInNote
         when inNote $ modify $ \s -> s{ stVerbInNote = True }
-        let chr = case "!\"'()*,-./:;?@" \\ T.unpack str of
-                       (c:_) -> c
-                       []    -> '!'
+        let chr = fromMaybe '!' $
+                    find (\c -> not (T.any (== c) str))
+                      ("!\"'()*,-./:;?@" :: String)
         let isEscapable '\\' = True
             isEscapable '{'  = True
             isEscapable '}'  = True
@@ -1027,7 +1023,7 @@
                  unless (T.null msg) $ report $ CouldNotHighlight msg
                  rawCode
                Right h -> modify (\st -> st{ stHighlighting = True }) >>
-                          return (text (T.unpack h))
+                          return (literal h)
   -- for soul commands we need to protect VERB in an mbox or we get an error
   -- (see #1294). with regular texttt we don't get an error, but we get
   -- incorrect results if there is a space (see #5529).
@@ -1183,7 +1179,7 @@
                      Nothing
                        | null description -> pure Nothing
                        | otherwise -> Just <$> stringToLaTeX TextString
-                                                  (stringify description)
+                                              (stringifyInlines description)
   let showDim dir = let d = text (show dir) <> "="
                     in case dimension dir attr of
                          Just (Pixel a)   ->
diff --git a/src/Text/Pandoc/Writers/LaTeX/Table.hs b/src/Text/Pandoc/Writers/LaTeX/Table.hs
--- a/src/Text/Pandoc/Writers/LaTeX/Table.hs
+++ b/src/Text/Pandoc/Writers/LaTeX/Table.hs
@@ -53,7 +53,6 @@
                            $ lookup "latex-placement" kvs
   -- if the float class is included in table attributes, we generate a floating
   -- table environment; otherwise we use longtable
-  beamer <- gets stBeamer
   let float = "float" `elem` classes
   let renderTable = do
        let unnumbered = "unnumbered" `elem` classes
@@ -83,11 +82,7 @@
           then makeUnnumbered
           else id) <$>
           makeTable colDesc mkHead mkRow capt thead tbodies tfoot
-  -- See #5367 -- footnotehyper/footnote don't work in beamer,
-  -- so we need to produce the notes outside the table...
-  if float || beamer
-     then ($$) <$> withExternalNotes renderTable <*> getAccumulatedNotes
-     else renderTable
+  ($$) <$> withExternalNotes renderTable <*> getAccumulatedNotes
 
 tableToLaTeXTable :: PandocMonad m
                   => Doc Text
@@ -353,11 +348,8 @@
 -- For simple latex tables (without minipages or parboxes),
 -- we need to go to some lengths to get line breaks working:
 -- as LineBreak bs = \vtop{\hbox{\strut as}\hbox{\strut bs}}.
-fixLineBreaks :: Block -> Block
-fixLineBreaks = walk fixLineBreaks'
-
-fixLineBreaks' :: [Inline] -> [Inline]
-fixLineBreaks' ils = case splitBy (== LineBreak) ils of
+fixLineBreaks :: [Inline] -> [Inline]
+fixLineBreaks ils = case splitBy (== LineBreak) ils of
                        []     -> []
                        [xs]   -> xs
                        chunks -> RawInline "tex" "\\vtop{" :
diff --git a/src/Text/Pandoc/Writers/LaTeX/Types.hs b/src/Text/Pandoc/Writers/LaTeX/Types.hs
--- a/src/Text/Pandoc/Writers/LaTeX/Types.hs
+++ b/src/Text/Pandoc/Writers/LaTeX/Types.hs
@@ -47,7 +47,6 @@
   , stHighlighting  :: Bool          -- ^ true if document has highlighted code
   , stIncremental   :: Bool          -- ^ true if beamer lists should be
   , stZwnj          :: Bool          -- ^ true if document has a ZWNJ character
-  , stInternalLinks :: [Text]        -- ^ list of internal link targets
   , stBeamer        :: Bool          -- ^ produce beamer
   , stEmptyLine     :: Bool          -- ^ true if no content on line
   , stHasCslRefs    :: Bool          -- ^ has a Div with class refs
@@ -96,7 +95,6 @@
   , stHighlighting = False
   , stIncremental = writerIncremental options
   , stZwnj = False
-  , stInternalLinks = []
   , stBeamer = False
   , stEmptyLine = True
   , stHasCslRefs = False
diff --git a/src/Text/Pandoc/Writers/LaTeX/Util.hs b/src/Text/Pandoc/Writers/LaTeX/Util.hs
--- a/src/Text/Pandoc/Writers/LaTeX/Util.hs
+++ b/src/Text/Pandoc/Writers/LaTeX/Util.hs
@@ -39,7 +39,7 @@
 import qualified Data.Text as T
 import Text.Pandoc.Extensions (Extension(Ext_smart))
 import Data.Char (isLetter, isSpace, isDigit, isAscii, ord, isAlphaNum)
-import Text.Printf (printf)
+import Numeric (showHex)
 import Text.Pandoc.Shared (safeRead)
 import qualified Data.Text.Normalize as Normalize
 import Data.List (uncons)
@@ -199,7 +199,7 @@
    go = T.concatMap $ \x -> case x of
      _ | (isLetter x || isDigit x) && isAscii x -> T.singleton x
        | T.any (== x) "_-+=:;." -> T.singleton x
-       | otherwise -> T.pack $ "ux" <> printf "%x" (ord x)
+       | otherwise -> T.pack $ "ux" <> showHex (ord x) ""
 
 -- | Puts contents into LaTeX command.
 inCmd :: Text -> Doc Text -> Doc Text
diff --git a/src/Text/Pandoc/Writers/Markdown.hs b/src/Text/Pandoc/Writers/Markdown.hs
--- a/src/Text/Pandoc/Writers/Markdown.hs
+++ b/src/Text/Pandoc/Writers/Markdown.hs
@@ -754,8 +754,8 @@
                             (i,c,kv)
                               | not (null alt)
                               , Nothing <- lookup "alt" kv
-                              , stringify descr /= stringify alt ->
-                                 (i, c, ("alt", stringify alt) : kv)
+                              , stringifyInlines descr /= stringifyInlines alt ->
+                                 (i, c, ("alt", stringifyInlines alt) : kv)
                             _ -> imgAttr'
           contents <- inlineListToMarkdown opts [Image imgAttr'' descr tgt']
           return $ contents <> blankline
diff --git a/src/Text/Pandoc/Writers/Markdown/Inline.hs b/src/Text/Pandoc/Writers/Markdown/Inline.hs
--- a/src/Text/Pandoc/Writers/Markdown/Inline.hs
+++ b/src/Text/Pandoc/Writers/Markdown/Inline.hs
@@ -678,13 +678,13 @@
         where result = "[" <> linktext <> "](" <> (literal src) <> ")"
               attributes = addKeyValueToAttr attr ("title", tit)
     -- Use wikilinks where possible
-    _ | src == stringify txt && useWikilink ->
-        return $ "[[" <> literal (stringify txt) <> "]]"
+    _ | src == stringifyInlines txt && useWikilink ->
+        return $ "[[" <> literal (stringifyInlines txt) <> "]]"
       | useAuto -> return $ "<" <> literal srcSuffix <> ">"
       | useWikilink && isEnabled Ext_wikilinks_title_after_pipe opts -> return $
-        "[[" <> literal src <> "|" <> literal (stringify txt) <> "]]"
+        "[[" <> literal src <> "|" <> literal (stringifyInlines txt) <> "]]"
       | useWikilink && isEnabled Ext_wikilinks_title_before_pipe opts -> return $
-        "[[" <> literal (stringify txt) <> "|" <> literal src <> "]]"
+        "[[" <> literal (stringifyInlines txt) <> "|" <> literal src <> "]]"
       | useRefLinks ->
            let first  = "[" <> linktext <> "]"
                second = if getKey linktext == getKey reftext
diff --git a/src/Text/Pandoc/Writers/MediaWiki.hs b/src/Text/Pandoc/Writers/MediaWiki.hs
--- a/src/Text/Pandoc/Writers/MediaWiki.hs
+++ b/src/Text/Pandoc/Writers/MediaWiki.hs
@@ -147,7 +147,7 @@
 blockToMediaWiki HorizontalRule = return $ blankline <> literal "-----" <> blankline
 
 blockToMediaWiki (Header level (ident,_,_) inlines) = do
-  let autoId = T.replace " " "_" $ stringify inlines
+  let autoId = T.replace " " "_" $ stringifyInlines inlines
   contents <- inlineListToMediaWiki inlines
   let eqs = literal $ T.replicate level "="
   return $
diff --git a/src/Text/Pandoc/Writers/Ms.hs b/src/Text/Pandoc/Writers/Ms.hs
--- a/src/Text/Pandoc/Writers/Ms.hs
+++ b/src/Text/Pandoc/Writers/Ms.hs
@@ -72,8 +72,8 @@
               meta
   main <- blockListToMs opts blocks
   hasInlineMath <- gets stHasInlineMath
-  let titleMeta = (escapeStr opts . stringify) $ docTitle meta
-  let authorsMeta = map (escapeStr opts . stringify) $ docAuthors meta
+  let titleMeta = (escapeStr opts . stringifyInlines) $ docTitle meta
+  let authorsMeta = map (escapeStr opts . stringifyInlines) $ docAuthors meta
   hasHighlighting <- gets stHighlighting
   let highlightingMacros = if hasHighlighting
                               then case writerHighlightMethod opts of
@@ -515,7 +515,7 @@
       Nothing -> contents
 inlineToMs opts (Image attr alternate (src, _)) = do
   let desc = literal "[IMAGE: " <>
-             literal (escapeStr opts (stringify alternate)) <> char ']'
+             literal (escapeStr opts (stringifyInlines alternate)) <> char ']'
   let sizeAttrs = getSizeAttrs opts attr
   let ext = takeExtension (T.unpack src)
   let cmd = case ext of
diff --git a/src/Text/Pandoc/Writers/Native.hs b/src/Text/Pandoc/Writers/Native.hs
--- a/src/Text/Pandoc/Writers/Native.hs
+++ b/src/Text/Pandoc/Writers/Native.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE OverloadedStrings #-}
 {- |
    Module      : Text.Pandoc.Writers.Native
    Copyright   : Copyright (C) 2006-2024 John MacFarlane
@@ -8,23 +9,297 @@
    Portability : portable
 
 Conversion of a 'Pandoc' document to a string representation.
+
+This used to be implemented using pretty-show's 'ppDoc' (which shows
+the document, tokenizes and parses the result, and lays it out with
+Text.PrettyPrint.HughesPJ).  For performance, we now build the layout
+directly from the AST, carefully reproducing the exact output of the
+old implementation (with @ribbonsPerLine = 1.2@).
 -}
 module Text.Pandoc.Writers.Native ( writeNative )
 where
+import Data.List (intersperse)
+import qualified Data.Map as M
 import Data.Text (Text)
 import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Builder as B
 import Text.Pandoc.Class.PandocMonad (PandocMonad)
 import Text.Pandoc.Definition
 import Text.Pandoc.Options (WriterOptions (..))
-import Text.Show.Pretty (ppDoc)
-import Text.PrettyPrint (renderStyle, Style(..), style, char)
 
 -- | Prettyprint Pandoc document.
 writeNative :: PandocMonad m => WriterOptions -> Pandoc -> m Text
-writeNative opts (Pandoc meta blocks) = do
-  let style' = style{ lineLength = writerColumns opts,
-                      ribbonsPerLine = 1.2 }
-  return $ T.pack $ renderStyle style' $
-    case writerTemplate opts of
-      Just _  -> ppDoc (Pandoc meta blocks) <> char '\n'
-      Nothing -> ppDoc blocks
+writeNative opts doc@(Pandoc _ blocks) = do
+  let cols = writerColumns opts
+  -- HughesPJ computes the ribbon length this way (with Float division):
+  let ribbon = round (fromIntegral cols / (1.2 :: Float))
+  return $ case writerTemplate opts of
+    -- The old code appended a (char '\n'), which participates in layout
+    -- as one extra glued character on the last line; hence glue0 = 1.
+    Just _  -> render cols ribbon 1 (vDoc (pandocV doc)) <> "\n"
+    Nothing -> render cols ribbon 0 (vDoc (blocksV blocks))
+
+--
+-- Layout documents (mirroring the structures pretty-show builds)
+--
+
+-- | A document together with the width of its one-line rendering.
+data Doc = Doc !Int DC
+
+-- | Either literal text, or a group that is rendered like HughesPJ's
+-- 'sep': all on one line (elements joined by single spaces) if it
+-- fits, otherwise vertically, with each element after the first on
+-- its own line, indented by its nesting relative to the column at
+-- which the group starts.
+data DC = DText !Text
+        | DGroup [Elt]
+
+-- | Group element: nesting, glued prefix text, document, glued suffix.
+data Elt = Elt !Int !Text Doc !Text
+
+width :: Doc -> Int
+width (Doc w _) = w
+
+dtext :: Text -> Doc
+dtext t = Doc (T.length t) (DText t)
+
+group :: [Elt] -> Doc
+group es = Doc (foldl (\acc e -> acc + 1 + eltWidth e) (-1) es) (DGroup es)
+  where eltWidth (Elt _ pre d post) = T.length pre + width d + T.length post
+
+-- | A value, i.e. a document plus an indication of whether it needs
+-- parentheses when used as a constructor argument.
+data V = V !Bool Doc
+
+vDoc :: V -> Doc
+vDoc (V _ d) = d
+
+-- | Constructor applied to arguments: @hang (text c) 2 (sep args)@,
+-- where non-atomic arguments are parenthesized.
+con :: Text -> [V] -> V
+con c [] = V True (dtext c)
+con c vs = V False $ group
+  [ Elt 0 "" (dtext c) ""
+  , Elt 2 "" (group (map atomElt vs)) "" ]
+  where
+    atomElt (V True d)  = Elt 0 "" d ""
+    atomElt (V False d) = Elt 0 "(" d ")"
+
+-- | Bracketed, comma-separated block: @sep [open <+> x1, ...commas..., close]@.
+block :: Text -> Text -> Text -> [Doc] -> Doc
+block open comma close ds =
+  group $ zipWith (\pre d -> Elt 0 pre d "") (open : repeat comma) ds
+          ++ [Elt 0 "" (dtext close) ""]
+
+listV :: (a -> V) -> [a] -> V
+listV _ [] = V True (dtext "[]")
+listV f xs = V True $ block "[ " ", " "]" (map (vDoc . f) xs)
+
+tupleV :: [V] -> V
+tupleV vs = V True $ block "( " ", " ")" (map vDoc vs)
+
+-- | Record: @hang (text c) 2 (block '{' '}' fields)@ where each field
+-- is @hang (text name <+> char '=') 2 value@.  Records count as atoms.
+recV :: Text -> [(Text, V)] -> V
+recV c fields = V True $ group
+  [ Elt 0 "" (dtext c) ""
+  , Elt 2 "" (block "{ " ", " "}" (map fieldDoc fields)) "" ]
+  where
+    fieldDoc (name, v) = group
+      [ Elt 0 "" (dtext (name <> " =")) ""
+      , Elt 2 "" (vDoc v) "" ]
+
+-- | Leaf rendered via 'show' (Text, Int, Double).  Values whose
+-- representation starts with @-@ get parentheses in argument position.
+showV :: Show a => a -> V
+showV x = V (not ("-" `T.isPrefixOf` t)) (dtext t)
+  where t = T.pack (show x)
+
+-- | Nullary constructors of enumeration types (and Bool).
+enumV :: Show a => a -> V
+enumV = V True . dtext . T.pack . show
+
+mapV :: (a -> V) -> M.Map Text a -> V
+mapV f m = con "fromList"
+  [listV (\(k, v) -> tupleV [showV k, f v]) (M.toAscList m)]
+
+--
+-- Conversion of the Pandoc AST
+--
+
+pandocV :: Pandoc -> V
+pandocV (Pandoc meta blocks) = con "Pandoc" [metaV meta, blocksV blocks]
+
+metaV :: Meta -> V
+metaV (Meta m) = recV "Meta" [("unMeta", mapV metaValueV m)]
+
+metaValueV :: MetaValue -> V
+metaValueV (MetaMap m)       = con "MetaMap" [mapV metaValueV m]
+metaValueV (MetaList xs)     = con "MetaList" [listV metaValueV xs]
+metaValueV (MetaBool b)      = con "MetaBool" [enumV b]
+metaValueV (MetaString t)    = con "MetaString" [showV t]
+metaValueV (MetaInlines ils) = con "MetaInlines" [inlinesV ils]
+metaValueV (MetaBlocks bs)   = con "MetaBlocks" [blocksV bs]
+
+blocksV :: [Block] -> V
+blocksV = listV blockV
+
+inlinesV :: [Inline] -> V
+inlinesV = listV inlineV
+
+attrV :: Attr -> V
+attrV (ident, classes, kvs) =
+  tupleV [ showV ident
+         , listV showV classes
+         , listV (\(k, v) -> tupleV [showV k, showV v]) kvs ]
+
+formatV :: Format -> V
+formatV (Format f) = con "Format" [showV f]
+
+blockV :: Block -> V
+blockV blk =
+  case blk of
+    Plain ils -> con "Plain" [inlinesV ils]
+    Para ils -> con "Para" [inlinesV ils]
+    LineBlock ilss -> con "LineBlock" [listV inlinesV ilss]
+    CodeBlock attr t -> con "CodeBlock" [attrV attr, showV t]
+    RawBlock f t -> con "RawBlock" [formatV f, showV t]
+    BlockQuote bs -> con "BlockQuote" [blocksV bs]
+    OrderedList (start, sty, delim) bss ->
+      con "OrderedList" [ tupleV [showV start, enumV sty, enumV delim]
+                        , listV blocksV bss ]
+    BulletList bss -> con "BulletList" [listV blocksV bss]
+    DefinitionList defs ->
+      con "DefinitionList"
+        [listV (\(ils, bss) -> tupleV [inlinesV ils, listV blocksV bss]) defs]
+    Header lev attr ils -> con "Header" [showV lev, attrV attr, inlinesV ils]
+    HorizontalRule -> con "HorizontalRule" []
+    Table attr cap colspecs thead tbodies tfoot ->
+      con "Table" [ attrV attr
+                  , captionV cap
+                  , listV colSpecV colspecs
+                  , tableHeadV thead
+                  , listV tableBodyV tbodies
+                  , tableFootV tfoot ]
+    Figure attr cap bs -> con "Figure" [attrV attr, captionV cap, blocksV bs]
+    Div attr bs -> con "Div" [attrV attr, blocksV bs]
+
+captionV :: Caption -> V
+captionV (Caption mshort bs) =
+  con "Caption" [maybeV inlinesV mshort, blocksV bs]
+
+maybeV :: (a -> V) -> Maybe a -> V
+maybeV _ Nothing  = con "Nothing" []
+maybeV f (Just x) = con "Just" [f x]
+
+colSpecV :: ColSpec -> V
+colSpecV (align, cw) = tupleV [enumV align, colWidthV cw]
+
+colWidthV :: ColWidth -> V
+colWidthV (ColWidth d)    = con "ColWidth" [showV d]
+colWidthV ColWidthDefault = con "ColWidthDefault" []
+
+tableHeadV :: TableHead -> V
+tableHeadV (TableHead attr rows) =
+  con "TableHead" [attrV attr, listV rowV rows]
+
+tableBodyV :: TableBody -> V
+tableBodyV (TableBody attr (RowHeadColumns rhc) hd bd) =
+  con "TableBody" [ attrV attr
+                  , con "RowHeadColumns" [showV rhc]
+                  , listV rowV hd
+                  , listV rowV bd ]
+
+tableFootV :: TableFoot -> V
+tableFootV (TableFoot attr rows) =
+  con "TableFoot" [attrV attr, listV rowV rows]
+
+rowV :: Row -> V
+rowV (Row attr cells) = con "Row" [attrV attr, listV cellV cells]
+
+cellV :: Cell -> V
+cellV (Cell attr align (RowSpan rs) (ColSpan cs) bs) =
+  con "Cell" [ attrV attr
+             , enumV align
+             , con "RowSpan" [showV rs]
+             , con "ColSpan" [showV cs]
+             , blocksV bs ]
+
+inlineV :: Inline -> V
+inlineV inln =
+  case inln of
+    Str t -> con "Str" [showV t]
+    Emph ils -> con "Emph" [inlinesV ils]
+    Underline ils -> con "Underline" [inlinesV ils]
+    Strong ils -> con "Strong" [inlinesV ils]
+    Strikeout ils -> con "Strikeout" [inlinesV ils]
+    Superscript ils -> con "Superscript" [inlinesV ils]
+    Subscript ils -> con "Subscript" [inlinesV ils]
+    SmallCaps ils -> con "SmallCaps" [inlinesV ils]
+    Quoted qt ils -> con "Quoted" [enumV qt, inlinesV ils]
+    Cite cits ils -> con "Cite" [listV citationV cits, inlinesV ils]
+    Code attr t -> con "Code" [attrV attr, showV t]
+    Space -> con "Space" []
+    SoftBreak -> con "SoftBreak" []
+    LineBreak -> con "LineBreak" []
+    Math mt t -> con "Math" [enumV mt, showV t]
+    RawInline f t -> con "RawInline" [formatV f, showV t]
+    Link attr ils (url, title) ->
+      con "Link" [attrV attr, inlinesV ils, tupleV [showV url, showV title]]
+    Image attr ils (url, title) ->
+      con "Image" [attrV attr, inlinesV ils, tupleV [showV url, showV title]]
+    Note bs -> con "Note" [blocksV bs]
+    Span attr ils -> con "Span" [attrV attr, inlinesV ils]
+
+citationV :: Citation -> V
+citationV cit = recV "Citation"
+  [ ("citationId", showV (citationId cit))
+  , ("citationPrefix", inlinesV (citationPrefix cit))
+  , ("citationSuffix", inlinesV (citationSuffix cit))
+  , ("citationMode", enumV (citationMode cit))
+  , ("citationNoteNum", showV (citationNoteNum cit))
+  , ("citationHash", showV (citationHash cit)) ]
+
+--
+-- Rendering
+--
+
+-- | Render a document, reproducing HughesPJ's layout exactly.  A
+-- group is rendered on one line if its width, plus any text glued
+-- after it up to the next line break ('glue'), stays within both the
+-- line length (measured from the start of the line) and the ribbon
+-- length (measured from the end of the indentation).  Otherwise it is
+-- rendered vertically, each element after the first starting on a new
+-- line, indented by the group's start column plus the element's
+-- nesting; each element then makes its own layout decisions.
+render :: Int -> Int -> Int -> Doc -> Text
+render lineLen ribbonLen glue0 d0 =
+  TL.toStrict $ B.toLazyText $ go 0 0 glue0 d0
+  where
+    go l c g d@(Doc w dc)
+      | c + w + g <= lineLen && (c - l) + w + g <= ribbonLen = flat d
+      | otherwise =
+          case dc of
+            DText t   -> B.fromText t
+            DGroup es -> vertical l c g es
+
+    vertical l0 c0 g = goElts True
+      where
+        goElts _ [] = mempty
+        goElts isFirst (Elt n pre d post : rest) =
+          let ind  = c0 + n
+              (l, c) = if isFirst then (l0, c0) else (ind, ind)
+              g' = T.length post + (if null rest then g else 0)
+              lead = if isFirst
+                        then mempty
+                        else B.singleton '\n' <> B.fromText (T.replicate ind " ")
+          in  lead <> B.fromText pre <> go l (c + T.length pre) g' d
+                   <> B.fromText post <> goElts False rest
+
+    flat (Doc _ (DText t))   = B.fromText t
+    flat (Doc _ (DGroup es)) =
+      mconcat $ intersperse (B.singleton ' ') $ map flatElt es
+
+    flatElt (Elt _ pre d post) =
+      B.fromText pre <> flat d <> B.fromText post
diff --git a/src/Text/Pandoc/Writers/ODT.hs b/src/Text/Pandoc/Writers/ODT.hs
--- a/src/Text/Pandoc/Writers/ODT.hs
+++ b/src/Text/Pandoc/Writers/ODT.hs
@@ -38,7 +38,7 @@
                             HighlightMethod(Skylighting, DefaultHighlighting))
 import Text.Pandoc.Highlighting (defaultStyle)
 import Text.DocLayout
-import Text.Pandoc.Shared (stringify, tshow)
+import Text.Pandoc.Shared (stringify, stringifyInlines, tshow)
 import Text.Pandoc.Version (pandocVersionText)
 import Text.Pandoc.Writers.Shared (lookupMetaString, lookupMetaBlocks,
                                    fixDisplayMath, getLang,
@@ -165,7 +165,7 @@
            ,("office:version","1.3")] ( inTags True "office:meta" []
                  ( metaTag "meta:generator" ("Pandoc/" <> pandocVersionText)
                    $$
-                   metaTag "dc:title" (stringify title)
+                   metaTag "dc:title" (stringifyInlines title)
                    $$
                    metaTag "dc:description"
                           (T.intercalate "\n" (map stringify $
@@ -184,7 +184,7 @@
                          $$ metaTag "meta:creation-date" d
                          $$ metaTag "dc:date" d
                    ) (T.pack $ formatTime defaultTimeLocale "%FT%XZ" utctime)
-                     (T.intercalate "; " (map stringify authors))
+                     (T.intercalate "; " (map stringifyInlines authors))
                    $$
                    vcat userDefinedMeta
                  )
diff --git a/src/Text/Pandoc/Writers/OOXML.hs b/src/Text/Pandoc/Writers/OOXML.hs
--- a/src/Text/Pandoc/Writers/OOXML.hs
+++ b/src/Text/Pandoc/Writers/OOXML.hs
@@ -89,8 +89,8 @@
 
 type NameSpaces = [(Text, Text)]
 
--- | Scales the image to fit the page
--- sizes are passed in emu
+-- | Scales the image to fit the page if it would overflow.
+-- Sizes are passed in emu.
 fitToPage :: (Double, Double) -> Integer -> (Integer, Integer)
 fitToPage (x, y) pageWidth
   -- Fixes width to the page width and scales the height
diff --git a/src/Text/Pandoc/Writers/OPML.hs b/src/Text/Pandoc/Writers/OPML.hs
--- a/src/Text/Pandoc/Writers/OPML.hs
+++ b/src/Text/Pandoc/Writers/OPML.hs
@@ -65,7 +65,8 @@
 
 convertDate :: [Inline] -> Text
 convertDate ils = maybe "" showDateTimeRFC822 $
-  parseTimeM True defaultTimeLocale "%F" . T.unpack =<< normalizeDate (stringify ils)
+  parseTimeM True defaultTimeLocale "%F" . T.unpack =<<
+    normalizeDate (stringifyInlines ils)
 
 -- | Convert a Block to OPML.
 blockToOPML :: PandocMonad m => WriterOptions -> Block -> m (Doc Text)
diff --git a/src/Text/Pandoc/Writers/Org.hs b/src/Text/Pandoc/Writers/Org.hs
--- a/src/Text/Pandoc/Writers/Org.hs
+++ b/src/Text/Pandoc/Writers/Org.hs
@@ -40,6 +40,7 @@
 
 data WriterState =
   WriterState { stNotes   :: [[Block]]
+              , stNoteNum :: Int
               , stHasMath :: Bool
               , stOptions :: WriterOptions
               }
@@ -50,6 +51,7 @@
 writeOrg :: PandocMonad m => WriterOptions -> Pandoc -> m Text
 writeOrg opts document = do
   let st = WriterState { stNotes = [],
+                         stNoteNum = 0,
                          stHasMath = False,
                          stOptions = opts }
   evalStateT (pandocToOrg document) st
@@ -66,7 +68,7 @@
                (fmap chomp . inlineListToOrg)
                meta
   body <- blockListToOrg blocks
-  notes <- gets (reverse . stNotes) >>= notesToOrg
+  notes <- notesToOrg
   hasMath <- gets stHasMath
   let main = body $+$ notes
   let context = defField "body" main
@@ -88,10 +90,19 @@
        Nothing  -> main
        Just tpl -> renderTemplate tpl context
 
--- | Return Org representation of notes.
-notesToOrg :: PandocMonad m => [[Block]] -> Org m (Doc Text)
-notesToOrg notes =
-  vsep <$> zipWithM noteToOrg [1..] notes
+-- | Return Org representation of the collected notes. Rendering a
+-- note may add further notes to the state (notes nested inside
+-- notes); keep going until all of them have been rendered.
+notesToOrg :: PandocMonad m => Org m (Doc Text)
+notesToOrg = vsep <$> go 0
+  where
+    go done = do
+      notes <- gets (drop done . reverse . stNotes)
+      if null notes
+        then return []
+        else do
+          docs <- zipWithM noteToOrg [done + 1 ..] notes
+          (docs ++) <$> go (done + length notes)
 
 -- | Return Org representation of a note.
 noteToOrg :: PandocMonad m => Int -> [Block] -> Org m (Doc Text)
@@ -114,14 +125,19 @@
 
 -- | Escape special characters for Org.
 escapeString :: Text -> Doc Text
-escapeString t
-  | T.all isAlphaNum t = literal t
-  | otherwise = mconcat $ map escChar (T.unpack t)
+escapeString t =
+  case T.break isSpecial t of
+    (_, "") -> literal t
+    (pre, post) ->
+      case T.uncons post of
+        -- escape special chars with ZERO WIDTH SPACE as org manual
+        -- suggests
+        Just (c, rest) -> (if T.null pre then mempty else literal pre)
+                          <> afterBreak "\x200B" <> char c
+                          <> escapeString rest
+        Nothing -> literal pre  -- not reachable
   where
-    -- escape special chars with ZERO WIDTH SPACE as org manual suggests
-   escChar c = if c == '*' || c == '#' || c == '|'
-     then afterBreak "\x200B" <> char c
-     else char c
+    isSpecial c = c == '*' || c == '#' || c == '|'
 
 isRawFormat :: Format -> Bool
 isRawFormat f =
@@ -161,8 +177,8 @@
   return $ blankline $$ "#+begin_verse" $$
            nest 2 contents $$ "#+end_verse" <> blankline
 blockToOrg (RawBlock "html" str) =
-  return $ blankline $$ "#+begin_html" $$
-           nest 2 (literal str) $$ "#+end_html" $$ blankline
+  return $ blankline $$ "#+begin_export html" $$
+           nest 2 (literal str) $$ "#+end_export" $$ blankline
 blockToOrg b@(RawBlock f str)
   | isRawFormat f = return $ literal str
   | otherwise     = do
@@ -213,13 +229,18 @@
   let (beg, end) = case lang of
         Nothing -> ("#+begin_example" <> numberlines, "#+end_example")
         Just x  -> ("#+begin_src " <> x <> numberlines <> args, "#+end_src")
-  -- escape special lines
+  -- Escape special lines by prepending a comma. Like Emacs'
+  -- org-escape-code-in-region, escape all lines consisting of
+  -- indentation, then any number of commas, then "*" or "#+"; this
+  -- keeps lines that already start with commas intact when the block
+  -- is unescaped again.
+  let needsEscape t = let t' = T.dropWhile (== ',') t
+                      in T.isPrefixOf "#+" t' || T.isPrefixOf "*" t'
   let escape_line line =
         let (spaces, code) = T.span (\c -> c == ' ' || c == '\t') line
-        in spaces <>
-           (if T.isPrefixOf "#+" code || T.isPrefixOf "*" code
-            then T.cons ',' code
-            else code)
+        in if needsEscape code
+           then spaces <> T.cons ',' code
+           else line
   let escaped = T.unlines . map escape_line . T.lines $ str
   return $ name $$ literal beg $$ literal escaped $$ literal end $$ blankline
 blockToOrg (BlockQuote blocks) = do
@@ -246,8 +267,7 @@
               middle = hcat $ intersperse sep' blocks
   let makeRow = hpipeBlocks . zipWith lblock widthsInChars
   let head' = makeRow headers'
-  rows' <- mapM (\row -> do cols <- mapM blockListToOrg row
-                            return $ makeRow cols) rows
+  let rows' = map makeRow rawRows
   let border ch = char '|' <> char ch <>
                   (hcat . intersperse (char ch <> char '+' <> char ch) $
                           map (\l -> text $ replicate l ch) widthsInChars) <>
@@ -476,13 +496,14 @@
         shouldFix Note{} = True    -- Prevent footnotes
         shouldFix (Str "-") = True -- Prevent bullet list items
         shouldFix (Str x)          -- Prevent ordered list items
-          | Just (cs, c) <- T.unsnoc x = T.all isDigit cs &&
+          | Just (cs, c) <- T.unsnoc x = not (T.null cs) &&
+                                         T.all isDigit cs &&
                                          (c == '.' || c == ')')
         shouldFix _ = False
 
 -- | Convert Pandoc inline element to Org.
 inlineToOrg :: PandocMonad m => Inline -> Org m (Doc Text)
-inlineToOrg (Span (uid, [], []) []) =
+inlineToOrg (Span (uid, [], []) []) | not (T.null uid) =
   return $ "<<" <> literal uid <> ">>"
 inlineToOrg (Span _ lst) =
   inlineListToOrg lst
@@ -548,7 +569,13 @@
                    _ -> mempty
        return $ "[cite" <> sty <> ":" <> citeItems <> "]"
      else inlineListToOrg lst
-inlineToOrg (Code _ str) = return $ "=" <> literal str <> "="
+inlineToOrg (Code _ str) = return $
+  -- Org offers no escape mechanism inside verbatim text; if the
+  -- content contains the delimiter, fall back to the other verbatim
+  -- delimiter.
+  if "=" `T.isInfixOf` str && not ("~" `T.isInfixOf` str)
+     then "~" <> literal str <> "~"
+     else "=" <> literal str <> "="
 inlineToOrg (Str str) = do
   opts <- gets stOptions
   let str' = if isEnabled Ext_smart opts || isEnabled Ext_special_strings opts
@@ -578,17 +605,49 @@
 inlineToOrg (Link _ txt (src, _)) =
   case txt of
         [Str x] | escapeURI x == src ->  -- autolink
-             return $ "[[" <> literal (orgPath x) <> "]]"
-        _ -> do contents <- nowrap <$> inlineListToOrg txt
-                return $ "[[" <> literal (orgPath src) <> "][" <> contents <> "]]"
+             return $ "[[" <> literal (escapeLinkTarget (orgPath x)) <> "]]"
+        _ -> do descr <- render Nothing . nowrap <$> inlineListToOrg txt
+                return $ "[[" <> literal (escapeLinkTarget (orgPath src)) <>
+                         "][" <> literal (escapeLinkDescription descr) <> "]]"
 inlineToOrg (Image _ _ (source, _)) =
-  return $ "[[" <> literal (orgPath source) <> "]]"
+  return $ "[[" <> literal (escapeLinkTarget (orgPath source)) <> "]]"
 inlineToOrg (Note contents) = do
   -- add to notes in state
-  notes <- gets stNotes
-  modify $ \st -> st { stNotes = contents:notes }
-  let ref = tshow $ length notes + 1
+  modify $ \st -> st { stNotes = contents : stNotes st
+                     , stNoteNum = stNoteNum st + 1 }
+  ref <- gets (tshow . stNoteNum)
   return $ "[fn:" <> literal ref <> "]"
+
+-- | Escape a link target like Emacs' @org-link-escape@:
+-- backslash-escape square brackets, and double any run of backslashes
+-- occurring directly before a bracket or at the end of the target.
+escapeLinkTarget :: Text -> Text
+escapeLinkTarget t =
+  let (pre, rest) = T.break (\c -> c == '\\' || c == '[' || c == ']') t
+  in pre <> case T.uncons rest of
+       Nothing -> ""
+       Just ('\\', _) ->
+         let (bs, rest') = T.span (== '\\') rest
+         in case T.uncons rest' of
+              Nothing -> bs <> bs
+              Just (c, rest'')
+                | c == '[' || c == ']'
+                  -> bs <> bs <> "\\" <> T.cons c (escapeLinkTarget rest'')
+                | otherwise -> bs <> T.cons c (escapeLinkTarget rest'')
+       Just (c, rest') -> "\\" <> T.cons c (escapeLinkTarget rest')
+
+-- | Make a link description safe: it must not contain @]]@ or end
+-- with @]@. Like Emacs' @org-link-make-string@, insert a zero-width
+-- space to break up the offending brackets.
+escapeLinkDescription :: Text -> Text
+escapeLinkDescription = fixEnd . fixDouble
+  where
+    fixDouble t | "]]" `T.isInfixOf` t
+                            = fixDouble $ T.replace "]]" "]\x200B]" t
+                | otherwise = t
+    fixEnd t = case T.unsnoc t of
+                 Just (t', ']') -> t' <> "\x200B]"
+                 _              -> t
 
 orgPath :: Text -> Text
 orgPath src = case T.uncons src of
diff --git a/src/Text/Pandoc/Writers/Powerpoint/Output.hs b/src/Text/Pandoc/Writers/Powerpoint/Output.hs
--- a/src/Text/Pandoc/Writers/Powerpoint/Output.hs
+++ b/src/Text/Pandoc/Writers/Powerpoint/Output.hs
@@ -28,6 +28,7 @@
 import Control.Monad.State
     ( StateT, gets, modify, evalStateT )
 import Codec.Archive.Zip
+import Data.Containers.ListUtils (nubOrdOn)
 import Data.List (intercalate, stripPrefix, nub, union, isPrefixOf, intersperse)
 import Data.Bifunctor (bimap)
 import Data.CaseInsensitive (CI)
@@ -57,6 +58,7 @@
 import Text.Pandoc.Writers.Shared (metaToContext)
 import Text.Pandoc.Writers.OOXML
 import qualified Data.Map as M
+import qualified Data.Set as Set
 import Data.Maybe (mapMaybe, listToMaybe, fromMaybe, maybeToList, catMaybes, isJust)
 import Text.Pandoc.ImageSize
 import Control.Applicative ((<|>))
@@ -67,7 +69,7 @@
 import Text.Pandoc.Logging (LogMessage(PowerpointTemplateWarning))
 import Text.Pandoc.Writers.Math (convertMath)
 import Text.Pandoc.Writers.Powerpoint.Presentation
-import Text.Pandoc.Shared (tshow, stringify)
+import Text.Pandoc.Shared (tshow, stringify, stringifyInlines)
 import Skylighting (fromColor)
 
 -- |The 'EMU' type is used to specify sizes in English Metric Units.
@@ -131,6 +133,9 @@
                            , envInSpeakerNotes :: Bool
                            , envSlideLayouts :: Maybe SlideLayouts
                            , envOtherStyleIndents :: Maybe Indents
+                           -- The parsed slide master, cached to avoid
+                           -- re-parsing it for every slide.
+                           , envMaster :: Maybe Element
                            }
                  deriving (Show)
 
@@ -151,6 +156,7 @@
                   , envInSpeakerNotes = False
                   , envSlideLayouts = Nothing
                   , envOtherStyleIndents = Nothing
+                  , envMaster = Nothing
                   }
 
 type SlideLayouts = SlideLayoutsOf SlideLayout
@@ -391,16 +397,28 @@
   mediaEntries <- makeMediaEntries
   contentTypesEntry <- presentationToContentTypes p >>= contentTypesToEntry
   -- fold everything into our inherited archive and return it.
-  return $ foldr addEntryToArchive newArch' $
-    slideEntries <>
-    slideRelEntries <>
-    spkNotesEntries <>
-    spkNotesRelEntries <>
-    mediaEntries <>
-    [updatedMasterEntry, updatedMasterRelEntry]  <>
-    [contentTypesEntry, docPropsEntry, docCustomPropsEntry, relsEntry,
-     presEntry, presRelsEntry, viewPropsEntry]
+  return $ addEntriesToArchive
+    (slideEntries <>
+     slideRelEntries <>
+     spkNotesEntries <>
+     spkNotesRelEntries <>
+     mediaEntries <>
+     [updatedMasterEntry, updatedMasterRelEntry]  <>
+     [contentTypesEntry, docPropsEntry, docCustomPropsEntry, relsEntry,
+      presEntry, presRelsEntry, viewPropsEntry])
+    newArch'
 
+-- | Add entries to an archive in a single pass.  Equivalent to (but
+-- faster than) folding 'addEntryToArchive' over the list: for
+-- duplicate paths the first entry in the list wins, and the new
+-- entries precede (and replace) existing entries with the same paths.
+addEntriesToArchive :: [Entry] -> Archive -> Archive
+addEntriesToArchive entries archive =
+  archive{ zEntries = nubOrdOn eRelativePath entries <>
+                      filter (\e -> eRelativePath e `Set.notMember` newPaths)
+                             (zEntries archive) }
+  where newPaths = Set.fromList $ map eRelativePath entries
+
 updateMasterElems :: SlideLayouts -> Element -> Element -> (Element, Element)
 updateMasterElems layouts master masterRels = (updatedMaster, updatedMasterRels)
   where
@@ -661,7 +679,7 @@
   context <- metaToContext opts{ writerTemplate =
                                   writerTemplate opts <|> Just mempty }
                 (return . literal . stringify)
-                (return . literal . stringify) meta
+                (return . literal . stringifyInlines) meta
 
   let env = def { envRefArchive = refArchive
                 , envDistArchive = distArchive
@@ -673,6 +691,7 @@
                 , envSpeakerNotesIdMap = makeSpeakerNotesMap pres
                 , envSlideLayouts = Just layouts
                 , envOtherStyleIndents = otherStyleIndents
+                , envMaster = Just master
                 }
 
   let st = def { stMediaGlobalIds = initialGlobalIds refArchive distArchive
@@ -983,9 +1002,13 @@
 
 getMaster :: PandocMonad m => P m Element
 getMaster = do
-  refArchive <- asks envRefArchive
-  distArchive <- asks envDistArchive
-  getMaster' refArchive distArchive
+  mbMaster <- asks envMaster
+  case mbMaster of
+    Just master -> pure master
+    Nothing -> do
+      refArchive <- asks envRefArchive
+      distArchive <- asks envDistArchive
+      getMaster' refArchive distArchive
 
 getMaster' :: PandocMonad m => Archive -> Archive -> m Element
 getMaster' refArchive distArchive =
diff --git a/src/Text/Pandoc/Writers/Powerpoint/Presentation.hs b/src/Text/Pandoc/Writers/Powerpoint/Presentation.hs
--- a/src/Text/Pandoc/Writers/Powerpoint/Presentation.hs
+++ b/src/Text/Pandoc/Writers/Powerpoint/Presentation.hs
@@ -63,6 +63,7 @@
 import qualified Data.Map as M
 import qualified Data.Set as S
 import Data.Maybe (maybeToList, fromMaybe, listToMaybe, isNothing)
+import Data.Monoid (Any(..))
 import Text.Pandoc.Highlighting
 import qualified Data.Text as T
 import Control.Applicative ((<|>))
@@ -845,11 +846,14 @@
   return $ filter (not . isNotesDiv) blks
 
 handleAndFilterSpeakerNotes :: [Block] -> Pres ([Block], SpeakerNotes)
-handleAndFilterSpeakerNotes blks = do
-  modify $ \st -> st{stSpeakerNotes = mempty}
-  blks' <- walkM handleAndFilterSpeakerNotes' blks
-  spkNotes <- gets stSpeakerNotes
-  return (blks', spkNotes)
+handleAndFilterSpeakerNotes blks
+  -- avoid an expensive walk in the common case of no notes divs:
+  | not (getAny (query (Any . isNotesDiv) blks)) = return (blks, mempty)
+  | otherwise = do
+      modify $ \st -> st{stSpeakerNotes = mempty}
+      blks' <- walkM handleAndFilterSpeakerNotes' blks
+      spkNotes <- gets stSpeakerNotes
+      return (blks', spkNotes)
 
 blocksToSlide :: [Block] -> Pres Slide
 blocksToSlide blks = do
@@ -1165,7 +1169,7 @@
                    Just (MetaList xs) -> Just $ map Shared.stringify xs
                    _                  -> Nothing
 
-      authors = case map Shared.stringify $ docAuthors meta of
+      authors = case map Shared.stringifyInlines $ docAuthors meta of
                   [] -> Nothing
                   ss -> Just $ T.intercalate "; " ss
 
@@ -1186,7 +1190,7 @@
             , dcDescription = description
             , cpCategory = Shared.stringify <$> lookupMeta "category" meta
             , dcDate =
-              let t = Shared.stringify (docDate meta)
+              let t = Shared.stringifyInlines (docDate meta)
               in if T.null t
                  then Nothing
                  else Just t
diff --git a/src/Text/Pandoc/Writers/RST.hs b/src/Text/Pandoc/Writers/RST.hs
--- a/src/Text/Pandoc/Writers/RST.hs
+++ b/src/Text/Pandoc/Writers/RST.hs
@@ -116,7 +116,11 @@
 -- | Return RST representation of a reference key.
 keyToRST :: PandocMonad m => ([Inline], (Text, Text)) -> RST m (Doc Text)
 keyToRST (label, (src, _)) = do
-  label' <- inlineListToRST label
+  -- The stored label has already been normalized by the walk in
+  -- inlineListToRST.  Use writeInlines to avoid re-applying the
+  -- (non-idempotent) transformations: the definition must render
+  -- exactly like the inline reference, which uses writeInlines.
+  label' <- writeInlines label
   let label'' = if (==':') `T.any` (render Nothing label' :: Text)
                    then char '`' <> label' <> char '`'
                    else label'
@@ -132,7 +136,7 @@
 noteToRST num note = do
   contents <- blockListToRST note
   let marker = ".. [" <> text (show num) <> "]"
-  return $ nowrap $ marker $$ nest 3 contents
+  return $ nowrap marker $$ nest 3 contents
 
 -- | Return RST representation of picture reference table.
 pictRefsToRST :: PandocMonad m
@@ -146,7 +150,8 @@
           => ([Inline], (Attr, Text, Text, Maybe Text))
           -> RST m (Doc Text)
 pictToRST (label, (attr, src, _, mbtarget)) = do
-  label' <- inlineListToRST label
+  -- the stored label was already normalized; see keyToRST
+  label' <- writeInlines label
   dims   <- imageDimsToRST attr
   let (_, cls, _) = attr
       classes = case cls of
@@ -900,7 +905,9 @@
                  modify $ \st -> st { stImages =
                         (alt', (attr,src,tit, mbtarget)):stImages st }
                  return alt'
-  inlineListToRST txt
+  -- the alt text has already been normalized by the walk in
+  -- inlineListToRST, so don't apply the transformations again
+  writeInlines txt
 
 imageDimsToRST :: PandocMonad m => Attr -> RST m (Doc Text)
 imageDimsToRST attr = do
diff --git a/src/Text/Pandoc/Writers/Shared.hs b/src/Text/Pandoc/Writers/Shared.hs
--- a/src/Text/Pandoc/Writers/Shared.hs
+++ b/src/Text/Pandoc/Writers/Shared.hs
@@ -77,7 +77,8 @@
 import Text.Pandoc.Parsing (runParser, eof, defaultParserState,
                             anyOrderedListMarker)
 import Text.DocLayout
-import Text.Pandoc.Shared (stringify, makeSections, blocksToInlines)
+import Text.Pandoc.Shared (stringify, stringifyInlines, makeSections,
+                           blocksToInlines)
 import Text.Pandoc.Walk (Walkable(..))
 import qualified Text.Pandoc.UTF8 as UTF8
 import Text.Pandoc.XML (escapeStringForXML, rdfaAttributes, html5Attributes)
@@ -195,24 +196,25 @@
                _                                 -> Nothing
 
 -- | Produce an HTML tag with the given pandoc attributes.
-tagWithAttrs :: HasChars a => a -> Attr -> Doc a
+tagWithAttrs :: (HasChars a, FromText a) => a -> Attr -> Doc a
 tagWithAttrs tag attr = "<" <> literal tag <> (htmlAttrs attr) <> ">"
 
 -- | Produce HTML for the given pandoc attributes, to be used in HTML tags
-htmlAttrs :: HasChars a => Attr -> Doc a
+htmlAttrs :: (HasChars a, FromText a) => Attr -> Doc a
 htmlAttrs (ident, classes, kvs) = addSpaceIfNotEmpty (hsep [
   if T.null ident
       then empty
-      else "id=" <> doubleQuotes (text $ T.unpack ident)
+      else "id=" <> doubleQuotes (literal $ fromText (escapeStringForXML ident))
   ,if null classes
       then empty
-      else "class=" <> doubleQuotes (text $ T.unpack (T.unwords classes))
+      else "class=" <> doubleQuotes
+             (literal $ fromText . escapeStringForXML $ T.unwords classes)
   ,hsep (map (\(k,v) -> formatKey k <> "=" <>
-                doubleQuotes (text $ T.unpack (escapeStringForXML v))) kvs)
+                doubleQuotes (literal $ fromText (escapeStringForXML v))) kvs)
   ])
  where
-   formatKey x = text . T.unpack $
-        if (x `Set.member` (html5Attributes <> rdfaAttributes)
+   formatKey x = literal . fromText $
+        if ((x `Set.member` html5Attributes || x `Set.member` rdfaAttributes)
             && x /= "label") -- #10048
              || T.any (== ':') x -- e.g. epub: namespace
              || "data-" `T.isPrefixOf` x
@@ -259,8 +261,8 @@
 -- | Remove leading and trailing 'Space' and 'SoftBreak' elements.
 stripLeadingTrailingSpace :: [Inline] -> [Inline]
 stripLeadingTrailingSpace = go . reverse . go . reverse
-  where go (Space:xs)     = xs
-        go (SoftBreak:xs) = xs
+  where go (Space:xs)     = go xs
+        go (SoftBreak:xs) = go xs
         go xs             = xs
 
 -- | Put display math in its own block (for ODT/DOCX).
@@ -607,8 +609,8 @@
 lookupMetaBool :: Text -> Meta -> Bool
 lookupMetaBool key meta =
   case lookupMeta key meta of
-      Just (MetaBlocks _)  -> True
-      Just (MetaInlines _) -> True
+      Just (MetaBlocks bs)  -> not (null bs)
+      Just (MetaInlines ils) -> not (null ils)
       Just (MetaString x)  -> not (T.null x)
       Just (MetaBool True) -> True
       _                    -> False
@@ -646,7 +648,7 @@
 lookupMetaString key meta =
   case lookupMeta key meta of
          Just (MetaString s)    -> s
-         Just (MetaInlines ils) -> stringify ils
+         Just (MetaInlines ils) -> stringifyInlines ils
          Just (MetaBlocks bs)   -> stringify bs
          Just (MetaBool b)      -> T.pack (show b)
          _                      -> ""
@@ -677,6 +679,7 @@
 toSubscript '=' = Just '\x208C'
 toSubscript '(' = Just '\x208D'
 toSubscript ')' = Just '\x208E'
+toSubscript '\x2212' = Just '\x208B' -- unicode minus
 toSubscript c
   | c >= '0' && c <= '9' =
                  Just $ chr (0x2080 + (ord c - 48))
@@ -716,6 +719,10 @@
     Just Plain{} -> True
     Just (BulletList is) -> maybe False endsWithPlain (lastMay is)
     Just (OrderedList _ is) -> maybe False endsWithPlain (lastMay is)
+    Just (DefinitionList defs) ->
+      case lastMay defs of
+        Just (_, ds) -> maybe False endsWithPlain (lastMay ds)
+        Nothing      -> False
     _ -> False
 
 -- | Convert the relevant components of a new-style table (with block
@@ -799,12 +806,13 @@
   isSentenceEnding t =
     case T.unsnoc t of
       Just (t',c)
-        | c == '.' || c == '!' || c == '?'
+        | c == '.'
         , not (isInitial t') -> True
+        | c == '!' || c == '?' -> True
         | c == ')' || c == ']' || c == '"' || c == '\x201D' ->
            case T.unsnoc t' of
-             Just (t'',d) -> d == '.' || d == '!' || d == '?' &&
-                             not (isInitial t'')
+             Just (t'',d) -> (d == '.' && not (isInitial t''))
+                               || d == '!' || d == '?'
              _ -> False
       _ -> False
    where
@@ -814,44 +822,46 @@
 -- and modify internal links accordingly. (Yes, XML allows an
 -- underscore, but HTML 4 doesn't, so we are more conservative.)
 ensureValidXmlIdentifiers :: Pandoc -> Pandoc
-ensureValidXmlIdentifiers = walk fixLinks . walkAttr fixIdentifiers
+ensureValidXmlIdentifiers = walk goInline . walk goBlock
  where
-  fixIdentifiers (ident, classes, kvs) =
+  fixAttr (ident, classes, kvs) =
     (case T.uncons ident of
       Nothing -> ident
       Just (c, _) | isLetter c -> ident
       _ -> "id_" <> ident,
      classes, kvs)
-  needsFixing src =
+  fixSrc src =
     case T.uncons src of
       Just ('#',t) ->
         case T.uncons t of
-          Just (c,_) | not (isLetter c) -> Just ("#id_" <> t)
-          _ -> Nothing
-      _ -> Nothing
-  fixLinks (Link attr ils (src, tit))
-    | Just src' <- needsFixing src = Link attr ils (src', tit)
-  fixLinks (Image attr ils (src, tit))
-    | Just src' <- needsFixing src = Image attr ils (src', tit)
-  fixLinks x = x
+          Just (c,_) | not (isLetter c) -> "#id_" <> t
+          _ -> src
+      _ -> src
 
--- | Walk Pandoc document, modifying attributes.
-walkAttr :: (Attr -> Attr) -> Pandoc -> Pandoc
-walkAttr f = walk goInline . walk goBlock
- where
-  goInline (Span attr ils) = Span (f attr) ils
-  goInline (Link attr ils target) = Link (f attr) ils target
-  goInline (Image attr ils target) = Image (f attr) ils target
-  goInline (Code attr txt) = Code (f attr) txt
+  goInline (Span attr ils) = Span (fixAttr attr) ils
+  goInline (Link attr ils (src, tit)) = Link (fixAttr attr) ils (fixSrc src, tit)
+  goInline (Image attr ils (src, tit)) =
+    Image (fixAttr attr) ils (fixSrc src, tit)
+  goInline (Code attr txt) = Code (fixAttr attr) txt
   goInline x = x
 
-  goBlock (Header lev attr ils) = Header lev (f attr) ils
-  goBlock (CodeBlock attr txt) = CodeBlock (f attr) txt
+  goBlock (Header lev attr ils) = Header lev (fixAttr attr) ils
+  goBlock (CodeBlock attr txt) = CodeBlock (fixAttr attr) txt
   goBlock (Table attr cap colspecs thead tbodies tfoot) =
-    Table (f attr) cap colspecs thead tbodies tfoot
-  goBlock (Div attr bs) = Div (f attr) bs
+    Table (fixAttr attr) cap colspecs
+      (goTableHead thead) (map goTableBody tbodies) (goTableFoot tfoot)
+  goBlock (Div attr bs) = Div (fixAttr attr) bs
+  goBlock (Figure attr cap bs) = Figure (fixAttr attr) cap bs
   goBlock x = x
 
+  goTableHead (TableHead attr rows) = TableHead (fixAttr attr) (map goRow rows)
+  goTableBody (TableBody attr rhc hd bd) =
+    TableBody (fixAttr attr) rhc (map goRow hd) (map goRow bd)
+  goTableFoot (TableFoot attr rows) = TableFoot (fixAttr attr) (map goRow rows)
+  goRow (Row attr cells) = Row (fixAttr attr) (map goCell cells)
+  goCell (Cell attr align rowspan colspan bs) =
+    Cell (fixAttr attr) align rowspan colspan bs
+
 -- | Convert links to spans; most useful when writing elements that must not
 -- contain links, e.g. to avoid nested links.
 removeLinks :: [Inline] -> [Inline]
@@ -878,8 +888,12 @@
 toTaskListItem :: MonadPlus m => [Block] -> m (Bool, [Block])
 toTaskListItem (Plain (Str "☐":Space:ils):xs) = pure (False, Plain ils:xs)
 toTaskListItem (Plain (Str "☒":Space:ils):xs) = pure (True, Plain ils:xs)
+toTaskListItem (Plain [Str "☐"]:xs)           = pure (False, Plain []:xs)
+toTaskListItem (Plain [Str "☒"]:xs)           = pure (True, Plain []:xs)
 toTaskListItem (Para  (Str "☐":Space:ils):xs) = pure (False, Para ils:xs)
 toTaskListItem (Para  (Str "☒":Space:ils):xs) = pure (True, Para ils:xs)
+toTaskListItem (Para  [Str "☐"]:xs)           = pure (False, Para []:xs)
+toTaskListItem (Para  [Str "☒"]:xs)           = pure (True, Para []:xs)
 toTaskListItem _                              = mzero
 
 -- | Add an opener and closer to a Doc. If the Doc begins or ends
@@ -960,9 +974,11 @@
 -- For handling previous row spans that are next to the end of a row's cells
 -- that were previously added with 'insertCurrentSpansAtColumn'.
 decrementTrailingRowSpans :: Int -> M.Map Int (RowSpan, ColSpan) -> M.Map Int (RowSpan, ColSpan)
-decrementTrailingRowSpans columnPosition = M.mapWithKey decrementTrailing
+decrementTrailingRowSpans columnPosition = M.mapMaybeWithKey decrementTrailing
   where
-    decrementTrailing previousColumnPosition previousSpan@(RowSpan rowSpan, colSpan) =
-      if previousColumnPosition >= columnPosition && rowSpan >= 1
-        then (RowSpan rowSpan - 1, colSpan)
-        else previousSpan
+    decrementTrailing previousColumnPosition previousSpan@(RowSpan rowSpan, colSpan)
+      | previousColumnPosition >= columnPosition && rowSpan >= 1 =
+          if rowSpan > 1
+            then Just (RowSpan rowSpan - 1, colSpan)
+            else Nothing  -- span is used up; drop it instead of keeping a 0 entry
+      | otherwise = Just previousSpan
diff --git a/src/Text/Pandoc/Writers/TEI.hs b/src/Text/Pandoc/Writers/TEI.hs
--- a/src/Text/Pandoc/Writers/TEI.hs
+++ b/src/Text/Pandoc/Writers/TEI.hs
@@ -179,7 +179,7 @@
 blockToTEI _ HorizontalRule = return $
   selfClosingTag "milestone" [("unit","undefined")
                              ,("type","separator")
-                             ,("rendition","line")]
+                             ,("rend","line")]
 blockToTEI opts (Figure attr capt bs) =
   blockToTEI opts (figureDiv attr capt bs)
 
diff --git a/src/Text/Pandoc/Writers/Typst.hs b/src/Text/Pandoc/Writers/Typst.hs
--- a/src/Text/Pandoc/Writers/Typst.hs
+++ b/src/Text/Pandoc/Writers/Typst.hs
@@ -33,7 +33,8 @@
 import Text.Pandoc.Writers.Shared ( lookupMetaInlines, lookupMetaString,
                                     metaToContext, defField, resetField,
                                     setupTranslations )
-import Text.Pandoc.Shared (isTightList, orderedListMarkers, tshow, stringify)
+import Text.Pandoc.Shared (isTightList, orderedListMarkers, tshow,
+                           stringifyInlines)
 import Text.Pandoc.Highlighting (highlight, formatTypstBlock, formatTypstInline,
                                  styleToTypst)
 import Text.Pandoc.Translations (Term(Abstract), translateTerm)
@@ -362,17 +363,18 @@
                 $$ footer
             )
             $$ ")"
-      return $ if "typst:no-figure" `elem` tabclasses
-        then toTypstBracesSetText typstTextAttrs table
-        else "#figure("
-            $$
-            nest 2
-            ("align(center)[" <> toTypstPoundSetText typstTextAttrs <> "#" <> table <> "]"
-              $$ capt'
-              $$ typstFigureKind
-              $$ ")")
-            $$ lab
-          $$ blankline
+      return $
+        (if "typst:no-figure" `elem` tabclasses
+            then toTypstBracesSetText typstTextAttrs table
+            else "#figure("
+                 $$
+                 nest 2
+                 ("align(center)[" <> toTypstPoundSetText typstTextAttrs <> "#" <> table <> "]"
+                   $$ capt'
+                   $$ typstFigureKind
+                   $$ ")"))
+        $$ lab
+        $$ blankline
     Figure (ident,_,kvs) (Caption _mbshort capt) blocks -> do
       caption <- blocksToTypst capt
       opts <-  gets stOptions
@@ -402,7 +404,7 @@
       contents <- blocksToTypst blocks
       return $ "#block" <> toTypstPropsListParens typstAttrs <> "["
         $$ toTypstPoundSetText typstTextAttrs
-        $$ contents
+        $$ chomp contents
         $$ ("]" <+> lab)
 
 defListItemToTypst :: PandocMonad m => ([Inline], [[Block]]) -> TW m (Doc Text)
@@ -410,7 +412,7 @@
   modify $ \st -> st{ stEscapeContext = TermContext }
   term' <- inlinesToTypst term
   modify $ \st -> st{ stEscapeContext = NormalContext }
-  defns' <- mapM blocksToTypst defns
+  defns' <- mapM (fmap chomp . blocksToTypst) defns
   return $
     case defns of
       [[Plain _]] -> hang 4 (nowrap ("/ " <> term' <> ": ")) (vcat defns')
@@ -598,7 +600,7 @@
     Just alt -> Just alt
     Nothing -> case imgInlines of
                  [] -> Nothing
-                 _ -> Just (stringify imgInlines)
+                 _ -> Just (stringifyInlines imgInlines)
 
 textstyle :: PandocMonad m => Doc Text -> [Inline] -> TW m (Doc Text)
 textstyle s inlines = do
diff --git a/src/Text/Pandoc/Writers/XML.hs b/src/Text/Pandoc/Writers/XML.hs
--- a/src/Text/Pandoc/Writers/XML.hs
+++ b/src/Text/Pandoc/Writers/XML.hs
@@ -1,6 +1,8 @@
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeOperators #-}
 
 -- |
 --   Module      : Text.Pandoc.Writers.XML
@@ -18,19 +20,19 @@
 import Data.Maybe (mapMaybe)
 import qualified Data.Text as T
 import Data.Version (versionBranch)
+import GHC.Generics
 import Text.Pandoc.Class.PandocMonad (PandocMonad)
 import Text.Pandoc.Definition
 import Text.Pandoc.Options (WriterOptions (..))
 import Text.Pandoc.XML.Light
 import qualified Text.Pandoc.XML.Light as XML
 import Text.Pandoc.XMLFormat
-import Text.XML.Light (xml_header)
 
 type PandocAttr = Text.Pandoc.Definition.Attr
 
 writeXML :: (PandocMonad m) => WriterOptions -> Pandoc -> m T.Text
-writeXML _ doc = do
-  return $ pandocToXmlText doc
+writeXML opts doc = do
+  return $ pandocToXmlText opts doc
 
 text_node :: T.Text -> Content
 text_node text = Text (CData CDataText text Nothing)
@@ -65,40 +67,85 @@
 elementWithAttrAndContents :: T.Text -> PandocAttr -> [Content] -> Element
 elementWithAttrAndContents tag attr contents = addAttrAttributes attr $ elementWithContents tag contents
 
-asBlockOfInlines :: Element -> [Content]
-asBlockOfInlines el = [Elem el, text_node "\n"]
+-- | Extract the name of a value's constructor via GHC.Generics.
+class GConName f where
+  gConName :: f p -> String
 
-asBlockOfBlocks :: Element -> [Content]
-asBlockOfBlocks el = [Elem newline_before_first, newline]
-  where
-    newline = text_node "\n"
-    newline_before_first = if null (elContent el) then el else prependContents [newline] el
+instance (GConName f) => GConName (M1 D d f) where
+  gConName (M1 x) = gConName x
 
-itemName :: (Show a) => a -> T.Text
-itemName a = T.pack $ takeWhile (/= ' ') (show a)
+instance (GConName f, GConName g) => GConName (f :+: g) where
+  gConName (L1 x) = gConName x
+  gConName (R1 x) = gConName x
 
+instance (Constructor c) => GConName (M1 C c f) where
+  gConName = conName
+
+itemName :: (Generic a, GConName (Rep a)) => a -> T.Text
+itemName = T.pack . gConName . from
+
 intAsText :: Int -> T.Text
 intAsText i = T.pack $ show i
 
-itemAsEmptyElement :: (Show a) => a -> Element
+itemAsEmptyElement :: (Generic a, GConName (Rep a)) => a -> Element
 itemAsEmptyElement item = emptyElement $ itemName item
 
-pandocToXmlText :: Pandoc -> T.Text
-pandocToXmlText (Pandoc (Meta meta) blocks) = with_header . with_blocks . with_meta . with_version $ el
-  where
-    el = prependContents [text_node "\n"] $ emptyElement "Pandoc"
-    with_version = addAttribute atNameApiVersion (T.intercalate "," $ map (T.pack . show) $ versionBranch pandocTypesVersion)
-    with_meta = appendContents (metaMapToXML meta "meta")
-    with_blocks = appendContents (asBlockOfBlocks $ elementWithContents "blocks" $ blocksToXML blocks)
-    with_header :: Element -> T.Text
-    with_header e = T.concat [T.pack xml_header, "\n", showElement e]
+pandocToXmlText :: WriterOptions -> Pandoc -> T.Text
+pandocToXmlText opts (Pandoc (Meta meta) blocks) =
+  case writerTemplate opts of
+       Just _ -> -- standalone document; include Pandoc and Meta
+         ppcTopElement configPP . with_blocks . with_meta . with_version $ el
+       Nothing -> -- fragment; just include blocks, as native writer does
+         mconcat $ map (ppcContent configPP) block_contents
+ where
+   el = emptyElement "Pandoc"
+   with_version = addAttribute atNameApiVersion version
+   version = (T.intercalate "," $ map (T.pack . show)
+                                $ versionBranch pandocTypesVersion)
+   with_meta = appendContents (metaMapToXML meta "meta")
+   with_blocks = appendContents $ asContents block_element
+   block_element = elementWithContents "blocks" block_contents
+   block_contents = blocksToXML blocks
 
+-- | Pretty-printing configuration: the contents of elements that
+-- contain inline content are kept on a single line, so that no
+-- significant whitespace is added inside them.
+configPP :: ConfigPP
+configPP = useInlineTags (isInlineTag . qName) prettyConfigPP
+
+-- | Check whether a tag is for an element with inline content.
+isInlineTag :: T.Text -> Bool
+isInlineTag t =
+  case t of
+    "Para" -> True
+    "Plain" -> True
+    "Header" -> True
+    "MetaInlines" -> True
+    "Emph" -> True
+    "Strong" -> True
+    "Strikeout" -> True
+    "Superscript" -> True
+    "Subscript" -> True
+    "SmallCaps" -> True
+    "Underline" -> True
+    "Quoted" -> True
+    "Cite" -> True
+    "Link" -> True
+    "Image" -> True
+    "Span" -> True
+    _ ->
+      t == tgNameLineItem
+        || t == tgNameDefListTerm
+        || t == tgNameCitationPrefix
+        || t == tgNameCitationSuffix
+        || t == tgNameShortCaption
+
 metaMapToXML :: Map T.Text MetaValue -> T.Text -> [Content]
-metaMapToXML mmap tag = asBlockOfBlocks $ elementWithContents tag entries
+metaMapToXML mmap tag = asContents $ elementWithContents tag entries
   where
     entries = concatMap to_entry $ toList mmap
     to_entry :: (T.Text, MetaValue) -> [Content]
-    to_entry (text, metavalue) = asBlockOfBlocks with_key
+    to_entry (text, metavalue) = asContents with_key
       where
         entry = elementWithContents tgNameMetaMapEntry $ metaValueToXML metavalue
         with_key = addAttribute atNameMetaMapEntryKey text entry
@@ -108,21 +155,60 @@
   let name = itemName value
       el = itemAsEmptyElement value
    in case (value) of
-        MetaBool b -> asBlockOfInlines $ addAttribute atNameMetaBoolValue bool_value el
+        MetaBool b -> asContents $ addAttribute atNameMetaBoolValue bool_value el
           where
             bool_value = if b then "true" else "false"
-        MetaString s -> asBlockOfInlines $ appendContents [text_node s] el
-        MetaInlines inlines -> asBlockOfInlines $ appendContents (inlinesToXML inlines) el
-        MetaBlocks blocks -> asBlockOfBlocks $ appendContents (blocksToXML blocks) el
-        MetaList items -> asBlockOfBlocks $ appendContents (concatMap metaValueToXML items) el
+        MetaString s -> asContents $ appendContents [text_node s] el
+        MetaInlines inlines -> asContents $ appendContents (inlinesToXML inlines) el
+        MetaBlocks blocks -> asContents $ appendContents (blocksToXML blocks) el
+        MetaList items -> asContents $ appendContents (concatMap metaValueToXML items) el
         MetaMap mm -> metaMapToXML mm name
 
 blocksToXML :: [Block] -> [Content]
 blocksToXML blocks = concatMap blockToXML blocks
 
 inlinesToXML :: [Inline] -> [Content]
-inlinesToXML inlines = concatMap inlineContentToContents (ilsToIlsContent inlines [])
+inlinesToXML inlines = concatMap wsRunsAsElements $ mergeTextNodes $ concatMap inlineContentToContents (ilsToIlsContent inlines [])
 
+-- | Merge consecutive text nodes into a single text node; otherwise
+-- the pretty-printer would render each one on a line of its own.
+mergeTextNodes :: [Content] -> [Content]
+mergeTextNodes (Text (CData CDataText t _) : rest) =
+  text_node (T.concat (t : ts)) : mergeTextNodes rest'
+  where
+    (ts, rest') = textRun rest
+    textRun (Text (CData CDataText t' _) : cs) =
+      let (ts', cs') = textRun cs in (t' : ts', cs')
+    textRun cs = ([], cs)
+mergeTextNodes (c : rest) = c : mergeTextNodes rest
+mergeTextNodes [] = []
+
+-- | A whitespace run in a text node is read back as a single Space
+-- (a run of spaces) or a single SoftBreak (a run containing a
+-- newline), so runs like " \n" or "\n\n" would not roundtrip: encode
+-- them as sequences of Space and SoftBreak elements instead.
+wsRunsAsElements :: Content -> [Content]
+wsRunsAsElements (Text (CData CDataText t _))
+  | hasLongWsRun = go [] (T.groupBy (\a b -> isWs a == isWs b) t)
+  where
+    isWs ch = ch == ' ' || ch == '\n'
+    -- a whitespace run needs encoding only if it is longer than one
+    -- character (single-character runs are " " or "\n", which are
+    -- kept); the common case of no such run avoids the work below
+    hasLongWsRun = fst $ T.foldl' adjacent (False, False) t
+    adjacent (found, prevWs) ch =
+      let ws = isWs ch in (found || (prevWs && ws), ws)
+    keep r = r == " " || r == "\n" || not (T.any isWs r)
+    go acc (r : rs)
+      | keep r = go (r : acc) rs
+      | otherwise = flush acc ++ map toElem (T.unpack r) ++ go [] rs
+    go acc [] = flush acc
+    flush [] = []
+    flush acc = [text_node $ T.concat $ reverse acc]
+    toElem '\n' = Elem $ emptyElement "SoftBreak"
+    toElem _ = Elem $ emptyElement "Space"
+wsRunsAsElements c = [c]
+
 data InlineContent
   = NormalInline Inline
   | ElSpace Int
@@ -159,28 +245,28 @@
 asContents el = [Elem el]
 
 wrapBlocks :: T.Text -> [Block] -> [Content]
-wrapBlocks tag blocks = asBlockOfBlocks $ elementWithContents tag $ blocksToXML blocks
+wrapBlocks tag blocks = asContents $ elementWithContents tag $ blocksToXML blocks
 
 wrapArrayOfBlocks :: T.Text -> [[Block]] -> [Content]
 wrapArrayOfBlocks tag array = concatMap (wrapBlocks tag) array
 
 -- wrapInlines :: T.Text -> [Inline] -> [Content]
--- wrapInlines tag inlines = asBlockOfInlines $ element_with_contents tag $ inlinesToXML inlines
+-- wrapInlines tag inlines = asContents $ element_with_contents tag $ inlinesToXML inlines
 
 blockToXML :: Block -> [Content]
 blockToXML block =
   let el = itemAsEmptyElement block
    in case (block) of
-        Para inlines -> asBlockOfInlines $ appendContents (inlinesToXML inlines) el
-        Header level (idn, cls, attrs) inlines -> asBlockOfInlines $ appendContents (inlinesToXML inlines) with_attr
+        Para inlines -> asContents $ appendContents (inlinesToXML inlines) el
+        Header level (idn, cls, attrs) inlines -> asContents $ appendContents (inlinesToXML inlines) with_attr
           where
             with_attr = addAttrAttributes (idn, cls, attrs ++ [(atNameLevel, intAsText level)]) el
-        Plain inlines -> asBlockOfInlines $ appendContents (inlinesToXML inlines) el
-        Div attr blocks -> asBlockOfBlocks $ appendContents (blocksToXML blocks) with_attr
+        Plain inlines -> asContents $ appendContents (inlinesToXML inlines) el
+        Div attr blocks -> asContents $ appendContents (blocksToXML blocks) with_attr
           where
             with_attr = addAttrAttributes attr el
-        BulletList items -> asBlockOfBlocks $ appendContents (wrapArrayOfBlocks tgNameListItem items) el
-        OrderedList (start, style, delim) items -> asBlockOfBlocks $ with_contents . with_attrs $ el
+        BulletList items -> asContents $ appendContents (wrapArrayOfBlocks tgNameListItem items) el
+        OrderedList (start, style, delim) items -> asContents $ with_contents . with_attrs $ el
           where
             with_attrs =
               addAttributes
@@ -191,16 +277,16 @@
                     ]
                 )
             with_contents = appendContents (wrapArrayOfBlocks tgNameListItem items)
-        BlockQuote blocks -> asBlockOfBlocks $ appendContents (blocksToXML blocks) el
-        HorizontalRule -> asBlockOfInlines el
-        CodeBlock attr text -> asBlockOfInlines $ with_contents . with_attr $ el
+        BlockQuote blocks -> asContents $ appendContents (blocksToXML blocks) el
+        HorizontalRule -> asContents el
+        CodeBlock attr text -> asContents $ with_contents . with_attr $ el
           where
             with_contents = appendContents [text_node text]
             with_attr = addAttrAttributes attr
-        LineBlock lins -> asBlockOfBlocks $ appendContents (concatMap wrapInlines lins) el
+        LineBlock lins -> asContents $ appendContents (concatMap wrapInlines lins) el
           where
             wrapInlines inlines = asContents $ appendContents (inlinesToXML inlines) $ emptyElement tgNameLineItem
-        Table attr caption colspecs thead tbodies tfoot -> asBlockOfBlocks $ with_foot . with_bodies . with_head . with_colspecs . with_caption . with_attr $ el
+        Table attr caption colspecs thead tbodies tfoot -> asContents $ with_foot . with_bodies . with_head . with_colspecs . with_caption . with_attr $ el
           where
             with_attr = addAttrAttributes attr
             with_caption = appendContents (captionToXML caption)
@@ -208,7 +294,7 @@
             with_head = appendContents (tableHeadToXML thead)
             with_bodies = appendContents (concatMap tableBodyToXML tbodies)
             with_foot = appendContents (tableFootToXML tfoot)
-        Figure attr caption blocks -> asBlockOfBlocks $ with_contents . with_caption . with_attr $ el
+        Figure attr caption blocks -> asContents $ with_contents . with_caption . with_attr $ el
           where
             with_attr = addAttrAttributes attr
             with_caption = appendContents (captionToXML caption)
@@ -216,7 +302,7 @@
         RawBlock (Format format) text -> asContents $ appendContents [text_node text] raw
           where
             raw = addAttribute atNameFormat format el
-        DefinitionList items -> asBlockOfBlocks $ appendContents (map definitionListItemToXML items) el
+        DefinitionList items -> asContents $ appendContents (map definitionListItemToXML items) el
 
 inlineToXML :: Inline -> [Content]
 inlineToXML inline =
@@ -235,17 +321,17 @@
         SmallCaps inlines -> wrapInlines inlines
         Superscript inlines -> wrapInlines inlines
         Subscript inlines -> wrapInlines inlines
-        SoftBreak -> asContents el
+        SoftBreak -> [text_node "\n"]
         LineBreak -> asContents el
         Span attr inlines -> asContents $ appendContents (inlinesToXML inlines) with_attr
           where
             with_attr = addAttrAttributes attr el
         Link (idn, cls, attrs) inlines (url, title) -> asContents $ appendContents (inlinesToXML inlines) with_attr
           where
-            with_attr = addAttrAttributes (idn, cls, attrs ++ [(atNameLinkUrl, url), (atNameTitle, title)]) el
+            with_attr = addAttrAttributes (idn, cls, attrs ++ optionalAttribute atNameLinkUrl url ++ optionalAttribute atNameTitle title) el
         Image (idn, cls, attrs) inlines (url, title) -> asContents $ appendContents (inlinesToXML inlines) with_attr
           where
-            with_attr = addAttrAttributes (idn, cls, attrs ++ [(atNameImageUrl, url), (atNameTitle, title)]) el
+            with_attr = addAttrAttributes (idn, cls, attrs ++ optionalAttribute atNameImageUrl url ++ optionalAttribute atNameTitle title) el
         RawInline (Format format) text -> asContents $ appendContents [text_node text] raw
           where
             raw = addAttribute atNameFormat format el
@@ -262,10 +348,15 @@
 
 -- TODO: don't let an attribute overwrite id or class
 maybeAttribute :: (T.Text, T.Text) -> Maybe XML.Attr
-maybeAttribute (_, "") = Nothing
 maybeAttribute ("", _) = Nothing
-maybeAttribute (name, value) = Just $ XML.Attr (unqual name) value
+maybeAttribute (name, value) = Just $ XML.Attr (unqual $ encodeAttrName name) value
 
+-- | An optional attribute, omitted when its value is empty (the
+-- reader treats a missing attribute as an empty value).
+optionalAttribute :: T.Text -> T.Text -> [(T.Text, T.Text)]
+optionalAttribute _ "" = []
+optionalAttribute name value = [(name, value)]
+
 validAttributes :: [(T.Text, T.Text)] -> [XML.Attr]
 validAttributes pairs = mapMaybe maybeAttribute pairs
 
@@ -286,13 +377,17 @@
 addAttrAttributes :: PandocAttr -> Element -> Element
 addAttrAttributes (identifier, classes, attributes) el = addAttributes attrs' el
   where
-    attrs' = mapMaybe maybeAttribute (("id", identifier) : ("class", T.intercalate " " classes) : attributes)
+    attrs' =
+      mapMaybe maybeAttribute $
+        optionalAttribute "id" identifier
+          ++ optionalAttribute "class" (T.intercalate " " classes)
+          ++ attributes
 
 addCitations :: [Citation] -> Element -> Element
-addCitations citations el = appendContents [Elem $ elementWithContents tgNameCitations $ (text_node "\n") : concatMap citation_to_elem citations] el
+addCitations citations el = appendContents [Elem $ elementWithContents tgNameCitations $ concatMap citation_to_elem citations] el
   where
     citation_to_elem :: Citation -> [Content]
-    citation_to_elem citation = asBlockOfInlines with_suffix
+    citation_to_elem citation = asContents with_suffix
       where
         cit_elem = elementWithAttributes (itemName citation) attrs
         prefix = citationPrefix citation
@@ -309,7 +404,7 @@
           map
             (\(n, v) -> XML.Attr (unqual n) v)
             [ ("id", citationId citation),
-              (atNameCitationMode, T.pack $ show $ citationMode citation),
+              (atNameCitationMode, itemName $ citationMode citation),
               (atNameCitationNoteNum, intAsText $ citationNoteNum citation),
               (atNameCitationHash, intAsText $ citationHash citation)
             ]
@@ -317,18 +412,18 @@
 definitionListItemToXML :: ([Inline], [[Block]]) -> Content
 definitionListItemToXML (inlines, defs) = Elem $ elementWithContents tgNameDefListItem $ term ++ wrapArrayOfBlocks tgNameDefListDef defs
   where
-    term = asBlockOfInlines $ appendContents (inlinesToXML inlines) $ emptyElement tgNameDefListTerm
+    term = asContents $ appendContents (inlinesToXML inlines) $ emptyElement tgNameDefListTerm
 
 captionToXML :: Caption -> [Content]
-captionToXML (Caption short blocks) = asBlockOfBlocks with_short_caption
+captionToXML (Caption short blocks) = asContents with_short_caption
   where
     el = elementWithContents "Caption" $ blocksToXML blocks
     with_short_caption = case (short) of
-      Just inlines -> prependContents (asBlockOfInlines $ elementWithContents tgNameShortCaption $ inlinesToXML inlines) el
+      Just inlines -> prependContents (asContents $ elementWithContents tgNameShortCaption $ inlinesToXML inlines) el
       _ -> el
 
 colSpecToXML :: (Alignment, ColWidth) -> [Content]
-colSpecToXML (align, cw) = asBlockOfInlines colspec
+colSpecToXML (align, cw) = asContents colspec
   where
     colspec = elementWithAttributes "ColSpec" $ validAttributes [(atNameAlignment, itemName align), (atNameColWidth, colwidth)]
     colwidth = case (cw) of
@@ -336,27 +431,27 @@
       ColWidthDefault -> "0"
 
 colSpecsToXML :: [(Alignment, ColWidth)] -> [Content]
-colSpecsToXML colspecs = asBlockOfBlocks $ elementWithContents tgNameColspecs $ concatMap colSpecToXML colspecs
+colSpecsToXML colspecs = asContents $ elementWithContents tgNameColspecs $ concatMap colSpecToXML colspecs
 
 tableHeadToXML :: TableHead -> [Content]
-tableHeadToXML (TableHead attr rows) = asBlockOfBlocks $ elementWithAttrAndContents "TableHead" attr $ concatMap rowToXML rows
+tableHeadToXML (TableHead attr rows) = asContents $ elementWithAttrAndContents "TableHead" attr $ concatMap rowToXML rows
 
 tableBodyToXML :: TableBody -> [Content]
-tableBodyToXML (TableBody (idn, cls, attrs) (RowHeadColumns headcols) hrows brows) = asBlockOfBlocks $ elementWithAttrAndContents "TableBody" attr children
+tableBodyToXML (TableBody (idn, cls, attrs) (RowHeadColumns headcols) hrows brows) = asContents $ elementWithAttrAndContents "TableBody" attr children
   where
     attr = (idn, cls, (atNameRowHeadColumns, intAsText headcols) : attrs)
-    header_rows = asBlockOfBlocks $ elementWithContents tgNameBodyHeader $ concatMap rowToXML hrows
-    body_rows = asBlockOfBlocks $ elementWithContents tgNameBodyBody $ concatMap rowToXML brows
+    header_rows = asContents $ elementWithContents tgNameBodyHeader $ concatMap rowToXML hrows
+    body_rows = asContents $ elementWithContents tgNameBodyBody $ concatMap rowToXML brows
     children = header_rows ++ body_rows
 
 tableFootToXML :: TableFoot -> [Content]
-tableFootToXML (TableFoot attr rows) = asBlockOfBlocks $ elementWithAttrAndContents "TableFoot" attr $ concatMap rowToXML rows
+tableFootToXML (TableFoot attr rows) = asContents $ elementWithAttrAndContents "TableFoot" attr $ concatMap rowToXML rows
 
 rowToXML :: Row -> [Content]
-rowToXML (Row attr cells) = asBlockOfBlocks $ elementWithAttrAndContents "Row" attr $ concatMap cellToXML cells
+rowToXML (Row attr cells) = asContents $ elementWithAttrAndContents "Row" attr $ concatMap cellToXML cells
 
 cellToXML :: Cell -> [Content]
-cellToXML (Cell (idn, cls, attrs) alignment (RowSpan rowspan) (ColSpan colspan) blocks) = asBlockOfBlocks $ elementWithAttrAndContents "Cell" attr $ blocksToXML blocks
+cellToXML (Cell (idn, cls, attrs) alignment (RowSpan rowspan) (ColSpan colspan) blocks) = asContents $ elementWithAttrAndContents "Cell" attr $ blocksToXML blocks
   where
     with_alignment a = (atNameAlignment, itemName alignment) : a
     with_rowspan a = if rowspan > 1 then (atNameRowspan, intAsText rowspan) : a else a
diff --git a/src/Text/Pandoc/XMLFormat.hs b/src/Text/Pandoc/XMLFormat.hs
--- a/src/Text/Pandoc/XMLFormat.hs
+++ b/src/Text/Pandoc/XMLFormat.hs
@@ -2,7 +2,9 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 
 module Text.Pandoc.XMLFormat
-  ( atNameAlignment,
+  ( decodeAttrName,
+    encodeAttrName,
+    atNameAlignment,
     atNameApiVersion,
     atNameCitationHash,
     atNameCitationMode,
@@ -41,7 +43,93 @@
   )
 where
 
+import Data.Char (chr, digitToInt, isAsciiLower, isAsciiUpper, isDigit, isHexDigit, ord, toUpper)
 import Data.Text (Text)
+import qualified Data.Text as T
+import Numeric (showHex)
+
+-- | Encode an attribute name so that it is a valid XML name.
+-- Characters that are not allowed in XML names -- and colons, which
+-- XML parsers treat as namespace separators -- are encoded as
+-- _xHHHH_, where HHHH is the hexadecimal code of the character
+-- (uppercase, at least four digits).  An underscore that introduces
+-- a literal "_x" is encoded as _x005F_, so that decoding is
+-- unambiguous.  For example, "typst:property" is encoded as
+-- "typst_x003A_property".
+encodeAttrName :: Text -> Text
+encodeAttrName name =
+  case T.unpack name of
+    [] -> name
+    c : cs
+      | isNameStartChar c && all isNameChar cs && not ("_x" `T.isInfixOf` name) ->
+          name
+      | otherwise ->
+          T.pack $ concat $ go isNameStartChar (c : cs)
+  where
+    go _ [] = []
+    go ok (c : cs)
+      | c == '_' && take 1 cs == "x" = encodeChar '_' : go isNameChar cs
+      | ok c = [c] : go isNameChar cs
+      | otherwise = encodeChar c : go isNameChar cs
+    encodeChar c = "_x" ++ replicate (4 - length h) '0' ++ h ++ "_"
+      where
+        h = map toUpper $ showHex (ord c) ""
+
+-- | Decode an attribute name encoded by 'encodeAttrName': _xHHHH_
+-- sequences (four to six hexadecimal digits) are decoded to the
+-- character with the given code.
+decodeAttrName :: Text -> Text
+decodeAttrName name
+  | "_x" `T.isInfixOf` name = T.concat $ go name
+  | otherwise = name
+  where
+    go t =
+      case T.breakOn "_x" t of
+        (pre, rest)
+          | T.null rest -> [pre]
+          | otherwise ->
+              let body = T.drop 2 rest
+                  digits = T.takeWhile isHexDigit body
+                  n = T.length digits
+                  code = T.foldl' (\acc d -> 16 * acc + digitToInt d) 0 digits
+               in if n >= 4
+                    && n <= 6
+                    && "_" `T.isPrefixOf` T.drop n body
+                    && validChar code
+                    then pre : T.singleton (chr code) : go (T.drop (n + 1) body)
+                    else pre : "_x" : go body
+    validChar code = code <= 0x10FFFF && not (code >= 0xD800 && code <= 0xDFFF)
+
+-- the XML NameStartChar production, without the colon (which XML
+-- parsers treat as a namespace separator)
+isNameStartChar :: Char -> Bool
+isNameStartChar c =
+  isAsciiUpper c
+    || isAsciiLower c
+    || c == '_'
+    || (c >= '\xC0' && c <= '\xD6')
+    || (c >= '\xD8' && c <= '\xF6')
+    || (c >= '\xF8' && c <= '\x2FF')
+    || (c >= '\x370' && c <= '\x37D')
+    || (c >= '\x37F' && c <= '\x1FFF')
+    || (c >= '\x200C' && c <= '\x200D')
+    || (c >= '\x2070' && c <= '\x218F')
+    || (c >= '\x2C00' && c <= '\x2FEF')
+    || (c >= '\x3001' && c <= '\xD7FF')
+    || (c >= '\xF900' && c <= '\xFDCF')
+    || (c >= '\xFDF0' && c <= '\xFFFD')
+    || (c >= '\x10000' && c <= '\xEFFFF')
+
+-- the XML NameChar production, without the colon
+isNameChar :: Char -> Bool
+isNameChar c =
+  isNameStartChar c
+    || isDigit c
+    || c == '-'
+    || c == '.'
+    || c == '\xB7'
+    || (c >= '\x300' && c <= '\x36F')
+    || (c >= '\x203F' && c <= '\x2040')
 
 -- the attribute carrying the API version of pandoc types in the main Pandoc element
 atNameApiVersion :: Text
diff --git a/test/Tests/ImageSize.hs b/test/Tests/ImageSize.hs
new file mode 100644
--- /dev/null
+++ b/test/Tests/ImageSize.hs
@@ -0,0 +1,335 @@
+{-# LANGUAGE OverloadedStrings #-}
+{- |
+   Module      : Tests.ImageSize
+   Copyright   : © 2025 John MacFarlane
+   License     : GNU GPL, version 2 or above
+
+   Maintainer  : John MacFarlane <jgm@berkeley.edu>
+   Stability   : alpha
+   Portability : portable
+
+Tests for image type and size detection.
+-}
+module Tests.ImageSize (tests) where
+
+import Data.Bits (shiftR)
+import qualified Data.ByteString as B
+import Data.Word (Word8)
+import Test.Tasty
+import Test.Tasty.HUnit
+import Text.Pandoc.ImageSize
+import Text.Pandoc.Options (def)
+
+-- helpers to construct binary test data:
+
+le32, be32 :: Int -> B.ByteString
+le32 n = B.pack $ map (fromIntegral . (n `shiftR`) . (8 *)) [0,1,2,3]
+be32 n = B.pack $ map (fromIntegral . (n `shiftR`) . (8 *)) [3,2,1,0]
+
+be16 :: Int -> B.ByteString
+be16 n = B.pack $ map (fromIntegral . (n `shiftR`) . (8 *)) [1,0]
+
+-- | An ISO BMFF box with the given type and contents.
+box :: B.ByteString -> B.ByteString -> B.ByteString
+box name body = be32 (8 + B.length body) <> name <> body
+
+be64 :: Int -> B.ByteString
+be64 n = B.pack $ map (fromIntegral . (n `shiftR`) . (8 *)) [7,6..0]
+
+-- | An ISO BMFF box using the 64-bit "largesize" field (size == 1).
+largeBox :: B.ByteString -> B.ByteString -> B.ByteString
+largeBox name body = be32 1 <> name <> be64 (16 + B.length body) <> body
+
+-- | An ISO BMFF box with size == 0 (extends to the end of the file).
+zeroBox :: B.ByteString -> B.ByteString -> B.ByteString
+zeroBox name body = be32 0 <> name <> body
+
+-- | An EMF header with the given frame bounds (1/100 mm), reference
+-- device size in pixels, and reference device size in mm.
+emfFile :: [Int] -> [Int] -> [Int] -> B.ByteString
+emfFile frame device mm = B.concat $
+  [le32 1, B.replicate 20 0] <> map le32 frame <>
+  [" EMF", B.replicate 28 0] <> map le32 (device <> mm)
+
+-- | A WebP RIFF container with the given chunk.
+webpFile :: B.ByteString -> B.ByteString
+webpFile chunk = "RIFF\0\0\0\0WEBP" <> chunk
+
+svgFile :: B.ByteString -> B.ByteString
+svgFile attrs =
+  "<svg xmlns=\"http://www.w3.org/2000/svg\" " <> attrs <> "></svg>"
+
+jpegBare, jpegApp0 :: B.ByteString
+jpegBare = B.pack [0xff, 0xd8, 0xff, 0xdb] <> "rest"
+jpegApp0 = B.pack [0xff, 0xd8, 0xff, 0xe0] <> "rest"
+
+-- | A PNG chunk with the given type and body (and a dummy CRC).
+pngChunk :: B.ByteString -> B.ByteString -> B.ByteString
+pngChunk name body = be32 (B.length body) <> name <> body <> B.replicate 4 0
+
+-- | A PNG header with the given extra chunks after IHDR (and no
+-- image data).
+pngFile :: [B.ByteString] -> Int -> Int -> B.ByteString
+pngFile chunks w h = B.concat $
+  [ "\x89PNG\r\n\x1a\n"
+  , pngChunk "IHDR" (be32 w <> be32 h <> B.pack [8, 3, 0, 0, 0]) ]
+  <> chunks
+
+-- | A pHYs chunk with the given unit (1 = pixels per meter) and
+-- pixel densities.
+physChunk :: Word8 -> Int -> Int -> B.ByteString
+physChunk unit x y = pngChunk "pHYs" (be32 x <> be32 y <> B.pack [unit])
+
+-- | A JPEG marker segment with the given marker and body.
+jpegSeg :: Word8 -> B.ByteString -> B.ByteString
+jpegSeg m body = B.pack [0xff, m] <> be16 (B.length body + 2) <> body
+
+-- | A JPEG header: SOI marker, the given segments, and a baseline
+-- SOF segment with the given width and height (and no image data).
+jpegFile :: [B.ByteString] -> Int -> Int -> B.ByteString
+jpegFile segs w h = B.concat $ [B.pack [0xff, 0xd8]] <> segs <>
+  [jpegSeg 0xc0 (B.pack [8] <> be16 h <> be16 w <> B.pack [3])]
+
+-- | A JFIF APP0 segment with the given density units (0 = aspect
+-- ratio only, 1 = dots per inch, 2 = dots per cm) and densities.
+jfifSeg :: Word8 -> Int -> Int -> B.ByteString
+jfifSeg units x y = jpegSeg 0xe0 $
+  "JFIF\0" <> B.pack [1, 2, units] <> be16 x <> be16 y <> "\0\0"
+
+-- | An Exif APP1 segment giving 300x200 dpi resolution.
+exifSeg :: B.ByteString
+exifSeg = jpegSeg 0xe1 $ "Exif\0\0"
+  <> "MM" <> be16 42 <> be32 8           -- TIFF header (big-endian)
+  <> be16 3                              -- IFD entry count
+  <> entry 0x011a 5 (be32 50)            -- XResolution at offset 50
+  <> entry 0x011b 5 (be32 58)            -- YResolution at offset 58
+  <> entry 0x0128 3 (be16 2 <> be16 0)   -- ResolutionUnit: inches
+  <> be32 0                              -- next IFD offset
+  <> be32 300 <> be32 1                  -- XResolution = 300/1
+  <> be32 200 <> be32 1                  -- YResolution = 200/1
+  where entry tag typ val = be16 tag <> be16 typ <> be32 1 <> val
+
+-- the contents of a meta box locating an ispe (image spatial
+-- extents) box giving dimensions 640x480:
+avifMeta :: B.ByteString
+avifMeta = B.replicate 4 0 <>  -- version/flags
+             box "iprp" (box "ipco"
+               (box "ispe" (B.replicate 4 0 <> be32 640 <> be32 480)))
+
+-- an AVIF image with an ispe (image spatial extents) box:
+avifIspe :: B.ByteString
+avifIspe = box "ftyp" ("avif" <> B.replicate 4 0) <> box "meta" avifMeta
+
+-- an (animated) AVIF image with dimensions only in a tkhd box:
+avisTkhd :: B.ByteString
+avisTkhd = box "ftyp" ("avis" <> B.replicate 4 0)
+        <> box "moov" (box "trak" (box "tkhd"
+             (B.replicate 4 0 <>   -- version/flags
+              B.replicate 72 0 <>  -- times, track id, duration, etc.
+              be32 (640 * 65536) <> be32 (480 * 65536))))  -- 16.16
+
+-- lossless webp, 100x200: width - 1 in bits 0-13, height - 1 in
+-- bits 14-27 of the little-endian word after the 0x2f signature
+webpLossless :: B.ByteString
+webpLossless = webpFile $ "VP8L\0\0\0\0"
+  <> B.pack [0x2f, 0x63, 0xc0, 0x31, 0x00]
+
+-- lossy webp, 320x240: frame tag, then 9d 01 2a keyframe signature,
+-- then 14-bit width and height as little-endian 16-bit words
+webpLossy :: B.ByteString
+webpLossy = webpFile $ "VP8 \0\0\0\0"
+  <> B.pack [0x00, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00]
+
+-- extended webp, 1000x500: canvas width and height - 1 as 24-bit
+-- little-endian words after the flags
+webpExtended :: B.ByteString
+webpExtended = webpFile $ "VP8X" <> B.replicate 8 0
+  <> B.pack [0xe7, 0x03, 0x00, 0xf3, 0x01, 0x00]
+
+-- zlib-compressed "<</MediaBox [0 0 100 200]>>"
+compressedMediaBox :: B.ByteString
+compressedMediaBox = B.pack
+  [ 120, 156, 179, 177, 209, 247, 77, 77, 201, 76, 116, 202, 175, 80
+  , 136, 54, 80, 48, 80, 48, 52, 48, 80, 48, 50, 48, 136, 181, 179
+  , 3, 0, 104, 186, 6, 232 ]
+
+tests :: [TestTree]
+tests =
+  [ testGroup "imageType"
+    [ testCase "png" $ imageType "\x89PNG\r\n\x1a\n" @?= Just Png
+    , testCase "gif" $ imageType "GIF89a" @?= Just Gif
+    , testCase "tiff (little-endian)" $ imageType "II\x2a\0" @?= Just Tiff
+    , testCase "tiff (big-endian)" $ imageType "MM\0\x2a" @?= Just Tiff
+    , testCase "jpeg with app segment" $ imageType jpegApp0 @?= Just Jpeg
+    , testCase "jpeg without app segment" $ imageType jpegBare @?= Just Jpeg
+    , testCase "pdf" $ imageType "%PDF-1.5" @?= Just Pdf
+    , testCase "eps" $ imageType "%!PS-Adobe-3.0 EPSF-3.0" @?= Just Eps
+    , testCase "svg" $ imageType (svgFile "") @?= Just Svg
+    , testCase "svg with xml declaration" $
+        imageType ("<?xml version=\"1.0\"?>\n" <> svgFile "") @?= Just Svg
+    , testCase "uppercase svg with xml declaration" $
+        imageType "<?xml version=\"1.0\"?>\n<SVG></SVG>" @?= Just Svg
+    , testCase "xml that is not svg" $
+        imageType "<?xml version=\"1.0\"?>\n<html><p>svg</p></html>"
+          @?= Nothing
+    , testCase "svg with BOM" $
+        imageType ("\xef\xbb\xbf" <> svgFile "") @?= Just Svg
+    , testCase "emf" $
+        imageType (emfFile [0,0,1,1] [1,1] [1,1]) @?= Just Emf
+    , testCase "webp" $ imageType webpLossless @?= Just Webp
+    , testCase "avif brand" $ imageType avifIspe @?= Just Avif
+    , testCase "avis brand" $ imageType avisTkhd @?= Just Avif
+    , testCase "other ISO media is not avif" $
+        imageType (box "ftyp" ("isom" <> B.replicate 4 0)) @?= Nothing
+    , testCase "garbage" $ imageType "garbage!" @?= Nothing
+    ]
+  , testGroup "numUnit"
+    [ testCase "number and unit" $ numUnit "3cm" @?= Just (3.0, "cm")
+    , testCase "space between number and unit" $
+        numUnit "3 cm" @?= Just (3.0, "cm")
+    , testCase "bare number" $ numUnit "3.5" @?= Just (3.5, "")
+    , testCase "no number" $ numUnit "cm" @?= Nothing
+    , testCase "lengthToDim with space" $
+        lengthToDim "3 cm" @?= Just (Centimeter 3)
+    ]
+  , testGroup "imageSize"
+    [ testGroup "eps"
+      [ testCase "zero origin" $
+          imageSize def "%!PS EPSF\n%%BoundingBox: 0 0 612 792\n"
+            @?= Right (ImageSize 612 792 72 72)
+      , testCase "nonzero origin" $
+          imageSize def "%!PS EPSF\n%%BoundingBox: 10 20 110 220\n"
+            @?= Right (ImageSize 100 200 72 72)
+      , testCase "negative origin" $
+          imageSize def "%!PS EPSF\n%%BoundingBox: -10 -10 90 190\n"
+            @?= Right (ImageSize 100 200 72 72)
+      ]
+    , testGroup "pdf"
+      [ testCase "MediaBox" $
+          imageSize def "%PDF-1.4\n<</MediaBox [0 0 612 792]>>"
+            @?= Right (ImageSize 612 792 72 72)
+      , testCase "MediaBox in compressed object stream" $
+          imageSize def ("%PDF-1.5\n<</Type /ObjStm>>\nstream\n"
+                          <> compressedMediaBox <> "\nendstream\n")
+            @?= Right (ImageSize 100 200 72 72)
+      , testCase "corrupt compressed object stream" $
+          imageSize def ("%PDF-1.5\n<</Type /ObjStm>>\nstream\n"
+                          <> "NOT ZLIB DATA\nendstream\n")
+            @?= Left "could not determine PDF size"
+      , testCase "MediaBox after corrupt object stream" $
+          imageSize def ("%PDF-1.5\n<</Type /ObjStm>>\nstream\n"
+                          <> "NOT ZLIB DATA\nendstream\n"
+                          <> "<</MediaBox [0 0 300 400]>>")
+            @?= Right (ImageSize 300 400 72 72)
+      , testCase "MediaBox after empty object stream" $
+          imageSize def ("%PDF-1.5\n<</Type /ObjStm>>\nstream\n"
+                          <> "endstream<</MediaBox [0 0 25 50]>>")
+            @?= Right (ImageSize 25 50 72 72)
+      ]
+    , testGroup "svg"
+      [ testCase "width and height attributes" $
+          imageSize def (svgFile "width=\"50\" height=\"60\"")
+            @?= Right (ImageSize 50 60 96 96)
+      , testCase "viewBox fallback" $
+          imageSize def (svgFile "viewBox=\"0 0 100 200\"")
+            @?= Right (ImageSize 100 200 96 96)
+      , testCase "viewBox with commas" $
+          imageSize def (svgFile "viewBox=\"0,0,100,200\"")
+            @?= Right (ImageSize 100 200 96 96)
+      , testCase "viewBox with fractional values" $
+          imageSize def (svgFile "viewBox=\"0, 0, 210.5, 297.3\"")
+            @?= Right (ImageSize 210 297 96 96)
+      , testCase "no size information" $
+          imageSize def (svgFile "")
+            @?= Left "could not determine SVG size"
+      ]
+    , testGroup "emf"
+      [ testCase "size and dpi from header" $
+          imageSize def (emfFile [0,0,10000,5000] [1024,768] [320,240])
+            @?= Right (ImageSize 320 160 81 81)
+      , testCase "nonzero frame origin" $
+          imageSize def (emfFile [2000,1000,12000,6000] [1024,768] [320,240])
+            @?= Right (ImageSize 320 160 81 81)
+      , testCase "zero-size reference device" $
+          imageSize def (emfFile [0,0,10000,5000] [1024,768] [0,240])
+            @?= Left "could not determine EMF size"
+      ]
+    , testGroup "webp"
+      [ testCase "lossless (VP8L)" $
+          imageSize def webpLossless @?= Right (ImageSize 100 200 96 96)
+      , testCase "lossy (VP8)" $
+          imageSize def webpLossy @?= Right (ImageSize 320 240 96 96)
+      , testCase "extended (VP8X)" $
+          imageSize def webpExtended @?= Right (ImageSize 1000 500 96 96)
+      ]
+    , testGroup "avif"
+      [ testCase "ispe box" $
+          imageSize def avifIspe @?= Right (ImageSize 640 480 96 96)
+      , testCase "tkhd box" $
+          imageSize def avisTkhd @?= Right (ImageSize 640 480 96 96)
+      , testCase "meta box with 64-bit largesize" $
+          imageSize def (box "ftyp" ("avif" <> B.replicate 4 0)
+                          <> largeBox "meta" avifMeta)
+            @?= Right (ImageSize 640 480 96 96)
+      , testCase "meta box with size 0 (extends to end of file)" $
+          imageSize def (box "ftyp" ("avif" <> B.replicate 4 0)
+                          <> zeroBox "meta" avifMeta)
+            @?= Right (ImageSize 640 480 96 96)
+      , testCase "unknown box before meta box" $
+          imageSize def (box "ftyp" ("avif" <> B.replicate 4 0)
+                          <> box "free" "junk" <> box "meta" avifMeta)
+            @?= Right (ImageSize 640 480 96 96)
+      ]
+    , testGroup "png"  -- headers without image data, so these only
+                       -- succeed if no decoding is attempted
+      [ testCase "no pHYs chunk" $
+          imageSize def (pngFile [] 640 480)
+            @?= Right (ImageSize 640 480 72 72)
+      , testCase "pHYs in pixels per meter" $
+          imageSize def (pngFile [physChunk 1 3937 3937] 640 480)
+            @?= Right (ImageSize 640 480 99 99)
+      , testCase "pHYs with unknown unit" $
+          imageSize def (pngFile [physChunk 0 4 3] 640 480)
+            @?= Right (ImageSize 640 480 72 72)
+      , testCase "pHYs after another chunk" $
+          imageSize def (pngFile [ pngChunk "tEXt" "k\0v"
+                                 , physChunk 1 3937 3937 ] 640 480)
+            @?= Right (ImageSize 640 480 99 99)
+      ]
+    , testGroup "jpeg"  -- headers without image data, so these only
+                        -- succeed if no decoding is attempted
+      [ testCase "jfif dpi" $
+          imageSize def (jpegFile [jfifSeg 1 96 96] 640 480)
+            @?= Right (ImageSize 640 480 96 96)
+      , testCase "jfif density in dots per cm" $
+          imageSize def (jpegFile [jfifSeg 2 100 100] 640 480)
+            @?= Right (ImageSize 640 480 254 254)
+      , testCase "jfif aspect ratio only" $
+          imageSize def (jpegFile [jfifSeg 0 1 1] 640 480)
+            @?= Right (ImageSize 640 480 72 72)
+      , testCase "exif resolution" $
+          imageSize def (jpegFile [exifSeg] 640 480)
+            @?= Right (ImageSize 640 480 300 200)
+      , testCase "exif overrides jfif" $
+          imageSize def (jpegFile [jfifSeg 1 96 96, exifSeg] 640 480)
+            @?= Right (ImageSize 640 480 300 200)
+      , testCase "no app segments" $
+          imageSize def (jpegFile [jpegSeg 0xdb (B.replicate 65 0)] 640 480)
+            @?= Right (ImageSize 640 480 72 72)
+      , testCase "defective zero density defaults to 72 (#6880)" $
+          imageSize def (jpegFile [jfifSeg 1 0 0] 640 480)
+            @?= Right (ImageSize 640 480 72 72)
+      ]
+    , testGroup "fixtures"
+      [ testCase "lalune.jpg" $ do
+          img <- B.readFile "lalune.jpg"
+          imageSize def img @?= Right (ImageSize 250 250 120 120)
+      , testCase "fb2/test-small.png" $ do
+          img <- B.readFile "fb2/test-small.png"
+          imageSize def img @?= Right (ImageSize 48 32 71 71)
+      , testCase "bodybg.gif" $ do
+          img <- B.readFile "bodybg.gif"
+          imageSize def img @?= Right (ImageSize 230 334 72 72)
+      ]
+    ]
+  ]
diff --git a/test/Tests/MediaBag.hs b/test/Tests/MediaBag.hs
--- a/test/Tests/MediaBag.hs
+++ b/test/Tests/MediaBag.hs
@@ -6,6 +6,7 @@
 -- import Tests.Helpers
 import Text.Pandoc.Class.IO (extractMedia)
 import Text.Pandoc.Class (fillMediaBag, runIOorExplode)
+import Text.Pandoc.MediaBag (insertMedia, lookupMedia, mediaPath)
 import System.IO.Temp (withTempDirectory)
 import System.FilePath
 import Text.Pandoc.Builder as B
@@ -14,6 +15,34 @@
 
 tests :: [TestTree]
 tests = [
+  testCase "insertMedia mediaPath sanitization" $ do
+      -- a ".." substring that is not a path component is harmless
+      -- and should not cause the file to be renamed:
+      let bag = insertMedia "foo..bar.png" Nothing "contents" mempty
+      (mediaPath <$> lookupMedia "foo..bar.png" bag) @?= Just "foo..bar.png"
+      -- a ".." path component must not survive into mediaPath:
+      let bag2 = insertMedia "../evil.png" Nothing "contents" mempty
+      case lookupMedia "../evil.png" bag2 of
+        Nothing -> assertFailure "item not found in media bag"
+        Just item -> assertBool "mediaPath contains a .. component"
+          (".." `notElem` splitDirectories (mediaPath item)),
+  testCase "path canonicalization" $ do
+      -- redundant . and .. components are collapsed, so equivalent
+      -- spellings of a path refer to the same item:
+      let bag = insertMedia "img/../sub/./lalune.png" Nothing "contents" mempty
+      (mediaPath <$> lookupMedia "sub/lalune.png" bag) @?= Just "sub/lalune.png"
+      (mediaPath <$> lookupMedia "img/../sub/lalune.png" bag)
+        @?= Just "sub/lalune.png",
+  testCase "no mediaPath collisions between escaped and literal keys" $ do
+      -- "a%20b.png" used to unescape to the same mediaPath as the
+      -- literal "a b.png", so one clobbered the other on extraction:
+      let bag = insertMedia "a%20b.png" Nothing "contents1" $
+                insertMedia "a b.png" Nothing "contents2" mempty
+      case (lookupMedia "a%20b.png" bag, lookupMedia "a b.png" bag) of
+        (Just i1, Just i2) -> assertBool
+          "escaped and literal keys share a mediaPath"
+          (mediaPath i1 /= mediaPath i2)
+        _ -> assertFailure "items not found in media bag",
   testCase "test fillMediaBag & extractMedia" $
       withTempDirectory "." "extractMediaTest" $ \tmpdir -> do
         -- Use absolute paths so the test does not need to change
@@ -27,7 +56,9 @@
                   -- absolute path -> extracted with hashed name
                   B.para (B.image (T.pack absLalune) "" mempty) <>
                   B.para (B.image "data:image/png;base64,cHJpbnQgImhlbGxvIgo=;.lua+%2f%2e%2e%2f%2e%2e%2fa%2elua" "" mempty) <>
-                  B.para (B.image "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" "" mempty)
+                  B.para (B.image "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" "" mempty) <>
+                  -- the data: scheme is case-insensitive
+                  B.para (B.image "DATA:image/gif;base64,dXBwZXJjYXNlIGRhdGEgdXJpIHRlc3QK" "" mempty)
         let fooDir = absTmpdir </> "foo"
         runIOorExplode $ do
           fillMediaBag d
@@ -42,6 +73,9 @@
           (exists3 && not exists4)
         exists5 <- doesFileExist (fooDir </> "d5fceb6532643d0d84ffe09c40c481ecdf59e15a.gif")
         assertBool "data uri with gif is not properly decoded" exists5
+        exists5a <- doesFileExist
+          (fooDir </> "81c7546d23179ce1b344a763aa9038c3a8ff85d0.gif")
+        assertBool "data uri with uppercase scheme is not extracted" exists5a
         -- double-encoded version:
         let e = B.doc $
                   B.para (B.image "data:image/png;base64,cHJpbnQgInB3bmVkIgo=;.lua+%252f%252e%252e%252f%252e%252e%252fb%252elua" "" mempty)
diff --git a/test/Tests/Readers/Docx.hs b/test/Tests/Readers/Docx.hs
--- a/test/Tests/Readers/Docx.hs
+++ b/test/Tests/Readers/Docx.hs
@@ -116,6 +116,10 @@
             "docx/links.docx"
             "docx/links.native"
           , testCompare
+            "hyperlinks with ScreenTips"
+            "docx/link_tooltips.docx"
+            "docx/link_tooltips.native"
+          , testCompare
             "hyperlinks in <w:instrText> tag"
             "docx/instrText_hyperlink.docx"
             "docx/instrText_hyperlink.native"
diff --git a/test/Tests/Readers/LaTeX.hs b/test/Tests/Readers/LaTeX.hs
--- a/test/Tests/Readers/LaTeX.hs
+++ b/test/Tests/Readers/LaTeX.hs
@@ -250,6 +250,51 @@
             mconcat ["^^" <> T.pack [i] | i <- hex] =?>
             para (str $ T.pack $ ['p'..'y']++['!'..'&'])
           ]
+        , testGroup "symbol commands"
+          [ "qed" =:
+            "A\\qed" =?> para (str "A\xa0\x25FB")
+          ]
+        , testGroup "newif"
+          [ "newif defines conditional" =:
+            "\\newif\\iffoo\\footrue\\iffoo yes\\fi" =?>
+            para (str "yes")
+          , "newif requires name starting with if" =:
+            "\\newif\\foobar\\foobar hi\\fi" =?>
+            para (str "hi")
+          ]
+        , testGroup "conditionals"
+          [ "unknown conditional nested in skipped branch" =:
+            "\\iffalse X\\ifdim\\wd0>0pt Y\\fi Z\\fi W" =?>
+            para "W"
+          , "nested conditionals with else branches" =:
+            "\\iftrue A\\iffalse B\\else C\\fi D\\else E\\fi F" =?>
+            para "ACDF"
+          , "newif conditional nested in skipped branch" =:
+            "\\newif\\iffoo\\iffalse x\\iffoo y\\fi z\\fi w" =?>
+            para "w"
+          , "nested conditional in else branch" =:
+            "\\iffalse a\\else b\\iffalse c\\else d\\fi e\\fi f" =?>
+            para "bdef"
+          ]
+        , testGroup "ligatures"
+          [ "quote ligatures in normal text" =:
+            "it's ``x'' `y'" =?>
+            para ("it\8217s " <> doubleQuoted "x" <> " " <> singleQuoted "y")
+          , "apostrophe in texttt stays ASCII" =:
+            "\\texttt{it's}" =?> para (code "it's")
+          , "backtick in texttt stays ASCII" =:
+            "\\texttt{`x'}" =?> para (code "`x'")
+          ]
+        , testGroup "urls"
+          [ "url with escaped %" =:
+            "\\url{http://example.com/a\\%b}" =?>
+            para (linkWith ("",["uri"],[]) "http://example.com/a%b" ""
+                   (str "http://example.com/a%b"))
+          , "url with escaped backslash" =:
+            "\\url{http://example.com/a\\\\b}" =?>
+            para (linkWith ("",["uri"],[]) "http://example.com/a\\b" ""
+                   (str "http://example.com/a\\b"))
+          ]
         , testGroup "memoir scene breaks"
           [ "plainbreak" =:
             "hello\\plainbreak{2}goodbye" =?>
diff --git a/test/Tests/Readers/Org/Directive.hs b/test/Tests/Readers/Org/Directive.hs
--- a/test/Tests/Readers/Org/Directive.hs
+++ b/test/Tests/Readers/Org/Directive.hs
@@ -52,6 +52,18 @@
                   ] =?>
         para "a^b"
 
+    , "disable braced sub/superscript syntax" =:
+        T.unlines [ "#+OPTIONS: ^:nil"
+                  , "a^{b} c_{d}"
+                  ] =?>
+        para "a^{b} c_{d}"
+
+    , "interpret only braced sub/superscripts" =:
+        T.unlines [ "#+OPTIONS: ^:{}"
+                  , "a^b a^{b} a^(b)"
+                  ] =?>
+        para ("a^b a" <> superscript "b" <> " a^(b)")
+
     , "directly select drawers to be exported" =:
         T.unlines [ "#+OPTIONS: d:(\"IMPORTANT\")"
                   , ":IMPORTANT:"
diff --git a/test/Tests/Readers/Org/Inline.hs b/test/Tests/Readers/Org/Inline.hs
--- a/test/Tests/Readers/Org/Inline.hs
+++ b/test/Tests/Readers/Org/Inline.hs
@@ -112,6 +112,14 @@
       "line \\\\ \nbreak" =?>
       para ("line" <> linebreak <> "break")
 
+  , "Bare backslash" =:
+      "a \\ b" =?>
+      para ("a" <> space <> "\\" <> space <> "b")
+
+  , "Unclosed LaTeX environment" =:
+      "\\begin{align} more text" =?>
+      para ("\\begin{align}" <> space <> "more" <> space <> "text")
+
   , "Inline note" =:
       "[fn::Schreib mir eine E-Mail]" =?>
       para (note $ para "Schreib mir eine E-Mail")
diff --git a/test/Tests/Readers/Org/Inline/Note.hs b/test/Tests/Readers/Org/Inline/Note.hs
--- a/test/Tests/Readers/Org/Inline/Note.hs
+++ b/test/Tests/Readers/Org/Inline/Note.hs
@@ -82,6 +82,13 @@
       , headerWith ("headline", [], []) 2 "Headline"
       ]
 
+  , "Inline note with hyphen and underscore in label" =:
+      "Some text[fn:my_note-1: the note]" =?>
+      para (mconcat
+           [ "Some", space, "text"
+           , note . para $ "the" <> space <> "note"
+           ])
+
   , "Footnote followed by two blank lines" =:
       T.unlines [ "footnote[fn:blanklines]"
                 , ""
diff --git a/test/Tests/Shared.hs b/test/Tests/Shared.hs
--- a/test/Tests/Shared.hs
+++ b/test/Tests/Shared.hs
@@ -34,6 +34,10 @@
         , testGroup "collapseFilePath" testCollapse
         , testGroup "toLegacyTable" testLegacyTable
         , testGroup "table of contents" testTOC
+        , testGroup "stringifyInlines"
+          [ testProperty "stringifyInlines matches stringify"
+              stringifyInlinesMatchesStringify
+          ]
         , testGroup "makeSections"
           [ testProperty "makeSections is idempotent" makeSectionsIsIdempotent
           , testCase "makeSections is idempotent for test case" $
@@ -44,6 +48,10 @@
                       (makeSections False Nothing d' == d')
           ]
         ]
+
+stringifyInlinesMatchesStringify :: [Inline] -> Bool
+stringifyInlinesMatchesStringify ils =
+  stringifyInlines ils == stringify ils
 
 makeSectionsIsIdempotent :: [Block] -> Bool
 makeSectionsIsIdempotent d =
diff --git a/test/Tests/Writers/Docx.hs b/test/Tests/Writers/Docx.hs
--- a/test/Tests/Writers/Docx.hs
+++ b/test/Tests/Writers/Docx.hs
@@ -64,6 +64,22 @@
             def
             "docx/links.native"
             "docx/golden/links.docx"
+          , testCase "link titles become ScreenTips (#11869)" $ do
+              doc <- documentXml def $ Pandoc mempty
+                [ Para [ Link ("", [], []) [Str "external"]
+                           ("https://example.org/r.pdf", "Annual report")
+                       , Space
+                       , Link ("", [], []) [Str "internal"]
+                           ("#methods", "Jump to Methods")
+                       , Space
+                       , Link ("", [], []) [Str "untitled"]
+                           ("https://example.org", "")
+                       ]
+                , Header 1 ("methods", [], []) [Str "Methods"]
+                ]
+              map (findAttr (wmlName "tooltip"))
+                  (findElements (wmlName "hyperlink") doc)
+                @?= [Just "Annual report", Just "Jump to Methods", Nothing]
           , docxTest
             "inline image"
             def{ writerExtensions =
@@ -107,6 +123,11 @@
             def
             "docx/lists.native"
             "docx/golden/lists.docx"
+          , docxTest
+            "CSL bibliography (hanging indent and spacing)"
+            def
+            "docx/csl_bibliography.native"
+            "docx/golden/csl_bibliography.docx"
           , docxTest
             "lists continuing after interruption"
             def
diff --git a/test/ansi-test.ansi b/test/ansi-test.ansi
--- a/test/ansi-test.ansi
+++ b/test/ansi-test.ansi
@@ -34,6 +34,13 @@
 │
 │ │ Nested block quote
 
+Numbered list item beginning with a block quote (regression test for
+issue #11804, where the leading bar was dropped on the block quote’s
+first line because it shared a line with the list marker):
+
+1. 
+   │ List item block quote
+
 Multiline table with caption:
 
 Centered    Left       Right        Default aligned
diff --git a/test/ansi-test.txt b/test/ansi-test.txt
--- a/test/ansi-test.txt
+++ b/test/ansi-test.txt
@@ -38,6 +38,12 @@
 >
 > > Nested block quote
 
+Numbered list item beginning with a block quote (regression test for
+issue #11804, where the leading bar was dropped on the block quote's
+first line because it shared a line with the list marker):
+
+1.  > List item block quote
+
 Multiline table with caption:
 
 :  Here's the caption.
diff --git a/test/asciidoc-reader.native b/test/asciidoc-reader.native
--- a/test/asciidoc-reader.native
+++ b/test/asciidoc-reader.native
@@ -256,3855 +256,3837 @@
       ]
   , Para
       [ Image
-          ( "" , [ "red icon" ] , [] )
-          []
-          ( "./images/icons/heart.png" , "" )
-      ]
-  , Para [ Str "anchor:tiger" ]
-  , Para [ Strong [ Str "*bold*" ] ]
-  , Para
-      [ Link
-          ( "" , [] , [] )
-          [ Str "Get" , Space , Str "Report" ]
-          ( "downloads/report.pdf" , "" )
-      ]
-  , Para
-      [ Link
-          ( "" , [] , [] )
-          [ Str "tools.html#editors" ]
-          ( "tools.html#editors" , "" )
-      ]
-  , Para
-      [ Link
-          ( "" , [] , [] )
-          [ Str "Your" , Space , Str "files" ]
-          ( "file:///home/username" , "" )
-      ]
-  , Para [ Str "Tricky" , Space , Str "cases:" ]
-  , Para
-      [ Link
-          ( "" , [] , [] )
-          [ Str "Get" , Space , Str "Report" ]
-          ( "My Documents/report.pdf" , "" )
-      ]
-  , Para
-      [ Link
-          ( "" , [] , [] )
-          [ Str "Get" , Space , Str "Report" ]
-          ( "My&#32;Documents/report.pdf" , "" )
-      ]
-  , Para
-      [ Link
-          ( "" , [] , [] )
-          [ Str "Get" , Space , Str "Report" ]
-          ( "My%20Documents/report.pdf" , "" )
-      ]
-  , Para
-      [ Link
-          ( "" , [] , [] )
-          [ Str "https://example.org/now_this__link_works.html" ]
-          ( "https://example.org/now_this__link_works.html" , "" )
-      ]
-  , Para
-      [ Link
-          ( "" , [] , [] )
-          [ Str "Subscribe" ]
-          ( "join@discuss.example.org" , "" )
-      ]
-  , Para
-      [ Link
-          ( "" , [ "mail" ] , [] )
-          [ Str "Click,"
-          , Space
-          , Str "subscribe,"
-          , Space
-          , Str "and"
-          , Space
-          , Str "participate!"
-          ]
-          ( "join@discuss.example.org" , "" )
-      ]
-  , Para
-      [ Link
-          ( "" , [ "cross-reference" ] , [] )
-          [ Str "use"
-          , Space
-          , Str "attributes"
-          , Space
-          , Str "within"
-          , Space
-          , Str "the"
-          , Space
-          , Str "link"
-          , Space
-          , Str "macro"
-          ]
-          ( "#link-macro-attributes" , "" )
-      ]
-  , Figure
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ Plain
-          [ Image
-              ( "" , [] , [] ) [ Str "Sunset" ] ( "sunset.jpg" , "" )
-          ]
-      ]
-  , Figure
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ Plain [ Image ( "" , [] , [] ) [] ( "name.png" , "" ) ] ]
-  , Figure
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ Plain
-          [ Image
-              ( ""
-              , []
-              , [ ( "width" , "300px" ) , ( "height" , "400px" ) ]
-              )
-              [ Str "Sunset" ]
-              ( "sunset.jpg" , "" )
-          ]
-      ]
-  , Div
-      ( ""
-      , []
-      , [ ( "wrapper" , "1" )
-        , ( "alt" , "Sunset" )
-        , ( "height" , "400" )
-        , ( "width" , "300" )
-        ]
-      )
-      [ Figure
-          ( "" , [] , [] )
-          (Caption Nothing [])
-          [ Plain [ Image ( "" , [] , [] ) [] ( "sunset.jpg" , "" ) ]
-          ]
-      ]
-  , Para [ Math DisplayMath "e=mc^2\n" ]
-  , Para [ Math DisplayMath "sin n / 3\n" ]
-  , Para [ Math DisplayMath "e^i\n" ]
-  , Header
-      2
-      ( "_attribute_substitutions" , [] , [] )
-      [ Str "Attribute" , Space , Str "substitutions" ]
-  , Para [ Str "Foo" , Space , Str "bar" , Space , Str "baz" ]
-  , Para [ Str "{nonexistent}" ]
-  , Para
-      [ Str "Built"
-      , Space
-      , Str "in:"
-      , Space
-      , Str "xyz"
-      , Space
-      , Str "a\160b\8203c'd\8216"
-      ]
-  , Header
-      2
-      ( "_bold_and_italic" , [] , [] )
-      [ Str "Bold" , Space , Str "and" , Space , Str "italic" ]
-  , Para
-      [ Str "Constrained:"
-      , Space
-      , Strong
-          [ Str "this"
-          , Space
-          , Str "is"
-          , Space
-          , Str "bold"
-          , Space
-          , Emph [ Str "and" , Space , Str "italic" ]
-          ]
-      , Str "."
-      ]
-  , Para
-      [ Str "Unconstrained:"
-      , Space
-      , Str "wild"
-      , Strong
-          [ Str "content"
-          , Emph [ Str "with" , Space , Str "italic" ]
-          , Str "stuff"
-          ]
-      , Str "."
-      ]
-  , Header 2 ( "_monospace" , [] , [] ) [ Str "Monospace" ]
-  , Para [ Code ( "" , [] , [] ) "simple" ]
-  , Para
-      [ Code ( "" , [] , [] ) "complex"
-      , Space
-      , Strong
-          [ Code ( "" , [] , [] ) "with"
-          , Space
-          , Code ( "" , [] , [] ) "bold"
-          ]
-      , Space
-      , Code ( "" , [] , [] ) "text"
-      , Space
-      , Code ( "" , [] , [] ) "and"
-      , Space
-      , Code ( "" , [] , [] ) "a"
-      , Space
-      , Link
-          ( "" , [] , [] )
-          [ Code ( "" , [] , [] ) "foo.html" ]
-          ( "foo.html" , "" )
-      ]
-  , Para
-      [ Str "unconstrained"
-      , Code ( "" , [] , [] ) "wwow"
-      , Str "okay"
-      ]
-  , Header
-      2
-      ( "_span_and_inline_attributes" , [] , [] )
-      [ Str "Span"
-      , Space
-      , Str "and"
-      , Space
-      , Str "inline"
-      , Space
-      , Str "attributes"
-      ]
-  , Para
-      [ Span
-          ( "" , [ "red" ] , [] )
-          [ Str "Bonjour" , Space , Strong [ Str "monsieur" ] ]
-      ]
-  , Para
-      [ Str "Un"
-      , Span ( "" , [ "red" ] , [] ) [ Str "constrained" ]
-      , Str "content"
-      ]
-  , Para
-      [ Str "With"
-      , Space
-      , Span
-          ( "" , [ "mark" ] , [] )
-          [ Str "no" , Space , Str "attribute" ]
-      , Space
-      , Str "it\8217s"
-      , Space
-      , Str "highlighted."
-      ]
-  , Header
-      2
-      ( "_sub_and_superscript" , [] , [] )
-      [ Str "Sub"
-      , Space
-      , Str "and"
-      , Space
-      , Str "superscript"
-      ]
-  , Para [ Str "H" , Subscript [ Str "2" ] , Str "O" ]
-  , Para
-      [ Str "H"
-      , Subscript [ Str "a" , Space , Str "b" ]
-      , Str "O"
-      ]
-  , Para
-      [ Str "Not"
-      , Space
-      , Str "subscript:"
-      , Space
-      , Str "H~a"
-      , Space
-      , Str "b~O."
-      ]
-  , Para [ Str "H^2&O" ]
-  , Para
-      [ Str "H"
-      , Superscript [ Str "a" , Space , Str "b" ]
-      , Str "O"
-      ]
-  , Para
-      [ Str "Not"
-      , Space
-      , Str "subscript:"
-      , Space
-      , Str "H^a"
-      , Space
-      , Str "b^O."
-      ]
-  , Header
-      2 ( "_passthrough" , [] , [] ) [ Str "Passthrough" ]
-  , Para
-      [ Str "Here"
-      , Space
-      , Str "the"
-      , Space
-      , Str "special"
-      , Space
-      , Str "characters"
-      , Space
-      , Str "just"
-      , Space
-      , Str "come"
-      , Space
-      , Str "through"
-      , Space
-      , Str "as"
-      , Space
-      , Str "literal:"
-      ]
-  , Para [ Str "<b>*test*</b>" ]
-  , Para [ Str "xx<b>*test*</b>xx" ]
-  , Para
-      [ Str "But"
-      , Space
-      , Str "here"
-      , Space
-      , Str "they"
-      , Space
-      , Str "are"
-      , Space
-      , Str "passed"
-      , Space
-      , Str "through:"
-      ]
-  , Para [ Str "xx" , Strong [ Str "*test*" ] , Str "xx" ]
-  , Header 2 ( "_quoted" , [] , [] ) [ Str "Quoted" ]
-  , Para
-      [ Quoted DoubleQuote [ Str "double" , Space , Str "quoted" ]
-      ]
-  , Para
-      [ Quoted SingleQuote [ Str "single" , Space , Str "quoted" ]
-      ]
-  , Header 2 ( "_footnotes" , [] , [] ) [ Str "Footnotes" ]
-  , Para
-      [ Str "Double"
-      , Note
-          [ Para
-              [ Str "The"
-              , Space
-              , Str "double"
-              , Space
-              , Str "hail-and-rainbow"
-              , Space
-              , Str "level"
-              , Space
-              , Str "makes"
-              , Space
-              , Str "my"
-              , Space
-              , Str "toes"
-              , Space
-              , Str "tingle."
-              ]
-          ]
-      ]
-  , Para
-      [ Str "A"
-      , Space
-      , Str "bold"
-      , Space
-      , Str "statement!"
-      , Note
-          [ Para
-              [ Str "Opinions"
-              , Space
-              , Str "are"
-              , Space
-              , Str "my"
-              , Space
-              , Str "own."
-              ]
-          ]
-      , SoftBreak
-      , Str "Another"
-      , Space
-      , Str "outrageous"
-      , Space
-      , Str "statement."
-      , Note
-          [ Para
-              [ Str "Opinions"
-              , Space
-              , Str "are"
-              , Space
-              , Str "my"
-              , Space
-              , Str "own."
-              ]
-          ]
-      ]
-  , Header
-      1
-      ( "_block_markup" , [] , [] )
-      [ Str "Block" , Space , Str "markup" ]
-  , Header 2 ( "_sections" , [] , [] ) [ Str "Sections" ]
-  , Header
-      3
-      ( "_another_level" , [] , [] )
-      [ Str "Another" , Space , Str "level" ]
-  , Header
-      4 ( "_level_5" , [] , [] ) [ Str "Level" , Space , Str "5" ]
-  , Header
-      3
-      ( "_markdown_style" , [] , [] )
-      [ Str "Markdown" , Space , Str "style" ]
-  , Header
-      4
-      ( "_level_5_2" , [] , [] )
-      [ Str "Level" , Space , Str "5" ]
-  , Header
-      2
-      ( "_discrete_heading" , [] , [] )
-      [ Str "Discrete" , Space , Str "heading" ]
-  , Header
-      3
-      ( "" , [] , [] )
-      [ Str "A"
-      , Space
-      , Str "discrete"
-      , Space
-      , Str "heading,"
-      , Space
-      , Str "not"
-      , Space
-      , Str "a"
-      , Space
-      , Str "section"
-      ]
-  , Header 2 ( "_paragraph" , [] , [] ) [ Str "Paragraph" ]
-  , Para
-      [ Str "This"
-      , Space
-      , Str "is"
-      , Space
-      , Str "a"
-      , Space
-      , Str "paragraph"
-      , SoftBreak
-      , Str "whose"
-      , Space
-      , Str "source"
-      , Space
-      , Str "fits"
-      , Space
-      , Str "on"
-      , Space
-      , Str "two"
-      , Space
-      , Str "lines."
-      ]
-  , Para
-      [ Str "{.This"
-      , Space
-      , Str "is"
-      , Space
-      , Str "my"
-      , Space
-      , Str "title}"
-      , SoftBreak
-      , Str "A"
-      , Space
-      , Str "paragraph"
-      , Space
-      , Str "with"
-      , Space
-      , Str "a"
-      , Space
-      , Str "title."
-      ]
-  , Header
-      2
-      ( "_example_block" , [] , [] )
-      [ Str "Example" , Space , Str "block" ]
-  , Div
-      ( "" , [] , [] )
-      [ Div
-          ( "" , [ "title" ] , [] )
-          [ Para [ Str "Optional" , Space , Str "title" ] ]
-      , Para
-          [ Str "This"
-          , Space
-          , Str "is"
-          , Space
-          , Str "an"
-          , Space
-          , Str "example"
-          , Space
-          , Str "of"
-          , Space
-          , Str "an"
-          , Space
-          , Str "example"
-          , Space
-          , Str "block."
-          ]
-      ]
-  , Div
-      ( "" , [ "example" ] , [] )
-      [ Div
-          ( "" , [ "title" ] , [] )
-          [ Para [ Str "Optional" , Space , Str "title" ] ]
-      , Para
-          [ Str "Paragraph" , Space , Strong [ Str "one" ] , Str "." ]
-      , Para
-          [ Str "Paragraph" , Space , Strong [ Str "two" ] , Str "." ]
-      ]
-  , Header 2 ( "_admonition" , [] , [] ) [ Str "Admonition" ]
-  , Para [ Str "Simple" , Space , Str "form:" ]
-  , Div
-      ( "" , [ "warning" ] , [] )
-      [ Div ( "" , [ "title" ] , [] ) [ Para [ Str "Warning" ] ]
-      , Para
-          [ Str "This"
-          , Space
-          , Str "is"
-          , Space
-          , Str "very"
-          , Space
-          , Str "dangerous."
-          , SoftBreak
-          , Str "Don\8217t"
-          , Space
-          , Str "do"
-          , Space
-          , Str "it"
-          , Space
-          , Str "unless"
-          , Space
-          , Str "you"
-          , Space
-          , Str "understand"
-          , Space
-          , Str "the"
-          , Space
-          , Str "risks."
-          ]
-      ]
-  , Div
-      ( "" , [ "important" ] , [] )
-      [ Div
-          ( "" , [ "title" ] , [] )
-          [ Para
-              [ Str "Title"
-              , Space
-              , Str "of"
-              , Space
-              , Str "the"
-              , Space
-              , Str "admonition"
-              ]
-          ]
-      , Para [ Str "Remember:" ]
-      , OrderedList
-          ( 1 , DefaultStyle , DefaultDelim )
-          [ [ Para
-                [ Str "Don\8217t"
-                , Space
-                , Str "do"
-                , Space
-                , Str "this."
-                ]
-            ]
-          , [ Para
-                [ Str "And"
-                , Space
-                , Str "don\8217t"
-                , Space
-                , Str "do"
-                , Space
-                , Str "that."
-                ]
-            ]
-          ]
-      ]
-  , Header 2 ( "_sidebar" , [] , [] ) [ Str "Sidebar" ]
-  , Para
-      [ Str "A" , Space , Str "simple" , Space , Str "sidebar." ]
-  , Div
-      ( "" , [ "sidebar" ] , [] )
-      [ Div
-          ( "" , [ "title" ] , [] )
-          [ Para
-              [ Str "Optional"
-              , Space
-              , Str "Title"
-              , Space
-              , Strong
-                  [ Str "with"
-                  , Space
-                  , Str "strong"
-                  , Space
-                  , Str "emphasis"
-                  ]
-              ]
-          ]
-      , Para
-          [ Str "Here"
-          , Space
-          , Str "is"
-          , Space
-          , Str "a"
-          , Space
-          , Str "sidebar."
-          ]
-      , Div
-          ( "" , [ "tip" ] , [] )
-          [ Div ( "" , [ "title" ] , [] ) [ Para [ Str "Tip" ] ]
-          , Para
-              [ Str "It"
-              , Space
-              , Str "can"
-              , Space
-              , Str "contain"
-              , Space
-              , Str "any"
-              , Space
-              , Str "type"
-              , Space
-              , Str "of"
-              , Space
-              , Str "content."
-              ]
-          ]
-      ]
-  , Header
-      2
-      ( "_literal_block" , [] , [] )
-      [ Str "Literal" , Space , Str "block" ]
-  , Para
-      [ Str "Short"
-      , Space
-      , Str "indented"
-      , Space
-      , Str "code:"
-      ]
-  , CodeBlock
-      ( "" , [] , [] )
-      "$ ls -a\n$ cat /foo/bar/baz \\\n     /bi/bim/bop\n"
-  , CodeBlock
-      ( "" , [] , [] ) "This is\n  a literal block too.\n"
-  , CodeBlock
-      ( "" , [] , [] )
-      "  Fenced\n    $+ *a* literal\n\n****\nnot a sidebar\n****\n"
-  , Header 2 ( "_listing" , [] , [] ) [ Str "Listing" ]
-  , CodeBlock
-      ( "" , [ "ruby" ] , [] )
-      "require 'sinatra'\n\nget '/hi' do\n  \"Hello World!\"\nend"
-  , Para [ Str "Implied:" ]
-  , CodeBlock
-      ( "" , [ "ruby" ] , [] )
-      "require 'sinatra'\n\nget '/hi' do\n  \"Hello World!\"\nend"
-  , CodeBlock
-      ( "" , [ "ruby" ] , [] )
-      "# A function\ndef foo\n  return 42\nend"
-  , CodeBlock
-      ( "hello" , [ "haskell" ] , [] )
-      "putStrLn $ unwords [\"Hello\", \"world\"]"
-  , Para [ Str "Line" , Space , Str "numbering:" ]
-  , CodeBlock
-      ( "" , [] , [ ( "options" , "linenums" ) ] )
-      "puts 1\nputs 2\nputs 3"
-  , CodeBlock
-      ( "" , [] , [] ) "This doesn't have a language.\n  +=\"hi\""
-  , Para
-      [ Str "And"
-      , Space
-      , Str "with"
-      , Space
-      , Str "a"
-      , Space
-      , Str "callout"
-      , Space
-      , Str "list:"
-      ]
-  , CodeBlock
-      ( "" , [ "ruby" ] , [] )
-      "require 'sinatra' \9312\n\nget '/hi' do \9313 \9314\n  \"Hello World!\"\nend"
-  , Div
-      ( "" , [ "callout-list" ] , [] )
-      [ OrderedList
-          ( 1 , DefaultStyle , DefaultDelim )
-          [ [ Para [ Str "Library" , Space , Str "import" ] ]
-          , [ Para [ Str "URL" , Space , Str "mapping" ] ]
-          , [ Para [ Str "Response" , Space , Str "block" ] ]
-          ]
-      ]
-  , Para [ Str "Markdown-style" , Space , Str "fenced:" ]
-  , CodeBlock
-      ( "" , [ "ruby" ] , [] ) "def foo\n  return 5\nend"
-  , Header 2 ( "_verse" , [] , [] ) [ Str "Verse" ]
-  , BlockQuote
-      [ Para
-          [ Str "The"
-          , Space
-          , Str "fog"
-          , Space
-          , Str "comes"
-          , LineBreak
-          , Str "on"
-          , Space
-          , Str "little"
-          , Space
-          , Str "cat"
-          , Space
-          , Str "feet."
-          ]
-      , Para
-          [ Str "\8212"
-          , Space
-          , Str "Carl"
-          , Space
-          , Str "Sandburg,"
-          , Space
-          , Str "two"
-          , Space
-          , Str "lines"
-          , Space
-          , Str "from"
-          , Space
-          , Str "the"
-          , Space
-          , Str "poem"
-          , Space
-          , Str "Fog"
-          ]
-      ]
-  , BlockQuote
-      [ Para
-          [ Str "The"
-          , Space
-          , Str "fog"
-          , Space
-          , Str "comes"
-          , LineBreak
-          , Str "on"
-          , Space
-          , Str "little"
-          , Space
-          , Str "cat"
-          , Space
-          , Str "feet."
-          , LineBreak
-          , Str "It"
-          , Space
-          , Str "sits"
-          , Space
-          , Str "looking"
-          , LineBreak
-          , Str "over"
-          , Space
-          , Str "harbor"
-          , Space
-          , Str "and"
-          , Space
-          , Str "city"
-          , LineBreak
-          , Str "on"
-          , Space
-          , Str "silent"
-          , Space
-          , Str "haunches"
-          , LineBreak
-          , Str "and"
-          , Space
-          , Str "then"
-          , Space
-          , Str "moves"
-          , Space
-          , Str "on."
-          ]
-      , Para
-          [ Str "\8212"
-          , Space
-          , Str "Carl"
-          , Space
-          , Str "Sandburg,"
-          , Space
-          , Str "Fog"
-          ]
-      ]
-  , Header
-      2 ( "_collapsible" , [] , [] ) [ Str "Collapsible" ]
-  , Para
-      [ Str "Click"
-      , Space
-      , Str "here"
-      , Space
-      , Str "for"
-      , Space
-      , Str "more."
-      ]
-  , Div
-      ( ""
-      , [ "example" ]
-      , [ ( "options" , "collapsible,open" ) ]
-      )
-      [ Para
-          [ Str "This"
-          , Space
-          , Str "is"
-          , Space
-          , Str "collapsible."
-          ]
-      , Para
-          [ Str "It"
-          , Space
-          , Str "can"
-          , Space
-          , Str "be"
-          , Space
-          , Str "hidden."
-          ]
-      ]
-  , Div
-      ( "" , [] , [ ( "options" , "collapsible" ) ] )
-      [ Div
-          ( "" , [ "title" ] , [] )
-          [ Para [ Str "Click" , Space , Str "me!" ] ]
-      , Para
-          [ Str "This"
-          , Space
-          , Str "paragraph"
-          , Space
-          , Str "is"
-          , SoftBreak
-          , Str "also"
-          , Space
-          , Str "collapsible."
-          ]
-      ]
-  , Header 2 ( "_quote" , [] , [] ) [ Str "Quote" ]
-  , BlockQuote
-      [ Para
-          [ Str "Everybody"
-          , Space
-          , Str "remember"
-          , Space
-          , Str "where"
-          , Space
-          , Str "we"
-          , Space
-          , Str "parked."
-          ]
-      , Para
-          [ Str "\8212"
-          , Space
-          , Str "Captain"
-          , Space
-          , Str "James"
-          , Space
-          , Str "T."
-          , Space
-          , Str "Kirk,"
-          , Space
-          , Str "Star"
-          , Space
-          , Str "Trek"
-          , Space
-          , Str "IV:"
-          , Space
-          , Str "The"
-          , Space
-          , Str "Voyage"
-          , Space
-          , Str "Home"
-          ]
-      ]
-  , BlockQuote
-      [ Para
-          [ Str "Dennis:"
-          , Space
-          , Str "Come"
-          , Space
-          , Str "and"
-          , Space
-          , Str "see"
-          , Space
-          , Str "the"
-          , Space
-          , Str "violence"
-          , Space
-          , Str "inherent"
-          , Space
-          , Str "in"
-          , Space
-          , Str "the"
-          , Space
-          , Str "system."
-          , Space
-          , Str "Help!"
-          , Space
-          , Str "Help!"
-          , Space
-          , Str "I\8217m"
-          , Space
-          , Str "being"
-          , SoftBreak
-          , Str "repressed."
-          ]
-      , Para
-          [ Str "King"
-          , Space
-          , Str "Arthur:"
-          , Space
-          , Str "Bloody"
-          , Space
-          , Str "peasant!"
-          ]
-      , Para
-          [ Str "Dennis:"
-          , Space
-          , Str "Oh,"
-          , Space
-          , Str "what"
-          , Space
-          , Str "a"
-          , Space
-          , Str "giveaway!"
-          , Space
-          , Str "Did"
-          , Space
-          , Str "you"
-          , Space
-          , Str "hear"
-          , Space
-          , Str "that?"
-          , Space
-          , Str "Did"
-          , Space
-          , Str "you"
-          , Space
-          , Str "hear"
-          , Space
-          , Str "that,"
-          , Space
-          , Str "eh?"
-          , Space
-          , Str "That\8217s"
-          , Space
-          , Str "what"
-          , Space
-          , Str "I\8217m"
-          , SoftBreak
-          , Str "on"
-          , Space
-          , Str "about!"
-          , Space
-          , Str "Did"
-          , Space
-          , Str "you"
-          , Space
-          , Str "see"
-          , Space
-          , Str "him"
-          , Space
-          , Str "repressing"
-          , Space
-          , Str "me?"
-          , Space
-          , Str "You"
-          , Space
-          , Str "saw"
-          , Space
-          , Str "him,"
-          , Space
-          , Str "Didn\8217t"
-          , Space
-          , Str "you?"
-          ]
-      , Para
-          [ Str "\8212"
-          , Space
-          , Str "Monty"
-          , Space
-          , Str "Python"
-          , Space
-          , Str "and"
-          , Space
-          , Str "the"
-          , Space
-          , Str "Holy"
-          , Space
-          , Str "Grail"
-          ]
-      ]
-  , Div
-      ( "roads" , [ "movie" ] , [ ( "wrapper" , "1" ) ] )
-      [ BlockQuote
-          [ Para
-              [ Str "Roads?"
-              , Space
-              , Str "Where"
-              , Space
-              , Str "we\8217re"
-              , Space
-              , Str "going,"
-              , Space
-              , Str "we"
-              , Space
-              , Str "don\8217t"
-              , Space
-              , Str "need"
-              , Space
-              , Str "roads."
-              ]
-          , Para
-              [ Str "\8212"
-              , Space
-              , Str "Dr."
-              , Space
-              , Str "Emmett"
-              , Space
-              , Str "Brown"
-              ]
-          ]
-      ]
-  , Header 2 ( "_pass" , [] , [] ) [ Str "Pass" ]
-  , Para [ Str "pass" , Space , Emph [ Str "through" ] ]
-  , Header
-      2
-      ( "_open_block" , [] , [] )
-      [ Str "Open" , Space , Str "block" ]
-  , Div
-      ( "" , [] , [ ( "key" , "a value" ) ] )
-      [ Div
-          ( "" , [ "title" ] , [] )
-          [ Para [ Str "A" , Space , Str "title." ] ]
-      , Para
-          [ Str "Any"
-          , Space
-          , Str "content"
-          , Space
-          , Str "can"
-          , Space
-          , Str "go"
-          , Space
-          , Str "here:"
-          ]
-      , OrderedList
-          ( 1 , DefaultStyle , DefaultDelim )
-          [ [ Para [ Str "one" ] ] , [ Para [ Str "two" ] ] ]
-      ]
-  , Header 2 ( "_anchor" , [] , [] ) [ Str "Anchor" ]
-  , Div
-      ( "goals" , [] , [ ( "wrapper" , "1" ) ] )
-      [ BulletList
-          [ [ Para [ Str "one" ] ] , [ Para [ Str "two" ] ] ]
-      ]
-  , Header 2 ( "_breaks" , [] , [] ) [ Str "Breaks" ]
-  , Para
-      [ Str "Asciidoc"
-      , Space
-      , Str "thematic"
-      , Space
-      , Str "break:"
-      ]
-  , HorizontalRule
-  , Para [ Str "Markdown" , Space , Str "style:" ]
-  , HorizontalRule
-  , HorizontalRule
-  , HorizontalRule
-  , HorizontalRule
-  , Para [ Str "Page" , Space , Str "breaks:" ]
-  , Div
-      ( "" , [ "page-break" ] , [ ( "wrapper" , "1" ) ] )
-      [ HorizontalRule ]
-  , Div
-      ( ""
-      , [ "page-break" ]
-      , [ ( "options" , "always" ) , ( "wrapper" , "1" ) ]
-      )
-      [ HorizontalRule ]
-  , Header 2 ( "_list" , [] , [] ) [ Str "List" ]
-  , BulletList
-      [ [ Para
-            [ Str "Edgar" , Space , Str "Allan" , Space , Str "Poe" ]
-        ]
-      , [ Para
-            [ Str "Sheri" , Space , Str "S." , Space , Str "Tepper" ]
-        ]
-      , [ Para [ Str "Bill" , Space , Str "Bryson" ] ]
-      ]
-  , Div
-      ( "" , [] , [] )
-      [ Div
-          ( "" , [ "title" ] , [] )
-          [ Para
-              [ Str "Kizmet\8217s"
-              , Space
-              , Str "Favorite"
-              , Space
-              , Str "Authors"
-              ]
-          ]
-      , BulletList
-          [ [ Para
-                [ Str "Edgar"
-                , Space
-                , Str "Allan"
-                , Space
-                , Str "Poe"
-                ]
-            ]
-          , [ Para
-                [ Str "Sheri"
-                , Space
-                , Str "S."
-                , Space
-                , Str "Tepper"
-                ]
-            ]
-          , [ Para [ Str "Bill" , Space , Str "Bryson" ] ]
-          ]
-      ]
-  , BulletList
-      [ [ Para
-            [ Str "Edgar" , Space , Str "Allan" , Space , Str "Poe" ]
-        ]
-      , [ Para
-            [ Str "Sheri" , Space , Str "S." , Space , Str "Tepper" ]
-        ]
-      , [ Para [ Str "Bill" , Space , Str "Bryson" ] ]
-      ]
-  , OrderedList
-      ( 1 , DefaultStyle , DefaultDelim )
-      [ [ Para [ Str "Nested" , Space , Str "list" ]
-        , BulletList
-            [ [ Para
-                  [ Str "West"
-                  , Space
-                  , Str "wood"
-                  , Space
-                  , Str "maze"
-                  ]
-              , BulletList
-                  [ [ Para [ Str "Maze" , Space , Str "heart" ]
-                    , BulletList
-                        [ [ Para
-                              [ Str "Reflection" , Space , Str "pool" ]
-                          ]
-                        ]
-                    ]
-                  , [ Para [ Str "Secret" , Space , Str "exit" ] ]
-                  ]
-              ]
-            , [ Para
-                  [ Str "Level"
-                  , Space
-                  , Str "1"
-                  , Space
-                  , Str "list"
-                  , Space
-                  , Str "item"
-                  ]
-              , BulletList
-                  [ [ Para
-                        [ Str "Level"
-                        , Space
-                        , Str "2"
-                        , Space
-                        , Str "list"
-                        , Space
-                        , Str "item"
-                        ]
-                    , BulletList
-                        [ [ Para
-                              [ Str "Level"
-                              , Space
-                              , Str "3"
-                              , Space
-                              , Str "list"
-                              , Space
-                              , Str "item"
-                              ]
-                          , BulletList
-                              [ [ Para
-                                    [ Str "Level"
-                                    , Space
-                                    , Str "4"
-                                    , Space
-                                    , Str "list"
-                                    , Space
-                                    , Str "item"
-                                    ]
-                                , BulletList
-                                    [ [ Para
-                                          [ Str "Level"
-                                          , Space
-                                          , Str "5"
-                                          , Space
-                                          , Str "list"
-                                          , Space
-                                          , Str "item"
-                                          ]
-                                      , BulletList
-                                          [ [ Para [ Str "etc." ] ] ]
-                                      ]
-                                    ]
-                                ]
-                              ]
-                          ]
-                        ]
-                    ]
-                  ]
-              ]
-            , [ Para
-                  [ Str "Level"
-                  , Space
-                  , Str "1"
-                  , Space
-                  , Str "list"
-                  , Space
-                  , Str "item"
-                  ]
-              ]
-            ]
-        ]
-      ]
-  , BulletList
-      [ [ Para [ Str "one" ] ]
-      , [ Para [ Str "two" ] ]
-      , [ Para [ Str "three" ] ]
-      ]
-  , OrderedList
-      ( 1 , DefaultStyle , DefaultDelim )
-      [ [ Para [ Str "Protons" ] ]
-      , [ Para [ Str "Electrons" ] ]
-      , [ Para [ Str "Neutrons" ] ]
-      ]
-  , OrderedList
-      ( 1 , DefaultStyle , DefaultDelim )
-      [ [ Para [ Str "Protons" ] ]
-      , [ Para [ Str "Electrons" ] ]
-      , [ Para [ Str "Neutrons" ] ]
-      ]
-  , Para
-      [ Str "Start" , Space , Str "with" , Space , Str "4:" ]
-  , OrderedList
-      ( 4 , DefaultStyle , DefaultDelim )
-      [ [ Para [ Str "Step" , Space , Str "four" ] ]
-      , [ Para [ Str "Step" , Space , Str "five" ] ]
-      , [ Para [ Str "Step" , Space , Str "six" ] ]
-      ]
-  , Para [ Str "or" ]
-  , OrderedList
-      ( 4 , DefaultStyle , DefaultDelim )
-      [ [ Para [ Str "Step" , Space , Str "four" ] ]
-      , [ Para [ Str "Step" , Space , Str "five" ] ]
-      , [ Para [ Str "Step" , Space , Str "six" ] ]
-      ]
-  , Para [ Str "Reversed:" ]
-  , Div
-      ( "" , [] , [ ( "options" , "reversed" ) ] )
-      [ Div
-          ( "" , [ "title" ] , [] )
-          [ Para
-              [ Str "Parts"
-              , Space
-              , Str "of"
-              , Space
-              , Str "an"
-              , Space
-              , Str "atom"
-              ]
-          ]
-      , OrderedList
-          ( 1 , DefaultStyle , DefaultDelim )
-          [ [ Para [ Str "Protons" ] ]
-          , [ Para [ Str "Electrons" ] ]
-          , [ Para [ Str "Neutrons" ] ]
-          ]
-      ]
-  , Para [ Str "Nested" ]
-  , OrderedList
-      ( 1 , DefaultStyle , DefaultDelim )
-      [ [ Para [ Str "Step" , Space , Str "1" ] ]
-      , [ Para [ Str "Step" , Space , Str "2" ]
-        , OrderedList
-            ( 1 , DefaultStyle , DefaultDelim )
-            [ [ Para [ Str "Step" , Space , Str "2a" ] ]
-            , [ Para [ Str "Step" , Space , Str "2b" ] ]
-            ]
-        ]
-      , [ Para [ Str "Step" , Space , Str "3" ] ]
-      ]
-  , Para [ Str "Mixed" , Space , Str "nested" ]
-  , OrderedList
-      ( 1 , DefaultStyle , DefaultDelim )
-      [ [ Para [ Str "Linux" ]
-        , BulletList
-            [ [ Para [ Str "Fedora" ] ]
-            , [ Para [ Str "Ubuntu" ] ]
-            , [ Para [ Str "Slackware" ] ]
-            ]
-        ]
-      , [ Para [ Str "BSD" ]
-        , BulletList
-            [ [ Para [ Str "FreeBSD" ] ] , [ Para [ Str "NetBSD" ] ] ]
-        ]
-      ]
-  , Para [ Str "With" , Space , Str "spacing" ]
-  , OrderedList
-      ( 1 , DefaultStyle , DefaultDelim )
-      [ [ Para [ Str "Linux" ]
-        , BulletList
-            [ [ Para [ Str "Fedora" ] ]
-            , [ Para [ Str "Ubuntu" ] ]
-            , [ Para [ Str "Slackware" ] ]
-            ]
-        ]
-      , [ Para [ Str "BSD" ]
-        , BulletList
-            [ [ Para [ Str "FreeBSD" ] ] , [ Para [ Str "NetBSD" ] ] ]
-        ]
-      ]
-  , Para
-      [ Str "With" , Space , Str "number" , Space , Str "styles" ]
-  , OrderedList
-      ( 5 , LowerRoman , DefaultDelim )
-      [ [ Para [ Str "Five" ] ]
-      , [ Para [ Str "Six" ]
-        , OrderedList
-            ( 1 , LowerAlpha , DefaultDelim )
-            [ [ Para [ Str "a" ] ]
-            , [ Para [ Str "b" ] ]
-            , [ Para [ Str "c" ] ]
-            ]
-        ]
-      , [ Para [ Str "Seven" ] ]
-      ]
-  , Para [ Str "Checklist" ]
-  , BulletList
-      [ [ Para [ Str "\9746" , Space , Str "checked" ] ]
-      , [ Para
-            [ Str "\9746" , Space , Str "also" , Space , Str "checked" ]
-        ]
-      , [ Para
-            [ Str "\9744" , Space , Str "not" , Space , Str "checked" ]
-        ]
-      , [ Para
-            [ Str "normal" , Space , Str "list" , Space , Str "item" ]
-        ]
-      ]
-  , Para
-      [ Str "Separate"
-      , Space
-      , Str "lists"
-      , Space
-      , Str "with"
-      , Space
-      , Str "block"
-      , Space
-      , Str "attribute"
-      ]
-  , BulletList
-      [ [ Para [ Str "Apples" ] ]
-      , [ Para [ Str "Oranges" ]
-        , OrderedList
-            ( 1 , DefaultStyle , DefaultDelim )
-            [ [ Para [ Str "Wash" ] ] , [ Para [ Str "Slice" ] ] ]
-        ]
-      ]
-  , Para [ Str "Multiline" , Space , Str "items" ]
-  , BulletList
-      [ [ Para
-            [ Str "Blah"
-            , Space
-            , Str "blah."
-            , SoftBreak
-            , Str "Blah"
-            , Space
-            , Str "blah."
-            ]
-        ]
-      , [ Para
-            [ Str "The"
-            , Space
-            , Str "document"
-            , Space
-            , Str "header"
-            , Space
-            , Str "in"
-            , Space
-            , Str "AsciiDoc"
-            , Space
-            , Str "is"
-            , Space
-            , Str "optional."
-            , SoftBreak
-            , Str "If"
-            , Space
-            , Str "present,"
-            , Space
-            , Str "it"
-            , Space
-            , Str "must"
-            , Space
-            , Str "start"
-            , Space
-            , Str "with"
-            , Space
-            , Str "a"
-            , Space
-            , Str "document"
-            , Space
-            , Str "title."
-            ]
-        ]
-      ]
-  , BulletList
-      [ [ Para
-            [ Str "Optional"
-            , Space
-            , Str "author"
-            , Space
-            , Str "and"
-            , Space
-            , Str "revision"
-            , Space
-            , Str "information"
-            , Space
-            , Str "lines"
-            , SoftBreak
-            , Str "immediately"
-            , Space
-            , Str "follow"
-            , Space
-            , Str "the"
-            , Space
-            , Str "document"
-            , Space
-            , Str "title."
-            ]
-        ]
-      ]
-  , BulletList
-      [ [ Para
-            [ Str "The"
-            , Space
-            , Str "document"
-            , Space
-            , Str "header"
-            , Space
-            , Str "must"
-            , Space
-            , Str "be"
-            , Space
-            , Str "separated"
-            , Space
-            , Str "from"
-            , SoftBreak
-            , Str "the"
-            , Space
-            , Str "remainder"
-            , Space
-            , Str "of"
-            , Space
-            , Str "the"
-            , Space
-            , Str "document"
-            , Space
-            , Str "by"
-            , Space
-            , Str "one"
-            , Space
-            , Str "or"
-            , Space
-            , Str "more"
-            , SoftBreak
-            , Str "empty"
-            , Space
-            , Str "lines"
-            , Space
-            , Str "and"
-            , Space
-            , Str "it"
-            , Space
-            , Str "cannot"
-            , Space
-            , Str "contain"
-            , Space
-            , Str "empty"
-            , Space
-            , Str "lines."
-            ]
-        ]
-      ]
-  , Para [ Str "Complex" , Space , Str "item" ]
-  , BulletList
-      [ [ Para
-            [ Str "The"
-            , Space
-            , Str "header"
-            , Space
-            , Str "in"
-            , Space
-            , Str "AsciiDoc"
-            , Space
-            , Str "must"
-            , Space
-            , Str "start"
-            , Space
-            , Str "with"
-            , Space
-            , Str "a"
-            , Space
-            , Str "document"
-            , Space
-            , Str "title."
-            ]
-        , CodeBlock ( "" , [] , [] ) "= Document Title"
-        , Para
-            [ Str "Keep"
-            , Space
-            , Str "in"
-            , Space
-            , Str "mind"
-            , Space
-            , Str "that"
-            , Space
-            , Str "the"
-            , Space
-            , Str "header"
-            , Space
-            , Str "is"
-            , Space
-            , Str "optional."
-            ]
-        ]
-      , [ Para
-            [ Str "Optional"
-            , Space
-            , Str "author"
-            , Space
-            , Str "and"
-            , Space
-            , Str "revision"
-            , Space
-            , Str "information"
-            , Space
-            , Str "lines"
-            , Space
-            , Str "immediately"
-            , Space
-            , Str "follow"
-            , Space
-            , Str "the"
-            , Space
-            , Str "document"
-            , SoftBreak
-            , Str "title."
-            ]
-        , CodeBlock
-            ( "" , [] , [] )
-            "= Document Title\nDoc Writer <doc.writer@asciidoc.org>\nv1.0, 2022-01-01"
-        ]
-      , [ Para [ Str "Second" , Space , Str "item" ] ]
-      ]
-  , Para
-      [ Str "Empty"
-      , Space
-      , Str "principle"
-      , Space
-      , Str "element:"
-      ]
-  , OrderedList
-      ( 1 , DefaultStyle , DefaultDelim )
-      [ [ Para [] , CodeBlock ( "" , [] , [] ) "test" ] ]
-  , Header 2 ( "_table" , [] , [] ) [ Str "Table" ]
-  , Header
-      3
-      ( "_simple_with_column_specs" , [] , [] )
-      [ Str "Simple"
-      , Space
-      , Str "with"
-      , Space
-      , Str "column"
-      , Space
-      , Str "specs"
-      ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidth 0.375 )
-      , ( AlignDefault , ColWidth 0.25 )
-      , ( AlignDefault , ColWidth 0.375 )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "This"
-                     , Space
-                     , Str "content"
-                     , Space
-                     , Str "is"
-                     , Space
-                     , Str "placed"
-                     , Space
-                     , Str "in"
-                     , Space
-                     , Str "the"
-                     , Space
-                     , Str "first"
-                     , Space
-                     , Str "cell"
-                     , Space
-                     , Str "of"
-                     , Space
-                     , Str "column"
-                     , Space
-                     , Str "1"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "This"
-                     , Space
-                     , Str "line"
-                     , Space
-                     , Str "starts"
-                     , Space
-                     , Str "with"
-                     , Space
-                     , Str "a"
-                     , Space
-                     , Str "vertical"
-                     , Space
-                     , Str "bar"
-                     , Space
-                     , Str "so"
-                     , Space
-                     , Str "this"
-                     , Space
-                     , Str "content"
-                     , Space
-                     , Str "is"
-                     , Space
-                     , Str "placed"
-                     , Space
-                     , Str "in"
-                     , Space
-                     , Str "a"
-                     , Space
-                     , Str "new"
-                     , Space
-                     , Str "cell"
-                     , Space
-                     , Str "in"
-                     , SoftBreak
-                     , Str "column"
-                     , Space
-                     , Str "2"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "When"
-                     , Space
-                     , Str "the"
-                     , Space
-                     , Str "processor"
-                     , Space
-                     , Str "encounters"
-                     , Space
-                     , Str "a"
-                     , Space
-                     , Str "whitespace"
-                     , Space
-                     , Str "followed"
-                     , Space
-                     , Str "by"
-                     , Space
-                     , Str "a"
-                     , Space
-                     , Str "vertical"
-                     , Space
-                     , Str "bar"
-                     , Space
-                     , Str "it"
-                     , SoftBreak
-                     , Str "ends"
-                     , Space
-                     , Str "the"
-                     , Space
-                     , Str "previous"
-                     , Space
-                     , Str "cell"
-                     , Space
-                     , Str "and"
-                     , Space
-                     , Str "starts"
-                     , Space
-                     , Str "a"
-                     , Space
-                     , Str "new"
-                     , Space
-                     , Str "cell"
-                     ]
-                 ]
-             ]
-         ])
-      [ TableBody ( "" , [] , [] ) (RowHeadColumns 0) [] [] ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_repeated_column_in_specs" , [] , [] )
-      [ Str "Repeated"
-      , Space
-      , Str "column"
-      , Space
-      , Str "in"
-      , Space
-      , Str "specs"
-      ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignRight
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Strong
-                         [ Str "This"
-                         , Space
-                         , Str "cell\8217s"
-                         , Space
-                         , Str "specifier"
-                         , Space
-                         , Str "indicates"
-                         , Space
-                         , Str "that"
-                         , Space
-                         , Str "this"
-                         , Space
-                         , Str "cell\8217s"
-                         , Space
-                         , Str "content"
-                         , Space
-                         , Str "is"
-                         , Space
-                         , Str "right-aligned"
-                         , Space
-                         , Str "and"
-                         , Space
-                         , Str "bold."
-                         ]
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "The"
-                     , Space
-                     , Str "cell"
-                     , Space
-                     , Str "specifier"
-                     , Space
-                     , Str "on"
-                     , Space
-                     , Str "this"
-                     , Space
-                     , Str "cell"
-                     , Space
-                     , Str "hasn\8217t"
-                     , Space
-                     , Str "been"
-                     , Space
-                     , Str "set"
-                     , Space
-                     , Str "explicitly,"
-                     , Space
-                     , Str "so"
-                     , Space
-                     , Str "the"
-                     , Space
-                     , Str "default"
-                     , SoftBreak
-                     , Str "properties"
-                     , Space
-                     , Str "are"
-                     , Space
-                     , Str "applied."
-                     ]
-                 ]
-             ]
-         ])
-      [ TableBody ( "" , [] , [] ) (RowHeadColumns 0) [] [] ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_simple_without_column_specs" , [] , [] )
-      [ Str "Simple"
-      , Space
-      , Str "without"
-      , Space
-      , Str "column"
-      , Space
-      , Str "specs"
-      ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "1,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "2,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "3"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "3"
-                      ]
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_with_caption" , [] , [] )
-      [ Str "With" , Space , Str "caption" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption
-         Nothing
-         [ Plain
-             [ Str "My" , Space , Str "cool" , Space , Str "table." ]
-         ])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "1,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "2,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "3"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "3"
-                      ]
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_no_header" , [] , [] )
-      [ Str "No" , Space , Str "header" ]
-  , Para
-      [ Str "By"
-      , Space
-      , Str "default"
-      , Space
-      , Str "the"
-      , Space
-      , Str "first"
-      , Space
-      , Str "line"
-      , Space
-      , Str "should"
-      , Space
-      , Str "turn"
-      , Space
-      , Str "into"
-      , Space
-      , Str "the"
-      , Space
-      , Str "header,"
-      , Space
-      , Str "but"
-      , Space
-      , Str "this"
-      , SoftBreak
-      , Str "can"
-      , Space
-      , Str "be"
-      , Space
-      , Str "disabled:"
-      ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead ( "" , [] , [] ) [])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "1"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "1"
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Para
-      [ Str "And"
-      , Space
-      , Str "also"
-      , Space
-      , Str "explicitly"
-      , Space
-      , Str "enabled:"
-      ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Cell" , Space , Str "A1" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Cell" , Space , Str "B1" ] ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Cell" , Space , Str "A2" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Cell" , Space , Str "B2" ] ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Cell" , Space , Str "A3" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Cell" , Space , Str "B3" ] ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header 3 ( "_footer" , [] , [] ) [ Str "Footer" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidth 0.4 )
-      , ( AlignDefault , ColWidth 0.4 )
-      , ( AlignDefault , ColWidth 0.2 )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "1,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "2,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "3,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "3,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "footer"
-                      , Space
-                      , Str "row"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "footer"
-                      , Space
-                      , Str "row"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Column"
-                      , Space
-                      , Str "3,"
-                      , Space
-                      , Str "footer"
-                      , Space
-                      , Str "row"
-                      ]
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Para [ Str "or" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "1,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "2,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "3"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "2,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "3"
-                      ]
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "1,"
-                     , Space
-                     , Str "footer"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "2,"
-                     , Space
-                     , Str "footer"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             ]
-         ])
-  , Header 3 ( "_alignment" , [] , [] ) [ Str "Alignment" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Column" , Space , Str "Name" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Column" , Space , Str "Name" ] ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignCenter
-                  (RowSpan 1)
-                  (ColSpan 2)
-                  [ Para
-                      [ Str "This"
-                      , Space
-                      , Str "cell"
-                      , Space
-                      , Str "spans"
-                      , Space
-                      , Str "two"
-                      , Space
-                      , Str "columns,"
-                      , Space
-                      , Str "and"
-                      , Space
-                      , Str "its"
-                      , Space
-                      , Str "content"
-                      , Space
-                      , Str "is"
-                      , Space
-                      , Str "horizontally"
-                      , Space
-                      , Str "centered"
-                      , Space
-                      , Str "because"
-                      , Space
-                      , Str "the"
-                      , SoftBreak
-                      , Str "cell"
-                      , Space
-                      , Str "specifier"
-                      , Space
-                      , Str "includes"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Code ( "" , [] , [] ) "^"
-                      , Space
-                      , Str "operator."
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignCenter
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "This"
-                      , Space
-                      , Str "content"
-                      , Space
-                      , Str "is"
-                      , Space
-                      , Str "duplicated"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "two"
-                      , Space
-                      , Str "adjacent"
-                      , Space
-                      , Str "columns."
-                      , SoftBreak
-                      , Str "Its"
-                      , Space
-                      , Str "content"
-                      , Space
-                      , Str "is"
-                      , Space
-                      , Str "horizontally"
-                      , Space
-                      , Str "centered"
-                      , Space
-                      , Str "because"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Str "cell"
-                      , Space
-                      , Str "specifier"
-                      , SoftBreak
-                      , Str "includes"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Code ( "" , [] , [] ) "^"
-                      , Space
-                      , Str "operator."
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignCenter
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "This"
-                      , Space
-                      , Str "content"
-                      , Space
-                      , Str "is"
-                      , Space
-                      , Str "duplicated"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "two"
-                      , Space
-                      , Str "adjacent"
-                      , Space
-                      , Str "columns."
-                      , SoftBreak
-                      , Str "Its"
-                      , Space
-                      , Str "content"
-                      , Space
-                      , Str "is"
-                      , Space
-                      , Str "horizontally"
-                      , Space
-                      , Str "centered"
-                      , Space
-                      , Str "because"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Str "cell"
-                      , Space
-                      , Str "specifier"
-                      , SoftBreak
-                      , Str "includes"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Code ( "" , [] , [] ) "^"
-                      , Space
-                      , Str "operator."
-                      ]
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_multiple_paragraphs_in_cells" , [] , [] )
-      [ Str "Multiple"
-      , Space
-      , Str "paragraphs"
-      , Space
-      , Str "in"
-      , Space
-      , Str "cells"
-      ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault ) ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Single"
-                     , Space
-                     , Str "paragraph"
-                     , Space
-                     , Str "on"
-                     , Space
-                     , Str "row"
-                     , Space
-                     , Str "1"
-                     ]
-                 ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "First"
-                      , Space
-                      , Str "paragraph"
-                      , Space
-                      , Str "on"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  , Para
-                      [ Str "Second"
-                      , Space
-                      , Str "paragraph"
-                      , Space
-                      , Str "on"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_complex_table" , [] , [] )
-      [ Str "Complex" , Space , Str "table" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignRight
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Code ( "" , [] , [] ) "This"
-                     , Space
-                     , Code ( "" , [] , [] ) "content"
-                     , Space
-                     , Code ( "" , [] , [] ) "is"
-                     , Space
-                     , Code ( "" , [] , [] ) "duplicated"
-                     , Space
-                     , Code ( "" , [] , [] ) "across"
-                     , Space
-                     , Code ( "" , [] , [] ) "two"
-                     , Space
-                     , Code ( "" , [] , [] ) "columns."
-                     ]
-                 , Para
-                     [ Code ( "" , [] , [] ) "It"
-                     , Space
-                     , Code ( "" , [] , [] ) "is"
-                     , Space
-                     , Code ( "" , [] , [] ) "aligned"
-                     , Space
-                     , Code ( "" , [] , [] ) "right"
-                     , Space
-                     , Code ( "" , [] , [] ) "horizontally."
-                     ]
-                 , Para
-                     [ Code ( "" , [] , [] ) "And"
-                     , Space
-                     , Code ( "" , [] , [] ) "it"
-                     , Space
-                     , Code ( "" , [] , [] ) "is"
-                     , Space
-                     , Code ( "" , [] , [] ) "monospaced."
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignRight
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Code ( "" , [] , [] ) "This"
-                     , Space
-                     , Code ( "" , [] , [] ) "content"
-                     , Space
-                     , Code ( "" , [] , [] ) "is"
-                     , Space
-                     , Code ( "" , [] , [] ) "duplicated"
-                     , Space
-                     , Code ( "" , [] , [] ) "across"
-                     , Space
-                     , Code ( "" , [] , [] ) "two"
-                     , Space
-                     , Code ( "" , [] , [] ) "columns."
-                     ]
-                 , Para
-                     [ Code ( "" , [] , [] ) "It"
-                     , Space
-                     , Code ( "" , [] , [] ) "is"
-                     , Space
-                     , Code ( "" , [] , [] ) "aligned"
-                     , Space
-                     , Code ( "" , [] , [] ) "right"
-                     , Space
-                     , Code ( "" , [] , [] ) "horizontally."
-                     ]
-                 , Para
-                     [ Code ( "" , [] , [] ) "And"
-                     , Space
-                     , Code ( "" , [] , [] ) "it"
-                     , Space
-                     , Code ( "" , [] , [] ) "is"
-                     , Space
-                     , Code ( "" , [] , [] ) "monospaced."
-                     ]
-                 ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignCenter
-                  (RowSpan 3)
-                  (ColSpan 1)
-                  [ Para
-                      [ Strong
-                          [ Str "This"
-                          , Space
-                          , Str "cell"
-                          , Space
-                          , Str "spans"
-                          , Space
-                          , Str "3"
-                          , Space
-                          , Str "rows."
-                          , Space
-                          , Str "The"
-                          , Space
-                          , Str "content"
-                          , Space
-                          , Str "is"
-                          , Space
-                          , Str "centered"
-                          , Space
-                          , Str "horizontally,"
-                          , Space
-                          , Str "aligned"
-                          , Space
-                          , Str "to"
-                          , Space
-                          , Str "the"
-                          , Space
-                          , Str "bottom"
-                          , Space
-                          , Str "of"
-                          , Space
-                          , Str "the"
-                          , Space
-                          , Str "cell,"
-                          , Space
-                          , Str "and"
-                          , Space
-                          , Str "strong."
-                          ]
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Emph
-                          [ Str "This"
-                          , Space
-                          , Str "content"
-                          , Space
-                          , Str "is"
-                          , Space
-                          , Str "emphasized."
-                          ]
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ CodeBlock
-                      ( "" , [] , [] )
-                      "This content is aligned to the top of the cell and literal.\n\n"
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ CodeBlock
-                      ( "" , [] , [] )
-                      "puts \"This is a source block!\""
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_column_styles" , [] , [] )
-      [ Str "Column" , Space , Str "styles" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Code ( "" , [] , [] ) "monospace" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Code ( "" , [] , [] ) "mono" ] ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "default" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Code ( "" , [] , [] ) "mono" ] ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_block_elements_in_cells" , [] , [] )
-      [ Str "Block"
-      , Space
-      , Str "elements"
-      , Space
-      , Str "in"
-      , Space
-      , Str "cells"
-      ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Normal" , Space , Str "Style" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "AsciiDoc" , Space , Str "Style" ] ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "This"
-                      , Space
-                      , Str "cell"
-                      , Space
-                      , Str "isn\8217t"
-                      , Space
-                      , Str "prefixed"
-                      , Space
-                      , Str "with"
-                      , Space
-                      , Str "an"
-                      , Space
-                      , Code ( "" , [] , [] ) "a"
-                      , Str ","
-                      , Space
-                      , Str "so"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Str "processor"
-                      , Space
-                      , Str "doesn\8217t"
-                      , Space
-                      , Str "interpret"
-                      , Space
-                      , Str "the"
-                      , SoftBreak
-                      , Str "following"
-                      , Space
-                      , Str "lines"
-                      , Space
-                      , Str "as"
-                      , Space
-                      , Str "an"
-                      , Space
-                      , Str "AsciiDoc"
-                      , Space
-                      , Str "list."
-                      ]
-                  , Para
-                      [ Str "*"
-                      , Space
-                      , Str "List"
-                      , Space
-                      , Str "item"
-                      , Space
-                      , Str "1"
-                      , SoftBreak
-                      , Str "*"
-                      , Space
-                      , Str "List"
-                      , Space
-                      , Str "item"
-                      , Space
-                      , Str "2"
-                      , SoftBreak
-                      , Str "*"
-                      , Space
-                      , Str "List"
-                      , Space
-                      , Str "item"
-                      , Space
-                      , Str "3"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "This"
-                      , Space
-                      , Str "cell"
-                      , Space
-                      , Str "is"
-                      , Space
-                      , Str "prefixed"
-                      , Space
-                      , Str "with"
-                      , Space
-                      , Str "an"
-                      , Space
-                      , Code ( "" , [] , [] ) "a"
-                      , Str ","
-                      , Space
-                      , Str "so"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Str "processor"
-                      , Space
-                      , Str "interprets"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Str "following"
-                      , Space
-                      , Str "lines"
-                      , SoftBreak
-                      , Str "as"
-                      , Space
-                      , Str "an"
-                      , Space
-                      , Str "AsciiDoc"
-                      , Space
-                      , Str "list."
-                      ]
-                  , BulletList
-                      [ [ Para
-                            [ Str "List"
-                            , Space
-                            , Str "item"
-                            , Space
-                            , Str "1"
-                            ]
-                        ]
-                      , [ Para
-                            [ Str "List"
-                            , Space
-                            , Str "item"
-                            , Space
-                            , Str "2"
-                            ]
-                        ]
-                      , [ Para
-                            [ Str "List"
-                            , Space
-                            , Str "item"
-                            , Space
-                            , Str "3"
-                            ]
-                        ]
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "This"
-                      , Space
-                      , Str "cell"
-                      , Space
-                      , Str "isn\8217t"
-                      , Space
-                      , Str "prefixed"
-                      , Space
-                      , Str "with"
-                      , Space
-                      , Str "an"
-                      , Space
-                      , Code ( "" , [] , [] ) "a"
-                      , Str ","
-                      , Space
-                      , Str "so"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Str "processor"
-                      , Space
-                      , Str "doesn\8217t"
-                      , Space
-                      , Str "interpret"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Str "listing"
-                      , SoftBreak
-                      , Str "block"
-                      , Space
-                      , Str "delimiters"
-                      , Space
-                      , Str "or"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Code ( "" , [] , [] ) "source"
-                      , Space
-                      , Str "style."
-                      ]
-                  , Para
-                      [ Str "----"
-                      , SoftBreak
-                      , Str "import"
-                      , Space
-                      , Str "os"
-                      , SoftBreak
-                      , Str "print"
-                      , Space
-                      , Str "(\"%s\""
-                      , Space
-                      , Str "%(os.uname()))"
-                      , SoftBreak
-                      , Str "----"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "This"
-                      , Space
-                      , Str "cell"
-                      , Space
-                      , Str "is"
-                      , Space
-                      , Str "prefixed"
-                      , Space
-                      , Str "with"
-                      , Space
-                      , Str "an"
-                      , Space
-                      , Code ( "" , [] , [] ) "a"
-                      , Str ","
-                      , Space
-                      , Str "so"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Str "listing"
-                      , Space
-                      , Str "block"
-                      , Space
-                      , Str "is"
-                      , Space
-                      , Str "processed"
-                      , Space
-                      , Str "and"
-                      , Space
-                      , Str "rendered"
-                      , SoftBreak
-                      , Str "according"
-                      , Space
-                      , Str "to"
-                      , Space
-                      , Str "the"
-                      , Space
-                      , Code ( "" , [] , [] ) "source"
-                      , Space
-                      , Str "style"
-                      , Space
-                      , Str "rules."
-                      ]
-                  , CodeBlock
-                      ( "" , [ "python" ] , [] )
-                      "import os\nprint \"%s\" %(os.uname())"
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_col_and_rowspan" , [] , [] )
-      [ Str "Col" , Space , Str "and" , Space , Str "rowspan" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "1,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "2,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para
-                     [ Str "Column"
-                     , Space
-                     , Str "3,"
-                     , Space
-                     , Str "header"
-                     , Space
-                     , Str "row"
-                     ]
-                 ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 2)
-                  (ColSpan 2)
-                  [ Para
-                      [ Str "This"
-                      , Space
-                      , Str "cell"
-                      , Space
-                      , Str "spans"
-                      , Space
-                      , Str "2"
-                      , Space
-                      , Str "cols"
-                      , Space
-                      , Str "and"
-                      , Space
-                      , Str "2"
-                      , Space
-                      , Str "rows"
-                      ]
-                  ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "3,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "2"
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "3,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "3"
-                      ]
-                  ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 3)
-                  [ Para
-                      [ Str "Cell"
-                      , Space
-                      , Str "in"
-                      , Space
-                      , Str "column"
-                      , Space
-                      , Str "1-3,"
-                      , Space
-                      , Str "row"
-                      , Space
-                      , Str "4"
-                      ]
-                  ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_csv_table" , [] , [] )
-      [ Str "CSV" , Space , Str "table" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Artist" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Track" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Genre" ] ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Baauer" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Harlem" , Space , Str "Shake" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Hip" , Space , Str "Hop" ] ]
-              ]
-          , Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "The" , Space , Str "Lumineers" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Ho" , Space , Str "Hey" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Folk" , Space , Str "Rock" ] ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Para [ Str "or" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Artist" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Track" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Genre" ] ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Baauer" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Harlem" , Space , Str "Shake" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Hip" , Space , Str "Hop" ] ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Header
-      3
-      ( "_dsv_table" , [] , [] )
-      [ Str "DSV" , Space , Str "table" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "a" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "b" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "c" ] ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "d" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "e" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "f" ] ]
-              ]
-          ]
-      ]
-      (TableFoot ( "" , [] , [] ) [])
-  , Para [ Str "or" ]
-  , Table
-      ( "" , [] , [] )
-      (Caption Nothing [])
-      [ ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      , ( AlignDefault , ColWidthDefault )
-      ]
-      (TableHead
-         ( "" , [] , [] )
-         [ Row
-             ( "" , [] , [] )
-             [ Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Artist" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Track" ] ]
-             , Cell
-                 ( "" , [] , [] )
-                 AlignDefault
-                 (RowSpan 1)
-                 (ColSpan 1)
-                 [ Para [ Str "Genre" ] ]
-             ]
-         ])
-      [ TableBody
-          ( "" , [] , [] )
-          (RowHeadColumns 0)
-          []
-          [ Row
-              ( "" , [] , [] )
-              [ Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Robyn" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Indestructible" ] ]
-              , Cell
-                  ( "" , [] , [] )
-                  AlignDefault
-                  (RowSpan 1)
-                  (ColSpan 1)
-                  [ Para [ Str "Dance" ] ]
+          ( "" , [ "red" , "icon" ] , [] )
+          []
+          ( "./images/icons/heart.png" , "" )
+      ]
+  , Para [ Str "anchor:tiger" ]
+  , Para [ Strong [ Str "*bold*" ] ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "Get" , Space , Str "Report" ]
+          ( "downloads/report.pdf" , "" )
+      ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "tools.html#editors" ]
+          ( "tools.html#editors" , "" )
+      ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "Your" , Space , Str "files" ]
+          ( "file:///home/username" , "" )
+      ]
+  , Para [ Str "Tricky" , Space , Str "cases:" ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "Get" , Space , Str "Report" ]
+          ( "My Documents/report.pdf" , "" )
+      ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "Get" , Space , Str "Report" ]
+          ( "My&#32;Documents/report.pdf" , "" )
+      ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "Get" , Space , Str "Report" ]
+          ( "My%20Documents/report.pdf" , "" )
+      ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "https://example.org/now_this__link_works.html" ]
+          ( "https://example.org/now_this__link_works.html" , "" )
+      ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "Subscribe" ]
+          ( "join@discuss.example.org" , "" )
+      ]
+  , Para
+      [ Link
+          ( "" , [ "mail" ] , [] )
+          [ Str "Click,"
+          , Space
+          , Str "subscribe,"
+          , Space
+          , Str "and"
+          , Space
+          , Str "participate!"
+          ]
+          ( "join@discuss.example.org" , "" )
+      ]
+  , Para
+      [ Link
+          ( "" , [ "cross-reference" ] , [] )
+          [ Str "use"
+          , Space
+          , Str "attributes"
+          , Space
+          , Str "within"
+          , Space
+          , Str "the"
+          , Space
+          , Str "link"
+          , Space
+          , Str "macro"
+          ]
+          ( "#link-macro-attributes" , "" )
+      ]
+  , Figure
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ Plain
+          [ Image
+              ( "" , [] , [] ) [ Str "Sunset" ] ( "sunset.jpg" , "" )
+          ]
+      ]
+  , Figure
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ Plain [ Image ( "" , [] , [] ) [] ( "name.png" , "" ) ] ]
+  , Figure
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ Plain
+          [ Image
+              ( ""
+              , []
+              , [ ( "width" , "300px" ) , ( "height" , "400px" ) ]
+              )
+              [ Str "Sunset" ]
+              ( "sunset.jpg" , "" )
+          ]
+      ]
+  , Div
+      ( ""
+      , []
+      , [ ( "wrapper" , "1" )
+        , ( "alt" , "Sunset" )
+        , ( "height" , "400" )
+        , ( "width" , "300" )
+        ]
+      )
+      [ Figure
+          ( "" , [] , [] )
+          (Caption Nothing [])
+          [ Plain [ Image ( "" , [] , [] ) [] ( "sunset.jpg" , "" ) ]
+          ]
+      ]
+  , Para [ Math DisplayMath "e=mc^2\n" ]
+  , Para [ Math DisplayMath "sin n / 3\n" ]
+  , Para [ Math DisplayMath "e^i\n" ]
+  , Header
+      2
+      ( "_attribute_substitutions" , [] , [] )
+      [ Str "Attribute" , Space , Str "substitutions" ]
+  , Para [ Str "Foo" , Space , Str "bar" , Space , Str "baz" ]
+  , Para [ Str "{nonexistent}" ]
+  , Para
+      [ Str "Built"
+      , Space
+      , Str "in:"
+      , Space
+      , Str "xyz"
+      , Space
+      , Str "a\160b\8203c'd\8216"
+      ]
+  , Header
+      2
+      ( "_bold_and_italic" , [] , [] )
+      [ Str "Bold" , Space , Str "and" , Space , Str "italic" ]
+  , Para
+      [ Str "Constrained:"
+      , Space
+      , Strong
+          [ Str "this"
+          , Space
+          , Str "is"
+          , Space
+          , Str "bold"
+          , Space
+          , Emph [ Str "and" , Space , Str "italic" ]
+          ]
+      , Str "."
+      ]
+  , Para
+      [ Str "Unconstrained:"
+      , Space
+      , Str "wild"
+      , Strong
+          [ Str "content"
+          , Emph [ Str "with" , Space , Str "italic" ]
+          , Str "stuff"
+          ]
+      , Str "."
+      ]
+  , Header 2 ( "_monospace" , [] , [] ) [ Str "Monospace" ]
+  , Para [ Code ( "" , [] , [] ) "simple" ]
+  , Para
+      [ Code ( "" , [] , [] ) "complex"
+      , Space
+      , Strong
+          [ Code ( "" , [] , [] ) "with"
+          , Space
+          , Code ( "" , [] , [] ) "bold"
+          ]
+      , Space
+      , Code ( "" , [] , [] ) "text"
+      , Space
+      , Code ( "" , [] , [] ) "and"
+      , Space
+      , Code ( "" , [] , [] ) "a"
+      , Space
+      , Link
+          ( "" , [] , [] )
+          [ Code ( "" , [] , [] ) "foo.html" ]
+          ( "foo.html" , "" )
+      ]
+  , Para
+      [ Str "unconstrained"
+      , Code ( "" , [] , [] ) "wwow"
+      , Str "okay"
+      ]
+  , Header
+      2
+      ( "_span_and_inline_attributes" , [] , [] )
+      [ Str "Span"
+      , Space
+      , Str "and"
+      , Space
+      , Str "inline"
+      , Space
+      , Str "attributes"
+      ]
+  , Para
+      [ Span
+          ( "" , [ "red" ] , [] )
+          [ Str "Bonjour" , Space , Strong [ Str "monsieur" ] ]
+      ]
+  , Para
+      [ Str "Un"
+      , Span ( "" , [ "red" ] , [] ) [ Str "constrained" ]
+      , Str "content"
+      ]
+  , Para
+      [ Str "With"
+      , Space
+      , Span
+          ( "" , [ "mark" ] , [] )
+          [ Str "no" , Space , Str "attribute" ]
+      , Space
+      , Str "it\8217s"
+      , Space
+      , Str "highlighted."
+      ]
+  , Header
+      2
+      ( "_sub_and_superscript" , [] , [] )
+      [ Str "Sub"
+      , Space
+      , Str "and"
+      , Space
+      , Str "superscript"
+      ]
+  , Para [ Str "H" , Subscript [ Str "2" ] , Str "O" ]
+  , Para
+      [ Str "H"
+      , Subscript [ Str "a" , Space , Str "b" ]
+      , Str "O"
+      ]
+  , Para
+      [ Str "Not"
+      , Space
+      , Str "subscript:"
+      , Space
+      , Str "H~a"
+      , Space
+      , Str "b~O."
+      ]
+  , Para [ Str "H^2&O" ]
+  , Para
+      [ Str "H"
+      , Superscript [ Str "a" , Space , Str "b" ]
+      , Str "O"
+      ]
+  , Para
+      [ Str "Not"
+      , Space
+      , Str "subscript:"
+      , Space
+      , Str "H^a"
+      , Space
+      , Str "b^O."
+      ]
+  , Header
+      2 ( "_passthrough" , [] , [] ) [ Str "Passthrough" ]
+  , Para
+      [ Str "Here"
+      , Space
+      , Str "the"
+      , Space
+      , Str "special"
+      , Space
+      , Str "characters"
+      , Space
+      , Str "just"
+      , Space
+      , Str "come"
+      , Space
+      , Str "through"
+      , Space
+      , Str "as"
+      , Space
+      , Str "literal:"
+      ]
+  , Para [ Str "<b>*test*</b>" ]
+  , Para [ Str "xx<b>*test*</b>xx" ]
+  , Para
+      [ Str "But"
+      , Space
+      , Str "here"
+      , Space
+      , Str "they"
+      , Space
+      , Str "are"
+      , Space
+      , Str "passed"
+      , Space
+      , Str "through:"
+      ]
+  , Para [ Str "xx" , Strong [ Str "*test*" ] , Str "xx" ]
+  , Header 2 ( "_quoted" , [] , [] ) [ Str "Quoted" ]
+  , Para
+      [ Quoted DoubleQuote [ Str "double" , Space , Str "quoted" ]
+      ]
+  , Para
+      [ Quoted SingleQuote [ Str "single" , Space , Str "quoted" ]
+      ]
+  , Header 2 ( "_footnotes" , [] , [] ) [ Str "Footnotes" ]
+  , Para
+      [ Str "Double"
+      , Note
+          [ Para
+              [ Str "The"
+              , Space
+              , Str "double"
+              , Space
+              , Str "hail-and-rainbow"
+              , Space
+              , Str "level"
+              , Space
+              , Str "makes"
+              , Space
+              , Str "my"
+              , Space
+              , Str "toes"
+              , Space
+              , Str "tingle."
+              ]
+          ]
+      ]
+  , Para
+      [ Str "A"
+      , Space
+      , Str "bold"
+      , Space
+      , Str "statement!"
+      , Note
+          [ Para
+              [ Str "Opinions"
+              , Space
+              , Str "are"
+              , Space
+              , Str "my"
+              , Space
+              , Str "own."
+              ]
+          ]
+      , SoftBreak
+      , Str "Another"
+      , Space
+      , Str "outrageous"
+      , Space
+      , Str "statement."
+      , Note
+          [ Para
+              [ Str "Opinions"
+              , Space
+              , Str "are"
+              , Space
+              , Str "my"
+              , Space
+              , Str "own."
+              ]
+          ]
+      ]
+  , Header
+      1
+      ( "_block_markup" , [] , [] )
+      [ Str "Block" , Space , Str "markup" ]
+  , Header 2 ( "_sections" , [] , [] ) [ Str "Sections" ]
+  , Header
+      3
+      ( "_another_level" , [] , [] )
+      [ Str "Another" , Space , Str "level" ]
+  , Header
+      4 ( "_level_5" , [] , [] ) [ Str "Level" , Space , Str "5" ]
+  , Header
+      3
+      ( "_markdown_style" , [] , [] )
+      [ Str "Markdown" , Space , Str "style" ]
+  , Header
+      4
+      ( "_level_5_2" , [] , [] )
+      [ Str "Level" , Space , Str "5" ]
+  , Header
+      2
+      ( "_discrete_heading" , [] , [] )
+      [ Str "Discrete" , Space , Str "heading" ]
+  , Header
+      3
+      ( "" , [] , [] )
+      [ Str "A"
+      , Space
+      , Str "discrete"
+      , Space
+      , Str "heading,"
+      , Space
+      , Str "not"
+      , Space
+      , Str "a"
+      , Space
+      , Str "section"
+      ]
+  , Header 2 ( "_paragraph" , [] , [] ) [ Str "Paragraph" ]
+  , Para
+      [ Str "This"
+      , Space
+      , Str "is"
+      , Space
+      , Str "a"
+      , Space
+      , Str "paragraph"
+      , SoftBreak
+      , Str "whose"
+      , Space
+      , Str "source"
+      , Space
+      , Str "fits"
+      , Space
+      , Str "on"
+      , Space
+      , Str "two"
+      , Space
+      , Str "lines."
+      ]
+  , Para
+      [ Str "{.This"
+      , Space
+      , Str "is"
+      , Space
+      , Str "my"
+      , Space
+      , Str "title}"
+      , SoftBreak
+      , Str "A"
+      , Space
+      , Str "paragraph"
+      , Space
+      , Str "with"
+      , Space
+      , Str "a"
+      , Space
+      , Str "title."
+      ]
+  , Header
+      2
+      ( "_example_block" , [] , [] )
+      [ Str "Example" , Space , Str "block" ]
+  , Div
+      ( "" , [] , [] )
+      [ Div
+          ( "" , [ "title" ] , [] )
+          [ Para [ Str "Optional" , Space , Str "title" ] ]
+      , Para
+          [ Str "This"
+          , Space
+          , Str "is"
+          , Space
+          , Str "an"
+          , Space
+          , Str "example"
+          , Space
+          , Str "of"
+          , Space
+          , Str "an"
+          , Space
+          , Str "example"
+          , Space
+          , Str "block."
+          ]
+      ]
+  , Div
+      ( "" , [ "example" ] , [] )
+      [ Div
+          ( "" , [ "title" ] , [] )
+          [ Para [ Str "Optional" , Space , Str "title" ] ]
+      , Para
+          [ Str "Paragraph" , Space , Strong [ Str "one" ] , Str "." ]
+      , Para
+          [ Str "Paragraph" , Space , Strong [ Str "two" ] , Str "." ]
+      ]
+  , Header 2 ( "_admonition" , [] , [] ) [ Str "Admonition" ]
+  , Para [ Str "Simple" , Space , Str "form:" ]
+  , Div
+      ( "" , [ "warning" ] , [] )
+      [ Div ( "" , [ "title" ] , [] ) [ Para [ Str "Warning" ] ]
+      , Para
+          [ Str "This"
+          , Space
+          , Str "is"
+          , Space
+          , Str "very"
+          , Space
+          , Str "dangerous."
+          , SoftBreak
+          , Str "Don\8217t"
+          , Space
+          , Str "do"
+          , Space
+          , Str "it"
+          , Space
+          , Str "unless"
+          , Space
+          , Str "you"
+          , Space
+          , Str "understand"
+          , Space
+          , Str "the"
+          , Space
+          , Str "risks."
+          ]
+      ]
+  , Div
+      ( "" , [ "important" ] , [] )
+      [ Div
+          ( "" , [ "title" ] , [] )
+          [ Para
+              [ Str "Title"
+              , Space
+              , Str "of"
+              , Space
+              , Str "the"
+              , Space
+              , Str "admonition"
+              ]
+          ]
+      , Para [ Str "Remember:" ]
+      , OrderedList
+          ( 1 , DefaultStyle , DefaultDelim )
+          [ [ Para
+                [ Str "Don\8217t"
+                , Space
+                , Str "do"
+                , Space
+                , Str "this."
+                ]
+            ]
+          , [ Para
+                [ Str "And"
+                , Space
+                , Str "don\8217t"
+                , Space
+                , Str "do"
+                , Space
+                , Str "that."
+                ]
+            ]
+          ]
+      ]
+  , Header 2 ( "_sidebar" , [] , [] ) [ Str "Sidebar" ]
+  , Para
+      [ Str "A" , Space , Str "simple" , Space , Str "sidebar." ]
+  , Div
+      ( "" , [ "sidebar" ] , [] )
+      [ Div
+          ( "" , [ "title" ] , [] )
+          [ Para
+              [ Str "Optional"
+              , Space
+              , Str "Title"
+              , Space
+              , Strong
+                  [ Str "with"
+                  , Space
+                  , Str "strong"
+                  , Space
+                  , Str "emphasis"
+                  ]
+              ]
+          ]
+      , Para
+          [ Str "Here"
+          , Space
+          , Str "is"
+          , Space
+          , Str "a"
+          , Space
+          , Str "sidebar."
+          ]
+      , Div
+          ( "" , [ "tip" ] , [] )
+          [ Div ( "" , [ "title" ] , [] ) [ Para [ Str "Tip" ] ]
+          , Para
+              [ Str "It"
+              , Space
+              , Str "can"
+              , Space
+              , Str "contain"
+              , Space
+              , Str "any"
+              , Space
+              , Str "type"
+              , Space
+              , Str "of"
+              , Space
+              , Str "content."
+              ]
+          ]
+      ]
+  , Header
+      2
+      ( "_literal_block" , [] , [] )
+      [ Str "Literal" , Space , Str "block" ]
+  , Para
+      [ Str "Short"
+      , Space
+      , Str "indented"
+      , Space
+      , Str "code:"
+      ]
+  , CodeBlock
+      ( "" , [] , [] )
+      "$ ls -a\n$ cat /foo/bar/baz \\\n     /bi/bim/bop\n"
+  , CodeBlock
+      ( "" , [] , [] ) "This is\n  a literal block too.\n"
+  , CodeBlock
+      ( "" , [] , [] )
+      "  Fenced\n    $+ *a* literal\n\n****\nnot a sidebar\n****\n"
+  , Header 2 ( "_listing" , [] , [] ) [ Str "Listing" ]
+  , CodeBlock
+      ( "" , [ "ruby" ] , [] )
+      "require 'sinatra'\n\nget '/hi' do\n  \"Hello World!\"\nend"
+  , Para [ Str "Implied:" ]
+  , CodeBlock
+      ( "" , [ "ruby" ] , [] )
+      "require 'sinatra'\n\nget '/hi' do\n  \"Hello World!\"\nend"
+  , CodeBlock
+      ( "" , [ "ruby" ] , [] )
+      "# A function\ndef foo\n  return 42\nend"
+  , CodeBlock
+      ( "hello" , [ "haskell" ] , [] )
+      "putStrLn $ unwords [\"Hello\", \"world\"]"
+  , Para [ Str "Line" , Space , Str "numbering:" ]
+  , CodeBlock
+      ( "" , [] , [ ( "options" , "linenums" ) ] )
+      "puts 1\nputs 2\nputs 3"
+  , CodeBlock
+      ( "" , [] , [] ) "This doesn't have a language.\n  +=\"hi\""
+  , Para
+      [ Str "And"
+      , Space
+      , Str "with"
+      , Space
+      , Str "a"
+      , Space
+      , Str "callout"
+      , Space
+      , Str "list:"
+      ]
+  , CodeBlock
+      ( "" , [ "ruby" ] , [] )
+      "require 'sinatra' \9312\n\nget '/hi' do \9313 \9314\n  \"Hello World!\"\nend"
+  , Div
+      ( "" , [ "callout-list" ] , [] )
+      [ OrderedList
+          ( 1 , DefaultStyle , DefaultDelim )
+          [ [ Para [ Str "Library" , Space , Str "import" ] ]
+          , [ Para [ Str "URL" , Space , Str "mapping" ] ]
+          , [ Para [ Str "Response" , Space , Str "block" ] ]
+          ]
+      ]
+  , Para [ Str "Markdown-style" , Space , Str "fenced:" ]
+  , CodeBlock
+      ( "" , [ "ruby" ] , [] ) "def foo\n  return 5\nend"
+  , Header 2 ( "_verse" , [] , [] ) [ Str "Verse" ]
+  , BlockQuote
+      [ Para
+          [ Str "The"
+          , Space
+          , Str "fog"
+          , Space
+          , Str "comes"
+          , LineBreak
+          , Str "on"
+          , Space
+          , Str "little"
+          , Space
+          , Str "cat"
+          , Space
+          , Str "feet."
+          ]
+      , Para
+          [ Str "\8212"
+          , Space
+          , Str "Carl"
+          , Space
+          , Str "Sandburg,"
+          , Space
+          , Str "two"
+          , Space
+          , Str "lines"
+          , Space
+          , Str "from"
+          , Space
+          , Str "the"
+          , Space
+          , Str "poem"
+          , Space
+          , Str "Fog"
+          ]
+      ]
+  , BlockQuote
+      [ Para
+          [ Str "The"
+          , Space
+          , Str "fog"
+          , Space
+          , Str "comes"
+          , LineBreak
+          , Str "on"
+          , Space
+          , Str "little"
+          , Space
+          , Str "cat"
+          , Space
+          , Str "feet."
+          , LineBreak
+          , Str "It"
+          , Space
+          , Str "sits"
+          , Space
+          , Str "looking"
+          , LineBreak
+          , Str "over"
+          , Space
+          , Str "harbor"
+          , Space
+          , Str "and"
+          , Space
+          , Str "city"
+          , LineBreak
+          , Str "on"
+          , Space
+          , Str "silent"
+          , Space
+          , Str "haunches"
+          , LineBreak
+          , Str "and"
+          , Space
+          , Str "then"
+          , Space
+          , Str "moves"
+          , Space
+          , Str "on."
+          ]
+      , Para
+          [ Str "\8212"
+          , Space
+          , Str "Carl"
+          , Space
+          , Str "Sandburg,"
+          , Space
+          , Str "Fog"
+          ]
+      ]
+  , Header
+      2 ( "_collapsible" , [] , [] ) [ Str "Collapsible" ]
+  , Para
+      [ Str "Click"
+      , Space
+      , Str "here"
+      , Space
+      , Str "for"
+      , Space
+      , Str "more."
+      ]
+  , Div
+      ( ""
+      , [ "example" ]
+      , [ ( "options" , "collapsible,open" ) ]
+      )
+      [ Para
+          [ Str "This"
+          , Space
+          , Str "is"
+          , Space
+          , Str "collapsible."
+          ]
+      , Para
+          [ Str "It"
+          , Space
+          , Str "can"
+          , Space
+          , Str "be"
+          , Space
+          , Str "hidden."
+          ]
+      ]
+  , Div
+      ( "" , [] , [ ( "options" , "collapsible" ) ] )
+      [ Div
+          ( "" , [ "title" ] , [] )
+          [ Para [ Str "Click" , Space , Str "me!" ] ]
+      , Para
+          [ Str "This"
+          , Space
+          , Str "paragraph"
+          , Space
+          , Str "is"
+          , SoftBreak
+          , Str "also"
+          , Space
+          , Str "collapsible."
+          ]
+      ]
+  , Header 2 ( "_quote" , [] , [] ) [ Str "Quote" ]
+  , BlockQuote
+      [ Para
+          [ Str "Everybody"
+          , Space
+          , Str "remember"
+          , Space
+          , Str "where"
+          , Space
+          , Str "we"
+          , Space
+          , Str "parked."
+          ]
+      , Para
+          [ Str "\8212"
+          , Space
+          , Str "Captain"
+          , Space
+          , Str "James"
+          , Space
+          , Str "T."
+          , Space
+          , Str "Kirk,"
+          , Space
+          , Str "Star"
+          , Space
+          , Str "Trek"
+          , Space
+          , Str "IV:"
+          , Space
+          , Str "The"
+          , Space
+          , Str "Voyage"
+          , Space
+          , Str "Home"
+          ]
+      ]
+  , BlockQuote
+      [ Para
+          [ Str "Dennis:"
+          , Space
+          , Str "Come"
+          , Space
+          , Str "and"
+          , Space
+          , Str "see"
+          , Space
+          , Str "the"
+          , Space
+          , Str "violence"
+          , Space
+          , Str "inherent"
+          , Space
+          , Str "in"
+          , Space
+          , Str "the"
+          , Space
+          , Str "system."
+          , Space
+          , Str "Help!"
+          , Space
+          , Str "Help!"
+          , Space
+          , Str "I\8217m"
+          , Space
+          , Str "being"
+          , SoftBreak
+          , Str "repressed."
+          ]
+      , Para
+          [ Str "King"
+          , Space
+          , Str "Arthur:"
+          , Space
+          , Str "Bloody"
+          , Space
+          , Str "peasant!"
+          ]
+      , Para
+          [ Str "Dennis:"
+          , Space
+          , Str "Oh,"
+          , Space
+          , Str "what"
+          , Space
+          , Str "a"
+          , Space
+          , Str "giveaway!"
+          , Space
+          , Str "Did"
+          , Space
+          , Str "you"
+          , Space
+          , Str "hear"
+          , Space
+          , Str "that?"
+          , Space
+          , Str "Did"
+          , Space
+          , Str "you"
+          , Space
+          , Str "hear"
+          , Space
+          , Str "that,"
+          , Space
+          , Str "eh?"
+          , Space
+          , Str "That\8217s"
+          , Space
+          , Str "what"
+          , Space
+          , Str "I\8217m"
+          , SoftBreak
+          , Str "on"
+          , Space
+          , Str "about!"
+          , Space
+          , Str "Did"
+          , Space
+          , Str "you"
+          , Space
+          , Str "see"
+          , Space
+          , Str "him"
+          , Space
+          , Str "repressing"
+          , Space
+          , Str "me?"
+          , Space
+          , Str "You"
+          , Space
+          , Str "saw"
+          , Space
+          , Str "him,"
+          , Space
+          , Str "Didn\8217t"
+          , Space
+          , Str "you?"
+          ]
+      , Para
+          [ Str "\8212"
+          , Space
+          , Str "Monty"
+          , Space
+          , Str "Python"
+          , Space
+          , Str "and"
+          , Space
+          , Str "the"
+          , Space
+          , Str "Holy"
+          , Space
+          , Str "Grail"
+          ]
+      ]
+  , Div
+      ( "roads" , [ "movie" ] , [ ( "wrapper" , "1" ) ] )
+      [ BlockQuote
+          [ Para
+              [ Str "Roads?"
+              , Space
+              , Str "Where"
+              , Space
+              , Str "we\8217re"
+              , Space
+              , Str "going,"
+              , Space
+              , Str "we"
+              , Space
+              , Str "don\8217t"
+              , Space
+              , Str "need"
+              , Space
+              , Str "roads."
+              ]
+          , Para
+              [ Str "\8212"
+              , Space
+              , Str "Dr."
+              , Space
+              , Str "Emmett"
+              , Space
+              , Str "Brown"
+              ]
+          ]
+      ]
+  , Header 2 ( "_pass" , [] , [] ) [ Str "Pass" ]
+  , Para [ Str "pass" , Space , Emph [ Str "through" ] ]
+  , Header
+      2
+      ( "_open_block" , [] , [] )
+      [ Str "Open" , Space , Str "block" ]
+  , Div
+      ( "" , [] , [ ( "key" , "a value" ) ] )
+      [ Div
+          ( "" , [ "title" ] , [] )
+          [ Para [ Str "A" , Space , Str "title." ] ]
+      , Para
+          [ Str "Any"
+          , Space
+          , Str "content"
+          , Space
+          , Str "can"
+          , Space
+          , Str "go"
+          , Space
+          , Str "here:"
+          ]
+      , OrderedList
+          ( 1 , DefaultStyle , DefaultDelim )
+          [ [ Para [ Str "one" ] ] , [ Para [ Str "two" ] ] ]
+      ]
+  , Header 2 ( "_anchor" , [] , [] ) [ Str "Anchor" ]
+  , Div
+      ( "goals" , [] , [ ( "wrapper" , "1" ) ] )
+      [ BulletList
+          [ [ Para [ Str "one" ] ] , [ Para [ Str "two" ] ] ]
+      ]
+  , Header 2 ( "_breaks" , [] , [] ) [ Str "Breaks" ]
+  , Para
+      [ Str "Asciidoc"
+      , Space
+      , Str "thematic"
+      , Space
+      , Str "break:"
+      ]
+  , HorizontalRule
+  , Para [ Str "Markdown" , Space , Str "style:" ]
+  , HorizontalRule
+  , HorizontalRule
+  , HorizontalRule
+  , HorizontalRule
+  , Para [ Str "Page" , Space , Str "breaks:" ]
+  , Div
+      ( "" , [ "page-break" ] , [ ( "wrapper" , "1" ) ] )
+      [ HorizontalRule ]
+  , Div
+      ( ""
+      , [ "page-break" ]
+      , [ ( "options" , "always" ) , ( "wrapper" , "1" ) ]
+      )
+      [ HorizontalRule ]
+  , Header 2 ( "_list" , [] , [] ) [ Str "List" ]
+  , BulletList
+      [ [ Para
+            [ Str "Edgar" , Space , Str "Allan" , Space , Str "Poe" ]
+        ]
+      , [ Para
+            [ Str "Sheri" , Space , Str "S." , Space , Str "Tepper" ]
+        ]
+      , [ Para [ Str "Bill" , Space , Str "Bryson" ] ]
+      ]
+  , Div
+      ( "" , [] , [] )
+      [ Div
+          ( "" , [ "title" ] , [] )
+          [ Para
+              [ Str "Kizmet\8217s"
+              , Space
+              , Str "Favorite"
+              , Space
+              , Str "Authors"
+              ]
+          ]
+      , BulletList
+          [ [ Para
+                [ Str "Edgar"
+                , Space
+                , Str "Allan"
+                , Space
+                , Str "Poe"
+                ]
+            ]
+          , [ Para
+                [ Str "Sheri"
+                , Space
+                , Str "S."
+                , Space
+                , Str "Tepper"
+                ]
+            ]
+          , [ Para [ Str "Bill" , Space , Str "Bryson" ] ]
+          ]
+      ]
+  , BulletList
+      [ [ Para
+            [ Str "Edgar" , Space , Str "Allan" , Space , Str "Poe" ]
+        ]
+      , [ Para
+            [ Str "Sheri" , Space , Str "S." , Space , Str "Tepper" ]
+        ]
+      , [ Para [ Str "Bill" , Space , Str "Bryson" ] ]
+      ]
+  , OrderedList
+      ( 1 , DefaultStyle , DefaultDelim )
+      [ [ Para [ Str "Nested" , Space , Str "list" ]
+        , BulletList
+            [ [ Para
+                  [ Str "West"
+                  , Space
+                  , Str "wood"
+                  , Space
+                  , Str "maze"
+                  ]
+              , BulletList
+                  [ [ Para [ Str "Maze" , Space , Str "heart" ]
+                    , BulletList
+                        [ [ Para
+                              [ Str "Reflection" , Space , Str "pool" ]
+                          ]
+                        ]
+                    ]
+                  , [ Para [ Str "Secret" , Space , Str "exit" ] ]
+                  ]
+              ]
+            , [ Para
+                  [ Str "Level"
+                  , Space
+                  , Str "1"
+                  , Space
+                  , Str "list"
+                  , Space
+                  , Str "item"
+                  ]
+              , BulletList
+                  [ [ Para
+                        [ Str "Level"
+                        , Space
+                        , Str "2"
+                        , Space
+                        , Str "list"
+                        , Space
+                        , Str "item"
+                        ]
+                    , BulletList
+                        [ [ Para
+                              [ Str "Level"
+                              , Space
+                              , Str "3"
+                              , Space
+                              , Str "list"
+                              , Space
+                              , Str "item"
+                              ]
+                          , BulletList
+                              [ [ Para
+                                    [ Str "Level"
+                                    , Space
+                                    , Str "4"
+                                    , Space
+                                    , Str "list"
+                                    , Space
+                                    , Str "item"
+                                    ]
+                                , BulletList
+                                    [ [ Para
+                                          [ Str "Level"
+                                          , Space
+                                          , Str "5"
+                                          , Space
+                                          , Str "list"
+                                          , Space
+                                          , Str "item"
+                                          ]
+                                      , BulletList
+                                          [ [ Para [ Str "etc." ] ] ]
+                                      ]
+                                    ]
+                                ]
+                              ]
+                          ]
+                        ]
+                    ]
+                  ]
+              ]
+            , [ Para
+                  [ Str "Level"
+                  , Space
+                  , Str "1"
+                  , Space
+                  , Str "list"
+                  , Space
+                  , Str "item"
+                  ]
+              ]
+            ]
+        ]
+      ]
+  , BulletList
+      [ [ Para [ Str "one" ] ]
+      , [ Para [ Str "two" ] ]
+      , [ Para [ Str "three" ] ]
+      ]
+  , OrderedList
+      ( 1 , DefaultStyle , DefaultDelim )
+      [ [ Para [ Str "Protons" ] ]
+      , [ Para [ Str "Electrons" ] ]
+      , [ Para [ Str "Neutrons" ] ]
+      ]
+  , OrderedList
+      ( 1 , DefaultStyle , DefaultDelim )
+      [ [ Para [ Str "Protons" ] ]
+      , [ Para [ Str "Electrons" ] ]
+      , [ Para [ Str "Neutrons" ] ]
+      ]
+  , Para
+      [ Str "Start" , Space , Str "with" , Space , Str "4:" ]
+  , OrderedList
+      ( 4 , DefaultStyle , DefaultDelim )
+      [ [ Para [ Str "Step" , Space , Str "four" ] ]
+      , [ Para [ Str "Step" , Space , Str "five" ] ]
+      , [ Para [ Str "Step" , Space , Str "six" ] ]
+      ]
+  , Para [ Str "or" ]
+  , OrderedList
+      ( 4 , DefaultStyle , DefaultDelim )
+      [ [ Para [ Str "Step" , Space , Str "four" ] ]
+      , [ Para [ Str "Step" , Space , Str "five" ] ]
+      , [ Para [ Str "Step" , Space , Str "six" ] ]
+      ]
+  , Para [ Str "Reversed:" ]
+  , Div
+      ( "" , [] , [ ( "options" , "reversed" ) ] )
+      [ Div
+          ( "" , [ "title" ] , [] )
+          [ Para
+              [ Str "Parts"
+              , Space
+              , Str "of"
+              , Space
+              , Str "an"
+              , Space
+              , Str "atom"
+              ]
+          ]
+      , OrderedList
+          ( 1 , DefaultStyle , DefaultDelim )
+          [ [ Para [ Str "Protons" ] ]
+          , [ Para [ Str "Electrons" ] ]
+          , [ Para [ Str "Neutrons" ] ]
+          ]
+      ]
+  , Para [ Str "Nested" ]
+  , OrderedList
+      ( 1 , DefaultStyle , DefaultDelim )
+      [ [ Para [ Str "Step" , Space , Str "1" ] ]
+      , [ Para [ Str "Step" , Space , Str "2" ]
+        , OrderedList
+            ( 1 , DefaultStyle , DefaultDelim )
+            [ [ Para [ Str "Step" , Space , Str "2a" ] ]
+            , [ Para [ Str "Step" , Space , Str "2b" ] ]
+            ]
+        ]
+      , [ Para [ Str "Step" , Space , Str "3" ] ]
+      ]
+  , Para [ Str "Mixed" , Space , Str "nested" ]
+  , OrderedList
+      ( 1 , DefaultStyle , DefaultDelim )
+      [ [ Para [ Str "Linux" ]
+        , BulletList
+            [ [ Para [ Str "Fedora" ] ]
+            , [ Para [ Str "Ubuntu" ] ]
+            , [ Para [ Str "Slackware" ] ]
+            ]
+        ]
+      , [ Para [ Str "BSD" ]
+        , BulletList
+            [ [ Para [ Str "FreeBSD" ] ] , [ Para [ Str "NetBSD" ] ] ]
+        ]
+      ]
+  , Para [ Str "With" , Space , Str "spacing" ]
+  , OrderedList
+      ( 1 , DefaultStyle , DefaultDelim )
+      [ [ Para [ Str "Linux" ]
+        , BulletList
+            [ [ Para [ Str "Fedora" ] ]
+            , [ Para [ Str "Ubuntu" ] ]
+            , [ Para [ Str "Slackware" ] ]
+            ]
+        ]
+      , [ Para [ Str "BSD" ]
+        , BulletList
+            [ [ Para [ Str "FreeBSD" ] ] , [ Para [ Str "NetBSD" ] ] ]
+        ]
+      ]
+  , Para
+      [ Str "With" , Space , Str "number" , Space , Str "styles" ]
+  , OrderedList
+      ( 5 , LowerRoman , DefaultDelim )
+      [ [ Para [ Str "Five" ] ]
+      , [ Para [ Str "Six" ]
+        , OrderedList
+            ( 1 , LowerAlpha , DefaultDelim )
+            [ [ Para [ Str "a" ] ]
+            , [ Para [ Str "b" ] ]
+            , [ Para [ Str "c" ] ]
+            ]
+        ]
+      , [ Para [ Str "Seven" ] ]
+      ]
+  , Para [ Str "Checklist" ]
+  , BulletList
+      [ [ Para [ Str "\9746" , Space , Str "checked" ] ]
+      , [ Para
+            [ Str "\9746" , Space , Str "also" , Space , Str "checked" ]
+        ]
+      , [ Para
+            [ Str "\9744" , Space , Str "not" , Space , Str "checked" ]
+        ]
+      , [ Para
+            [ Str "normal" , Space , Str "list" , Space , Str "item" ]
+        ]
+      ]
+  , Para
+      [ Str "Separate"
+      , Space
+      , Str "lists"
+      , Space
+      , Str "with"
+      , Space
+      , Str "block"
+      , Space
+      , Str "attribute"
+      ]
+  , BulletList
+      [ [ Para [ Str "Apples" ] ]
+      , [ Para [ Str "Oranges" ]
+        , OrderedList
+            ( 1 , DefaultStyle , DefaultDelim )
+            [ [ Para [ Str "Wash" ] ] , [ Para [ Str "Slice" ] ] ]
+        ]
+      ]
+  , Para [ Str "Multiline" , Space , Str "items" ]
+  , BulletList
+      [ [ Para
+            [ Str "Blah"
+            , Space
+            , Str "blah."
+            , SoftBreak
+            , Str "Blah"
+            , Space
+            , Str "blah."
+            ]
+        ]
+      , [ Para
+            [ Str "The"
+            , Space
+            , Str "document"
+            , Space
+            , Str "header"
+            , Space
+            , Str "in"
+            , Space
+            , Str "AsciiDoc"
+            , Space
+            , Str "is"
+            , Space
+            , Str "optional."
+            , SoftBreak
+            , Str "If"
+            , Space
+            , Str "present,"
+            , Space
+            , Str "it"
+            , Space
+            , Str "must"
+            , Space
+            , Str "start"
+            , Space
+            , Str "with"
+            , Space
+            , Str "a"
+            , Space
+            , Str "document"
+            , Space
+            , Str "title."
+            ]
+        ]
+      ]
+  , BulletList
+      [ [ Para
+            [ Str "Optional"
+            , Space
+            , Str "author"
+            , Space
+            , Str "and"
+            , Space
+            , Str "revision"
+            , Space
+            , Str "information"
+            , Space
+            , Str "lines"
+            , SoftBreak
+            , Str "immediately"
+            , Space
+            , Str "follow"
+            , Space
+            , Str "the"
+            , Space
+            , Str "document"
+            , Space
+            , Str "title."
+            ]
+        ]
+      ]
+  , BulletList
+      [ [ Para
+            [ Str "The"
+            , Space
+            , Str "document"
+            , Space
+            , Str "header"
+            , Space
+            , Str "must"
+            , Space
+            , Str "be"
+            , Space
+            , Str "separated"
+            , Space
+            , Str "from"
+            , SoftBreak
+            , Str "the"
+            , Space
+            , Str "remainder"
+            , Space
+            , Str "of"
+            , Space
+            , Str "the"
+            , Space
+            , Str "document"
+            , Space
+            , Str "by"
+            , Space
+            , Str "one"
+            , Space
+            , Str "or"
+            , Space
+            , Str "more"
+            , SoftBreak
+            , Str "empty"
+            , Space
+            , Str "lines"
+            , Space
+            , Str "and"
+            , Space
+            , Str "it"
+            , Space
+            , Str "cannot"
+            , Space
+            , Str "contain"
+            , Space
+            , Str "empty"
+            , Space
+            , Str "lines."
+            ]
+        ]
+      ]
+  , Para [ Str "Complex" , Space , Str "item" ]
+  , BulletList
+      [ [ Para
+            [ Str "The"
+            , Space
+            , Str "header"
+            , Space
+            , Str "in"
+            , Space
+            , Str "AsciiDoc"
+            , Space
+            , Str "must"
+            , Space
+            , Str "start"
+            , Space
+            , Str "with"
+            , Space
+            , Str "a"
+            , Space
+            , Str "document"
+            , Space
+            , Str "title."
+            ]
+        , CodeBlock ( "" , [] , [] ) "= Document Title"
+        , Para
+            [ Str "Keep"
+            , Space
+            , Str "in"
+            , Space
+            , Str "mind"
+            , Space
+            , Str "that"
+            , Space
+            , Str "the"
+            , Space
+            , Str "header"
+            , Space
+            , Str "is"
+            , Space
+            , Str "optional."
+            ]
+        ]
+      , [ Para
+            [ Str "Optional"
+            , Space
+            , Str "author"
+            , Space
+            , Str "and"
+            , Space
+            , Str "revision"
+            , Space
+            , Str "information"
+            , Space
+            , Str "lines"
+            , Space
+            , Str "immediately"
+            , Space
+            , Str "follow"
+            , Space
+            , Str "the"
+            , Space
+            , Str "document"
+            , SoftBreak
+            , Str "title."
+            ]
+        , CodeBlock
+            ( "" , [] , [] )
+            "= Document Title\nDoc Writer <doc.writer@asciidoc.org>\nv1.0, 2022-01-01"
+        ]
+      , [ Para [ Str "Second" , Space , Str "item" ] ]
+      ]
+  , Para
+      [ Str "Empty"
+      , Space
+      , Str "principle"
+      , Space
+      , Str "element:"
+      ]
+  , OrderedList
+      ( 1 , DefaultStyle , DefaultDelim )
+      [ [ Para [] , CodeBlock ( "" , [] , [] ) "test" ] ]
+  , Header 2 ( "_table" , [] , [] ) [ Str "Table" ]
+  , Header
+      3
+      ( "_simple_with_column_specs" , [] , [] )
+      [ Str "Simple"
+      , Space
+      , Str "with"
+      , Space
+      , Str "column"
+      , Space
+      , Str "specs"
+      ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidth 0.375 )
+      , ( AlignDefault , ColWidth 0.25 )
+      , ( AlignDefault , ColWidth 0.375 )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "This"
+                      , Space
+                      , Str "content"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "placed"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "first"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "of"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "This"
+                      , Space
+                      , Str "line"
+                      , Space
+                      , Str "starts"
+                      , Space
+                      , Str "with"
+                      , Space
+                      , Str "a"
+                      , Space
+                      , Str "vertical"
+                      , Space
+                      , Str "bar"
+                      , Space
+                      , Str "so"
+                      , Space
+                      , Str "this"
+                      , Space
+                      , Str "content"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "placed"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "a"
+                      , Space
+                      , Str "new"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "in"
+                      , SoftBreak
+                      , Str "column"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "When"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "processor"
+                      , Space
+                      , Str "encounters"
+                      , Space
+                      , Str "a"
+                      , Space
+                      , Str "whitespace"
+                      , Space
+                      , Str "followed"
+                      , Space
+                      , Str "by"
+                      , Space
+                      , Str "a"
+                      , Space
+                      , Str "vertical"
+                      , Space
+                      , Str "bar"
+                      , Space
+                      , Str "it"
+                      , SoftBreak
+                      , Str "ends"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "previous"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "and"
+                      , Space
+                      , Str "starts"
+                      , Space
+                      , Str "a"
+                      , Space
+                      , Str "new"
+                      , Space
+                      , Str "cell"
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_repeated_column_in_specs" , [] , [] )
+      [ Str "Repeated"
+      , Space
+      , Str "column"
+      , Space
+      , Str "in"
+      , Space
+      , Str "specs"
+      ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignRight
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Strong
+                          [ Str "This"
+                          , Space
+                          , Str "cell\8217s"
+                          , Space
+                          , Str "specifier"
+                          , Space
+                          , Str "indicates"
+                          , Space
+                          , Str "that"
+                          , Space
+                          , Str "this"
+                          , Space
+                          , Str "cell\8217s"
+                          , Space
+                          , Str "content"
+                          , Space
+                          , Str "is"
+                          , Space
+                          , Str "right-aligned"
+                          , Space
+                          , Str "and"
+                          , Space
+                          , Str "bold."
+                          ]
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "The"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "specifier"
+                      , Space
+                      , Str "on"
+                      , Space
+                      , Str "this"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "hasn\8217t"
+                      , Space
+                      , Str "been"
+                      , Space
+                      , Str "set"
+                      , Space
+                      , Str "explicitly,"
+                      , Space
+                      , Str "so"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "default"
+                      , SoftBreak
+                      , Str "properties"
+                      , Space
+                      , Str "are"
+                      , Space
+                      , Str "applied."
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_simple_without_column_specs" , [] , [] )
+      [ Str "Simple"
+      , Space
+      , Str "without"
+      , Space
+      , Str "column"
+      , Space
+      , Str "specs"
+      ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead
+         ( "" , [] , [] )
+         [ Row
+             ( "" , [] , [] )
+             [ Cell
+                 ( "" , [] , [] )
+                 AlignDefault
+                 (RowSpan 1)
+                 (ColSpan 1)
+                 [ Plain
+                     [ Str "Column"
+                     , Space
+                     , Str "1,"
+                     , Space
+                     , Str "header"
+                     , Space
+                     , Str "row"
+                     ]
+                 ]
+             , Cell
+                 ( "" , [] , [] )
+                 AlignDefault
+                 (RowSpan 1)
+                 (ColSpan 1)
+                 [ Plain
+                     [ Str "Column"
+                     , Space
+                     , Str "2,"
+                     , Space
+                     , Str "header"
+                     , Space
+                     , Str "row"
+                     ]
+                 ]
+             ]
+         ])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "3"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "3"
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_with_caption" , [] , [] )
+      [ Str "With" , Space , Str "caption" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption
+         Nothing
+         [ Plain
+             [ Str "My" , Space , Str "cool" , Space , Str "table." ]
+         ])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead
+         ( "" , [] , [] )
+         [ Row
+             ( "" , [] , [] )
+             [ Cell
+                 ( "" , [] , [] )
+                 AlignDefault
+                 (RowSpan 1)
+                 (ColSpan 1)
+                 [ Plain
+                     [ Str "Column"
+                     , Space
+                     , Str "1,"
+                     , Space
+                     , Str "header"
+                     , Space
+                     , Str "row"
+                     ]
+                 ]
+             , Cell
+                 ( "" , [] , [] )
+                 AlignDefault
+                 (RowSpan 1)
+                 (ColSpan 1)
+                 [ Plain
+                     [ Str "Column"
+                     , Space
+                     , Str "2,"
+                     , Space
+                     , Str "header"
+                     , Space
+                     , Str "row"
+                     ]
+                 ]
+             ]
+         ])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "3"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "3"
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_no_header" , [] , [] )
+      [ Str "No" , Space , Str "header" ]
+  , Para
+      [ Str "By"
+      , Space
+      , Str "default"
+      , Space
+      , Str "the"
+      , Space
+      , Str "first"
+      , Space
+      , Str "line"
+      , Space
+      , Str "should"
+      , Space
+      , Str "turn"
+      , Space
+      , Str "into"
+      , Space
+      , Str "the"
+      , Space
+      , Str "header,"
+      , Space
+      , Str "but"
+      , Space
+      , Str "this"
+      , SoftBreak
+      , Str "can"
+      , Space
+      , Str "be"
+      , Space
+      , Str "disabled:"
+      ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "1"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "1"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Para
+      [ Str "And"
+      , Space
+      , Str "also"
+      , Space
+      , Str "explicitly"
+      , Space
+      , Str "enabled:"
+      ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Cell" , Space , Str "A1" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Cell" , Space , Str "B1" ] ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Cell" , Space , Str "A2" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Cell" , Space , Str "B2" ] ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Cell" , Space , Str "A3" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Cell" , Space , Str "B3" ] ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header 3 ( "_footer" , [] , [] ) [ Str "Footer" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidth 0.4 )
+      , ( AlignDefault , ColWidth 0.4 )
+      , ( AlignDefault , ColWidth 0.2 )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "header"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "header"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "3,"
+                      , Space
+                      , Str "header"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "3,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "footer"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "footer"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "3,"
+                      , Space
+                      , Str "footer"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Para [ Str "or" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "header"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "header"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "3"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "3"
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot
+         ( "" , [] , [] )
+         [ Row
+             ( "" , [] , [] )
+             [ Cell
+                 ( "" , [] , [] )
+                 AlignDefault
+                 (RowSpan 1)
+                 (ColSpan 1)
+                 [ Plain
+                     [ Str "Column"
+                     , Space
+                     , Str "1,"
+                     , Space
+                     , Str "footer"
+                     , Space
+                     , Str "row"
+                     ]
+                 ]
+             , Cell
+                 ( "" , [] , [] )
+                 AlignDefault
+                 (RowSpan 1)
+                 (ColSpan 1)
+                 [ Plain
+                     [ Str "Column"
+                     , Space
+                     , Str "2,"
+                     , Space
+                     , Str "footer"
+                     , Space
+                     , Str "row"
+                     ]
+                 ]
+             ]
+         ])
+  , Header 3 ( "_alignment" , [] , [] ) [ Str "Alignment" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Column" , Space , Str "Name" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Column" , Space , Str "Name" ] ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignCenter
+                  (RowSpan 1)
+                  (ColSpan 2)
+                  [ Plain
+                      [ Str "This"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "spans"
+                      , Space
+                      , Str "two"
+                      , Space
+                      , Str "columns,"
+                      , Space
+                      , Str "and"
+                      , Space
+                      , Str "its"
+                      , Space
+                      , Str "content"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "horizontally"
+                      , Space
+                      , Str "centered"
+                      , Space
+                      , Str "because"
+                      , Space
+                      , Str "the"
+                      , SoftBreak
+                      , Str "cell"
+                      , Space
+                      , Str "specifier"
+                      , Space
+                      , Str "includes"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Code ( "" , [] , [] ) "^"
+                      , Space
+                      , Str "operator."
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignCenter
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "This"
+                      , Space
+                      , Str "content"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "duplicated"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "two"
+                      , Space
+                      , Str "adjacent"
+                      , Space
+                      , Str "columns."
+                      , SoftBreak
+                      , Str "Its"
+                      , Space
+                      , Str "content"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "horizontally"
+                      , Space
+                      , Str "centered"
+                      , Space
+                      , Str "because"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "specifier"
+                      , SoftBreak
+                      , Str "includes"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Code ( "" , [] , [] ) "^"
+                      , Space
+                      , Str "operator."
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignCenter
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "This"
+                      , Space
+                      , Str "content"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "duplicated"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "two"
+                      , Space
+                      , Str "adjacent"
+                      , Space
+                      , Str "columns."
+                      , SoftBreak
+                      , Str "Its"
+                      , Space
+                      , Str "content"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "horizontally"
+                      , Space
+                      , Str "centered"
+                      , Space
+                      , Str "because"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "specifier"
+                      , SoftBreak
+                      , Str "includes"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Code ( "" , [] , [] ) "^"
+                      , Space
+                      , Str "operator."
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_multiple_paragraphs_in_cells" , [] , [] )
+      [ Str "Multiple"
+      , Space
+      , Str "paragraphs"
+      , Space
+      , Str "in"
+      , Space
+      , Str "cells"
+      ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault ) ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para
+                      [ Str "Single"
+                      , Space
+                      , Str "paragraph"
+                      , Space
+                      , Str "on"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "1"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para
+                      [ Str "First"
+                      , Space
+                      , Str "paragraph"
+                      , Space
+                      , Str "on"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  , Para
+                      [ Str "Second"
+                      , Space
+                      , Str "paragraph"
+                      , Space
+                      , Str "on"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_complex_table" , [] , [] )
+      [ Str "Complex" , Space , Str "table" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignRight
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para
+                      [ Code ( "" , [] , [] ) "This"
+                      , Space
+                      , Code ( "" , [] , [] ) "content"
+                      , Space
+                      , Code ( "" , [] , [] ) "is"
+                      , Space
+                      , Code ( "" , [] , [] ) "duplicated"
+                      , Space
+                      , Code ( "" , [] , [] ) "across"
+                      , Space
+                      , Code ( "" , [] , [] ) "two"
+                      , Space
+                      , Code ( "" , [] , [] ) "columns."
+                      ]
+                  , Para
+                      [ Code ( "" , [] , [] ) "It"
+                      , Space
+                      , Code ( "" , [] , [] ) "is"
+                      , Space
+                      , Code ( "" , [] , [] ) "aligned"
+                      , Space
+                      , Code ( "" , [] , [] ) "right"
+                      , Space
+                      , Code ( "" , [] , [] ) "horizontally."
+                      ]
+                  , Para
+                      [ Code ( "" , [] , [] ) "And"
+                      , Space
+                      , Code ( "" , [] , [] ) "it"
+                      , Space
+                      , Code ( "" , [] , [] ) "is"
+                      , Space
+                      , Code ( "" , [] , [] ) "monospaced."
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignRight
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para
+                      [ Code ( "" , [] , [] ) "This"
+                      , Space
+                      , Code ( "" , [] , [] ) "content"
+                      , Space
+                      , Code ( "" , [] , [] ) "is"
+                      , Space
+                      , Code ( "" , [] , [] ) "duplicated"
+                      , Space
+                      , Code ( "" , [] , [] ) "across"
+                      , Space
+                      , Code ( "" , [] , [] ) "two"
+                      , Space
+                      , Code ( "" , [] , [] ) "columns."
+                      ]
+                  , Para
+                      [ Code ( "" , [] , [] ) "It"
+                      , Space
+                      , Code ( "" , [] , [] ) "is"
+                      , Space
+                      , Code ( "" , [] , [] ) "aligned"
+                      , Space
+                      , Code ( "" , [] , [] ) "right"
+                      , Space
+                      , Code ( "" , [] , [] ) "horizontally."
+                      ]
+                  , Para
+                      [ Code ( "" , [] , [] ) "And"
+                      , Space
+                      , Code ( "" , [] , [] ) "it"
+                      , Space
+                      , Code ( "" , [] , [] ) "is"
+                      , Space
+                      , Code ( "" , [] , [] ) "monospaced."
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignCenter
+                  (RowSpan 3)
+                  (ColSpan 1)
+                  [ Para
+                      [ Strong
+                          [ Str "This"
+                          , Space
+                          , Str "cell"
+                          , Space
+                          , Str "spans"
+                          , Space
+                          , Str "3"
+                          , Space
+                          , Str "rows."
+                          , Space
+                          , Str "The"
+                          , Space
+                          , Str "content"
+                          , Space
+                          , Str "is"
+                          , Space
+                          , Str "centered"
+                          , Space
+                          , Str "horizontally,"
+                          , Space
+                          , Str "aligned"
+                          , Space
+                          , Str "to"
+                          , Space
+                          , Str "the"
+                          , Space
+                          , Str "bottom"
+                          , Space
+                          , Str "of"
+                          , Space
+                          , Str "the"
+                          , Space
+                          , Str "cell,"
+                          , Space
+                          , Str "and"
+                          , Space
+                          , Str "strong."
+                          ]
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para
+                      [ Emph
+                          [ Str "This"
+                          , Space
+                          , Str "content"
+                          , Space
+                          , Str "is"
+                          , Space
+                          , Str "emphasized."
+                          ]
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ CodeBlock
+                      ( "" , [] , [] )
+                      "This content is aligned to the top of the cell and literal.\n\n"
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ CodeBlock
+                      ( "" , [] , [] )
+                      "puts \"This is a source block!\""
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_column_styles" , [] , [] )
+      [ Str "Column" , Space , Str "styles" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Code ( "" , [] , [] ) "monospace" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Code ( "" , [] , [] ) "mono" ] ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "default" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Code ( "" , [] , [] ) "mono" ] ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_block_elements_in_cells" , [] , [] )
+      [ Str "Block"
+      , Space
+      , Str "elements"
+      , Space
+      , Str "in"
+      , Space
+      , Str "cells"
+      ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para [ Str "Normal" , Space , Str "Style" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para [ Str "AsciiDoc" , Space , Str "Style" ] ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para
+                      [ Str "This"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "isn\8217t"
+                      , Space
+                      , Str "prefixed"
+                      , Space
+                      , Str "with"
+                      , Space
+                      , Str "an"
+                      , Space
+                      , Code ( "" , [] , [] ) "a"
+                      , Str ","
+                      , Space
+                      , Str "so"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "processor"
+                      , Space
+                      , Str "doesn\8217t"
+                      , Space
+                      , Str "interpret"
+                      , Space
+                      , Str "the"
+                      , SoftBreak
+                      , Str "following"
+                      , Space
+                      , Str "lines"
+                      , Space
+                      , Str "as"
+                      , Space
+                      , Str "an"
+                      , Space
+                      , Str "AsciiDoc"
+                      , Space
+                      , Str "list."
+                      ]
+                  , Para
+                      [ Str "*"
+                      , Space
+                      , Str "List"
+                      , Space
+                      , Str "item"
+                      , Space
+                      , Str "1"
+                      , SoftBreak
+                      , Str "*"
+                      , Space
+                      , Str "List"
+                      , Space
+                      , Str "item"
+                      , Space
+                      , Str "2"
+                      , SoftBreak
+                      , Str "*"
+                      , Space
+                      , Str "List"
+                      , Space
+                      , Str "item"
+                      , Space
+                      , Str "3"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para
+                      [ Str "This"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "prefixed"
+                      , Space
+                      , Str "with"
+                      , Space
+                      , Str "an"
+                      , Space
+                      , Code ( "" , [] , [] ) "a"
+                      , Str ","
+                      , Space
+                      , Str "so"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "processor"
+                      , Space
+                      , Str "interprets"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "following"
+                      , Space
+                      , Str "lines"
+                      , SoftBreak
+                      , Str "as"
+                      , Space
+                      , Str "an"
+                      , Space
+                      , Str "AsciiDoc"
+                      , Space
+                      , Str "list."
+                      ]
+                  , BulletList
+                      [ [ Para
+                            [ Str "List"
+                            , Space
+                            , Str "item"
+                            , Space
+                            , Str "1"
+                            ]
+                        ]
+                      , [ Para
+                            [ Str "List"
+                            , Space
+                            , Str "item"
+                            , Space
+                            , Str "2"
+                            ]
+                        ]
+                      , [ Para
+                            [ Str "List"
+                            , Space
+                            , Str "item"
+                            , Space
+                            , Str "3"
+                            ]
+                        ]
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para
+                      [ Str "This"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "isn\8217t"
+                      , Space
+                      , Str "prefixed"
+                      , Space
+                      , Str "with"
+                      , Space
+                      , Str "an"
+                      , Space
+                      , Code ( "" , [] , [] ) "a"
+                      , Str ","
+                      , Space
+                      , Str "so"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "processor"
+                      , Space
+                      , Str "doesn\8217t"
+                      , Space
+                      , Str "interpret"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "listing"
+                      , SoftBreak
+                      , Str "block"
+                      , Space
+                      , Str "delimiters"
+                      , Space
+                      , Str "or"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Code ( "" , [] , [] ) "source"
+                      , Space
+                      , Str "style."
+                      ]
+                  , Para
+                      [ Str "----"
+                      , SoftBreak
+                      , Str "import"
+                      , Space
+                      , Str "os"
+                      , SoftBreak
+                      , Str "print"
+                      , Space
+                      , Str "(\"%s\""
+                      , Space
+                      , Str "%(os.uname()))"
+                      , SoftBreak
+                      , Str "----"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Para
+                      [ Str "This"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "prefixed"
+                      , Space
+                      , Str "with"
+                      , Space
+                      , Str "an"
+                      , Space
+                      , Code ( "" , [] , [] ) "a"
+                      , Str ","
+                      , Space
+                      , Str "so"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Str "listing"
+                      , Space
+                      , Str "block"
+                      , Space
+                      , Str "is"
+                      , Space
+                      , Str "processed"
+                      , Space
+                      , Str "and"
+                      , Space
+                      , Str "rendered"
+                      , SoftBreak
+                      , Str "according"
+                      , Space
+                      , Str "to"
+                      , Space
+                      , Str "the"
+                      , Space
+                      , Code ( "" , [] , [] ) "source"
+                      , Space
+                      , Str "style"
+                      , Space
+                      , Str "rules."
+                      ]
+                  , CodeBlock
+                      ( "" , [ "python" ] , [] )
+                      "import os\nprint \"%s\" %(os.uname())"
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_col_and_rowspan" , [] , [] )
+      [ Str "Col" , Space , Str "and" , Space , Str "rowspan" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "1,"
+                      , Space
+                      , Str "header"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "2,"
+                      , Space
+                      , Str "header"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Column"
+                      , Space
+                      , Str "3,"
+                      , Space
+                      , Str "header"
+                      , Space
+                      , Str "row"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 2)
+                  (ColSpan 2)
+                  [ Plain
+                      [ Str "This"
+                      , Space
+                      , Str "cell"
+                      , Space
+                      , Str "spans"
+                      , Space
+                      , Str "2"
+                      , Space
+                      , Str "cols"
+                      , Space
+                      , Str "and"
+                      , Space
+                      , Str "2"
+                      , Space
+                      , Str "rows"
+                      ]
+                  ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "3,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "2"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "3,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "3"
+                      ]
+                  ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 3)
+                  [ Plain
+                      [ Str "Cell"
+                      , Space
+                      , Str "in"
+                      , Space
+                      , Str "column"
+                      , Space
+                      , Str "1-3,"
+                      , Space
+                      , Str "row"
+                      , Space
+                      , Str "4"
+                      ]
+                  ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_csv_table" , [] , [] )
+      [ Str "CSV" , Space , Str "table" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead
+         ( "" , [] , [] )
+         [ Row
+             ( "" , [] , [] )
+             [ Cell
+                 ( "" , [] , [] )
+                 AlignDefault
+                 (RowSpan 1)
+                 (ColSpan 1)
+                 [ Plain [ Str "Artist" ] ]
+             , Cell
+                 ( "" , [] , [] )
+                 AlignDefault
+                 (RowSpan 1)
+                 (ColSpan 1)
+                 [ Plain [ Str "Track" ] ]
+             , Cell
+                 ( "" , [] , [] )
+                 AlignDefault
+                 (RowSpan 1)
+                 (ColSpan 1)
+                 [ Plain [ Str "Genre" ] ]
+             ]
+         ])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Baauer" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Harlem" , Space , Str "Shake" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Hip" , Space , Str "Hop" ] ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "The" , Space , Str "Lumineers" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Ho" , Space , Str "Hey" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Folk" , Space , Str "Rock" ] ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Para [ Str "or" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Artist" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Track" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Genre" ] ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Baauer" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Harlem" , Space , Str "Shake" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Hip" , Space , Str "Hop" ] ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Header
+      3
+      ( "_dsv_table" , [] , [] )
+      [ Str "DSV" , Space , Str "table" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "a" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "b" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "c" ] ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "d" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "e" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "f" ] ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Para [ Str "or" ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      , ( AlignDefault , ColWidthDefault )
+      ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Artist" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Track" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Genre" ] ]
+              ]
+          , Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Robyn" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Indestructible" ] ]
+              , Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "Dance" ] ]
               ]
           ]
       ]
diff --git a/test/command/10390.md b/test/command/10390.md
--- a/test/command/10390.md
+++ b/test/command/10390.md
@@ -11,12 +11,12 @@
 <caption>Overview of basic table markup</caption>
 <thead>
 <tr>
-<th><p>Key</p></th>
+<th>Key</th>
 </tr>
 </thead>
 <tbody>
 <tr>
-<td><p>Value</p></td>
+<td>Value</td>
 </tr>
 </tbody>
 </table>
diff --git a/test/command/10490.md b/test/command/10490.md
--- a/test/command/10490.md
+++ b/test/command/10490.md
@@ -18,31 +18,31 @@
 <table>
 <thead>
 <tr>
-<th style=""><p>Witness program version</p></th>
-<th colspan="4" style=""><p>Hash size</p></th>
+<th style="">Witness program version</th>
+<th colspan="4" style="">Hash size</th>
 </tr>
 </thead>
 <tbody>
 <tr>
-<td><p>Mainnet</p></td>
-<td><p>Testnet</p></td>
-<td><p>Mainnet</p></td>
-<td><p>Testnet</p></td>
+<td>Mainnet</td>
+<td>Testnet</td>
+<td>Mainnet</td>
+<td>Testnet</td>
 <td></td>
 </tr>
 <tr>
-<td><p>0</p></td>
-<td><p>p2</p></td>
-<td><p>QW</p></td>
-<td><p>7Xh</p></td>
-<td><p>T7n</p></td>
+<td>0</td>
+<td>p2</td>
+<td>QW</td>
+<td>7Xh</td>
+<td>T7n</td>
 </tr>
 <tr>
-<td><p>1</p></td>
-<td><p>p4</p></td>
-<td><p>QY</p></td>
-<td><p>7Xq</p></td>
-<td><p>T7w</p></td>
+<td>1</td>
+<td>p4</td>
+<td>QY</td>
+<td>7Xq</td>
+<td>T7w</td>
 </tr>
 </tbody>
 </table>
diff --git a/test/command/10643.md b/test/command/10643.md
--- a/test/command/10643.md
+++ b/test/command/10643.md
@@ -1,5 +1,5 @@
 ```
-% pandoc -f html -t native
+% pandoc -f html+raw_html -t native
 <p>A<style></style>B</p>
 ^D
 [ Para
@@ -8,4 +8,14 @@
     , Str "B"
     ]
 ]
+```
+
+The style element's contents should not be parsed as text
+even when raw_html is disabled:
+
+```
+% pandoc -f html -t native
+<p>A<style>p { color: red; }</style>B</p>
+^D
+[ Para [ Str "AB" ] ]
 ```
diff --git a/test/command/10965.md b/test/command/10965.md
--- a/test/command/10965.md
+++ b/test/command/10965.md
@@ -7,7 +7,6 @@
 #block[
 #set text(lang: "en");
 This text should be in English.
-
 ]
 ```
 
@@ -20,7 +19,6 @@
 #block[
 #set text(lang: "fr");
 Ce texte devrait être en français.
-
 ]
 ```
 
@@ -33,7 +31,6 @@
 #block[
 #set text(lang: "de");
 Dieser Text sollte auf Deutsch sein.
-
 ]
 ```
 
@@ -45,6 +42,5 @@
 ^D
 #block[
 This should not have lang set.
-
 ]
 ```
diff --git a/test/command/11583.md b/test/command/11583.md
--- a/test/command/11583.md
+++ b/test/command/11583.md
@@ -11,6 +11,5 @@
 + item
 + item
 + item
-
 ]
 ```
diff --git a/test/command/1166.md b/test/command/1166.md
--- a/test/command/1166.md
+++ b/test/command/1166.md
@@ -24,15 +24,15 @@
 </thead>
 <tbody>
 <tr>
-<td>1</td>
-<td>Second column of row 1.</td>
+<td><p>1</p></td>
+<td><p>Second column of row 1.</p></td>
 </tr>
 <tr>
-<td>2</td>
-<td>Second column of row 2. Second line of paragraph.</td>
+<td><p>2</p></td>
+<td><p>Second column of row 2. Second line of paragraph.</p></td>
 </tr>
 <tr>
-<td>3</td>
+<td><p>3</p></td>
 <td><ul>
 <li>Second column of row 3.</li>
 <li>Second item in bullet list (row 3, column 2).</li>
@@ -40,7 +40,7 @@
 </tr>
 <tr>
 <td></td>
-<td>Row 4; column 1 will be empty.</td>
+<td><p>Row 4; column 1 will be empty.</p></td>
 </tr>
 </tbody>
 </table>
diff --git a/test/command/11794.md b/test/command/11794.md
--- a/test/command/11794.md
+++ b/test/command/11794.md
@@ -6,7 +6,6 @@
 ^D
 #block[
 Hello
-
 ] <foo>
 
 ```
diff --git a/test/command/11849.md b/test/command/11849.md
new file mode 100644
--- /dev/null
+++ b/test/command/11849.md
@@ -0,0 +1,42 @@
+```
+% pandoc -t typst
+| A | B |
+|---|---|
+| 1 | 2 |
+
+: Example {#table-id}
+^D
+#figure(
+  align(center)[#table(
+    columns: 2,
+    align: (auto,auto,),
+    table.header([A], [B],),
+    table.hline(),
+    [1], [2],
+  )]
+  , caption: [Example]
+  , kind: table
+  )
+<table-id>
+
+```
+
+```
+% pandoc -t typst
+| A | B |
+|---|---|
+| 1 | 2 |
+
+: Example {#table-id .typst:no-figure}
+^D
+#table(
+  columns: 2,
+  align: (auto,auto,),
+  table.header([A], [B],),
+  table.hline(),
+  [1], [2],
+)
+<table-id>
+
+```
+
diff --git a/test/command/11858.md b/test/command/11858.md
new file mode 100644
--- /dev/null
+++ b/test/command/11858.md
@@ -0,0 +1,11 @@
+```
+% pandoc -f markdown+rebase_relative_paths -t html
+[Zotero item](zotero://select/library/items/ABCD1234)
+[Mail](mailto:a@b.c)
+^D
+<p><a href="zotero://select/library/items/ABCD1234">Zotero item</a> <a
+href="mailto:a@b.c">Mail</a></p>
+
+```
+
+
diff --git a/test/command/11869.md b/test/command/11869.md
new file mode 100644
--- /dev/null
+++ b/test/command/11869.md
@@ -0,0 +1,8 @@
+A link's title is written to docx as a ScreenTip (`w:tooltip`) and read back.
+
+```
+% pandoc -f markdown -t docx -o - | pandoc -f docx -t markdown
+See [the report](https://example.org/r.pdf "Annual report, PDF, 2 MB").
+^D
+See [the report](https://example.org/r.pdf "Annual report, PDF, 2 MB").
+```
diff --git a/test/command/11888.md b/test/command/11888.md
new file mode 100644
--- /dev/null
+++ b/test/command/11888.md
@@ -0,0 +1,46 @@
+```
+% pandoc -f markdown+rebase_relative_paths command/11888/section1/lab.md command/11888/section2/lab.md command/11888/section3/lab.md
+^D
+<h1 id="heading-1">Heading 1</h1>
+<figure>
+<img src="command/11888/section1/media/Pic1_1.png" alt="pic 1" />
+<figcaption aria-hidden="true">pic 1</figcaption>
+</figure>
+<blockquote>
+<p>a blockquote</p>
+<figure>
+<img src="command/11888/section1/media/Pic1_2.png" alt="pic 2" />
+<figcaption aria-hidden="true">pic 2</figcaption>
+</figure>
+<p>with an image reference</p>
+<figure>
+<img src="command/11888/section1/media/Pic1_3.png" alt="pic 3" />
+<figcaption aria-hidden="true">pic 3</figcaption>
+</figure>
+</blockquote>
+<h1 id="heading-2">Heading 2</h1>
+<figure>
+<img src="command/11888/section2/media/Pic2_1.png" alt="pic 3" />
+<figcaption aria-hidden="true">pic 3</figcaption>
+</figure>
+<ol type="1">
+<li><p>A list</p>
+<figure>
+<img src="command/11888/section2/media/Pic2_2.png" alt="pic 4" />
+<figcaption aria-hidden="true">pic 4</figcaption>
+</figure></li>
+</ol>
+<h1 id="heading-3">Heading 3</h1>
+<figure>
+<img src="command/11888/section3/media/Pic3_1.png" alt="pic 5" />
+<figcaption aria-hidden="true">pic 5</figcaption>
+</figure>
+<ol type="1">
+<li><p>A list</p>
+<figure>
+<img src="command/11888/section3/media/Pic3_2.png" alt="pic 6" />
+<figcaption aria-hidden="true">pic 6</figcaption>
+</figure></li>
+</ol>
+
+```
diff --git a/test/command/11888/section1/lab.md b/test/command/11888/section1/lab.md
new file mode 100644
--- /dev/null
+++ b/test/command/11888/section1/lab.md
@@ -0,0 +1,11 @@
+# Heading 1
+
+![pic 1](media/Pic1_1.png)
+
+> a blockquote
+>
+> ![pic 2](media/Pic1_2.png)
+>
+> with an image reference
+>
+> ![pic 3](media/Pic1_3.png)
diff --git a/test/command/11888/section2/lab.md b/test/command/11888/section2/lab.md
new file mode 100644
--- /dev/null
+++ b/test/command/11888/section2/lab.md
@@ -0,0 +1,8 @@
+# Heading 2
+
+![pic 3](media/Pic2_1.png)
+
+1. A list
+
+    ![pic 4](media/Pic2_2.png)
+
diff --git a/test/command/11888/section3/lab.md b/test/command/11888/section3/lab.md
new file mode 100644
--- /dev/null
+++ b/test/command/11888/section3/lab.md
@@ -0,0 +1,7 @@
+# Heading 3
+
+![pic 5](media/Pic3_1.png)
+
+1. A list
+    
+    ![pic 6](media/Pic3_2.png)
diff --git a/test/command/11897.md b/test/command/11897.md
new file mode 100644
--- /dev/null
+++ b/test/command/11897.md
@@ -0,0 +1,67 @@
+```
+% pandoc -f typst -t native
+One cluster: @a @b.
+
+Split by a newline:
+@c
+@d.
+^D
+[ Para
+    [ Str "One"
+    , Space
+    , Str "cluster:"
+    , Space
+    , Cite
+        [ Citation
+            { citationId = "a"
+            , citationPrefix = []
+            , citationSuffix = []
+            , citationMode = NormalCitation
+            , citationNoteNum = 0
+            , citationHash = 0
+            }
+        , Citation
+            { citationId = "b"
+            , citationPrefix = []
+            , citationSuffix = []
+            , citationMode = NormalCitation
+            , citationNoteNum = 0
+            , citationHash = 0
+            }
+        ]
+        [ Str "[a]" , Str "[b]" ]
+    , Str "."
+    ]
+, Para
+    [ Str "Split"
+    , Space
+    , Str "by"
+    , Space
+    , Str "a"
+    , Space
+    , Str "newline:"
+    , SoftBreak
+    , Cite
+        [ Citation
+            { citationId = "c"
+            , citationPrefix = []
+            , citationSuffix = []
+            , citationMode = NormalCitation
+            , citationNoteNum = 0
+            , citationHash = 0
+            }
+        , Citation
+            { citationId = "d"
+            , citationPrefix = []
+            , citationSuffix = []
+            , citationMode = NormalCitation
+            , citationNoteNum = 0
+            , citationHash = 0
+            }
+        ]
+        [ Str "[c]" , Str "[d]" ]
+    , Str "."
+    ]
+]
+
+```
diff --git a/test/command/2378.md b/test/command/2378.md
--- a/test/command/2378.md
+++ b/test/command/2378.md
@@ -14,7 +14,7 @@
 \begin{longtable}[]{@{}ll@{}}
 \caption{a table}\tabularnewline
 \toprule\noalign{}
-x & y\footnote{a footnote} \\
+x & y\footnotemark{} \\
 \midrule\noalign{}
 \endfirsthead
 \toprule\noalign{}
@@ -25,4 +25,5 @@
 \endlastfoot
 1 & 2 \\
 \end{longtable}
+\footnotetext{a footnote}
 ```
diff --git a/test/command/2606.md b/test/command/2606.md
--- a/test/command/2606.md
+++ b/test/command/2606.md
@@ -7,7 +7,7 @@
 <table>
 <tbody>
 <tr>
-<td><p>* hello</p></td>
+<td>* hello</td>
 </tr>
 </tbody>
 </table>
@@ -41,7 +41,7 @@
 <table>
 <tbody>
 <tr>
-<td><p><code>* hello</code></p></td>
+<td><code>* hello</code></td>
 </tr>
 </tbody>
 </table>
diff --git a/test/command/2623.md b/test/command/2623.md
new file mode 100644
--- /dev/null
+++ b/test/command/2623.md
@@ -0,0 +1,7 @@
+```
+% pandoc command/2623.odt
+^D
+<p><em>Italics</em></p>
+<p>not <em>italics</em></p>
+```
+
diff --git a/test/command/2623.odt b/test/command/2623.odt
new file mode 100644
Binary files /dev/null and b/test/command/2623.odt differ
diff --git a/test/command/2649.md b/test/command/2649.md
--- a/test/command/2649.md
+++ b/test/command/2649.md
@@ -32,27 +32,27 @@
 <table>
 <tbody>
 <tr>
-<td><p>peildatum Simbase</p></td>
-<td><p>november 2005</p></td>
-<td colspan="2"><p><strong>uitslagen Flohrgambiet</strong></p></td>
+<td>peildatum Simbase</td>
+<td>november 2005</td>
+<td colspan="2"><strong>uitslagen Flohrgambiet</strong></td>
 </tr>
 <tr>
-<td><p>totaal aantal partijen Simbase</p></td>
-<td><p>7.316.773</p></td>
-<td><p>wit wint</p></td>
-<td><p>53%</p></td>
+<td>totaal aantal partijen Simbase</td>
+<td>7.316.773</td>
+<td>wit wint</td>
+<td>53%</td>
 </tr>
 <tr>
-<td><p>percentage (en partijen) Flohrgambiet</p></td>
-<td><p>0.023 % (1.699)</p></td>
-<td><p>zwart wint</p></td>
-<td><p>27%</p></td>
+<td>percentage (en partijen) Flohrgambiet</td>
+<td>0.023 % (1.699)</td>
+<td>zwart wint</td>
+<td>27%</td>
 </tr>
 <tr>
-<td><p>percentage Flohrgambiet in aug 2003</p></td>
-<td><p>0.035 %</p></td>
-<td><p>remise</p></td>
-<td><p>20%</p></td>
+<td>percentage Flohrgambiet in aug 2003</td>
+<td>0.035 %</td>
+<td>remise</td>
+<td>20%</td>
 </tr>
 </tbody>
 </table>
diff --git a/test/command/2994.md b/test/command/2994.md
--- a/test/command/2994.md
+++ b/test/command/2994.md
@@ -4,7 +4,7 @@
 author="Jesse Rosenthal" date="2016-05-09T16:13:00Z"}some text to have a
 comment []{.comment-end id="0"}on it.
 ^D
-I want [I left a comment.]{.comment-start id="0"
+I want [I left a comment.]{.comment-start comment-id="0"
 author="Jesse Rosenthal" date="2016-05-09T16:13:00Z"}some text to have a
-comment []{.comment-end id="0"}on it.
+comment []{.comment-end comment-id="0"}on it.
 ```
diff --git a/test/command/3958.md b/test/command/3958.md
--- a/test/command/3958.md
+++ b/test/command/3958.md
@@ -9,12 +9,12 @@
 % pandoc -f latex -t native
 \texttt{``hi''}
 ^D
-[ Para [ Code ( "" , [] , [] ) "\8216\8216hi\8217\8217" ] ]
+[ Para [ Code ( "" , [] , [] ) "``hi''" ] ]
 ```
 
 ```
 % pandoc -f latex -t native
 \texttt{`hi'}
 ^D
-[ Para [ Code ( "" , [] , [] ) "\8216hi\8217" ] ]
+[ Para [ Code ( "" , [] , [] ) "`hi'" ] ]
 ```
diff --git a/test/command/5367.md b/test/command/5367.md
--- a/test/command/5367.md
+++ b/test/command/5367.md
@@ -22,10 +22,10 @@
 
 \begin{longtable}[]{@{}
   >{\centering\arraybackslash}p{(\linewidth - 0\tabcolsep) * \real{0.1667}}@{}}
-\caption[Sample table.]{Sample table.\footnote{caption footnote}}\tabularnewline
+\caption[Sample table.]{Sample table.\footnotemark{}}\tabularnewline
 \toprule\noalign{}
 \begin{minipage}[b]{\linewidth}\centering
-Fruit\footnote{header footnote}
+Fruit\footnotemark{}
 \end{minipage} \\
 \midrule\noalign{}
 \endfirsthead
@@ -37,8 +37,14 @@
 \endhead
 \bottomrule\noalign{}
 \endlastfoot
-Bans\footnote{table cell footnote} \\
+Bans\footnotemark{} \\
 \end{longtable}
+\addtocounter{footnote}{-2}
+\footnotetext{caption footnote}
+\addtocounter{footnote}{1}
+\footnotetext{header footnote}
+\addtocounter{footnote}{1}
+\footnotetext{table cell footnote}
 
 dolly\footnote{doc footnote}
 ```
diff --git a/test/command/6549.md b/test/command/6549.md
--- a/test/command/6549.md
+++ b/test/command/6549.md
@@ -9,13 +9,13 @@
 <caption>Test table</caption>
 <thead>
 <tr>
-<th>Column1</th>
-<th>Column2</th>
+<th><p>Column1</p></th>
+<th><p>Column2</p></th>
 </tr>
 </thead>
 <tbody>
 <tr>
-<td>Data1</td>
+<td><p>Data1</p></td>
 <td><ul>
 <li>data1</li>
 <li>data2</li>
diff --git a/test/command/6959.md b/test/command/6959.md
--- a/test/command/6959.md
+++ b/test/command/6959.md
@@ -5,8 +5,9 @@
 author="Mike" date="2020-12-17T17:39:00Z"} This is the content being
 commented on [[]{.comment-end id="2"}]{.comment-end id="1"}
 ^D
-[This is the comment]{.comment-start id="1" author="Mike"
-date="2020-12-17T16:53:00Z"} [Here is my reply]{.comment-start id="2"
-author="Mike" date="2020-12-17T17:39:00Z"} This is the content being
-commented on [[]{.comment-end id="2"}]{.comment-end id="1"}
+[This is the comment]{.comment-start comment-id="1" author="Mike"
+date="2020-12-17T16:53:00Z"} [Here is my reply]{.comment-start
+comment-id="2" author="Mike" date="2020-12-17T17:39:00Z"} This is the
+content being commented on [[]{.comment-end
+comment-id="2"}]{.comment-end comment-id="1"}
 ```
diff --git a/test/command/6992.md b/test/command/6992.md
--- a/test/command/6992.md
+++ b/test/command/6992.md
@@ -23,7 +23,7 @@
                AlignDefault
                (RowSpan 1)
                (ColSpan 4)
-               [ Para [ Str "template" , Space , Str "request" ] ]
+               [ Plain [ Str "template" , Space , Str "request" ] ]
            ]
        ])
     [ TableBody
@@ -37,25 +37,25 @@
                 AlignDefault
                 (RowSpan 1)
                 (ColSpan 1)
-                [ Para [ Str "mode" ] ]
+                [ Plain [ Str "mode" ] ]
             , Cell
                 ( "" , [] , [] )
                 AlignDefault
                 (RowSpan 1)
                 (ColSpan 1)
-                [ Para [ Str "No" ] ]
+                [ Plain [ Str "No" ] ]
             , Cell
                 ( "" , [] , [] )
                 AlignDefault
                 (RowSpan 1)
                 (ColSpan 1)
-                [ Para [ Str "String" ] ]
+                [ Plain [ Str "String" ] ]
             , Cell
                 ( "" , [] , [] )
                 AlignDefault
                 (RowSpan 1)
                 (ColSpan 1)
-                [ Para
+                [ Plain
                     [ Str "MUST"
                     , Space
                     , Str "be"
diff --git a/test/command/7214.md b/test/command/7214.md
--- a/test/command/7214.md
+++ b/test/command/7214.md
@@ -6,7 +6,7 @@
 |abc defghij | def      | xyz              |
 +------------+----------+------------------+
 ^D
-<table style="width:60%;">
+<table style="width:59%;">
 <colgroup>
 <col style="width: 18%" />
 <col style="width: 15%" />
diff --git a/test/command/7436.md b/test/command/7436.md
--- a/test/command/7436.md
+++ b/test/command/7436.md
@@ -9,10 +9,10 @@
 .. include:: command/three.txt
 ^D
 [ CodeBlock
-    ( "" , [ "" ] , [ ( "code" , "" ) ] )
+    ( "" , [] , [ ( "code" , "" ) ] )
     "1st line.\n2nd line.\n3rd line.\n"
 , CodeBlock
-    ( "" , [ "" ] , [ ( "literal" , "" ) ] )
+    ( "" , [] , [ ( "literal" , "" ) ] )
     "1st line.\n2nd line.\n3rd line.\n"
 , Para
     [ Str "1st"
diff --git a/test/command/8110.md b/test/command/8110.md
--- a/test/command/8110.md
+++ b/test/command/8110.md
@@ -19,21 +19,21 @@
 <table>
 <thead>
 <tr>
-<th><p>Header text</p></th>
-<th><p>Header text</p></th>
-<th><p>Header text</p></th>
+<th>Header text</th>
+<th>Header text</th>
+<th>Header text</th>
 </tr>
 </thead>
 <tbody>
 <tr>
-<td><p>Example</p></td>
-<td><p>Example</p></td>
-<td><p>Example</p></td>
+<td>Example</td>
+<td>Example</td>
+<td>Example</td>
 </tr>
 <tr>
-<td><p>Example</p></td>
-<td><p>Example</p></td>
-<td><p>Example</p></td>
+<td>Example</td>
+<td>Example</td>
+<td>Example</td>
 </tr>
 </tbody>
 </table>
diff --git a/test/command/8257.md b/test/command/8257.md
--- a/test/command/8257.md
+++ b/test/command/8257.md
@@ -10,7 +10,7 @@
 | Sum  | 8£    |
 +======+=======+
 ^D
-<table style="width:21%;">
+<table style="width:20%;">
 <colgroup>
 <col style="width: 9%" />
 <col style="width: 11%" />
diff --git a/test/command/8659.md b/test/command/8659.md
--- a/test/command/8659.md
+++ b/test/command/8659.md
@@ -22,7 +22,7 @@
 | a   |  b  | c   | d         |
 +-----+-----+-----+-----------+
 ^D
-<table style="width:42%;">
+<table style="width:41%;">
 <colgroup>
 <col style="width: 8%" />
 <col style="width: 8%" />
@@ -44,10 +44,10 @@
 </thead>
 <tbody>
 <tr>
-<td style="text-align: left;">a</td>
-<td>b</td>
-<td style="text-align: right;">c</td>
-<td style="text-align: right;">d</td>
+<td style="text-align: left;"><p>a</p></td>
+<td><p>b</p></td>
+<td style="text-align: right;"><p>c</p></td>
+<td style="text-align: right;"><p>d</p></td>
 </tr>
 </tbody>
 </table>
diff --git a/test/command/8770-block.md b/test/command/8770-block.md
--- a/test/command/8770-block.md
+++ b/test/command/8770-block.md
@@ -32,7 +32,7 @@
 role="doc-backlink">↩︎</a></p></li>
 </ol>
 </aside>
-<table style="width:17%;">
+<table style="width:16%;">
 <caption>Sample table.<a href="#fn2" class="footnote-ref" id="fnref2"
 role="doc-noteref"><sup>2</sup></a></caption>
 <colgroup>
diff --git a/test/command/8770-document.md b/test/command/8770-document.md
--- a/test/command/8770-document.md
+++ b/test/command/8770-document.md
@@ -25,7 +25,7 @@
 <h1 id="section-1">Section 1</h1>
 <p>hello<a href="#fn1" class="footnote-ref" id="fnref1"
 role="doc-noteref"><sup>1</sup></a></p>
-<table style="width:17%;">
+<table style="width:16%;">
 <caption>Sample table.<a href="#fn2" class="footnote-ref" id="fnref2"
 role="doc-noteref"><sup>2</sup></a></caption>
 <colgroup>
diff --git a/test/command/8770-section.md b/test/command/8770-section.md
--- a/test/command/8770-section.md
+++ b/test/command/8770-section.md
@@ -25,7 +25,7 @@
 <h1 id="section-1">Section 1</h1>
 <p>hello<a href="#fn1" class="footnote-ref" id="fnref1"
 role="doc-noteref"><sup>1</sup></a></p>
-<table style="width:17%;">
+<table style="width:16%;">
 <caption>Sample table.<a href="#fn2" class="footnote-ref" id="fnref2"
 role="doc-noteref"><sup>2</sup></a></caption>
 <colgroup>
diff --git a/test/command/8948.md b/test/command/8948.md
--- a/test/command/8948.md
+++ b/test/command/8948.md
@@ -25,7 +25,7 @@
 ![minimal](command/minimal.svg)
 ^D
 <figure>
-<img role="img" aria-label="minimal" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PgoKPHN2ZyB2aWV3Qm94PSItLjMzMyAtLjMzMyA0ODAgMTUwIiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CiAgICA8cGF0aCBkPSJNIDAgMzUuNSBMIDYuNSAyMi41IEwgMTYgMzcgTCAyMyAyNCBMIDM0LjggNDMuNyBMIDQyLjUgMzAgTCA1MC4zIDQ3IEwgNTkuNyAyNy43IEwgNjkgNDcgTCA4NSAxNy43IEwgOTguMyAzOSBMIDExMyA5LjcgTCAxMjcuNyA0Mi4zIEwgMTM2LjMgMjMuNyBMIDE0NyA0NC4zIEwgMTU4LjMgMjAuMyBMIDE3MC4zIDQwLjMgTCAxNzcuNyAyNS43IEwgMTg5LjcgNDMgTCAxOTkuNyAyMSBMIDIwNy43IDM1IEwgMjE5IDExIEwgMjMzIDM3IEwgMjQwLjMgMjMuNyBMIDI1MSA0MyBMIDI2MyAxOC4zIEwgMjcyLjcgMzMuMyBMIDI4MyAxMCBMIDI5NSAzMi4zIEwgMzAxLjMgMjMgTCAzMTEuNyAzNyBMIDMyMy43IDcuNyBMIDMzOS4zIDM5IEwgMzQ2LjMgMjUuNyBMIDM1Ni4zIDQyLjMgTCAzNjkuNyAxNSBMIDM3Ni4zIDI1LjcgTCAzODQgOSBMIDM5MyAyOC4zIEwgNDAwLjMgMTkgTCA0MTEuNyAzOC4zIEwgNDIxIDIxIEwgNDM0LjMgNDMgTCA0NDUgMjUgTCA0NTMgMzYuMyBMIDQ2NC4zIDE4LjMgTCA0NzYuMiA0MC4zIEwgNDgwIDMzLjUgTCA0ODAgMjE1IEwgMCAyMTUgTCAwIDM1LjUgWiIgZmlsbD0iIzE3NTcyMCIvPgo8L3N2Zz4K" alt="minimal" />
+<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PgoKPHN2ZyB2aWV3Qm94PSItLjMzMyAtLjMzMyA0ODAgMTUwIiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CiAgICA8cGF0aCBkPSJNIDAgMzUuNSBMIDYuNSAyMi41IEwgMTYgMzcgTCAyMyAyNCBMIDM0LjggNDMuNyBMIDQyLjUgMzAgTCA1MC4zIDQ3IEwgNTkuNyAyNy43IEwgNjkgNDcgTCA4NSAxNy43IEwgOTguMyAzOSBMIDExMyA5LjcgTCAxMjcuNyA0Mi4zIEwgMTM2LjMgMjMuNyBMIDE0NyA0NC4zIEwgMTU4LjMgMjAuMyBMIDE3MC4zIDQwLjMgTCAxNzcuNyAyNS43IEwgMTg5LjcgNDMgTCAxOTkuNyAyMSBMIDIwNy43IDM1IEwgMjE5IDExIEwgMjMzIDM3IEwgMjQwLjMgMjMuNyBMIDI1MSA0MyBMIDI2MyAxOC4zIEwgMjcyLjcgMzMuMyBMIDI4MyAxMCBMIDI5NSAzMi4zIEwgMzAxLjMgMjMgTCAzMTEuNyAzNyBMIDMyMy43IDcuNyBMIDMzOS4zIDM5IEwgMzQ2LjMgMjUuNyBMIDM1Ni4zIDQyLjMgTCAzNjkuNyAxNSBMIDM3Ni4zIDI1LjcgTCAzODQgOSBMIDM5MyAyOC4zIEwgNDAwLjMgMTkgTCA0MTEuNyAzOC4zIEwgNDIxIDIxIEwgNDM0LjMgNDMgTCA0NDUgMjUgTCA0NTMgMzYuMyBMIDQ2NC4zIDE4LjMgTCA0NzYuMiA0MC4zIEwgNDgwIDMzLjUgTCA0ODAgMjE1IEwgMCAyMTUgTCAwIDM1LjUgWiIgZmlsbD0iIzE3NTcyMCIvPgo8L3N2Zz4K" alt="minimal" />
 <figcaption aria-hidden="true">minimal</figcaption>
 </figure>
 ```
diff --git a/test/command/9387.md b/test/command/9387.md
--- a/test/command/9387.md
+++ b/test/command/9387.md
@@ -7,7 +7,6 @@
 ^D
 #block[
 test
-
 ] #label("my label")
 See #link(label("my label"))[my label].
 
diff --git a/test/command/9467.md b/test/command/9467.md
--- a/test/command/9467.md
+++ b/test/command/9467.md
@@ -2,6 +2,6 @@
 % pandoc --embed-resources
 ![](command/9467.svg)
 ^D
-<p><img role="img" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTkiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB2ZXJzaW9uPSIxLjEiIGlkPSJzdmcyIiB3aWR0aD0iMTkxLjU2MjY3IiBoZWlnaHQ9IjE1MS43MTIwMSIgdmlld0JveD0iMCAwIDE5MS41NjI2NyAxNTEuNzEyMDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlZnMgaWQ9ImRlZnM2Ij4KICAgIDxjbGlwUGF0aCBjbGlwUGF0aFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgaWQ9ImNsaXBQYXRoMjQiPgogICAgICA8cGF0aCBkPSJNIDU2LjY5MzYyLDAgMTEzLjM4NzI0LDExMy4zODcyNCAxNzAuMDgwODYsMCBaIiBpZD0icGF0aDIyIiAvPgogICAgPC9jbGlwUGF0aD4KICAgIDxjbGlwUGF0aCBjbGlwUGF0aFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgaWQ9ImNsaXBQYXRoMzgiPgogICAgICA8cGF0aCBkPSJNIDAsMCBIIDEwMCBWIDEwMCBIIDAgWiIgaWQ9InBhdGgzNiIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8cmFkaWFsR3JhZGllbnQgZng9IjUwLjAwMDY0MSIgZnk9IjUwLjAwMDY0MSIgY3g9IjUwLjAwMDY0MSIgY3k9IjUwLjAwMDY0MSIgcj0iNTAuMDAwNjQxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgaWQ9InJhZGlhbEdyYWRpZW50NDYiPgogICAgICA8c3RvcCBzdHlsZT0ic3RvcC1vcGFjaXR5OjE7c3RvcC1jb2xvcjojZmZmZmZmIiBvZmZzZXQ9IjAiIGlkPSJzdG9wNDAiIC8+CiAgICAgIDxzdG9wIHN0eWxlPSJzdG9wLW9wYWNpdHk6MTtzdG9wLWNvbG9yOiM5OTk5ZmYiIG9mZnNldD0iMjUuMDAwMzIiIGlkPSJzdG9wNDIiIC8+CiAgICAgIDxzdG9wIHN0eWxlPSJzdG9wLW9wYWNpdHk6MTtzdG9wLWNvbG9yOiM5OTk5ZmYiIG9mZnNldD0iNTAuMDAwNjQiIGlkPSJzdG9wNDQiIC8+CiAgICA8L3JhZGlhbEdyYWRpZW50PgogIDwvZGVmcz4KICA8ZyBpZD0iZzgiIHRyYW5zZm9ybT0ibWF0cml4KDEuMzMzMzMzMywwLDAsLTEuMzMzMzMzMywwLDE1MS43MTIpIj4KICAgIDxnIGlkPSJnMTAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yNi42MDYsMC4xOTkpIj4KICAgICAgPGcgaWQ9ImcxMiI+CiAgICAgICAgPGcgaWQ9ImcxNCI+CiAgICAgICAgICA8ZyBpZD0iZzE2Ij4KICAgICAgICAgICAgPGcgaWQ9ImcxOCI+CiAgICAgICAgICAgICAgPGcgaWQ9ImcyMCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXBQYXRoMjQpIj4KICAgICAgICAgICAgICAgIDxnIGlkPSJnMjYiIHRyYW5zZm9ybT0ibWF0cml4KDIuMjY4MDIsMCwwLDIuMjY4MDIsMTEzLjM4NzI0LDU2LjY5MzYyKSI+CiAgICAgICAgICAgICAgICAgIDxnIGlkPSJnMjgiPgogICAgICAgICAgICAgICAgICAgIDxnIGlkPSJnMzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01MCwtNTApIj4KICAgICAgICAgICAgICAgICAgICAgIDxnIGlkPSJnMzIiPgogICAgICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iZzM0IiBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgzOCkiPgogICAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0gMCwwIEggMTAwIFYgMTAwIEggMCBaIiBzdHlsZT0iZmlsbDp1cmwoI3JhZGlhbEdyYWRpZW50NDYpO3N0cm9rZTpub25lIiBpZD0icGF0aDQ4IiAvPgogICAgICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0gNTYuNjkzNjIsMCAxMTMuMzg3MjQsMTEzLjM4NzI0IDE3MC4wODA4NiwwIFoiIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiM5OTk5ZmY7c3Ryb2tlLXdpZHRoOjAuMzk4NTtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLW9wYWNpdHk6MSIgaWQ9InBhdGg1MCIgLz4KICAgICAgICAgICAgPGcgaWQ9Imc1MiI+CiAgICAgICAgICAgICAgPGcgaWQ9Imc1NCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjMuMzM3LDUuNDU3KSI+CiAgICAgICAgICAgICAgICA8ZyBpZD0iZzU2Ij4KICAgICAgICAgICAgICAgICAgPGcgaWQ9Imc1OCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTM2LjczMSwtNS42NTYpIj4KICAgICAgICAgICAgICAgICAgICA8dGV4dCB4bWw6c3BhY2U9InByZXNlcnZlIiB0cmFuc2Zvcm09Im1hdHJpeCgxLDAsMCwtMSwzNi43MzEsNS42NTYpIiBzdHlsZT0iZm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zaXplOjkuOTYyNnB4O2ZvbnQtZmFtaWx5OkNNU1MxMDstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOkNNU1MxMDt3cml0aW5nLW1vZGU6bHItdGI7ZmlsbDojOTk5OWZmO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiBpZD0idGV4dDYyIj48dHNwYW4geD0iMCA0Ljk4MTI5OTkgOC4zODU1MiAxMy4xNzM1NDYgMTguMzIxMjIyIDIwLjcwMTI4NiAyNS4xMjg2NjYgMzAuMjc2MzQgMzcuMTkxMzgzIDQxLjAxMDA0OCA0Ni4xNTc3MjIgNTAuOTQ1NzQ3IDU2LjA5MzQyMiA2MC41MjA4MDIgNjguOTk1OTg3IDcyLjU5MzQ4MyA3NS45OTc3MDQgNzguMzc3NzY5IDgzLjE2NTc5NCA4OC4zMDM1MDUgOTMuMjg0ODA1IDk1LjY2NDg3MSIgeT0iMCIgaWQ9InRzcGFuNjAiPmdyYWRpZW50c2hhZGVkdHJpYW5nbGU8L3RzcGFuPjwvdGV4dD4KICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iZzY0IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNi43MzEsNS42NTYpIiAvPgogICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8ZyBpZD0iZzY2IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNjMuMzM3LC01LjQ1NykiIC8+CiAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICA8L2c+CiAgICAgICAgPC9nPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K" /></p>
+<p><img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTkiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB2ZXJzaW9uPSIxLjEiIGlkPSJzdmcyIiB3aWR0aD0iMTkxLjU2MjY3IiBoZWlnaHQ9IjE1MS43MTIwMSIgdmlld0JveD0iMCAwIDE5MS41NjI2NyAxNTEuNzEyMDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlZnMgaWQ9ImRlZnM2Ij4KICAgIDxjbGlwUGF0aCBjbGlwUGF0aFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgaWQ9ImNsaXBQYXRoMjQiPgogICAgICA8cGF0aCBkPSJNIDU2LjY5MzYyLDAgMTEzLjM4NzI0LDExMy4zODcyNCAxNzAuMDgwODYsMCBaIiBpZD0icGF0aDIyIiAvPgogICAgPC9jbGlwUGF0aD4KICAgIDxjbGlwUGF0aCBjbGlwUGF0aFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgaWQ9ImNsaXBQYXRoMzgiPgogICAgICA8cGF0aCBkPSJNIDAsMCBIIDEwMCBWIDEwMCBIIDAgWiIgaWQ9InBhdGgzNiIgLz4KICAgIDwvY2xpcFBhdGg+CiAgICA8cmFkaWFsR3JhZGllbnQgZng9IjUwLjAwMDY0MSIgZnk9IjUwLjAwMDY0MSIgY3g9IjUwLjAwMDY0MSIgY3k9IjUwLjAwMDY0MSIgcj0iNTAuMDAwNjQxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgaWQ9InJhZGlhbEdyYWRpZW50NDYiPgogICAgICA8c3RvcCBzdHlsZT0ic3RvcC1vcGFjaXR5OjE7c3RvcC1jb2xvcjojZmZmZmZmIiBvZmZzZXQ9IjAiIGlkPSJzdG9wNDAiIC8+CiAgICAgIDxzdG9wIHN0eWxlPSJzdG9wLW9wYWNpdHk6MTtzdG9wLWNvbG9yOiM5OTk5ZmYiIG9mZnNldD0iMjUuMDAwMzIiIGlkPSJzdG9wNDIiIC8+CiAgICAgIDxzdG9wIHN0eWxlPSJzdG9wLW9wYWNpdHk6MTtzdG9wLWNvbG9yOiM5OTk5ZmYiIG9mZnNldD0iNTAuMDAwNjQiIGlkPSJzdG9wNDQiIC8+CiAgICA8L3JhZGlhbEdyYWRpZW50PgogIDwvZGVmcz4KICA8ZyBpZD0iZzgiIHRyYW5zZm9ybT0ibWF0cml4KDEuMzMzMzMzMywwLDAsLTEuMzMzMzMzMywwLDE1MS43MTIpIj4KICAgIDxnIGlkPSJnMTAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yNi42MDYsMC4xOTkpIj4KICAgICAgPGcgaWQ9ImcxMiI+CiAgICAgICAgPGcgaWQ9ImcxNCI+CiAgICAgICAgICA8ZyBpZD0iZzE2Ij4KICAgICAgICAgICAgPGcgaWQ9ImcxOCI+CiAgICAgICAgICAgICAgPGcgaWQ9ImcyMCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXBQYXRoMjQpIj4KICAgICAgICAgICAgICAgIDxnIGlkPSJnMjYiIHRyYW5zZm9ybT0ibWF0cml4KDIuMjY4MDIsMCwwLDIuMjY4MDIsMTEzLjM4NzI0LDU2LjY5MzYyKSI+CiAgICAgICAgICAgICAgICAgIDxnIGlkPSJnMjgiPgogICAgICAgICAgICAgICAgICAgIDxnIGlkPSJnMzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC01MCwtNTApIj4KICAgICAgICAgICAgICAgICAgICAgIDxnIGlkPSJnMzIiPgogICAgICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iZzM0IiBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgzOCkiPgogICAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0gMCwwIEggMTAwIFYgMTAwIEggMCBaIiBzdHlsZT0iZmlsbDp1cmwoI3JhZGlhbEdyYWRpZW50NDYpO3N0cm9rZTpub25lIiBpZD0icGF0aDQ4IiAvPgogICAgICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0gNTYuNjkzNjIsMCAxMTMuMzg3MjQsMTEzLjM4NzI0IDE3MC4wODA4NiwwIFoiIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiM5OTk5ZmY7c3Ryb2tlLXdpZHRoOjAuMzk4NTtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLW9wYWNpdHk6MSIgaWQ9InBhdGg1MCIgLz4KICAgICAgICAgICAgPGcgaWQ9Imc1MiI+CiAgICAgICAgICAgICAgPGcgaWQ9Imc1NCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjMuMzM3LDUuNDU3KSI+CiAgICAgICAgICAgICAgICA8ZyBpZD0iZzU2Ij4KICAgICAgICAgICAgICAgICAgPGcgaWQ9Imc1OCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTM2LjczMSwtNS42NTYpIj4KICAgICAgICAgICAgICAgICAgICA8dGV4dCB4bWw6c3BhY2U9InByZXNlcnZlIiB0cmFuc2Zvcm09Im1hdHJpeCgxLDAsMCwtMSwzNi43MzEsNS42NTYpIiBzdHlsZT0iZm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zaXplOjkuOTYyNnB4O2ZvbnQtZmFtaWx5OkNNU1MxMDstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOkNNU1MxMDt3cml0aW5nLW1vZGU6bHItdGI7ZmlsbDojOTk5OWZmO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiBpZD0idGV4dDYyIj48dHNwYW4geD0iMCA0Ljk4MTI5OTkgOC4zODU1MiAxMy4xNzM1NDYgMTguMzIxMjIyIDIwLjcwMTI4NiAyNS4xMjg2NjYgMzAuMjc2MzQgMzcuMTkxMzgzIDQxLjAxMDA0OCA0Ni4xNTc3MjIgNTAuOTQ1NzQ3IDU2LjA5MzQyMiA2MC41MjA4MDIgNjguOTk1OTg3IDcyLjU5MzQ4MyA3NS45OTc3MDQgNzguMzc3NzY5IDgzLjE2NTc5NCA4OC4zMDM1MDUgOTMuMjg0ODA1IDk1LjY2NDg3MSIgeT0iMCIgaWQ9InRzcGFuNjAiPmdyYWRpZW50c2hhZGVkdHJpYW5nbGU8L3RzcGFuPjwvdGV4dD4KICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iZzY0IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNi43MzEsNS42NTYpIiAvPgogICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICA8ZyBpZD0iZzY2IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNjMuMzM3LC01LjQ1NykiIC8+CiAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8L2c+CiAgICAgICAgICA8L2c+CiAgICAgICAgPC9nPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K" /></p>
 
 ```
diff --git a/test/command/9635.md b/test/command/9635.md
--- a/test/command/9635.md
+++ b/test/command/9635.md
@@ -6,7 +6,7 @@
 
 okay
 ^D
-2> [WARNING] Div at _chunk line 1 column 1 unclosed at _chunk line 5 column 1, closing implicitly.
+2> [WARNING] Div at line 1 column 1 unclosed at line 5 column 1, closing implicitly.
 <blockquote>
 <div class="fence">
 <p>that is not closed</p>
diff --git a/test/command/9716.md b/test/command/9716.md
--- a/test/command/9716.md
+++ b/test/command/9716.md
@@ -4,7 +4,7 @@
 > A suggestion.
 ^D
 [ Div
-    ( "" , [ "tip" ] , [] )
+    ( "" , [ "alert" , "tip" ] , [] )
     [ Div ( "" , [ "title" ] , [] ) [ Para [ Str "Tip" ] ]
     , Para [ Str "A" , Space , Str "suggestion." ]
     ]
diff --git a/test/command/9807.md b/test/command/9807.md
--- a/test/command/9807.md
+++ b/test/command/9807.md
@@ -6,8 +6,6 @@
 *#foo*
 ^D
 [ Para
-    [ Strong
-        [ SoftBreak , Str "bar" , Space , Str "baz" , SoftBreak ]
-    ]
+    [ Strong [ SoftBreak , Str "bar" , Space , Str "baz" ] ]
 ]
 ```
diff --git a/test/command/biblatex-edtf-date.md b/test/command/biblatex-edtf-date.md
--- a/test/command/biblatex-edtf-date.md
+++ b/test/command/biblatex-edtf-date.md
@@ -35,6 +35,7 @@
   issued: 2004-04-05
   type: article-journal
 - id: item5-1
+  issued: "-001"
   type: article-journal
 - id: item5-2
   issued: "-0876"
diff --git a/test/command/completion.md b/test/command/completion.md
--- a/test/command/completion.md
+++ b/test/command/completion.md
@@ -18,7 +18,7 @@
     outformats="ansi asciidoc asciidoc_legacy asciidoctor bbcode bbcode_fluxbb bbcode_hubzilla bbcode_phpbb bbcode_steam bbcode_xenforo beamer biblatex bibtex chunkedhtml commonmark commonmark_x context csljson djot docbook docbook4 docbook5 docx dokuwiki dzslides epub epub2 epub3 fb2 gfm haddock html html4 html5 icml ipynb jats jats_archiving jats_articleauthoring jats_publishing jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict markua mediawiki ms muse native odt opendocument opml org pdf plain pptx revealjs rst rtf s5 slideous slidy t2t tei texinfo textile typst vimdoc xml xwiki zimwiki"
     highlight_styles="pygments tango espresso zenburn kate monochrome breezedark haddock"
     math_methods="plain mathml webtex mathjax katex gladtex"
-    datafiles="reference.docx reference.odt reference.pptx MANUAL.txt docx/_rels/.rels pptx/_rels/.rels abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml"
+    datafiles="MANUAL.txt abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/_rels/.rels docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/_rels/.rels pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml reference.docx reference.odt reference.pptx templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml"
 
     case "${prev}" in
          -f|-r|--from|--read)
@@ -247,7 +247,7 @@
     '--list-highlight-styles[List highlighting styles]'
     '-D[Format to print template for]:FORMAT:(ansi asciidoc asciidoc_legacy asciidoctor bbcode bbcode_fluxbb bbcode_hubzilla bbcode_phpbb bbcode_steam bbcode_xenforo beamer biblatex bibtex chunkedhtml commonmark commonmark_x context csljson djot docbook docbook4 docbook5 docx dokuwiki dzslides epub epub2 epub3 fb2 gfm haddock html html4 html5 icml ipynb jats jats_archiving jats_articleauthoring jats_publishing jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict markua mediawiki ms muse native odt opendocument opml org pdf plain pptx revealjs rst rtf s5 slideous slidy t2t tei texinfo textile typst vimdoc xml xwiki zimwiki)'
     '--print-default-template[Format to print template for]:FORMAT:(ansi asciidoc asciidoc_legacy asciidoctor bbcode bbcode_fluxbb bbcode_hubzilla bbcode_phpbb bbcode_steam bbcode_xenforo beamer biblatex bibtex chunkedhtml commonmark commonmark_x context csljson djot docbook docbook4 docbook5 docx dokuwiki dzslides epub epub2 epub3 fb2 gfm haddock html html4 html5 icml ipynb jats jats_archiving jats_articleauthoring jats_publishing jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict markua mediawiki ms muse native odt opendocument opml org pdf plain pptx revealjs rst rtf s5 slideous slidy t2t tei texinfo textile typst vimdoc xml xwiki zimwiki)'
-    '--print-default-data-file[Data file to print]:FILE:(reference.docx reference.odt reference.pptx MANUAL.txt docx/_rels/.rels pptx/_rels/.rels abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml)'
+    '--print-default-data-file[Data file to print]:FILE:(MANUAL.txt abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/_rels/.rels docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/_rels/.rels pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml reference.docx reference.odt reference.pptx templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml)'
     '--print-highlight-style[Highlighting style]:STYLE:(pygments tango espresso zenburn kate monochrome breezedark haddock)'
     '-v[Print version]'
     '--version[Print version]'
@@ -371,7 +371,7 @@
 complete -c pandoc -l list-highlight-languages -d "List highlighting languages"
 complete -c pandoc -l list-highlight-styles -d "List highlighting styles"
 complete -c pandoc -s D -l print-default-template -d "Format to print template for" -r -a "ansi asciidoc asciidoc_legacy asciidoctor bbcode bbcode_fluxbb bbcode_hubzilla bbcode_phpbb bbcode_steam bbcode_xenforo beamer biblatex bibtex chunkedhtml commonmark commonmark_x context csljson djot docbook docbook4 docbook5 docx dokuwiki dzslides epub epub2 epub3 fb2 gfm haddock html html4 html5 icml ipynb jats jats_archiving jats_articleauthoring jats_publishing jira json latex man markdown markdown_github markdown_mmd markdown_phpextra markdown_strict markua mediawiki ms muse native odt opendocument opml org pdf plain pptx revealjs rst rtf s5 slideous slidy t2t tei texinfo textile typst vimdoc xml xwiki zimwiki"
-complete -c pandoc -l print-default-data-file -d "Data file to print" -r -a "reference.docx reference.odt reference.pptx MANUAL.txt docx/_rels/.rels pptx/_rels/.rels abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml"
+complete -c pandoc -l print-default-data-file -d "Data file to print" -r -a "MANUAL.txt abbreviations creole.lua default.csl docbook-entities.txt docx/[Content_Types].xml docx/_rels/.rels docx/docProps/app.xml docx/docProps/core.xml docx/docProps/custom.xml docx/word/_rels/document.xml.rels docx/word/_rels/footnotes.xml.rels docx/word/comments.xml docx/word/document.xml docx/word/fontTable.xml docx/word/footnotes.xml docx/word/numbering.xml docx/word/settings.xml docx/word/styles.xml docx/word/theme/theme1.xml docx/word/webSettings.xml dzslides/template.html epub.css init.lua odt/META-INF/manifest.xml odt/content.xml odt/manifest.rdf odt/meta.xml odt/mimetype odt/styles.xml pptx/[Content_Types].xml pptx/_rels/.rels pptx/docProps/app.xml pptx/docProps/core.xml pptx/ppt/_rels/presentation.xml.rels pptx/ppt/notesMasters/_rels/notesMaster1.xml.rels pptx/ppt/notesMasters/notesMaster1.xml pptx/ppt/notesSlides/_rels/notesSlide1.xml.rels pptx/ppt/notesSlides/_rels/notesSlide2.xml.rels pptx/ppt/notesSlides/notesSlide1.xml pptx/ppt/notesSlides/notesSlide2.xml pptx/ppt/presProps.xml pptx/ppt/presentation.xml pptx/ppt/slideLayouts/_rels/slideLayout1.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout10.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout11.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout2.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout3.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout4.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout5.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout6.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout7.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout8.xml.rels pptx/ppt/slideLayouts/_rels/slideLayout9.xml.rels pptx/ppt/slideLayouts/slideLayout1.xml pptx/ppt/slideLayouts/slideLayout10.xml pptx/ppt/slideLayouts/slideLayout11.xml pptx/ppt/slideLayouts/slideLayout2.xml pptx/ppt/slideLayouts/slideLayout3.xml pptx/ppt/slideLayouts/slideLayout4.xml pptx/ppt/slideLayouts/slideLayout5.xml pptx/ppt/slideLayouts/slideLayout6.xml pptx/ppt/slideLayouts/slideLayout7.xml pptx/ppt/slideLayouts/slideLayout8.xml pptx/ppt/slideLayouts/slideLayout9.xml pptx/ppt/slideMasters/_rels/slideMaster1.xml.rels pptx/ppt/slideMasters/slideMaster1.xml pptx/ppt/slides/_rels/slide1.xml.rels pptx/ppt/slides/_rels/slide2.xml.rels pptx/ppt/slides/_rels/slide3.xml.rels pptx/ppt/slides/_rels/slide4.xml.rels pptx/ppt/slides/slide1.xml pptx/ppt/slides/slide2.xml pptx/ppt/slides/slide3.xml pptx/ppt/slides/slide4.xml pptx/ppt/tableStyles.xml pptx/ppt/theme/theme1.xml pptx/ppt/theme/theme2.xml pptx/ppt/viewProps.xml reference.docx reference.odt reference.pptx templates/affiliations.jats templates/after-header-includes.latex templates/article.jats_publishing templates/common.latex templates/default.ansi templates/default.asciidoc templates/default.bbcode templates/default.beamer templates/default.biblatex templates/default.bibtex templates/default.chunkedhtml templates/default.commonmark templates/default.context templates/default.djot templates/default.docbook4 templates/default.docbook5 templates/default.dokuwiki templates/default.dzslides templates/default.epub2 templates/default.epub3 templates/default.haddock templates/default.html4 templates/default.html5 templates/default.icml templates/default.jats_archiving templates/default.jats_articleauthoring templates/default.jats_publishing templates/default.jira templates/default.latex templates/default.man templates/default.markdown templates/default.markua templates/default.mediawiki templates/default.ms templates/default.muse templates/default.opendocument templates/default.openxml templates/default.opml templates/default.org templates/default.plain templates/default.revealjs templates/default.rst templates/default.rtf templates/default.s5 templates/default.slideous templates/default.slidy templates/default.t2t templates/default.tei templates/default.texinfo templates/default.textile templates/default.typst templates/default.vimdoc templates/default.xwiki templates/default.zimwiki templates/document-metadata.latex templates/font-settings.latex templates/fonts.latex templates/hypersetup.latex templates/passoptions.latex templates/styles.citations.html templates/styles.html templates/template.typst translations/af.yaml translations/alt.yaml translations/am.yaml translations/ar.yaml translations/as.yaml translations/ast.yaml translations/az.yaml translations/be.yaml translations/bg.yaml translations/bn.yaml translations/bo.yaml translations/br.yaml translations/bs.yaml translations/bua.yaml translations/ca.yaml translations/ckb-Arab.yaml translations/ckb-Latn.yaml translations/cs.yaml translations/cu.yaml translations/cy.yaml translations/cz.yaml translations/da.yaml translations/de.yaml translations/dsb.yaml translations/el.yaml translations/en.yaml translations/eo.yaml translations/es-ES.yaml translations/es-MX.yaml translations/es.yaml translations/et.yaml translations/eu.yaml translations/fa.yaml translations/fi.yaml translations/fil.yaml translations/fr.yaml translations/fur.yaml translations/ga.yaml translations/gd.yaml translations/gl.yaml translations/grc.yaml translations/gu.yaml translations/ha.yaml translations/he.yaml translations/hi.yaml translations/hr.yaml translations/hsb.yaml translations/hu.yaml translations/hy.yaml translations/ia.yaml translations/id.yaml translations/is.yaml translations/it.yaml translations/ja.yaml translations/ka.yaml translations/km.yaml translations/kmr-Arab.yaml translations/kmr-Latn.yaml translations/kn.yaml translations/ko.yaml translations/la.yaml translations/lb.yaml translations/lo.yaml translations/lt.yaml translations/lv.yaml translations/mk.yaml translations/ml.yaml translations/mn.yaml translations/mr.yaml translations/ms.yaml translations/nb.yaml translations/nko.yaml translations/nl.yaml translations/nn.yaml translations/no.yaml translations/oc.yaml translations/or.yaml translations/pa.yaml translations/pl.yaml translations/pms.yaml translations/pt-BR.yaml translations/pt-PT.yaml translations/pt.yaml translations/rm.yaml translations/ro.yaml translations/ru.yaml translations/se.yaml translations/si.yaml translations/sk.yaml translations/sl.yaml translations/sq.yaml translations/sr-Cyrl.yaml translations/sr-Latn.yaml translations/sr.yaml translations/sv.yaml translations/ta.yaml translations/te.yaml translations/th.yaml translations/tk.yaml translations/tr.yaml translations/ua.yaml translations/ug.yaml translations/uk.yaml translations/ur.yaml translations/vi.yaml translations/zh-Hans.yaml translations/zh-Hant.yaml"
 complete -c pandoc -l print-highlight-style -d "Highlighting style" -r -a "pygments tango espresso zenburn kate monochrome breezedark haddock"
 complete -c pandoc -s v -l version -d "Print version"
 complete -c pandoc -s h -l help -d "Show help"
diff --git a/test/command/latex-opt-arg-state.md b/test/command/latex-opt-arg-state.md
new file mode 100644
--- /dev/null
+++ b/test/command/latex-opt-arg-state.md
@@ -0,0 +1,38 @@
+State changes made while parsing an optional argument (which is not
+a TeX group) should persist, instead of being discarded with the
+sub-parse.
+
+```
+% pandoc -f latex -t native
+\begin{description}
+\item[\def\x{Y}k] b \x
+\end{description}
+^D
+[ DefinitionList
+    [ ( [ Str "k" ]
+      , [ [ Para [ Str "b" , Space , Str "Y" ] ] ]
+      )
+    ]
+]
+```
+
+```
+% pandoc -f latex -t native
+\cite[\def\x{q}]{a}\x
+^D
+[ Para
+    [ Cite
+        [ Citation
+            { citationId = "a"
+            , citationPrefix = []
+            , citationSuffix = []
+            , citationMode = NormalCitation
+            , citationNoteNum = 0
+            , citationHash = 0
+            }
+        ]
+        [ RawInline (Format "latex") "\\cite[\\def\\x{q}]{a}" ]
+    , Str "q"
+    ]
+]
+```
diff --git a/test/command/latex-tokenize-hash-positions.md b/test/command/latex-tokenize-hash-positions.md
new file mode 100644
--- /dev/null
+++ b/test/command/latex-tokenize-hash-positions.md
@@ -0,0 +1,13 @@
+Source positions of tokens following `##` should not be off by one.
+The skipped `\zzz ` spans columns 4-8, so the position after it is
+column 9.
+
+```
+% pandoc -f latex -t plain --verbose
+## \zzz hi
+^D
+2> [INFO] Parsing unescaped '#' at line 1 column 1
+2> [INFO] Parsing unescaped '#' at line 1 column 2
+2> [INFO] Skipped '\zzz ' at line 1 column 9
+## hi
+```
diff --git a/test/command/latex-url-comment-positions.md b/test/command/latex-url-comment-positions.md
new file mode 100644
--- /dev/null
+++ b/test/command/latex-url-comment-positions.md
@@ -0,0 +1,12 @@
+Tokens produced by retokenizing a comment inside a URL argument
+should keep their original source positions.  Here the `}` and
+`\zzz` come from the retokenized comment; the skipped `\zzz ` spans
+columns 12-16, so the position after it is column 17.
+
+```
+% pandoc -f latex -t plain --verbose
+\url{ab%cd}\zzz x
+^D
+2> [INFO] Skipped '\zzz ' at line 1 column 17
+ab%cdx
+```
diff --git a/test/command/latex3-commands.md b/test/command/latex3-commands.md
new file mode 100644
--- /dev/null
+++ b/test/command/latex3-commands.md
@@ -0,0 +1,473 @@
+Tests for LaTeX3 (xparse) document command support.
+
+`o` specifier and `\IfNoValueTF`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand{\name}{o m}{\IfNoValueTF{#1}{#2}{#1: #2}}
+\name{Alice}
+
+\name[Dr]{Bob}
+^D
+Alice
+
+Dr: Bob
+```
+
+`O` specifier with default:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\greet{O{Hello} m}{#1, #2!}
+\greet{world}
+
+\greet[Hi]{there}
+^D
+Hello, world!
+
+Hi, there!
+```
+
+`s` specifier and `\IfBooleanTF`:
+
+```
+% pandoc -f latex -t latex
+\NewDocumentCommand\maybeemph{s m}{\IfBooleanTF{#1}{\emph{#2}}{#2}}
+\maybeemph{plain} and \maybeemph*{starred}
+^D
+plain and \emph{starred}
+```
+
+`t` specifier:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\q{t+ m}{\IfBooleanTF{#1}{plus #2}{noplus #2}}
+\q+{a} \q{b}
+^D
+plus a noplus b
+```
+
+`d` specifier with custom delimiters:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\point{d()}{\IfNoValueTF{#1}{origin}{(#1)}}
+\point, \point(3,4)
+^D
+origin, (3,4)
+```
+
+`r` specifier (required, custom delimiters):
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\req{r()}{req #1}
+\req(ok)
+^D
+req ok
+```
+
+`\DeclareDocumentCommand` redefines silently:
+
+```
+% pandoc -f latex -t plain
+\DeclareDocumentCommand\x{m}{first: #1}
+\DeclareDocumentCommand\x{m}{second: #1}
+\x{y}
+^D
+second: y
+```
+
+`\ProvideDocumentCommand` does not redefine:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\y{m}{first: #1}
+\ProvideDocumentCommand\y{m}{second: #1}
+\y{z}
+^D
+first: z
+```
+
+A `-NoValue-` marker that leaks into the document is printed
+like in xparse:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\val{o}{value: #1}
+\val
+^D
+value: -NoValue-
+```
+
+Nested brackets in optional arguments:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\opt{o}{got #1}
+\opt[a[b]c]
+^D
+got a[b]c
+```
+
+`\IfValueT`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\test{o}{\IfValueT{#1}{seen #1}}
+\test[x] \test
+^D
+seen x
+```
+
+Definitions pass through as raw LaTeX when `latex_macros`
+is disabled:
+
+```
+% pandoc -f markdown-latex_macros -t markdown+raw_tex-raw_attribute
+\NewDocumentCommand{\my}{m}{\emph{#1}}
+\my{hi}
+^D
+\NewDocumentCommand{\my}{m}{\emph{#1}}
+\my{hi}
+```
+
+`\NewDocumentEnvironment`: arguments are available in the
+end code, too:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentEnvironment{titled}{m}{start #1.}{end #1.}
+\begin{titled}{T}
+middle
+\end{titled}
+^D
+start T. middle end T.
+```
+
+Environments with arguments nest properly:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentEnvironment{wrap}{m}{[#1 }{ #1]}
+\begin{wrap}{a}
+one
+\begin{wrap}{b}
+two
+\end{wrap}
+three
+\end{wrap}
+^D
+[a one [b two b] three a]
+```
+
+Environment wrapping another environment, with optional
+argument:
+
+```
+% pandoc -f latex -t latex
+\NewDocumentEnvironment{myquote}{o}
+  {\begin{quote}\IfValueT{#1}{\textbf{#1}: }}
+  {\end{quote}}
+\begin{myquote}[Note]
+Hello.
+\end{myquote}
+^D
+\begin{quote}
+\textbf{Note}: Hello.
+\end{quote}
+```
+
+`O` specifier with default in environments:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentEnvironment{sec}{O{A} m}{(#1/#2 }{ #2)}
+\begin{sec}[B]{t}
+body
+\end{sec}
+
+\begin{sec}{u}
+more
+\end{sec}
+^D
+(B/t body t)
+
+(A/u more u)
+```
+
+`b` specifier grabs the environment body, trimming
+surrounding spaces:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentEnvironment{titledb}{m b}{Title: #1. Body: #2.}{}
+\begin{titledb}{T}
+some text
+\end{titledb}
+^D
+Title: T. Body: some text.
+```
+
+`\DeclareDocumentEnvironment` redefines silently:
+
+```
+% pandoc -f latex -t plain
+\DeclareDocumentEnvironment{z}{}{one}{}
+\DeclareDocumentEnvironment{z}{}{two}{}
+\begin{z}x\end{z}
+^D
+twox
+```
+
+`\ProvideDocumentEnvironment` does not redefine:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentEnvironment{p}{}{one}{}
+\ProvideDocumentEnvironment{p}{}{two}{}
+\begin{p}x\end{p}
+^D
+onex
+```
+
+`v` specifier (verbatim), with `\verb`-style delimiters or
+braces; special characters are not reinterpreted:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\showv{v}{[#1]}
+\showv|a_b&\foo|
+
+\showv{xy}
+^D
+[a_b&\foo]
+
+[xy]
+```
+
+`e` specifier (embellishments), matched in any order:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\x{e{^_}}{sup=#1, sub=#2.}
+\x^{up}_{down}
+
+\x_{down}^{up}
+
+\x_{d}
+^D
+sup=up, sub=down.
+
+sup=up, sub=down.
+
+sup=-NoValue-, sub=d.
+```
+
+`e` specifier with `\IfNoValueTF` and single-token argument:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\z{e{^}}{\IfNoValueTF{#1}{none}{got #1}}
+\z^2 \z
+^D
+got 2 none
+```
+
+`E` specifier (embellishments with defaults):
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\y{E{^_}{{U}{D}}}{#1/#2}
+\y^{a}
+
+\y
+^D
+a/D
+
+U/D
+```
+
+`\TrimSpaces` argument processor:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\trim{>{\TrimSpaces}m}{[#1]}
+\trim{  hi  }
+^D
+[hi]
+```
+
+`\ReverseBoolean` argument processor:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\rs{>{\ReverseBoolean}s}{\IfBooleanTF{#1}{T}{F}}
+\rs* \rs
+^D
+F T
+```
+
+`\SplitArgument` processor: splits into braced groups, trims
+spaces, pads with `-NoValue-`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\three{mmm}{1=#1, 2=#2, 3=#3.}
+\NewDocumentCommand\splt{>{\SplitArgument{2}{;}}m}{\three#1}
+\splt{a; b ;c}
+
+\splt{a;b}
+^D
+1=a, 2=b, 3=c.
+
+1=a, 2=b, 3=-NoValue-.
+```
+
+`\SplitList` processor:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\lst{>{\SplitList{,}}m}{items: #1}
+\lst{x, y ,z}
+^D
+items: xyz
+```
+
+Multiple processors are applied starting from the one nearest
+the argument specifier:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\two{mm}{(#1)(#2)}
+\NewDocumentCommand\c{>{\SplitArgument{1}{;}}>{\TrimSpaces}m}{\two#1}
+\c{ a;b }
+^D
+(a)(b)
+```
+
+Unknown processors are skipped, keeping the argument:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\u{>{\SomeUnknownProc}m}{[#1]}
+\u{ok}
+^D
+[ok]
+```
+
+`\NewCommandCopy` snapshots the definition (like `\let`):
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\old{m}{orig: #1}
+\NewCommandCopy\new\old
+\RenewDocumentCommand\old{m}{changed: #1}
+\new{a} \old{b}
+^D
+orig: a changed: b
+```
+
+`\NewEnvironmentCopy`, copying a built-in environment:
+
+```
+% pandoc -f latex -t latex
+\NewEnvironmentCopy{myquote}{quote}
+\begin{myquote}
+Hello.
+\end{myquote}
+^D
+\begin{quote}
+Hello.
+\end{quote}
+```
+
+`\MakeTitlecase` uppercases the first character:
+
+```
+% pandoc -f latex -t plain
+\MakeTitlecase{hello world} \MakeTitlecase{\emph{foo bar}}
+^D
+Hello world Foo bar
+```
+
+Code between `\ExplSyntaxOn` and `\ExplSyntaxOff` is
+tokenized with `:` and `_` as letters, so it can be skipped
+cleanly:
+
+```
+% pandoc -f latex -t plain
+\ExplSyntaxOn
+\tl_new:N \l_my_tl
+\tl_set:Nn \l_my_tl {stuff}
+\ExplSyntaxOff
+Text after.
+^D
+Text after.
+```
+
+`\ShowCommand` and the key–value commands are parsed and
+ignored:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foo{m}{x#1}
+\ShowCommand\foo
+\DeclareKeys[fam]{key .store = \myval}
+\SetKeys[fam]{key=5}
+\ProcessKeyOptions[fam]
+Done.
+^D
+Done.
+```
+
+`\inteval`: `+ - * /` with division rounding to nearest (ties
+away from zero, as in eTeX):
+
+```
+% pandoc -f latex -t plain
+\inteval{2+3*4}, \inteval{7/2}, \inteval{-7/2}, \inteval{(1+2)*3}
+^D
+14, 4, -4, 9
+```
+
+Macros in the evaluator argument are expanded:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\n{}{4}
+\inteval{\n + 1}
+^D
+5
+```
+
+`\fpeval`: floating point expressions; results are rounded to
+16 significant digits as in l3fp:
+
+```
+% pandoc -f latex -t plain
+\fpeval{2^10/8}, \fpeval{0.1 + 0.2}, \fpeval{2**3}, \fpeval{1e3*2}, \fpeval{1/3}
+^D
+128, 0.3, 8, 2000, 0.3333333333333333
+```
+
+Expressions that cannot be evaluated are passed through as
+text:
+
+```
+% pandoc -f latex -t plain
+\fpeval{foo(1)}, \fpeval{1/0}, \inteval{1/0}
+^D
+foo(1), 1/0, 1/0
+```
+
+`\dimeval` and `\skipeval` substitute their (unevaluated)
+expression:
+
+```
+% pandoc -f latex -t plain
+\dimeval{2pt+3pt}, \skipeval{1em plus 2pt}
+^D
+2pt+3pt, 1em plus 2pt
+```
diff --git a/test/command/latex3-usrguide.md b/test/command/latex3-usrguide.md
new file mode 100644
--- /dev/null
+++ b/test/command/latex3-usrguide.md
@@ -0,0 +1,453 @@
+Tests corresponding to the examples in the LaTeX usrguide
+(<https://www.latex-project.org/help/documentation/usrguide.pdf>).
+
+§2.2: the `\chapter`-like example with `s o m`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\mychapter{s o m}
+  {\IfBooleanTF{#1}{star: #3}{normal: \IfNoValueTF{#2}{#3}{#2} / #3}\par}
+\mychapter{One}
+\mychapter*{Two}
+\mychapter[Short]{Long}
+^D
+normal: One / One
+
+star: Two
+
+normal: Short / Long
+```
+
+§2.3: spaces around the environment name are ignored:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentEnvironment{ foo }{}{[}{]}
+\begin{foo}x\end{foo}
+^D
+[x]
+```
+
+§2.5: optional arguments may safely be nested:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foo{om}{I grabbed (#1) and (#2)}
+\NewDocumentCommand\baz{o}{#1-#1}
+\foo[\baz[stuff]]{more stuff}
+^D
+I grabbed (stuff-stuff) and (more stuff)
+```
+
+§2.5: the default for `O` can be the result of grabbing another
+argument:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foo{O{#2} m}{opt=#1, mand=#2.}
+\foo{A}
+
+\foo[B]{A}
+^D
+opt=A, mand=A.
+
+opt=B, mand=A.
+```
+
+§2.6: spaces before optional arguments are allowed by default,
+but not with the `!` modifier:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foobar{m o}{(#1|#2)}
+\NewDocumentCommand\foobang{m !o}{(#1|#2)}
+\foobar{arg1} [arg2]
+
+\foobang{arg1} [arg2]
+^D
+(arg1|arg2)
+
+(arg1|-NoValue-) [arg2]
+```
+
+§2.8: `\IfNoValueTF`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foo{o m}
+  {%
+    \IfNoValueTF {#1}%
+      {just #2}%
+      {both #1 and #2}}
+\foo{m}, \foo[o]{m}
+^D
+just m, both o and m
+```
+
+§2.8: the `\IfBlankTF` example, including the effect of `!` on
+the last `\foo`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foo{m!o}{\par #1:
+  \IfNoValueTF{#2}
+    {No optional}%
+    {%
+       \IfBlankTF{#2}
+         {Blanks in or empty}%
+         {Real content in}%
+    }%
+  \space argument!}
+\foo{1}[bar] \foo{2}[ ] \foo{3}[] \foo{4}[\space] \foo{5} [x]
+^D
+1: Real content in argument!
+
+2: Blanks in or empty argument!
+
+3: Blanks in or empty argument!
+
+4: Real content in argument!
+
+5: No optional argument! [x]
+```
+
+§2.8: `\IfBooleanTF`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foo{sm}
+  {%
+    \IfBooleanTF {#1}%
+      {with star: #2}%
+      {without star: #2}}
+\foo{a}, \foo*{b}
+^D
+without star: a, with star: b
+```
+
+§2.9: the `=` modifier (key–value interface for an argument) is
+accepted:
+
+```
+% pandoc -f latex -t plain
+\DeclareDocumentCommand\mycaption{s ={short-text} +O{#3} +m}{[#2|#3]}
+\mycaption{Text}
+\mycaption[Short]{Long}
+^D
+[Text|Text] [Short|Long]
+```
+
+§2.10: `\SplitArgument` splits into a fixed number of parts,
+trimming spaces, padding with `-NoValue-`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\InternalFunctionOfThreeArguments{mmm}{1=#1, 2=#2, 3=#3.}
+\NewDocumentCommand\foo{>{\SplitArgument{2}{;}} m}
+  {\InternalFunctionOfThreeArguments#1}
+\foo{a ; b ; c}
+
+\foo{a;b}
+^D
+1=a, 2=b, 3=c.
+
+1=a, 2=b, 3=-NoValue-.
+```
+
+§2.10: a processor applied to an `e`-type argument applies to
+all of its arguments:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foo{ >{\TrimSpaces} e{_^} }
+  { [#1](#2) }
+\foo_{ a }^{ b }
+^D
+[a](b)
+```
+
+§2.10: `\SplitList` and `\ProcessList`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\SomeDocumentCommand{m}{<#1>}
+\NewDocumentCommand\foo{>{\SplitList{;}} m}
+  {\ProcessList{#1}{\SomeDocumentCommand}}
+\foo{a; b ;c}
+^D
+<a><b><c>
+```
+
+§2.10: `\ProcessList` with arbitrary tokens expecting one
+argument:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\SomeDocumentCommand{m}{<#1>}
+\NewDocumentCommand\foo{>{\SplitList{;}} m}
+  {\ProcessList{#1}{Abc \SomeDocumentCommand}}
+\foo{a;b}
+^D
+Abc <a>Abc <b>
+```
+
+§2.10: `\ReverseBoolean`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foo{>{\ReverseBoolean} s m}
+  {%
+    \IfBooleanTF#1%
+      {without star: #2}%
+      {with star: #2}}
+\foo{a}, \foo*{b}
+^D
+without star: a, with star: b
+```
+
+§2.10: `\TrimSpaces`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\foo{>{\TrimSpaces} m}{[#1]}
+\foo{ hello world }
+^D
+[hello world]
+```
+
+§2.11: the `b` argument type grabs the body of the environment;
+spaces are trimmed at both ends:
+
+```
+% pandoc -f latex -t html
+\NewDocumentEnvironment{twice}{O{\ttfamily} +b}
+  {#2#1#2} {}
+\begin{twice}[\itshape]
+  Hello world!
+\end{twice}
+^D
+<p>Hello world!<em>Hello world!</em></p>
+```
+
+§2.12–2.13: `\NewExpandableDocumentCommand`, wrapping
+`\multicolumn` in a tabular cell:
+
+```
+% pandoc -f latex -t plain
+\NewExpandableDocumentCommand\MyMultiCol{m}{\multicolumn{3}{c}{#1}}
+\begin{tabular}{lcr}
+a & b & c \\
+\MyMultiCol{stuff} \\
+\end{tabular}
+^D
++:----+:---:+----:+
+| a   | b   | c   |
++-----+-----+-----+
+| stuff           |
++-----------------+
+```
+
+§2.16: the `c` argument type grabs the body of the environment
+verbatim; it is typeset with spaces as visible spaces (U+2423):
+
+```
+% pandoc -f latex -t plain
+\NewDocumentEnvironment{MyVerbatim}{!O{\ttfamily} c}
+  {\begin{center} #1 #2\end{center}} {}
+\begin{MyVerbatim}[\ttfamily\itshape]
+  % Some code is shown here
+  $y = mx + c$
+\end{MyVerbatim}
+^D
+␣␣%␣Some␣code␣is␣shown␣here
+␣␣$y␣=␣mx␣+␣c$
+```
+
+§3: `\NewCommandCopy` copies a definition, so the original can
+be redefined in terms of the copy:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\hi{}{Hello!}
+\NewCommandCopy\hiorig\hi
+\RenewDocumentCommand\hi{}{\hiorig{} And again: \hiorig}
+\hi
+^D
+Hello! And again: Hello!
+```
+
+§4: `\UseName` turns a string into a csname and executes it:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\hello{}{Hello!}
+\UseName{hello}
+^D
+Hello!
+```
+
+§4: `\ExpandArgs{c}` constructs a command name for
+`\NewDocumentCommand`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\newnoter{m}
+  {\ExpandArgs{c}\NewDocumentCommand{#1}{m}{#1 note: ##1.}}
+\newnoter{todo}
+\todo{fix this}
+^D
+todo note: fix this.
+```
+
+§4: `\ExpandArgs{cc}` with `\NewCommandCopy`, copying a command
+by string name:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\savebyname{m}
+  {\ExpandArgs{cc}\NewCommandCopy{saved#1}{#1}}
+\NewDocumentCommand\greeting{}{Hello!}
+\savebyname{greeting}
+\RenewDocumentCommand\greeting{}{Goodbye!}
+\greeting{} \UseName{savedgreeting}
+^D
+Goodbye! Hello!
+```
+
+§4: `\ExpandArgs{Nc}`:
+
+```
+% pandoc -f latex -t plain
+\NewDocumentCommand\target{}{found}
+\ExpandArgs{Nc}\NewCommandCopy\mycopy{target}
+\mycopy
+^D
+found
+```
+
+§5: the `\fpeval` example:
+
+```
+% pandoc -f latex -t plain
+\LaTeX{} can now compute: $\fpeval{sin(3.5)/2 + 2e-3}$.
+^D
+LaTeX can now compute: −0.1733916138448099.
+```
+
+§5: assorted `\fpeval` operations:
+
+```
+% pandoc -f latex -t plain
+\fpeval{pi}, \fpeval{sqrt 2}, \fpeval{exp(0)}, \fpeval{max(1,2,3)},
+\fpeval{fact(5)}, \fpeval{abs(-4)}, \fpeval{round(2.345,2)},
+\fpeval{floor(2.7)}, \fpeval{ceil(2.2)}, \fpeval{trunc(-2.7)}
+^D
+3.141592653589793, 1.414213562373095, 1, 3, 120, 4, 2.34, 2, 3, -2
+```
+
+§5: the `\inteval` example:
+
+```
+% pandoc -f latex -t plain
+\LaTeX{} can now compute: The sum of the numbers is $\inteval{1 + 2 + 3}$.
+^D
+LaTeX can now compute: The sum of the numbers is 6.
+```
+
+§6: `\expandableinput` reads a file like `\input`:
+
+```
+% pandoc -f latex -t html
+Before. \expandableinput{command/bar}
+^D
+<p>Before. <em>hi there</em></p>
+```
+
+§7: `\MakeUppercase`, `\MakeLowercase`, `\MakeTitlecase`:
+
+```
+% pandoc -f latex -t plain
+\MakeUppercase{hello WORLD ßüé}
+
+\MakeLowercase{hello WORLD ßüé}
+
+\MakeTitlecase{hello WORLD ßüé}
+^D
+HELLO WORLD SSÜÉ
+
+hello world ßüé
+
+Hello WORLD ßüé
+```
+
+§7: the `words` option of `\MakeTitlecase`:
+
+```
+% pandoc -f latex -t plain
+\MakeTitlecase[words = first]{some words}
+\MakeTitlecase[words = all]{some words}
+^D
+Some words Some Words
+```
+
+§7: mathematical content is excluded from case changing, and
+`\NoCaseChange` excludes its argument:
+
+```
+% pandoc -f latex -t markdown
+\MakeUppercase{Some text $y = mx + c$}
+
+\MakeUppercase{\NoCaseChange{iPhone}}
+^D
+SOME TEXT $y = mx + c$
+
+iPhone
+```
+
+§7: `\DeclareTitlecaseExclusions`:
+
+```
+% pandoc -f latex -t plain
+\DeclareTitlecaseExclusions{a,an,and,on,of,the}
+\MakeTitlecase[words = all]{Of mice and men}
+
+\MakeTitlecase[words = all]{The mill on the floss}
+
+\MakeTitlecase[words = all]{The comedy of errors}
+^D
+Of Mice and Men
+
+The Mill on the Floss
+
+The Comedy of Errors
+```
+
+§7: `\DeclareUppercaseMapping` etc. are parsed and ignored:
+
+```
+% pandoc -f latex -t plain
+\DeclareUppercaseMapping{"01F0}{\v{J}}
+\DeclareLowercaseMapping[xx]{"0049}{\i}
+Done.
+^D
+Done.
+```
+
+§8: `\textsubscript` and `\textsuperscript`:
+
+```
+% pandoc -f latex -t html
+A\textsubscript{low} and B\textsuperscript{high}.
+^D
+<p>A<sub>low</sub> and B<sup>high</sup>.</p>
+```
+
+§9: `\listfiles` is parsed and ignored:
+
+```
+% pandoc -f latex -t plain
+\listfiles[hashes]
+Done.
+^D
+Done.
+```
diff --git a/test/command/pandoc-citeproc-392.md b/test/command/pandoc-citeproc-392.md
--- a/test/command/pandoc-citeproc-392.md
+++ b/test/command/pandoc-citeproc-392.md
@@ -19,11 +19,11 @@
 
 ::::: {#refs .references .csl-bib-body}
 ::: {#ref-james .csl-entry}
-[1. ]{.csl-left-margin}[James MRCEL. ]{.csl-right-inline}
+[1. ]{.csl-left-margin}[James MRCEL.]{.csl-right-inline}
 :::
 
 ::: {#ref-macfarlane .csl-entry}
-[2. ]{.csl-left-margin}[MacFarlane JG. ]{.csl-right-inline}
+[2. ]{.csl-left-margin}[MacFarlane JG.]{.csl-right-inline}
 :::
 :::::
 ```
diff --git a/test/command/pandoc-citeproc-chicago-fullnote-bibliography.md b/test/command/pandoc-citeproc-chicago-fullnote-bibliography.md
--- a/test/command/pandoc-citeproc-chicago-fullnote-bibliography.md
+++ b/test/command/pandoc-citeproc-chicago-fullnote-bibliography.md
@@ -127,8 +127,9 @@
 
 [^12]: [Doe, *First Book*](#ref-item1) and nowhere else.
 
-[^13]: Like a citation without author: (), and again (), and now Doe
-    with a locator (["Article," 44](#ref-item2)).
+[^13]: Like a citation without author: ([](#ref-item1)), and again
+    ([](#ref-item1)), and now Doe with a locator (["Article,"
+    44](#ref-item2)).
 
 [^14]: *See* [Doe, *First Book*, 32](#ref-item1).
 ```
diff --git a/test/command/typst-highlight.md b/test/command/typst-highlight.md
new file mode 100644
--- /dev/null
+++ b/test/command/typst-highlight.md
@@ -0,0 +1,98 @@
+Highlighted text should be read as a `mark` span, round-tripping with
+the Typst writer.
+
+```
+% pandoc -f typst -t native
+#highlight[hello]
+^D
+[ Para [ Span ( "" , [ "mark" ] , [] ) [ Str "hello" ] ] ]
+```
+
+```
+% pandoc -f markdown -t typst
+[hello]{.mark}
+^D
+#highlight[hello]
+```
+
+```
+% pandoc -f typst -t typst
+#highlight[hello]
+^D
+#highlight[hello]
+```
+
+`highlight` may also wrap multiple paragraphs. A pandoc inline cannot
+span paragraphs, so the body is split at paragraph breaks and each
+paragraph is read as a `mark` span. The same holds for other
+inline-styling elements, such as `emph`.
+
+```
+% pandoc -f typst -t native
+Before.
+
+#highlight[
+  Para one.
+
+  Para two.
+]
+^D
+[ Para [ Str "Before." ]
+, Para
+    [ Span
+        ( "" , [ "mark" ] , [] )
+        [ SoftBreak , Str "Para" , Space , Str "one." ]
+    ]
+, Para
+    [ Span
+        ( "" , [ "mark" ] , [] ) [ Str "Para" , Space , Str "two." ]
+    ]
+]
+```
+
+```
+% pandoc -f typst -t typst
+Before.
+
+#highlight[
+  Para one.
+
+  Para two.
+]
+^D
+Before.
+
+#highlight[ Para one.]
+
+#highlight[Para two.]
+```
+
+Highlight bodies may contain math, inline or display, without breaking
+the reader.
+
+```
+% pandoc -f typst -t native
+#highlight[$ a = b $]
+^D
+[ Para
+    [ Span ( "" , [ "mark" ] , [] ) [ Math DisplayMath "a = b" ]
+    ]
+]
+```
+
+```
+% pandoc -f typst -t native
+#highlight[
+  Para.
+
+  $ c = d $
+]
+^D
+[ Para
+    [ Span ( "" , [ "mark" ] , [] ) [ SoftBreak , Str "Para." ]
+    ]
+, Para
+    [ Span ( "" , [ "mark" ] , [] ) [ Math DisplayMath "c = d" ]
+    ]
+]
+```
diff --git a/test/command/typst-property-output.md b/test/command/typst-property-output.md
--- a/test/command/typst-property-output.md
+++ b/test/command/typst-property-output.md
@@ -177,6 +177,7 @@
   align: (auto,auto,auto,),
   [A], [B], [C],
 )}
+
 Paragraph after.
 ```
 
@@ -231,5 +232,6 @@
   align: (auto,auto,auto,),
   [A], [B], [C],
 )
+
 Paragraph after.
 ```
diff --git a/test/djot-reader.native b/test/djot-reader.native
--- a/test/djot-reader.native
+++ b/test/djot-reader.native
@@ -299,7 +299,7 @@
               , Str "quote:"
               ]
           , CodeBlock
-              ( "" , [ "" ] , [] )
+              ( "" , [] , [] )
               "sub status {\n    print \"working\";\n}\n"
           , Para [ Str "A" , Space , Str "list:" ]
           , OrderedList
@@ -355,11 +355,11 @@
           1 ( "" , [] , [] ) [ Str "Code" , Space , Str "Blocks" ]
       , Para [ Str "Code:" ]
       , CodeBlock
-          ( "" , [ "" ] , [] )
+          ( "" , [] , [] )
           "---- (should be four hyphens)\n\nsub status {\n    print \"working\";\n}\n\nthis code block is indented by one tab\n"
       , Para [ Str "And:" ]
       , CodeBlock
-          ( "" , [ "" ] , [] )
+          ( "" , [] , [] )
           "    this code block is indented by two tabs\n\nThese should not be escaped:  \\$ \\\\ \\> \\[ \\{\n"
       , HorizontalRule
       ]
@@ -839,8 +839,7 @@
             )
           , ( [ Emph [ Str "orange" ] ]
             , [ [ Para [ Str "orange" , Space , Str "fruit" ]
-                , CodeBlock
-                    ( "" , [ "" ] , [] ) "{ orange code block }\n"
+                , CodeBlock ( "" , [] , [] ) "{ orange code block }\n"
                 , BlockQuote
                     [ Para
                         [ Str "orange"
@@ -1016,10 +1015,10 @@
           , Space
           , Str "though:"
           ]
-      , CodeBlock ( "" , [ "" ] , [] ) "<div>\n    foo\n</div>\n"
+      , CodeBlock ( "" , [] , [] ) "<div>\n    foo\n</div>\n"
       , Para
           [ Str "As" , Space , Str "should" , Space , Str "this:" ]
-      , CodeBlock ( "" , [ "" ] , [] ) "<div>foo</div>\n"
+      , CodeBlock ( "" , [] , [] ) "<div>foo</div>\n"
       , Para [ Str "Now," , Space , Str "nested:" ]
       , Div
           ( "" , [] , [] )
@@ -1044,7 +1043,7 @@
           ]
       , Para [ Str "Multiline:" ]
       , Para [ Str "Code" , Space , Str "block:" ]
-      , CodeBlock ( "" , [ "" ] , [] ) "<!-- Comment -->\n"
+      , CodeBlock ( "" , [] , [] ) "<!-- Comment -->\n"
       , Para
           [ Str "Just"
           , Space
@@ -1065,7 +1064,7 @@
           , Str "line:"
           ]
       , Para [ Str "Code:" ]
-      , CodeBlock ( "" , [ "" ] , [] ) "<hr />\n"
+      , CodeBlock ( "" , [] , [] ) "<hr />\n"
       , Para [ Str "Hr\8217s:" ]
       , HorizontalRule
       ]
@@ -1824,7 +1823,7 @@
               , Space
               , Str "link."
               ]
-          , CodeBlock ( "" , [ "" ] , [] ) "[not]: /url\n"
+          , CodeBlock ( "" , [] , [] ) "[not]: /url\n"
           , Para
               [ Str "Foo"
               , Space
@@ -2003,7 +2002,7 @@
               , Code ( "" , [] , [] ) "<http://example.com/>"
               ]
           , CodeBlock
-              ( "" , [ "" ] , [] ) "or here: <http://example.com/>\n"
+              ( "" , [] , [] ) "or here: <http://example.com/>\n"
           , HorizontalRule
           ]
       ]
@@ -2177,7 +2176,7 @@
                   , Space
                   , Str "items)."
                   ]
-              , CodeBlock ( "" , [ "" ] , [] ) "{ <code> }\n"
+              , CodeBlock ( "" , [] , [] ) "{ <code> }\n"
               , Para
                   [ Str "If"
                   , Space
diff --git a/test/docx/comments.native b/test/docx/comments.native
--- a/test/docx/comments.native
+++ b/test/docx/comments.native
@@ -1,4 +1,152 @@
-[Para [Str "I",Space,Str "want",Space,Span ("",["comment-start"],[("id","0"),("author","Jesse Rosenthal"),("date","2016-05-09T16:13:00Z")]) [Str "I",Space,Str "left",Space,Str "a",Space,Str "comment."],Str "some",Space,Str "text",Space,Str "to",Space,Str "have",Space,Str "a",Space,Str "comment",Space,Span ("",["comment-end"],[("id","0")]) [],Str "on",Space,Str "it."]
-,Para [Str "This",Space,Str "is",Space,Span ("",["comment-start"],[("id","1"),("author","Jesse Rosenthal"),("date","2016-05-09T16:13:00Z")]) [Str "A",Space,Str "comment",Space,Str "across",Space,Str "paragraphs."],Str "a",Space,Str "new",Space,Str "paragraph."]
-,Para [Str "And",Space,Str "so",Span ("",["comment-end"],[("id","1")]) [],Space,Str "is",Space,Str "this."]
-,Para [Str "One",Space,Span ("",["comment-start"],[("id","2"),("author","Jesse Rosenthal"),("date","2016-05-09T16:14:00Z")]) [Str "This",Space,Str "one",Space,Str "has",Space,Str "multiple",Space,Str "paragraphs.",LineBreak, Str "See?"],Str "more",Span ("",["comment-end"],[("id","2")]) [],Str ".",Space,Str "And",Space,Str "this",Space,Str "is",Space,Str "one",Space,Str "with",Space,Str "a",Space,Span ("",["comment-start"],[("id","3"),("author","Jesse Rosenthal"),("date","2016-06-22T14:35:00Z")]) [Str "Do",Space,Str "something."],Span ("",["comment-start"],[("id","4"),("author","Jesse Rosenthal"),("date","2016-06-22T14:36:00Z")]) [Str "Do",Space,Str "something",Space,Str "else."],Str "comment",Space,Str "in",Space,Str "a",Space,Str "comment",Span ("",["comment-end"],[("id","3")]) [Span ("",["comment-end"],[("id","4")]) []],Str "."]]
+Pandoc
+  Meta { unMeta = fromList [] }
+  [ Para
+      [ Str "I"
+      , Space
+      , Str "want"
+      , Space
+      , Span
+          ( ""
+          , [ "comment-start" ]
+          , [ ( "comment-id" , "0" )
+            , ( "author" , "Jesse Rosenthal" )
+            , ( "date" , "2016-05-09T16:13:00Z" )
+            ]
+          )
+          [ Str "I"
+          , Space
+          , Str "left"
+          , Space
+          , Str "a"
+          , Space
+          , Str "comment."
+          ]
+      , Str "some"
+      , Space
+      , Str "text"
+      , Space
+      , Str "to"
+      , Space
+      , Str "have"
+      , Space
+      , Str "a"
+      , Space
+      , Str "comment"
+      , Space
+      , Span
+          ( "" , [ "comment-end" ] , [ ( "comment-id" , "0" ) ] ) []
+      , Str "on"
+      , Space
+      , Str "it."
+      ]
+  , Para
+      [ Str "This"
+      , Space
+      , Str "is"
+      , Space
+      , Span
+          ( ""
+          , [ "comment-start" ]
+          , [ ( "comment-id" , "1" )
+            , ( "author" , "Jesse Rosenthal" )
+            , ( "date" , "2016-05-09T16:13:00Z" )
+            ]
+          )
+          [ Str "A"
+          , Space
+          , Str "comment"
+          , Space
+          , Str "across"
+          , Space
+          , Str "paragraphs."
+          ]
+      , Str "a"
+      , Space
+      , Str "new"
+      , Space
+      , Str "paragraph."
+      ]
+  , Para
+      [ Str "And"
+      , Space
+      , Str "so"
+      , Span
+          ( "" , [ "comment-end" ] , [ ( "comment-id" , "1" ) ] ) []
+      , Space
+      , Str "is"
+      , Space
+      , Str "this."
+      ]
+  , Para
+      [ Str "One"
+      , Space
+      , Span
+          ( ""
+          , [ "comment-start" ]
+          , [ ( "comment-id" , "2" )
+            , ( "author" , "Jesse Rosenthal" )
+            , ( "date" , "2016-05-09T16:14:00Z" )
+            ]
+          )
+          [ Str "This"
+          , Space
+          , Str "one"
+          , Space
+          , Str "has"
+          , Space
+          , Str "multiple"
+          , Space
+          , Str "paragraphs."
+          , LineBreak
+          , Str "See?"
+          ]
+      , Str "more"
+      , Span
+          ( "" , [ "comment-end" ] , [ ( "comment-id" , "2" ) ] ) []
+      , Str "."
+      , Space
+      , Str "And"
+      , Space
+      , Str "this"
+      , Space
+      , Str "is"
+      , Space
+      , Str "one"
+      , Space
+      , Str "with"
+      , Space
+      , Str "a"
+      , Space
+      , Span
+          ( ""
+          , [ "comment-start" ]
+          , [ ( "comment-id" , "3" )
+            , ( "author" , "Jesse Rosenthal" )
+            , ( "date" , "2016-06-22T14:35:00Z" )
+            ]
+          )
+          [ Str "Do" , Space , Str "something." ]
+      , Span
+          ( ""
+          , [ "comment-start" ]
+          , [ ( "comment-id" , "4" )
+            , ( "author" , "Jesse Rosenthal" )
+            , ( "date" , "2016-06-22T14:36:00Z" )
+            ]
+          )
+          [ Str "Do" , Space , Str "something" , Space , Str "else." ]
+      , Str "comment"
+      , Space
+      , Str "in"
+      , Space
+      , Str "a"
+      , Space
+      , Str "comment"
+      , Span
+          ( "" , [ "comment-end" ] , [ ( "comment-id" , "3" ) ] )
+          [ Span
+              ( "" , [ "comment-end" ] , [ ( "comment-id" , "4" ) ] ) []
+          ]
+      , Str "."
+      ]
+  ]
diff --git a/test/docx/csl_bibliography.native b/test/docx/csl_bibliography.native
new file mode 100644
--- /dev/null
+++ b/test/docx/csl_bibliography.native
@@ -0,0 +1,50 @@
+[ Header 1 ( "refs-title" , [] , [] ) [ Str "References" ]
+, Div
+    ( "refs"
+    , [ "references" , "csl-bib-body" , "hanging-indent" ]
+    , [ ( "entry-spacing" , "1.0" ) , ( "line-spacing" , "2.0" ) ]
+    )
+    [ Div
+        ( "ref-doe2020" , [ "csl-entry" ] , [] )
+        [ Para
+            [ Str "Doe,"
+            , Space
+            , Str "J.,"
+            , Space
+            , Str "&"
+            , Space
+            , Str "Smith,"
+            , Space
+            , Str "J."
+            , Space
+            , Str "(2020)."
+            , Space
+            , Str "A"
+            , Space
+            , Str "very"
+            , Space
+            , Str "long"
+            , Space
+            , Str "title"
+            , Space
+            , Str "that"
+            , Space
+            , Str "wraps"
+            , Space
+            , Str "onto"
+            , Space
+            , Str "multiple"
+            , Space
+            , Str "lines."
+            , Space
+            , Emph [ Str "Journal" , Space , Str "of" , Space , Str "Testing" ]
+            , Str ","
+            , Space
+            , Emph [ Str "12" ]
+            , Str ","
+            , Space
+            , Str "1\8211\&25."
+            ]
+        ]
+    ]
+]
diff --git a/test/docx/golden/block_quotes.docx b/test/docx/golden/block_quotes.docx
Binary files a/test/docx/golden/block_quotes.docx and b/test/docx/golden/block_quotes.docx differ
diff --git a/test/docx/golden/comments.docx b/test/docx/golden/comments.docx
Binary files a/test/docx/golden/comments.docx and b/test/docx/golden/comments.docx differ
diff --git a/test/docx/golden/csl_bibliography.docx b/test/docx/golden/csl_bibliography.docx
new file mode 100644
Binary files /dev/null and b/test/docx/golden/csl_bibliography.docx differ
diff --git a/test/docx/golden/custom_style_preserve.docx b/test/docx/golden/custom_style_preserve.docx
Binary files a/test/docx/golden/custom_style_preserve.docx and b/test/docx/golden/custom_style_preserve.docx differ
diff --git a/test/docx/golden/headers.docx b/test/docx/golden/headers.docx
Binary files a/test/docx/golden/headers.docx and b/test/docx/golden/headers.docx differ
diff --git a/test/docx/golden/image.docx b/test/docx/golden/image.docx
Binary files a/test/docx/golden/image.docx and b/test/docx/golden/image.docx differ
diff --git a/test/docx/golden/links.docx b/test/docx/golden/links.docx
Binary files a/test/docx/golden/links.docx and b/test/docx/golden/links.docx differ
diff --git a/test/docx/golden/lists.docx b/test/docx/golden/lists.docx
Binary files a/test/docx/golden/lists.docx and b/test/docx/golden/lists.docx differ
diff --git a/test/docx/golden/lists_continuing.docx b/test/docx/golden/lists_continuing.docx
Binary files a/test/docx/golden/lists_continuing.docx and b/test/docx/golden/lists_continuing.docx differ
diff --git a/test/docx/golden/lists_div_bullets.docx b/test/docx/golden/lists_div_bullets.docx
Binary files a/test/docx/golden/lists_div_bullets.docx and b/test/docx/golden/lists_div_bullets.docx differ
diff --git a/test/docx/golden/lists_multiple_initial.docx b/test/docx/golden/lists_multiple_initial.docx
Binary files a/test/docx/golden/lists_multiple_initial.docx and b/test/docx/golden/lists_multiple_initial.docx differ
diff --git a/test/docx/golden/lists_restarting.docx b/test/docx/golden/lists_restarting.docx
Binary files a/test/docx/golden/lists_restarting.docx and b/test/docx/golden/lists_restarting.docx differ
diff --git a/test/docx/golden/nested_anchors_in_header.docx b/test/docx/golden/nested_anchors_in_header.docx
Binary files a/test/docx/golden/nested_anchors_in_header.docx and b/test/docx/golden/nested_anchors_in_header.docx differ
diff --git a/test/docx/golden/notes.docx b/test/docx/golden/notes.docx
Binary files a/test/docx/golden/notes.docx and b/test/docx/golden/notes.docx differ
diff --git a/test/docx/golden/table_with_list_cell.docx b/test/docx/golden/table_with_list_cell.docx
Binary files a/test/docx/golden/table_with_list_cell.docx and b/test/docx/golden/table_with_list_cell.docx differ
diff --git a/test/docx/golden/tables-default-widths.docx b/test/docx/golden/tables-default-widths.docx
Binary files a/test/docx/golden/tables-default-widths.docx and b/test/docx/golden/tables-default-widths.docx differ
diff --git a/test/docx/golden/tables.docx b/test/docx/golden/tables.docx
Binary files a/test/docx/golden/tables.docx and b/test/docx/golden/tables.docx differ
diff --git a/test/docx/golden/task_list.docx b/test/docx/golden/task_list.docx
Binary files a/test/docx/golden/task_list.docx and b/test/docx/golden/task_list.docx differ
diff --git a/test/docx/golden/track_changes_scrubbed_metadata.docx b/test/docx/golden/track_changes_scrubbed_metadata.docx
Binary files a/test/docx/golden/track_changes_scrubbed_metadata.docx and b/test/docx/golden/track_changes_scrubbed_metadata.docx differ
diff --git a/test/docx/link_tooltips.docx b/test/docx/link_tooltips.docx
new file mode 100644
Binary files /dev/null and b/test/docx/link_tooltips.docx differ
diff --git a/test/docx/link_tooltips.native b/test/docx/link_tooltips.native
new file mode 100644
--- /dev/null
+++ b/test/docx/link_tooltips.native
@@ -0,0 +1,112 @@
+Pandoc
+  Meta { unMeta = fromList [] }
+  [ Para
+      [ Str "An"
+      , Space
+      , Link
+          ( "" , [] , [] )
+          [ Str "external" , Space , Str "link" ]
+          ( "https://example.org/report.pdf"
+          , "Annual report, PDF, 2 MB"
+          )
+      , Space
+      , Str "with"
+      , Space
+      , Str "a"
+      , Space
+      , Str "ScreenTip."
+      ]
+  , Para
+      [ Str "An"
+      , Space
+      , Link
+          ( "" , [] , [] )
+          [ Str "external"
+          , Space
+          , Str "link"
+          , Space
+          , Str "to"
+          , Space
+          , Str "a"
+          , Space
+          , Str "fragment"
+          ]
+          ( "https://example.org/guide.html#install"
+          , "Installation steps"
+          )
+      , Str ","
+      , Space
+      , Str "with"
+      , Space
+      , Str "a"
+      , Space
+      , Str "ScreenTip."
+      ]
+  , Para
+      [ Str "An"
+      , Space
+      , Link
+          ( "" , [] , [] )
+          [ Str "internal" , Space , Str "link" ]
+          ( "#methods" , "Jump to the Methods section" )
+      , Space
+      , Str "with"
+      , Space
+      , Str "a"
+      , Space
+      , Str "ScreenTip."
+      ]
+  , Para
+      [ Str "A"
+      , Space
+      , Link
+          ( "" , [] , [] )
+          [ Str "link" ]
+          ( "https://example.com/" , "" )
+      , Space
+      , Str "without"
+      , Space
+      , Str "a"
+      , Space
+      , Str "ScreenTip."
+      ]
+  , Para
+      [ Str "A"
+      , Space
+      , Str "ScreenTip"
+      , Space
+      , Str "with"
+      , Space
+      , Link
+          ( "" , [] , [] )
+          [ Str "markup" , Space , Str "characters" ]
+          ( "https://example.org/escape" , "Q&A: \"Why?\" <draft>" )
+      , Str "."
+      ]
+  , Para
+      [ Str "A"
+      , Space
+      , Link
+          ( "" , [] , [] )
+          [ Str "field-code" , Space , Str "link" ]
+          ( "https://example.org/field"
+          , "ScreenTip from a field code"
+          )
+      , Space
+      , Str "with"
+      , Space
+      , Str "a"
+      , Space
+      , Str "ScreenTip."
+      ]
+  , Para
+      [ Span ( "methods" , [ "anchor" ] , [] ) []
+      , Str "Methods"
+      , Space
+      , Str "are"
+      , Space
+      , Str "described"
+      , Space
+      , Str "here."
+      ]
+  ]
diff --git a/test/docx/table_with_list_cell.native b/test/docx/table_with_list_cell.native
--- a/test/docx/table_with_list_cell.native
+++ b/test/docx/table_with_list_cell.native
@@ -13,7 +13,7 @@
                AlignDefault
                (RowSpan 1)
                (ColSpan 1)
-               [ Plain
+               [ Para
                    [ Str "Cell"
                    , Space
                    , Str "with"
@@ -26,7 +26,7 @@
                AlignDefault
                (RowSpan 1)
                (ColSpan 1)
-               [ Plain
+               [ Para
                    [ Str "Cell"
                    , Space
                    , Str "with"
diff --git a/test/docx/track_changes_scrubbed_metadata.native b/test/docx/track_changes_scrubbed_metadata.native
--- a/test/docx/track_changes_scrubbed_metadata.native
+++ b/test/docx/track_changes_scrubbed_metadata.native
@@ -1,9 +1,28 @@
-[Para [ Str "Here", Space, Str "is", Space, Str "a", Space
-      , Span ("",["deletion"],[("author","Author")]) [Str "dummy"]
-      , Span ("",["insertion"],[("author","Author")]) [Str "test"]
+Pandoc
+  Meta { unMeta = fromList [] }
+  [ Para
+      [ Str "Here"
       , Space
-      , Span ("",["comment-start"],[("id","3"),("author","Author")]) 
-        [Str "With",Space,Str "a",Space,Str "comment!"]
-      , Str "document",Span ("",["comment-end"],[("id","3")]) [],Str "."
+      , Str "is"
+      , Space
+      , Str "a"
+      , Space
+      , Span
+          ( "" , [ "deletion" ] , [ ( "author" , "Author" ) ] )
+          [ Str "dummy" ]
+      , Span
+          ( "" , [ "insertion" ] , [ ( "author" , "Author" ) ] )
+          [ Str "test" ]
+      , Space
+      , Span
+          ( ""
+          , [ "comment-start" ]
+          , [ ( "comment-id" , "3" ) , ( "author" , "Author" ) ]
+          )
+          [ Str "With" , Space , Str "a" , Space , Str "comment!" ]
+      , Str "document"
+      , Span
+          ( "" , [ "comment-end" ] , [ ( "comment-id" , "3" ) ] ) []
+      , Str "."
       ]
-]
+  ]
diff --git a/test/jats-reader.native b/test/jats-reader.native
--- a/test/jats-reader.native
+++ b/test/jats-reader.native
@@ -835,7 +835,9 @@
               , Space
               , Str "1"
               ]
-              ( "Title 3" , "" )
+              ( "https://lh3.googleusercontent.com/dB7iirJ3ncQaVMBGE2YX-WCeoAVIChb6NAzoFcKCFChMsrixJvD7ZRbvcaC-ceXEzXYaoH4K5vaoRDsUyBHFkpIDPnsn3bnzovbvi0a2Gg=s660"
+              , "Title 3"
+              )
           ]
       , Para [ Math DisplayMath "e=mc^2" ]
       ]
diff --git a/test/jats-reader.xml b/test/jats-reader.xml
--- a/test/jats-reader.xml
+++ b/test/jats-reader.xml
@@ -227,7 +227,7 @@
       </abstract>
       <graphic id="graphic003"
       xlink:href="https://lh3.googleusercontent.com/dB7iirJ3ncQaVMBGE2YX-WCeoAVIChb6NAzoFcKCFChMsrixJvD7ZRbvcaC-ceXEzXYaoH4K5vaoRDsUyBHFkpIDPnsn3bnzovbvi0a2Gg=s660"
-      xlink:href="Title 3">
+      xlink:title="Title 3">
         <alt-text>Alternative text 1</alt-text>
         <caption><p>Google doodle from 14 March 2003</p></caption>
       </graphic>
@@ -236,7 +236,7 @@
         <mml:math display="block" xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mrow><mml:mi>e</mml:mi><mml:mo>=</mml:mo><mml:mi>m</mml:mi><mml:msup><mml:mi>c</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:mrow></mml:math>
         <graphic id="graphic004"
         xlink:href="https://lh3.googleusercontent.com/dB7iirJ3ncQaVMBGE2YX-WCeoAVIChb6NAzoFcKCFChMsrixJvD7ZRbvcaC-ceXEzXYaoH4K5vaoRDsUyBHFkpIDPnsn3bnzovbvi0a2Gg=s660"
-        xlink:href="Title 4">
+        xlink:title="Title 4">
           <alt-text>Alternative text 2</alt-text>
           <caption><p>Google doodle from 14 March 2003</p></caption>
         </graphic>
diff --git a/test/markdown-reader-more.native b/test/markdown-reader-more.native
--- a/test/markdown-reader-more.native
+++ b/test/markdown-reader-more.native
@@ -1069,7 +1069,7 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Plain
+                  [ Para
                       [ Str "c"
                       , SoftBreak
                       , Str "c"
diff --git a/test/mediawiki-reader.native b/test/mediawiki-reader.native
--- a/test/mediawiki-reader.native
+++ b/test/mediawiki-reader.native
@@ -864,13 +864,13 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Orange" ] ]
+                  [ Plain [ Str "Orange" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Apple" ] ]
+                  [ Plain [ Str "Apple" ] ]
               ]
           , Row
               ( "" , [] , [] )
@@ -879,13 +879,13 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Bread" ] ]
+                  [ Plain [ Str "Bread" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Pie" ] ]
+                  [ Plain [ Str "Pie" ] ]
               ]
           , Row
               ( "" , [] , [] )
@@ -894,13 +894,13 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Butter" ] ]
+                  [ Plain [ Str "Butter" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Ice" , Space , Str "cream" ] ]
+                  [ Plain [ Str "Ice" , Space , Str "cream" ] ]
               ]
           ]
       ]
@@ -922,13 +922,13 @@
                  AlignDefault
                  (RowSpan 1)
                  (ColSpan 1)
-                 [ Para [ Str "Orange" ] ]
+                 [ Plain [ Str "Orange" ] ]
              , Cell
                  ( "" , [] , [] )
                  AlignDefault
                  (RowSpan 1)
                  (ColSpan 1)
-                 [ Para [ Str "Apple" ] ]
+                 [ Plain [ Str "Apple" ] ]
              ]
          ])
       [ TableBody
@@ -942,13 +942,13 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Bread" ] ]
+                  [ Plain [ Str "Bread" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Pie" ] ]
+                  [ Plain [ Str "Pie" ] ]
               ]
           , Row
               ( "" , [] , [] )
@@ -957,13 +957,13 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Butter" ] ]
+                  [ Plain [ Str "Butter" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Ice" , Space , Str "cream" ] ]
+                  [ Plain [ Str "Ice" , Space , Str "cream" ] ]
               ]
           ]
       ]
@@ -1043,19 +1043,19 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Orange" ] ]
+                  [ Plain [ Str "Orange" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Apple" ] ]
+                  [ Plain [ Str "Apple" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "more" ] ]
+                  [ Plain [ Str "more" ] ]
               ]
           , Row
               ( "" , [] , [] )
@@ -1064,19 +1064,19 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Bread" ] ]
+                  [ Plain [ Str "Bread" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Pie" ] ]
+                  [ Plain [ Str "Pie" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "more" ] ]
+                  [ Plain [ Str "more" ] ]
               ]
           , Row
               ( "" , [] , [] )
@@ -1085,19 +1085,19 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Butter" ] ]
+                  [ Plain [ Str "Butter" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Ice" , Space , Str "cream" ] ]
+                  [ Plain [ Str "Ice" , Space , Str "cream" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "and" , Space , Str "more" ] ]
+                  [ Plain [ Str "and" , Space , Str "more" ] ]
               ]
           ]
       ]
@@ -1118,19 +1118,19 @@
                  AlignLeft
                  (RowSpan 1)
                  (ColSpan 1)
-                 [ Para [ Str "Left" ] ]
+                 [ Plain [ Str "Left" ] ]
              , Cell
                  ( "" , [] , [] )
                  AlignRight
                  (RowSpan 1)
                  (ColSpan 1)
-                 [ Para [ Str "Right" ] ]
+                 [ Plain [ Str "Right" ] ]
              , Cell
                  ( "" , [] , [] )
                  AlignCenter
                  (RowSpan 1)
                  (ColSpan 1)
-                 [ Para [ Str "Center" ] ]
+                 [ Plain [ Str "Center" ] ]
              ]
          ])
       [ TableBody
@@ -1144,19 +1144,19 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "left" ] ]
+                  [ Plain [ Str "left" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "15.00" ] ]
+                  [ Plain [ Str "15.00" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "centered" ] ]
+                  [ Plain [ Str "centered" ] ]
               ]
           , Row
               ( "" , [] , [] )
@@ -1165,19 +1165,19 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "more" ] ]
+                  [ Plain [ Str "more" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "2.0" ] ]
+                  [ Plain [ Str "2.0" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "more" ] ]
+                  [ Plain [ Str "more" ] ]
               ]
           ]
       ]
@@ -1236,13 +1236,13 @@
                                  AlignDefault
                                  (RowSpan 1)
                                  (ColSpan 1)
-                                 [ Para [ Str "fruit" ] ]
+                                 [ Plain [ Str "fruit" ] ]
                              , Cell
                                  ( "" , [] , [] )
                                  AlignDefault
                                  (RowSpan 1)
                                  (ColSpan 1)
-                                 [ Para [ Str "topping" ] ]
+                                 [ Plain [ Str "topping" ] ]
                              ]
                          ])
                       [ TableBody
@@ -1256,13 +1256,13 @@
                                   AlignDefault
                                   (RowSpan 1)
                                   (ColSpan 1)
-                                  [ Para [ Str "apple" ] ]
+                                  [ Plain [ Str "apple" ] ]
                               , Cell
                                   ( "" , [] , [] )
                                   AlignDefault
                                   (RowSpan 1)
                                   (ColSpan 1)
-                                  [ Para
+                                  [ Plain
                                       [ Str "ice"
                                       , Space
                                       , Str "cream"
@@ -1308,7 +1308,7 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "Orange" ] ]
+                  [ Plain [ Str "Orange" ] ]
               ]
           ]
       ]
@@ -1337,13 +1337,13 @@
                  AlignDefault
                  (RowSpan 1)
                  (ColSpan 1)
-                 [ Para [ Str "fruit" ] ]
+                 [ Plain [ Str "fruit" ] ]
              , Cell
                  ( "" , [] , [] )
                  AlignDefault
                  (RowSpan 1)
                  (ColSpan 1)
-                 [ Para [ Str "topping" ] ]
+                 [ Plain [ Str "topping" ] ]
              ]
          ])
       [ TableBody
@@ -1357,13 +1357,13 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "apple" ] ]
+                  [ Plain [ Str "apple" ] ]
               , Cell
                   ( "" , [] , [] )
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Para [ Str "ice" , Space , Str "cream" ] ]
+                  [ Plain [ Str "ice" , Space , Str "cream" ] ]
               ]
           ]
       ]
diff --git a/test/odt/native/simpleTableWithCaption.native b/test/odt/native/simpleTableWithCaption.native
--- a/test/odt/native/simpleTableWithCaption.native
+++ b/test/odt/native/simpleTableWithCaption.native
@@ -1,1 +1,1 @@
-[Table ("",[],[]) (Caption Nothing [Para [Str "Table",Space,Str "1:",Space,Str "Some",Space,Str "caption",Space,Str "for",Space,Str "a",Space,Str "table"]]) [(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)] (TableHead ("",[],[]) []) [TableBody ("",[],[]) (RowHeadColumns 0) [] [Row ("",[],[]) [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) [Plain [Str "Content"]],Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) [Plain [Str "More",Space,Str "content"]]]]] (TableFoot ("",[],[]) []),Para []]
+[Table ("",[],[]) (Caption Nothing [Para [Str "Table",Space,Str "1:",Space,Str "Some",Space,Str "caption",Space,Str "for",Space,Str "a",Space,Str "table"]]) [(AlignDefault,ColWidthDefault),(AlignDefault,ColWidthDefault)] (TableHead ("",[],[]) []) [TableBody ("",[],[]) (RowHeadColumns 0) [] [Row ("",[],[]) [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) [Plain [Str "Content"]],Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) [Plain [Str "More",Space,Str "content"]]]]] (TableFoot ("",[],[]) []),Para [Emph []]]
diff --git a/test/rst-reader.native b/test/rst-reader.native
--- a/test/rst-reader.native
+++ b/test/rst-reader.native
@@ -1458,7 +1458,7 @@
                   AlignDefault
                   (RowSpan 1)
                   (ColSpan 1)
-                  [ Plain
+                  [ Para
                       [ Str "c"
                       , SoftBreak
                       , Str "c"
diff --git a/test/test-pandoc.hs b/test/test-pandoc.hs
--- a/test/test-pandoc.hs
+++ b/test/test-pandoc.hs
@@ -9,6 +9,7 @@
 import GHC.IO.Encoding
 import Test.Tasty
 import qualified Tests.Command
+import qualified Tests.ImageSize
 import qualified Tests.Old
 import qualified Tests.Readers.Creole
 import qualified Tests.Readers.Docx
@@ -65,6 +66,7 @@
         , testGroup "Shared" Tests.Shared.tests
         , testGroup "MediaBag" Tests.MediaBag.tests
         , testGroup "XML" Tests.XML.tests
+        , testGroup "ImageSize" Tests.ImageSize.tests
         , testGroup "Writers"
           [ testGroup "Native" Tests.Writers.Native.tests
           , testGroup "ConTeXt" Tests.Writers.ConTeXt.tests
diff --git a/test/typst-reader.native b/test/typst-reader.native
--- a/test/typst-reader.native
+++ b/test/typst-reader.native
@@ -88,49 +88,49 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Math InlineMath "F_{1}" ] ]
+                      [ Plain [ Math InlineMath "F_{1}" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Math InlineMath "F_{2}" ] ]
+                      [ Plain [ Math InlineMath "F_{2}" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Math InlineMath "F_{3}" ] ]
+                      [ Plain [ Math InlineMath "F_{3}" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Math InlineMath "F_{4}" ] ]
+                      [ Plain [ Math InlineMath "F_{4}" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Math InlineMath "F_{5}" ] ]
+                      [ Plain [ Math InlineMath "F_{5}" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Math InlineMath "F_{6}" ] ]
+                      [ Plain [ Math InlineMath "F_{6}" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Math InlineMath "F_{7}" ] ]
+                      [ Plain [ Math InlineMath "F_{7}" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Math InlineMath "F_{8}" ] ]
+                      [ Plain [ Math InlineMath "F_{8}" ] ]
                   ]
               , Row
                   ( "" , [] , [] )
@@ -139,49 +139,49 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Str "1" ] ]
+                      [ Plain [ Str "1" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Str "1" ] ]
+                      [ Plain [ Str "1" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Str "2" ] ]
+                      [ Plain [ Str "2" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Str "3" ] ]
+                      [ Plain [ Str "3" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Str "5" ] ]
+                      [ Plain [ Str "5" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Str "8" ] ]
+                      [ Plain [ Str "8" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Str "13" ] ]
+                      [ Plain [ Str "13" ] ]
                   , Cell
                       ( "" , [] , [] )
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para [ Str "21" ] ]
+                      [ Plain [ Str "21" ] ]
                   ]
               ]
           ]
@@ -204,11 +204,11 @@
                       , Space
                       , Str "Undergrads"
                       ]
-                  , SoftBreak
                   ]
                   ( "https://github.com/johanvx/typst-undergradmath"
                   , ""
                   )
+              , SoftBreak
               ]
           ]
       , Para
@@ -294,7 +294,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Span
                               ( "" , [ "box" ] , [] )
                               [ Str "2023-05-22"
@@ -308,7 +308,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Str "This"
                           , Space
                           , Str "is"
@@ -349,7 +349,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Span
                               ( "" , [ "box" ] , [] ) [ Str "\128166" ]
                           ]
@@ -359,7 +359,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Str "Get"
                           , Space
                           , Str "this"
@@ -404,7 +404,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Span
                               ( "" , [ "box" ] , [] )
                               [ Str "No"
@@ -420,7 +420,7 @@
                       AlignDefault
                       (RowSpan 1)
                       (ColSpan 1)
-                      [ Para
+                      [ Plain
                           [ Str "Don\8217t"
                           , Space
                           , Str "know"
@@ -558,7 +558,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "x^{2}"
@@ -572,7 +572,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sqrt{2}"
@@ -595,7 +595,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "x_{i,j}"
@@ -609,7 +609,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\frac{2}{3}"
@@ -683,7 +683,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\alpha"
@@ -697,7 +697,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\xi"
@@ -720,7 +720,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\beta"
@@ -734,7 +734,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\959"
@@ -751,7 +751,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\gamma"
@@ -771,7 +771,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\pi"
@@ -794,7 +794,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\delta"
@@ -814,7 +814,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varpi"
@@ -831,7 +831,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\epsilon"
@@ -845,7 +845,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\rho"
@@ -862,7 +862,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varepsilon"
@@ -876,7 +876,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varrho"
@@ -893,7 +893,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\zeta"
@@ -907,7 +907,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sigma"
@@ -930,7 +930,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\eta"
@@ -944,7 +944,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varsigma"
@@ -966,7 +966,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\theta"
@@ -986,7 +986,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\tau"
@@ -1003,7 +1003,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\vartheta"
@@ -1017,7 +1017,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\upsilon"
@@ -1040,7 +1040,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\iota"
@@ -1054,7 +1054,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\phi"
@@ -1077,7 +1077,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\kappa"
@@ -1091,7 +1091,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varphi"
@@ -1108,7 +1108,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\lambda"
@@ -1128,7 +1128,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\chi"
@@ -1145,7 +1145,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mu"
@@ -1159,7 +1159,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\psi"
@@ -1182,7 +1182,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nu"
@@ -1196,7 +1196,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\omega"
@@ -1246,7 +1246,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cup"
@@ -1260,7 +1260,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{R}"
@@ -1277,7 +1277,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\forall"
@@ -1294,7 +1294,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cap"
@@ -1308,7 +1308,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{Z}"
@@ -1325,7 +1325,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\exists"
@@ -1342,7 +1342,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\subset"
@@ -1356,7 +1356,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{Q}"
@@ -1373,7 +1373,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\neg"
@@ -1390,7 +1390,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\subseteq"
@@ -1404,7 +1404,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{N}"
@@ -1421,7 +1421,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\vee"
@@ -1438,7 +1438,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\supset"
@@ -1452,7 +1452,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mathbb{C}"
@@ -1469,7 +1469,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\land"
@@ -1486,7 +1486,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\supseteq"
@@ -1500,7 +1500,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varnothing"
@@ -1514,7 +1514,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\vdash"
@@ -1531,7 +1531,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\in"
@@ -1545,7 +1545,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\varnothing"
@@ -1559,7 +1559,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\models"
@@ -1576,7 +1576,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\notin"
@@ -1590,7 +1590,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\1488"
@@ -1604,7 +1604,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\smallsetminus"
@@ -1814,7 +1814,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "f'"
@@ -1831,7 +1831,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\dot{a}"
@@ -1845,10 +1845,10 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
-                                  [ Math InlineMath "\\widetilde{a}"
+                                  [ Math InlineMath "\\tilde{a}"
                                   , Str "\8192"
                                   , Code ( "" , [] , [] ) "tilde(a)"
                                   ]
@@ -1862,7 +1862,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "f''"
@@ -1877,7 +1877,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ddot{a}"
@@ -1891,7 +1891,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\overline{a}"
@@ -1908,7 +1908,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\Sigma^{\\ast}"
@@ -1922,7 +1922,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\hat{a}"
@@ -1936,7 +1936,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -2181,7 +2181,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sin"
@@ -2195,7 +2195,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sinh"
@@ -2209,7 +2209,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\arcsin"
@@ -2226,7 +2226,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cos"
@@ -2240,7 +2240,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cosh"
@@ -2254,7 +2254,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\arccos"
@@ -2271,7 +2271,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\tan"
@@ -2285,7 +2285,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\tanh"
@@ -2299,7 +2299,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\arctan"
@@ -2316,7 +2316,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sec"
@@ -2330,7 +2330,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\coth"
@@ -2344,7 +2344,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\min"
@@ -2361,7 +2361,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\csc"
@@ -2375,7 +2375,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\det"
@@ -2389,7 +2389,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\max"
@@ -2406,7 +2406,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cot"
@@ -2420,7 +2420,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\dim"
@@ -2434,7 +2434,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\inf"
@@ -2451,7 +2451,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\exp"
@@ -2465,7 +2465,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ker"
@@ -2479,7 +2479,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sup"
@@ -2496,7 +2496,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\log"
@@ -2510,7 +2510,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\deg"
@@ -2524,7 +2524,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\liminf"
@@ -2541,7 +2541,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ln"
@@ -2555,7 +2555,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\arg"
@@ -2569,7 +2569,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\limsup"
@@ -2586,7 +2586,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\lg"
@@ -2600,7 +2600,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\gcd"
@@ -2614,7 +2614,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\lim"
@@ -2651,7 +2651,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "<"
@@ -2668,7 +2668,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\angle"
@@ -2682,7 +2682,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cdot"
@@ -2699,7 +2699,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\leq"
@@ -2716,7 +2716,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\measuredangle"
@@ -2730,7 +2730,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\pm"
@@ -2747,7 +2747,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath ">"
@@ -2764,7 +2764,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ell"
@@ -2778,7 +2778,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mp"
@@ -2795,7 +2795,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\geq"
@@ -2812,7 +2812,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\parallel"
@@ -2826,7 +2826,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\times"
@@ -2843,7 +2843,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\neq"
@@ -2860,7 +2860,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "45{^\\circ}"
@@ -2874,7 +2874,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\div"
@@ -2891,7 +2891,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ll"
@@ -2908,7 +2908,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\cong"
@@ -2922,7 +2922,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ast"
@@ -2942,7 +2942,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\gg"
@@ -2959,7 +2959,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ncong"
@@ -2974,7 +2974,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mid"
@@ -2991,7 +2991,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\approx"
@@ -3005,7 +3005,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sim"
@@ -3019,7 +3019,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nmid"
@@ -3036,7 +3036,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\asymp"
@@ -3055,7 +3055,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\simeq"
@@ -3069,7 +3069,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "n!"
@@ -3086,7 +3086,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\equiv"
@@ -3100,7 +3100,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nsim"
@@ -3114,7 +3114,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\partial"
@@ -3131,7 +3131,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\prec"
@@ -3145,7 +3145,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\oplus"
@@ -3159,7 +3159,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nabla"
@@ -3176,7 +3176,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\preceq"
@@ -3190,7 +3190,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\ominus"
@@ -3204,7 +3204,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\295"
@@ -3221,7 +3221,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\succ"
@@ -3235,7 +3235,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\odot"
@@ -3249,7 +3249,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\circ"
@@ -3268,7 +3268,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\succeq"
@@ -3282,7 +3282,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\otimes"
@@ -3296,7 +3296,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\star"
@@ -3313,7 +3313,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\propto"
@@ -3327,7 +3327,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\oslash"
@@ -3346,7 +3346,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\sqrt{}"
@@ -3363,7 +3363,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\doteq"
@@ -3382,7 +3382,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\upharpoonright"
@@ -3396,7 +3396,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\10003"
@@ -3483,7 +3483,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\rightarrow"
@@ -3500,7 +3500,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\mapsto"
@@ -3520,7 +3520,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nrightarrow"
@@ -3534,7 +3534,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\longmapsto"
@@ -3553,7 +3553,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\longrightarrow"
@@ -3567,7 +3567,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\leftarrow"
@@ -3587,7 +3587,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\Rightarrow"
@@ -3605,7 +3605,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -3627,7 +3627,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\nRightarrow"
@@ -3643,7 +3643,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\downarrow"
@@ -3660,7 +3660,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\Longrightarrow"
@@ -3676,7 +3676,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\uparrow"
@@ -3693,7 +3693,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\rightsquigarrow"
@@ -3708,7 +3708,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\updownarrow"
@@ -3837,7 +3837,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\int"
@@ -3851,7 +3851,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\iiint"
@@ -3866,7 +3866,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\bigcup"
@@ -3883,7 +3883,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\iint"
@@ -3898,7 +3898,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\oint"
@@ -3913,7 +3913,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\bigcap"
@@ -3949,7 +3949,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "()"
@@ -3963,7 +3963,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\langle\\rangle"
@@ -3979,7 +3979,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -3997,7 +3997,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math InlineMath "\\lbrack\\rbrack"
@@ -4011,7 +4011,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -4027,7 +4027,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -4046,7 +4046,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -4061,7 +4061,7 @@
                           AlignDefault
                           (RowSpan 1)
                           (ColSpan 1)
-                          [ Para
+                          [ Plain
                               [ Span
                                   ( "" , [ "box" ] , [] )
                                   [ Math
@@ -5345,5 +5345,216 @@
           , Space
           , Str "2023-05-22"
           ]
+      ]
+  , Header 1 ( "" , [] , [] ) [ Str "Citations" ]
+  , Para
+      [ Str "Normal:"
+      , Space
+      , Cite
+          [ Citation
+              { citationId = "brown01"
+              , citationPrefix = []
+              , citationSuffix = []
+              , citationMode = NormalCitation
+              , citationNoteNum = 0
+              , citationHash = 0
+              }
+          ]
+          [ Str "[brown01]" ]
+      , Space
+      , Str "or"
+      , Space
+      , Cite
+          [ Citation
+              { citationId = "brown01"
+              , citationPrefix = []
+              , citationSuffix = []
+              , citationMode = NormalCitation
+              , citationNoteNum = 0
+              , citationHash = 0
+              }
+          ]
+          [ Str "[brown01]" ]
+      , Str "."
+      ]
+  , Para
+      [ Str "Prose:"
+      , Space
+      , Cite
+          [ Citation
+              { citationId = "brown01"
+              , citationPrefix = []
+              , citationSuffix = []
+              , citationMode = AuthorInText
+              , citationNoteNum = 0
+              , citationHash = 0
+              }
+          ]
+          [ Str "[brown01]" ]
+      ]
+  , Para
+      [ Str "Year:"
+      , Space
+      , Cite
+          [ Citation
+              { citationId = "brown01"
+              , citationPrefix = []
+              , citationSuffix = []
+              , citationMode = SuppressAuthor
+              , citationNoteNum = 0
+              , citationHash = 0
+              }
+          ]
+          [ Str "[brown01]" ]
+      ]
+  , Para
+      [ Str "Author:"
+      , Space
+      , Cite
+          [ Citation
+              { citationId = "brown01"
+              , citationPrefix = []
+              , citationSuffix = []
+              , citationMode = NormalCitation
+              , citationNoteNum = 0
+              , citationHash = 0
+              }
+          ]
+          [ Str "[brown01]" ]
+      ]
+  , Header
+      1
+      ( "" , [] , [] )
+      [ Str "Inline"
+      , Space
+      , Str "elements"
+      , Space
+      , Str "across"
+      , Space
+      , Str "paragraphs"
+      ]
+  , Para [ Emph [ Str "hi" ] ]
+  , Para [ Emph [ Str "there" ] ]
+  , Para [ Str "Hello" , Space , Strong [ Str "again" ] ]
+  , Para
+      [ Strong [ Str "and" , Space , Str "again" ]
+      , Space
+      , Str "world."
+      ]
+  , Para [ Underline [ Str "Para" , Space , Str "one." ] ]
+  , Para [ Underline [ Str "Para" , Space , Str "two." ] ]
+  , Header
+      2
+      ( "" , [] , [] )
+      [ SmallCaps [ Str "smallcaps" , Space , Str "heading" ] ]
+  , Header
+      1
+      ( "" , [] , [] )
+      [ Str "More"
+      , Space
+      , Str "inline"
+      , Space
+      , Str "elements"
+      , Space
+      , Str "across"
+      , Space
+      , Str "paragraphs"
+      ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "link" , Space , Str "one" ]
+          ( "https://example.com/typst" , "" )
+      ]
+  , Para
+      [ Link
+          ( "" , [] , [] )
+          [ Str "link" , Space , Str "two" ]
+          ( "https://example.com/typst" , "" )
+      ]
+  , Para [ Emph [ Str "hi" , Space ] ]
+  , Para [ Emph [ Str "struck" , Space , Str "middle" ] ]
+  , Para [ Emph [ Space , Str "there" ] ]
+  , Para [ Str "mixed" , Space , Str "case" ]
+  , Para [ Str "also" , Space , Str "here" ]
+  , DefinitionList
+      [ ( [ Str "term" ]
+        , [ [ Para [ Emph [ Str "term" , Space , Str "one" ] ]
+            , Para [ Emph [ Str "term" , Space , Str "two" ] ]
+            ]
+          ]
+        )
+      ]
+  , Para [ Str "Edge" , Space , Str "case" ]
+  , Para
+      [ Emph [ Str "split" , Space , Str "here" ]
+      , Space
+      , Str "continues."
+      ]
+  , Header
+      1
+      ( "" , [] , [] )
+      [ Str "Splitting"
+      , Space
+      , Str "edge"
+      , Space
+      , Str "cases"
+      ]
+  , Para [ Span ( "edge-label" , [] , [] ) [] ]
+  , Para
+      [ Str "Trailing"
+      , Space
+      , Str "edge"
+      , Space
+      , Emph [ Str "splits" ]
+      ]
+  , Para [ Emph [ Str "here" ] , Space , Str "continues." ]
+  , Para
+      [ Str "Consecutive"
+      , Space
+      , Str "breaks"
+      , Space
+      , Emph [ Str "a" ]
+      ]
+  , Para [ Emph [ Str "b" ] , Space , Str "end." ]
+  , Para [ Str "Vanishing" ]
+  , Para [ Str "body." ]
+  , Para
+      [ Emph [ Underline [ Str "nested" , Space , Str "one" ] ] ]
+  , Para
+      [ Emph [ Underline [ Str "nested" , Space , Str "two" ] ] ]
+  , Para [ Emph [ Str "a" , Space ] ]
+  , Table
+      ( "" , [] , [] )
+      (Caption Nothing [])
+      [ ( AlignDefault , ColWidthDefault ) ]
+      (TableHead ( "" , [] , [] ) [])
+      [ TableBody
+          ( "" , [] , [] )
+          (RowHeadColumns 0)
+          []
+          [ Row
+              ( "" , [] , [] )
+              [ Cell
+                  ( "" , [] , [] )
+                  AlignDefault
+                  (RowSpan 1)
+                  (ColSpan 1)
+                  [ Plain [ Str "cell" ] ]
+              ]
+          ]
+      ]
+      (TableFoot ( "" , [] , [] ) [])
+  , Para [ Emph [ Space , Str "c" ] ]
+  , Para
+      [ Str "Ref"
+      , Space
+      , Str "supplement:"
+      , Space
+      , Link
+          ( "" , [ "ref" ] , [] )
+          [ Str "see" , Space , Str "this" , Space , Str "section" ]
+          ( "#edge-label" , "" )
+      , Str "."
       ]
   ]
diff --git a/test/typst-reader.typ b/test/typst-reader.typ
--- a/test/typst-reader.typ
+++ b/test/typst-reader.typ
@@ -26,3 +26,75 @@
 
 #include "undergradmath.typ"
 
+
+= Citations
+
+Normal: #cite(<brown01>) or @brown01.
+
+Prose: #cite(<brown01>, form: "prose")
+
+Year: #cite(<brown01>, form: "year")
+
+Author: #cite(<brown01>, form: "author")
+
+
+= Inline elements across paragraphs
+
+#emph[hi
+
+there]
+
+Hello #strong[again
+
+and again] world.
+
+#underline[Para one.
+
+Para two.]
+
+#smallcaps[#heading(level: 2)[smallcaps heading]]
+
+
+= More inline elements across paragraphs
+
+#link("https://example.com/typst")[link one
+
+link two]
+
+#emph[hi #block[struck middle] there]
+
+#lower[Mixed Case
+
+Also Here]
+
+/ term: #emph[term one
+
+  term two]
+
+Edge case #emph[
+
+split here] continues.
+
+
+= Splitting edge cases <edge-label>
+
+Trailing edge #emph[splits
+
+here] continues.
+
+Consecutive breaks #emph[a
+
+
+b] end.
+
+Vanishing #emph[
+
+] body.
+
+#emph[#underline[nested one
+
+nested two]]
+
+#emph[a #grid(columns: 1)[cell] c]
+
+Ref supplement: @edge-label[see this section].
diff --git a/test/vimwiki-reader.native b/test/vimwiki-reader.native
--- a/test/vimwiki-reader.native
+++ b/test/vimwiki-reader.native
@@ -357,10 +357,7 @@
       , Str "attributes"
       ]
   , CodeBlock
-      ( ""
-      , []
-      , [ ( "class" , "python" ) , ( "style" , "color:blue" ) ]
-      )
+      ( "" , [ "python" ] , [ ( "style" , "color:blue" ) ] )
       " for i in range(1, 5):\n     print(i)"
   , Header
       3
diff --git a/test/writer.html4 b/test/writer.html4
--- a/test/writer.html4
+++ b/test/writer.html4
@@ -14,7 +14,10 @@
     */
     html {
       color: #1a1a1a;
+      color: light-dark(#1a1a1a, #fdfdfd);
       background-color: #fdfdfd;
+      background-color: light-dark(#fdfdfd, #1a1a1a);
+      color-scheme: light dark;
     }
     body {
       margin: 0 auto;
@@ -39,12 +42,17 @@
     }
     @media print {
       html {
-        background-color: white;
+        color-scheme: light only;
       }
-      body {
+      html, body, pre, code {
         background-color: transparent;
+      }
+      body, a, a:visited, blockquote {
         color: black;
       }
+      pre, code {
+        padding: 0;
+      }
       p, h2, h3 {
         orphans: 3;
         widows: 3;
@@ -56,11 +64,8 @@
     p {
       margin: 1em 0;
     }
-    a {
-      color: #1a1a1a;
-    }
-    a:visited {
-      color: #1a1a1a;
+    a, a:visited {
+      color: inherit;
     }
     img {
       max-width: 100%;
@@ -89,8 +94,11 @@
     blockquote {
       margin: 1em 0 1em 1.7em;
       padding-left: 1em;
-      border-left: 2px solid #e6e6e6;
+      border-left: 2px solid;
+      border-color: #e6e6e6;
+      border-color: light-dark(#e6e6e6, #4c4c4c);
       color: #606060;
+      color: light-dark(#606060, #b6b6b6);
     }
     code {
       white-space: pre-wrap;
@@ -114,7 +122,7 @@
     }
     hr {
       border: none;
-      border-top: 1px solid #1a1a1a;
+      border-top: 1px solid currentColor;
       height: 1px;
       margin: 1em 0;
     }
@@ -131,11 +139,11 @@
     }
     tbody {
       margin-top: 0.5em;
-      border-top: 1px solid #1a1a1a;
-      border-bottom: 1px solid #1a1a1a;
+      border-top: 1px solid currentColor;
+      border-bottom: 1px solid currentColor;
     }
     th {
-      border-top: 1px solid #1a1a1a;
+      border-top: 1px solid currentColor;
       padding: 0.25em 0.5em 0.25em 0.5em;
     }
     td {
diff --git a/test/writer.html5 b/test/writer.html5
--- a/test/writer.html5
+++ b/test/writer.html5
@@ -14,7 +14,10 @@
     */
     html {
       color: #1a1a1a;
+      color: light-dark(#1a1a1a, #fdfdfd);
       background-color: #fdfdfd;
+      background-color: light-dark(#fdfdfd, #1a1a1a);
+      color-scheme: light dark;
     }
     body {
       margin: 0 auto;
@@ -39,12 +42,17 @@
     }
     @media print {
       html {
-        background-color: white;
+        color-scheme: light only;
       }
-      body {
+      html, body, pre, code {
         background-color: transparent;
+      }
+      body, a, a:visited, blockquote {
         color: black;
       }
+      pre, code {
+        padding: 0;
+      }
       p, h2, h3 {
         orphans: 3;
         widows: 3;
@@ -56,11 +64,8 @@
     p {
       margin: 1em 0;
     }
-    a {
-      color: #1a1a1a;
-    }
-    a:visited {
-      color: #1a1a1a;
+    a, a:visited {
+      color: inherit;
     }
     img {
       max-width: 100%;
@@ -89,8 +94,11 @@
     blockquote {
       margin: 1em 0 1em 1.7em;
       padding-left: 1em;
-      border-left: 2px solid #e6e6e6;
+      border-left: 2px solid;
+      border-color: #e6e6e6;
+      border-color: light-dark(#e6e6e6, #4c4c4c);
       color: #606060;
+      color: light-dark(#606060, #b6b6b6);
     }
     code {
       white-space: pre-wrap;
@@ -114,7 +122,7 @@
     }
     hr {
       border: none;
-      border-top: 1px solid #1a1a1a;
+      border-top: 1px solid currentColor;
       height: 1px;
       margin: 1em 0;
     }
@@ -131,11 +139,11 @@
     }
     tbody {
       margin-top: 0.5em;
-      border-top: 1px solid #1a1a1a;
-      border-bottom: 1px solid #1a1a1a;
+      border-top: 1px solid currentColor;
+      border-bottom: 1px solid currentColor;
     }
     th {
-      border-top: 1px solid #1a1a1a;
+      border-top: 1px solid currentColor;
       padding: 0.25em 0.5em 0.25em 0.5em;
     }
     td {
diff --git a/test/writer.org b/test/writer.org
--- a/test/writer.org
+++ b/test/writer.org
@@ -387,45 +387,45 @@
 
 Interpreted markdown in a table:
 
-#+begin_html
+#+begin_export html
   <table>
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <tr>
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <td>
-#+end_html
+#+end_export
 
 This is /emphasized/
 
-#+begin_html
+#+begin_export html
   </td>
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <td>
-#+end_html
+#+end_export
 
 And this is *strong*
 
-#+begin_html
+#+begin_export html
   </td>
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   </tr>
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   </table>
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <script type="text/javascript">document.write('This *should not* be interpreted as markdown');</script>
-#+end_html
+#+end_export
 
 Here's a simple block:
 
@@ -451,24 +451,24 @@
 
 This should just be an HTML comment:
 
-#+begin_html
+#+begin_export html
   <!-- Comment -->
-#+end_html
+#+end_export
 
 Multiline:
 
-#+begin_html
+#+begin_export html
   <!--
   Blah
   Blah
   -->
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <!--
       This is another comment.
   -->
-#+end_html
+#+end_export
 
 Code block:
 
@@ -478,9 +478,9 @@
 
 Just plain comment, with trailing spaces on the line:
 
-#+begin_html
+#+begin_export html
   <!-- foo -->
-#+end_html
+#+end_export
 
 Code:
 
@@ -490,41 +490,41 @@
 
 Hr's:
 
-#+begin_html
+#+begin_export html
   <hr>
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <hr />
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <hr />
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <hr>
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <hr />
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <hr />
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <hr class="foo" id="bar" />
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <hr class="foo" id="bar" />
-#+end_html
+#+end_export
 
-#+begin_html
+#+begin_export html
   <hr class="foo" id="bar">
-#+end_html
+#+end_export
 
 --------------
 
@@ -598,7 +598,7 @@
 
 These shouldn't be math:
 
-- To get the famous equation, write =$e = mc^2$=.
+- To get the famous equation, write ~$e = mc^2$~.
 - $22,000 is a /lot/ of money. So is $34,000. (It worked if “lot” is
   emphasized.)
 - Shoes ($20) and socks ($5).
@@ -702,7 +702,7 @@
 :END:
 Foo [[/url/][bar]].
 
-With [[/url/][embedded [brackets]]].
+With [[/url/][embedded [brackets​]]].
 
 [[/url/][b]] by itself should be a link.
 
diff --git a/test/writer.tei b/test/writer.tei
--- a/test/writer.tei
+++ b/test/writer.tei
@@ -19,7 +19,7 @@
 <body>
 <p>This is a set of tests for pandoc. Most of them are adapted from John
 Gruber’s markdown test suite.</p>
-<milestone unit="undefined" type="separator" rendition="line" />
+<milestone unit="undefined" type="separator" rend="line" />
 <div type="level1" xml:id="headers">
   <head>Headers</head>
   <div type="level2" xml:id="level-2-with-an-embedded-link">
@@ -48,7 +48,7 @@
   <div type="level2" xml:id="level-2">
     <head>Level 2</head>
     <p>with no blank line</p>
-    <milestone unit="undefined" type="separator" rendition="line" />
+    <milestone unit="undefined" type="separator" rend="line" />
   </div>
 </div>
 <div type="level1" xml:id="paragraphs">
@@ -59,7 +59,7 @@
   item.</p>
   <p>Here’s one with a bullet. * criminey.</p>
   <p>There should be a hard line break<lb />here.</p>
-  <milestone unit="undefined" type="separator" rendition="line" />
+  <milestone unit="undefined" type="separator" rend="line" />
 </div>
 <div type="level1" xml:id="block-quotes">
   <head>Block Quotes</head>
@@ -93,7 +93,7 @@
   </quote>
   <p>This should not be a block quote: 2 &gt; 1.</p>
   <p>And a following paragraph.</p>
-  <milestone unit="undefined" type="separator" rendition="line" />
+  <milestone unit="undefined" type="separator" rend="line" />
 </div>
 <div type="level1" xml:id="code-blocks">
   <head>Code Blocks</head>
@@ -113,7 +113,7 @@
 
 These should not be escaped:  \$ \\ \&gt; \[ \{
 </ab>
-  <milestone unit="undefined" type="separator" rendition="line" />
+  <milestone unit="undefined" type="separator" rend="line" />
 </div>
 <div type="level1" xml:id="lists">
   <head>Lists</head>
@@ -405,7 +405,7 @@
     <p>Should not be a list item:</p>
     <p>M.A. 2007</p>
     <p>B. Williams</p>
-    <milestone unit="undefined" type="separator" rendition="line" />
+    <milestone unit="undefined" type="separator" rend="line" />
   </div>
 </div>
 <div type="level1" xml:id="definition-lists">
@@ -590,7 +590,7 @@
 &lt;hr /&gt;
 </ab>
   <p>Hr’s:</p>
-  <milestone unit="undefined" type="separator" rendition="line" />
+  <milestone unit="undefined" type="separator" rend="line" />
 </div>
 <div type="level1" xml:id="inline-markup">
   <head>Inline Markup</head>
@@ -623,7 +623,7 @@
   H<hi rendition="simple:subscript">many of them</hi>O.</p>
   <p>These should not be superscripts or subscripts, because of the unescaped
   spaces: a^b c^d, a~b c~d.</p>
-  <milestone unit="undefined" type="separator" rendition="line" />
+  <milestone unit="undefined" type="separator" rend="line" />
 </div>
 <div type="level1" xml:id="smart-quotes-ellipses-dashes">
   <head>Smart quotes, ellipses, dashes</head>
@@ -640,7 +640,7 @@
   <p>Some dashes: one—two — three—four — five.</p>
   <p>Dashes between numbers: 5–7, 255–66, 1987–1999.</p>
   <p>Ellipses…and…and….</p>
-  <milestone unit="undefined" type="separator" rendition="line" />
+  <milestone unit="undefined" type="separator" rend="line" />
 </div>
 <div type="level1" xml:id="latex">
   <head>LaTeX</head>
@@ -692,7 +692,7 @@
     </item>
   </list>
   <p>Here’s a LaTeX table:</p>
-  <milestone unit="undefined" type="separator" rendition="line" />
+  <milestone unit="undefined" type="separator" rend="line" />
 </div>
 <div type="level1" xml:id="special-characters">
   <head>Special Characters</head>
@@ -735,7 +735,7 @@
   <p>Bang: !</p>
   <p>Plus: +</p>
   <p>Minus: -</p>
-  <milestone unit="undefined" type="separator" rendition="line" />
+  <milestone unit="undefined" type="separator" rend="line" />
 </div>
 <div type="level1" xml:id="links">
   <head>Links</head>
@@ -801,7 +801,7 @@
     <ab type='codeblock '>
 or here: &lt;http://example.com/&gt;
 </ab>
-    <milestone unit="undefined" type="separator" rendition="line" />
+    <milestone unit="undefined" type="separator" rend="line" />
   </div>
 </div>
 <div type="level1" xml:id="images">
@@ -817,7 +817,7 @@
     <head>movie</head>
     <graphic url="movie.jpg" />
   </figure> icon.</p>
-  <milestone unit="undefined" type="separator" rendition="line" />
+  <milestone unit="undefined" type="separator" rend="line" />
 </div>
 <div type="level1" xml:id="footnotes">
   <head>Footnotes</head>
diff --git a/test/writer.typst b/test/writer.typst
--- a/test/writer.typst
+++ b/test/writer.typst
@@ -542,7 +542,6 @@
 #block[
 #block[
 foo
-
 ]
 ]
 #block[
@@ -557,7 +556,6 @@
 
 #block[
 foo
-
 ]
 This should be a code block, though:
 
diff --git a/xml-light/Text/Pandoc/XML/Light.hs b/xml-light/Text/Pandoc/XML/Light.hs
--- a/xml-light/Text/Pandoc/XML/Light.hs
+++ b/xml-light/Text/Pandoc/XML/Light.hs
@@ -43,7 +43,10 @@
 
 import qualified Control.Exception as E
 import qualified Text.XML as Conduit
-import Text.XML.Unresolved (InvalidEventStream(..))
+import qualified Text.XML.Stream.Parse as P
+import Data.Conduit (runConduit, (.|))
+import qualified Data.Conduit.List as CL
+import Data.Char (isSpace)
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as TL
 import qualified Data.Map as M
@@ -64,46 +67,137 @@
   elementToElement .  Conduit.documentRoot <$>
     either (Left . T.pack . E.displayException) Right
     (Conduit.parseText Conduit.def{ Conduit.psRetainNamespaces = True
-                                  , Conduit.psDecodeEntities = decodeEnts } t)
- where
-   decodeEnts ref = case M.lookup ref entityMap of
-                      Nothing -> XML.ContentEntity ref
-                      Just t' -> XML.ContentText t'
+                                  , Conduit.psDecodeEntities =
+                                      entityResolver entityMap } t)
 
 parseXMLContents :: TL.Text -> Either T.Text [Content]
 parseXMLContents = parseXMLContentsWithEntities mempty
 
+-- | Parse a list of XML contents.  Unlike 'parseXMLElementWithEntities',
+-- this does not require a single root element: multiple sibling
+-- elements and top-level text are accepted.  An XML declaration,
+-- DOCTYPE, comments, and processing instructions are skipped.
 parseXMLContentsWithEntities :: M.Map T.Text T.Text
                              -> TL.Text -> Either T.Text [Content]
 parseXMLContentsWithEntities entityMap t =
-  case Conduit.parseText Conduit.def{ Conduit.psRetainNamespaces = True
-                                    , Conduit.psDecodeEntities = decodeEnts
-                                    } t of
-    Left e ->
-      case E.fromException e of
-        Just (ContentAfterRoot _) ->
-          elContent <$> parseXMLElementWithEntities entityMap
-                          ("<wrapper>" <> t <> "</wrapper>")
-        _ -> Left . T.pack . E.displayException $ e
-    Right x -> Right [Elem . elementToElement . Conduit.documentRoot $ x]
+  case runConduit (CL.sourceList (TL.toChunks t) .| parser
+                    .| CL.fold step (Right ([], []))) of
+    Left err -> Left . T.pack . E.displayException $ (err :: E.SomeException)
+    Right st -> normalizeTop <$> finishContents st
  where
-   decodeEnts ref = case M.lookup ref entityMap of
-                      Nothing -> XML.ContentEntity ref
-                      Just t' -> XML.ContentText t'
+   parser = P.parseTextPos P.def{ P.psRetainNamespaces = True
+                                , P.psDecodeEntities =
+                                    entityResolver entityMap }
 
+entityResolver :: M.Map T.Text T.Text -> T.Text -> XML.Content
+entityResolver entityMap ref =
+  case M.lookup ref entityMap of
+    Nothing -> XML.ContentEntity ref
+    Just t' -> XML.ContentText t'
+
+-- An element that is being built, with its children so far in reverse order.
+data Frame = Frame QName [Attr] [Content]
+
+-- The top-level contents built so far (reversed) and the stack of
+-- open elements (innermost first); or an error.
+type BuildState = Either T.Text ([Content], [Frame])
+
+-- Fold one parse event into the content forest being built, checking
+-- that start and end tags are balanced.
+step :: BuildState -> P.EventPos -> BuildState
+step st@(Left _) _ = st
+step (Right (cs, stack)) (pos, event) =
+  case event of
+    XML.EventBeginElement name attribs ->
+      case mapM toAttr attribs of
+        Left e -> Left e
+        Right attrs -> Right (cs, Frame (nameToQName name) attrs [] : stack)
+    XML.EventEndElement name ->
+      case stack of
+        Frame fname attrs children : stack'
+          | nameToQName name == fname ->
+              emit (Elem (Element fname attrs (mergeText (reverse children))
+                           Nothing)) stack'
+        _ -> Left $ atPos $ "Unexpected close tag " <>
+                      showQ (nameToQName name)
+    XML.EventContent c ->
+      case contentToText c of
+        Left e -> Left e
+        Right txt -> emit (textContent txt) stack
+    XML.EventCDATA txt -> emit (textContent txt) stack
+    _ -> Right (cs, stack)
+      -- skip begin/end document, doctype, comments, and PIs
+ where
+  -- add finished content to the enclosing element (or the top level)
+  emit c [] = Right (c : cs, [])
+  emit c (Frame name attrs children : stack') =
+    Right (cs, Frame name attrs (c : children) : stack')
+
+  textContent txt = Text (CData CDataText txt Nothing)
+
+  toAttr (name, vals) =
+    Attr (nameToQName name) . T.concat <$> mapM contentToText vals
+
+  contentToText (XML.ContentText txt) = Right txt
+  contentToText (XML.ContentEntity ref) =
+    Left $ atPos $ "Unresolved entity &" <> ref <> ";"
+
+  atPos msg = case pos of
+                Just pr -> T.pack (show pr) <> ": " <> msg
+                Nothing -> msg
+
+finishContents :: BuildState -> Either T.Text [Content]
+finishContents (Left e) = Left e
+finishContents (Right (cs, [])) = Right (mergeText (reverse cs))
+finishContents (Right (_, Frame name _ _ : _)) =
+  Left $ "Missing close tag for " <> showQ name
+
+showQ :: QName -> T.Text
+showQ (QName name _ Nothing)    = name
+showQ (QName name _ (Just pre)) = pre <> ":" <> name
+
+-- Merge adjacent text nodes (the stream parser splits text at
+-- entity and CDATA boundaries).
+mergeText :: [Content] -> [Content]
+mergeText (Text cd : cs) =
+  case span isText cs of
+    ([], _)    -> Text cd : mergeText cs
+    (ts, rest) -> Text cd{ cdData = T.concat (cdData cd :
+                                      [d | Text (CData _ d _) <- ts]) }
+                  : mergeText rest
+ where
+  isText Text{} = True
+  isText _      = False
+mergeText (c : cs) = c : mergeText cs
+mergeText [] = []
+
+-- If the content is a single element surrounded only by whitespace
+-- (as in a complete XML document, where there may be newlines after
+-- an XML declaration or DOCTYPE), drop the whitespace.
+normalizeTop :: [Content] -> [Content]
+normalizeTop cs =
+  case filter (not . isWhitespaceText) cs of
+    cs'@[Elem _] -> cs'
+    _            -> cs
+ where
+  isWhitespaceText (Text cd) = T.all isSpace (cdData cd)
+  isWhitespaceText _         = False
+
+nameToQName :: Conduit.Name -> QName
+nameToQName (Conduit.Name localName mbns mbpref) =
+  case mbpref of
+    Nothing ->
+      case T.stripPrefix "xmlns:" localName of
+        Just rest -> QName rest mbns (Just "xmlns")
+        Nothing   -> QName localName mbns mbpref
+    _ -> QName localName mbns mbpref
+
 elementToElement :: Conduit.Element -> Element
 elementToElement (Conduit.Element name attribMap nodes) =
-  Element (nameToQname name) attrs (mapMaybe nodeToContent nodes) Nothing
+  Element (nameToQName name) attrs (mapMaybe nodeToContent nodes) Nothing
  where
-  attrs = map (\(n,v) -> Attr (nameToQname n) v) $
+  attrs = map (\(n,v) -> Attr (nameToQName n) v) $
               M.toList attribMap
-  nameToQname (Conduit.Name localName mbns mbpref) =
-    case mbpref of
-      Nothing ->
-        case T.stripPrefix "xmlns:" localName of
-          Just rest -> QName rest mbns (Just "xmlns")
-          Nothing   -> QName localName mbns mbpref
-      _ -> QName localName mbns mbpref
 
 nodeToContent :: Conduit.Node -> Maybe Content
 nodeToContent (Conduit.NodeElement el) =
diff --git a/xml-light/Text/Pandoc/XML/Light/Output.hs b/xml-light/Text/Pandoc/XML/Light/Output.hs
--- a/xml-light/Text/Pandoc/XML/Light/Output.hs
+++ b/xml-light/Text/Pandoc/XML/Light/Output.hs
@@ -11,27 +11,30 @@
    Portability : portable
 
    This code is based on code from xml-light, released under the BSD3 license.
-   We use a text Builder instead of ShowS.
+   We use a TextBuilder (from the text-builder package) instead of ShowS.
 -}
 module Text.Pandoc.XML.Light.Output
   ( -- * Replacement for xml-light's Text.XML.Output
     ppTopElement
   , ppElement
   , ppContent
+  , ppcTopElement
   , ppcElement
   , ppcContent
   , showTopElement
   , showElement
   , showContent
   , useShortEmptyTags
+  , useInlineTags
   , defaultConfigPP
+  , prettyConfigPP
   , ConfigPP(..)
   ) where
 
+import Data.List (intersperse)
 import Data.Text (Text)
 import qualified Data.Text as T
-import qualified Data.Text.Lazy as TL
-import Data.Text.Lazy.Builder (Builder, singleton, fromText, toLazyText)
+import TextBuilder (TextBuilder, char, text, toText)
 import Text.Pandoc.XML.Light.Types
 
 --
@@ -46,6 +49,7 @@
 --------------------------------------------------------------------------------
 data ConfigPP = ConfigPP
   { shortEmptyTag :: QName -> Bool
+  , inlineTag     :: QName -> Bool
   , prettify      :: Bool
   }
 
@@ -53,6 +57,7 @@
 --  * Always use abbreviate empty tags.
 defaultConfigPP :: ConfigPP
 defaultConfigPP = ConfigPP { shortEmptyTag = const True
+                           , inlineTag     = const False
                            , prettify      = False
                            }
 
@@ -63,7 +68,13 @@
 useShortEmptyTags :: (QName -> Bool) -> ConfigPP -> ConfigPP
 useShortEmptyTags p c = c { shortEmptyTag = p }
 
+-- | The predicate specifies which tags should be treated as inline:
+-- when pretty-printing, the whole content of an inline element is
+-- kept on a single line, so that no whitespace is added inside it.
+useInlineTags :: (QName -> Bool) -> ConfigPP -> ConfigPP
+useInlineTags p c = c { inlineTag = p }
 
+
 -- | Specify if we should use extra white-space to make document more readable.
 -- WARNING: This adds additional white-space to text elements,
 -- and so it may change the meaning of the document.
@@ -102,47 +113,70 @@
 
 -- | Pretty printing elements
 ppcElement         :: ConfigPP -> Element -> Text
-ppcElement c        = TL.toStrict . toLazyText . ppElementS c mempty
+ppcElement c        = toText . ppElementS c mempty
 
 -- | Pretty printing content
 ppcContent         :: ConfigPP -> Content -> Text
-ppcContent c        = TL.toStrict . toLazyText . ppContentS c mempty
-
-ppcCData           :: ConfigPP -> CData -> Text
-ppcCData c         = TL.toStrict . toLazyText . ppCDataS c mempty
+ppcContent c        = toText . ppContentS c mempty
 
-type Indent = Builder
+type Indent = TextBuilder
 
 -- | Pretty printing content using ShowT
-ppContentS         :: ConfigPP -> Indent -> Content -> Builder
+ppContentS         :: ConfigPP -> Indent -> Content -> TextBuilder
 ppContentS c i x = case x of
                      Elem e -> ppElementS c i e
                      Text t -> ppCDataS c i t
                      CRef r -> showCRefS r
 
-ppElementS         :: ConfigPP -> Indent -> Element -> Builder
-ppElementS c i e = i <> tagStart (elName e) (elAttribs e) <>
+ppElementS         :: ConfigPP -> Indent -> Element -> TextBuilder
+ppElementS c i e = i <> ppElementS' c i e
+
+-- | Like ppElementS, but without indentation before the element
+-- itself.  The indentation is still passed down, so that block-level
+-- elements nested inside an inline element are indented properly.
+ppElementS'        :: ConfigPP -> Indent -> Element -> TextBuilder
+ppElementS' c i e = tagStart (elName e) (elAttribs e) <>
   (case elContent e of
-    [] | "?" `T.isPrefixOf` qName name -> fromText " ?>"
-       | shortEmptyTag c name  -> fromText " />"
-    [Text t] -> singleton '>' <> ppCDataS c mempty t <> tagEnd name
-    cs -> singleton '>' <> nl <>
+    [] | "?" `T.isPrefixOf` qName name -> text " ?>"
+       | shortEmptyTag c name  -> text " />"
+    [Text t] -> char '>' <>
+                ppCDataS' c (if inlineTag c name then i else mempty) t <>
+                tagEnd name
+    cs | inlineTag c name ->
+          char '>' <> mconcat (map inlineContent cs) <> tagEnd name
+       | otherwise ->
+          char '>' <> nl <>
           mconcat (map ((<> nl) . ppContentS c (sp <> i)) cs) <>
           i <> tagEnd name
-      where (nl,sp)  = if prettify c then ("\n","  ") else ("","")
+      where (nl,sp)  = if prettify c
+                          then (text "\n", text "  ")
+                          else (mempty, mempty)
+            -- content of an inline element: no indentation is
+            -- emitted, so that no whitespace is added to the content
+            inlineContent (Elem el) = ppElementS' c i el
+            inlineContent (Text t)  = ppCDataS' c i t
+            inlineContent (CRef r)  = showCRefS r
   )
   where name = elName e
 
-ppCDataS           :: ConfigPP -> Indent -> CData -> Builder
-ppCDataS c i t     = i <> if cdVerbatim t /= CDataText || not (prettify c)
-                             then showCDataS t
-                             else foldr cons mempty (T.unpack (showCData t))
-  where cons         :: Char -> Builder -> Builder
-        cons '\n' ys  = singleton '\n' <> i <> ys
-        cons y ys     = singleton y <> ys
+ppCDataS           :: ConfigPP -> Indent -> CData -> TextBuilder
+ppCDataS c i t     = i <> ppCDataS' c i t
 
+-- | Like ppCDataS, but without indentation before the text itself.
+-- The indentation is still used after newlines in the text.
+ppCDataS'          :: ConfigPP -> Indent -> CData -> TextBuilder
+ppCDataS' c i t    = if cdVerbatim t /= CDataText || not (prettify c)
+                        then showCDataS t
+                        -- add indentation after newlines; escaping
+                        -- neither adds nor removes newlines, so we
+                        -- can split the unescaped text
+                        else mconcat
+                             (intersperse (char '\n' <> i)
+                               (map escStr
+                                 (T.split (=='\n') (cdData t))))
 
 
+
 --------------------------------------------------------------------------------
 
 -- | Adds the <?xml?> header.
@@ -155,43 +189,39 @@
 showElement        :: Element -> Text
 showElement         = ppcElement defaultConfigPP
 
-showCData          :: CData -> Text
-showCData           = ppcCData defaultConfigPP
-
 -- Note: crefs should not contain '&', ';', etc.
-showCRefS          :: Text -> Builder
-showCRefS r         = singleton '&' <> fromText r <> singleton ';'
+showCRefS          :: Text -> TextBuilder
+showCRefS r         = char '&' <> text r <> char ';'
 
 -- | Convert a text element to characters.
-showCDataS         :: CData -> Builder
+showCDataS         :: CData -> TextBuilder
 showCDataS cd =
  case cdVerbatim cd of
    CDataText     -> escStr (cdData cd)
-   CDataVerbatim -> fromText "<![CDATA[" <> escCData (cdData cd) <>
-                    fromText "]]>"
-   CDataRaw      -> fromText (cdData cd)
+   CDataVerbatim -> text "<![CDATA[" <> escCData (cdData cd) <>
+                    text "]]>"
+   CDataRaw      -> text (cdData cd)
 
 --------------------------------------------------------------------------------
-escCData           :: Text -> Builder
-escCData t
-  | "]]>" `T.isPrefixOf` t =
-     fromText "]]]]><![CDATA[>" <> fromText (T.drop 3 t)
-escCData t
-  = case T.uncons t of
-      Nothing     -> mempty
-      Just (c,t') -> singleton c <> escCData t'
+escCData           :: Text -> TextBuilder
+escCData t =
+  case T.breakOn "]]>" t of
+    (chunk, rest)
+      | T.null rest -> text chunk
+      | otherwise   -> text chunk <> text "]]]]><![CDATA[>" <>
+                       escCData (T.drop 3 rest)
 
-escChar            :: Char -> Builder
+escChar            :: Char -> TextBuilder
 escChar c = case c of
-  '<'   -> fromText "&lt;"
-  '>'   -> fromText "&gt;"
-  '&'   -> fromText "&amp;"
-  '"'   -> fromText "&quot;"
+  '<'   -> text "&lt;"
+  '>'   -> text "&gt;"
+  '&'   -> text "&amp;"
+  '"'   -> text "&quot;"
   -- we use &#39 instead of &apos; because IE apparently has difficulties
   -- rendering &apos; in xhtml.
   -- Reported by Rohan Drape <rohan.drape@gmail.com>.
-  '\''  -> fromText "&#39;"
-  _     -> singleton c
+  '\''  -> text "&#39;"
+  _     -> char c
 
   {- original xml-light version:
   -- NOTE: We escape '\r' explicitly because otherwise they get lost
@@ -201,10 +231,13 @@
       where oc = ord c
   -}
 
-escStr             :: Text -> Builder
-escStr cs          = if T.any needsEscape cs
-                        then mconcat (map escChar (T.unpack cs))
-                        else fromText cs
+escStr             :: Text -> TextBuilder
+escStr cs          = case T.break needsEscape cs of
+                       (chunk, rest) ->
+                         case T.uncons rest of
+                           Nothing -> text chunk
+                           Just (c, rest') ->
+                             text chunk <> escChar c <> escStr rest'
  where
   needsEscape '<' = True
   needsEscape '>' = True
@@ -213,22 +246,22 @@
   needsEscape '\'' = True
   needsEscape _ = False
 
-tagEnd             :: QName -> Builder
-tagEnd qn           = fromText "</" <> showQName qn <> singleton '>'
+tagEnd             :: QName -> TextBuilder
+tagEnd qn           = text "</" <> showQName qn <> char '>'
 
-tagStart           :: QName -> [Attr] -> Builder
-tagStart qn as      = singleton '<' <> showQName qn <> as_str
+tagStart           :: QName -> [Attr] -> TextBuilder
+tagStart qn as      = char '<' <> showQName qn <> as_str
  where as_str       = if null as
                          then mempty
                          else mconcat (map showAttr as)
 
-showAttr           :: Attr -> Builder
-showAttr (Attr qn v) = singleton ' ' <> showQName qn <>
-                       singleton '=' <>
-                       singleton '"' <> escStr v <> singleton '"'
+showAttr           :: Attr -> TextBuilder
+showAttr (Attr qn v) = char ' ' <> showQName qn <>
+                       char '=' <>
+                       char '"' <> escStr v <> char '"'
 
-showQName          :: QName -> Builder
+showQName          :: QName -> TextBuilder
 showQName q         =
   case qPrefix q of
-    Nothing -> fromText (qName q)
-    Just p  -> fromText p <> singleton ':' <> fromText (qName q)
+    Nothing -> text (qName q)
+    Just p  -> text p <> char ':' <> text (qName q)
